2021-08-06 18:29:01 +02:00
|
|
|
# standard imports
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
# external imports
|
|
|
|
import celery
|
|
|
|
import i18n
|
|
|
|
from sqlalchemy.orm.session import Session
|
|
|
|
from tinydb.table import Document
|
|
|
|
|
|
|
|
# local imports
|
2022-01-04 17:16:00 +01:00
|
|
|
from cic_ussd.db.models.account import Account
|
2021-08-06 18:29:01 +02:00
|
|
|
from cic_ussd.db.models.base import SessionBase
|
|
|
|
from cic_ussd.db.models.ussd_session import UssdSession
|
|
|
|
from cic_ussd.menu.ussd_menu import UssdMenu
|
|
|
|
from cic_ussd.processor.menu import response
|
|
|
|
from cic_ussd.processor.util import latest_input, resume_last_ussd_session
|
|
|
|
from cic_ussd.session.ussd_session import create_or_update_session, persist_ussd_session
|
|
|
|
from cic_ussd.state_machine import UssdStateMachine
|
|
|
|
from cic_ussd.validator import is_valid_response
|
|
|
|
|
|
|
|
|
|
|
|
def handle_menu(account: Account, session: Session) -> Document:
|
|
|
|
"""
|
|
|
|
:param account:
|
|
|
|
:type account:
|
|
|
|
:param session:
|
|
|
|
:type session:
|
|
|
|
:return:
|
|
|
|
:rtype:
|
|
|
|
"""
|
|
|
|
if account.pin_is_blocked(session):
|
|
|
|
return UssdMenu.find_by_name('exit_pin_blocked')
|
|
|
|
|
|
|
|
if account.has_valid_pin(session):
|
|
|
|
last_ussd_session = UssdSession.last_ussd_session(account.phone_number, session)
|
|
|
|
if last_ussd_session:
|
|
|
|
return resume_last_ussd_session(last_ussd_session.state)
|
|
|
|
else:
|
|
|
|
return UssdMenu.find_by_name('initial_pin_entry')
|
|
|
|
|
|
|
|
|
|
|
|
def get_menu(account: Account,
|
|
|
|
session: Session,
|
|
|
|
user_input: str,
|
|
|
|
ussd_session: Optional[dict]) -> Document:
|
|
|
|
"""
|
|
|
|
:param account:
|
|
|
|
:type account:
|
|
|
|
:param session:
|
|
|
|
:type session:
|
|
|
|
:param user_input:
|
|
|
|
:type user_input:
|
|
|
|
:param ussd_session:
|
|
|
|
:type ussd_session:
|
|
|
|
:return:
|
|
|
|
:rtype:
|
|
|
|
"""
|
|
|
|
user_input = latest_input(user_input)
|
|
|
|
if not ussd_session:
|
|
|
|
return handle_menu(account, session)
|
|
|
|
if user_input == '':
|
|
|
|
return UssdMenu.find_by_name(name='exit_invalid_input')
|
|
|
|
if user_input == '0':
|
|
|
|
return UssdMenu.parent_menu(ussd_session.get('state'))
|
|
|
|
session = SessionBase.bind_session(session)
|
|
|
|
state = next_state(account, session, user_input, ussd_session)
|
|
|
|
return UssdMenu.find_by_name(state)
|
|
|
|
|
|
|
|
|
2022-01-04 17:16:00 +01:00
|
|
|
def handle_menu_operations(external_session_id: str,
|
2021-08-06 18:29:01 +02:00
|
|
|
phone_number: str,
|
|
|
|
queue: str,
|
|
|
|
service_code: str,
|
|
|
|
session,
|
|
|
|
user_input: str):
|
|
|
|
"""
|
|
|
|
:param external_session_id:
|
|
|
|
:type external_session_id:
|
|
|
|
:param phone_number:
|
|
|
|
:type phone_number:
|
|
|
|
:param queue:
|
|
|
|
:type queue:
|
|
|
|
:param service_code:
|
|
|
|
:type service_code:
|
|
|
|
:param session:
|
|
|
|
:type session:
|
|
|
|
:param user_input:
|
|
|
|
:type user_input:
|
|
|
|
:return:
|
|
|
|
:rtype:
|
|
|
|
"""
|
|
|
|
session = SessionBase.bind_session(session=session)
|
|
|
|
account: Account = Account.get_by_phone_number(phone_number, session)
|
|
|
|
if account:
|
|
|
|
return handle_account_menu_operations(account, external_session_id, queue, session, service_code, user_input)
|
2022-01-04 17:16:00 +01:00
|
|
|
else:
|
|
|
|
return handle_no_account_menu_operations(
|
|
|
|
account, external_session_id, phone_number, queue, session, service_code, user_input)
|
|
|
|
|
|
|
|
|
|
|
|
def handle_no_account_menu_operations(account: Optional[Account],
|
|
|
|
external_session_id: str,
|
|
|
|
phone_number: str,
|
|
|
|
queue: str,
|
|
|
|
session: Session,
|
|
|
|
service_code: str,
|
|
|
|
user_input: str):
|
|
|
|
"""
|
|
|
|
:param account:
|
|
|
|
:type account:
|
|
|
|
:param external_session_id:
|
|
|
|
:type external_session_id:
|
|
|
|
:param phone_number:
|
|
|
|
:type phone_number:
|
|
|
|
:param queue:
|
|
|
|
:type queue:
|
|
|
|
:param session:
|
|
|
|
:type session:
|
|
|
|
:param service_code:
|
|
|
|
:type service_code:
|
|
|
|
:param user_input:
|
|
|
|
:type user_input:
|
|
|
|
:return:
|
|
|
|
:rtype:
|
|
|
|
"""
|
|
|
|
menu = UssdMenu.find_by_name('initial_language_selection')
|
|
|
|
ussd_session = create_or_update_session(
|
2021-08-06 18:29:01 +02:00
|
|
|
external_session_id=external_session_id,
|
|
|
|
msisdn=phone_number,
|
|
|
|
service_code=service_code,
|
|
|
|
state=menu.get('name'),
|
|
|
|
session=session,
|
|
|
|
user_input=user_input)
|
|
|
|
persist_ussd_session(external_session_id, queue)
|
2022-01-04 17:16:00 +01:00
|
|
|
last_ussd_session: UssdSession = UssdSession.last_ussd_session(phone_number, session)
|
|
|
|
if last_ussd_session:
|
|
|
|
if not user_input:
|
|
|
|
menu = resume_last_ussd_session(last_ussd_session.state)
|
|
|
|
else:
|
|
|
|
session = SessionBase.bind_session(session)
|
|
|
|
state = next_state(account, session, user_input, last_ussd_session.to_json())
|
|
|
|
menu = UssdMenu.find_by_name(state)
|
|
|
|
|
|
|
|
return response(account=account,
|
|
|
|
display_key=menu.get('display_key'),
|
|
|
|
menu_name=menu.get('name'),
|
|
|
|
session=session,
|
|
|
|
ussd_session=ussd_session.to_json())
|
2021-08-06 18:29:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
def handle_account_menu_operations(account: Account,
|
|
|
|
external_session_id: str,
|
|
|
|
queue: str,
|
|
|
|
session: Session,
|
|
|
|
service_code: str,
|
|
|
|
user_input: str):
|
|
|
|
"""
|
|
|
|
:param account:
|
|
|
|
:type account:
|
|
|
|
:param external_session_id:
|
|
|
|
:type external_session_id:
|
|
|
|
:param queue:
|
|
|
|
:type queue:
|
|
|
|
:param session:
|
|
|
|
:type session:
|
|
|
|
:param service_code:
|
|
|
|
:type service_code:
|
|
|
|
:param user_input:
|
|
|
|
:type user_input:
|
|
|
|
:return:
|
|
|
|
:rtype:
|
|
|
|
"""
|
|
|
|
phone_number = account.phone_number
|
|
|
|
s_query_person_metadata = celery.signature(
|
|
|
|
'cic_ussd.tasks.metadata.query_person_metadata', [account.blockchain_address], queue='cic-ussd')
|
|
|
|
s_query_person_metadata.apply_async()
|
|
|
|
s_query_preferences_metadata = celery.signature(
|
|
|
|
'cic_ussd.tasks.metadata.query_preferences_metadata', [account.blockchain_address], queue='cic-ussd')
|
|
|
|
s_query_preferences_metadata.apply_async()
|
|
|
|
existing_ussd_session = session.query(UssdSession).filter_by(external_session_id=external_session_id).first()
|
|
|
|
last_ussd_session = UssdSession.last_ussd_session(phone_number, session)
|
|
|
|
if existing_ussd_session:
|
|
|
|
menu = get_menu(account, session, user_input, existing_ussd_session.to_json())
|
|
|
|
else:
|
|
|
|
menu = get_menu(account, session, user_input, None)
|
|
|
|
if last_ussd_session:
|
|
|
|
ussd_session = create_or_update_session(
|
|
|
|
external_session_id, phone_number, service_code, user_input, menu.get('name'), session,
|
2022-01-04 17:16:00 +01:00
|
|
|
last_ussd_session.data)
|
2021-08-06 18:29:01 +02:00
|
|
|
else:
|
|
|
|
ussd_session = create_or_update_session(
|
2022-01-04 17:16:00 +01:00
|
|
|
external_session_id, phone_number, service_code, user_input, menu.get('name'), session, {})
|
2021-08-06 18:29:01 +02:00
|
|
|
menu_response = response(
|
2022-01-04 17:16:00 +01:00
|
|
|
account, menu.get('display_key'), menu.get('name'), session, ussd_session.to_json())
|
2021-08-06 18:29:01 +02:00
|
|
|
if not is_valid_response(menu_response):
|
|
|
|
raise ValueError(f'Invalid response: {response}')
|
|
|
|
persist_ussd_session(external_session_id, queue)
|
|
|
|
return menu_response
|
|
|
|
|
|
|
|
|
|
|
|
def next_state(account: Account, session, user_input: str, ussd_session: dict) -> str:
|
|
|
|
"""
|
|
|
|
:param account:
|
|
|
|
:type account:
|
|
|
|
:param session:
|
|
|
|
:type session:
|
|
|
|
:param user_input:
|
|
|
|
:type user_input:
|
|
|
|
:param ussd_session:
|
|
|
|
:type ussd_session:
|
|
|
|
:return:
|
|
|
|
:rtype:
|
|
|
|
"""
|
|
|
|
state_machine = UssdStateMachine(ussd_session=ussd_session)
|
|
|
|
state_machine.scan_data((user_input, ussd_session, account, session))
|
|
|
|
return state_machine.state
|