From 4d76588ad0f31e331aee1072f57a71385fbf114c Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:11:54 +0530 Subject: [PATCH 01/16] Mt940 Writer Helper --- .../utils/mt940_writer.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py new file mode 100644 index 0000000..e2df9fd --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py @@ -0,0 +1,83 @@ +from enum import Enum + +from openg2p_fastapi_common.service import BaseService + + +class TransactionType(Enum): + transfer = "NTRF" + + +class Mt940Writer(BaseService): + def create_transaction( + self, + value_date, + entry_date, + dr_cr, + transaction_amount, + transaction_type, + customer_reference, + bank_reference, + funds_code="0", + supplementary_details=None, + additional_info=None, + ): + transaction = { + "value_date": value_date, + "entry_date": entry_date, + "dr_cr": dr_cr, + "funds_code": funds_code, + "transaction_amount": transaction_amount, + "transaction_type": transaction_type, + "customer_reference": customer_reference, + "bank_reference": f"//{bank_reference}", + "supplementary_details": supplementary_details, + "additional_info": additional_info, + } + return transaction + + def format_transaction(self, transaction): + return "{value_date}{entry_date}{dr_cr}{funds_code}{transaction_amount}{transaction_type}{customer_reference}{bank_reference}{supplementary_details}".format( + value_date=transaction["value_date"].strftime("%y%m%d"), + entry_date=transaction["entry_date"].strftime("%m%d"), + dr_cr=transaction["dr_cr"], + funds_code=transaction["funds_code"], + transaction_amount="{:015.2f}".format( + transaction["transaction_amount"] + ).replace(".", ","), + transaction_type=transaction["transaction_type"].value, + customer_reference="{:<17}".format(transaction["customer_reference"]), + bank_reference="{:<17}".format(transaction["bank_reference"]), + supplementary_details="{:<34}".format(transaction["supplementary_details"]), + ) + + def create_statement( + self, + reference_number, + account, + statement_number, + opening_balance, + closing_balance, + transactions, + ): + statement = { + "reference_number": reference_number, + "account": account, + "statement_number": statement_number, + "opening_balance": opening_balance, + "closing_balance": closing_balance, + "transactions": transactions, + } + return statement + + def format_statement(self, statement): + result = [] + result.append(f':20:{statement["reference_number"]}') + result.append(f':25:{statement["account"]}') + result.append(f':28C:{statement["statement_number"]}') + result.append(f':60F:{statement["opening_balance"]}') + for transaction in statement["transactions"]: + result.append(f":61:{self.format_transaction(transaction)}") + if transaction["additional_info"]: + result.append(f':86:{transaction["additional_info"]}') + result.append(f':62F:{statement["closing_balance"]}') + return "\n".join(result) From 5de689856f2c54ea6af37df3864e58b6e928df81 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:12:53 +0530 Subject: [PATCH 02/16] Account Statement --- .../app.py | 9 +- .../controllers/account_statement.py | 60 +++++++++ .../app.py | 7 +- .../tasks/__init__.py | 1 + .../tasks/account_statement_generator.py | 122 ++++++++++++++++++ .../utils/__init__.py | 4 + .../models/__init__.py | 1 + .../models/account.py | 13 +- .../schemas/__init__.py | 2 + 9 files changed, 213 insertions(+), 6 deletions(-) create mode 100644 openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py create mode 100644 openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py create mode 100644 openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/__init__.py diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py index 6b9be17..e484de4 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py @@ -5,16 +5,20 @@ from openg2p_g2p_bridge_example_bank_api.config import Settings _config = Settings.get_config() - from openg2p_fastapi_common.app import Initializer as BaseInitializer +from openg2p_g2p_bridge_example_bank_models.models import ( + Account, + FundBlock, + InitiatePaymentRequest, +) from sqlalchemy import create_engine from openg2p_g2p_bridge_example_bank_api.controllers import ( + AccountStatementController, BlockFundsController, FundAvailabilityController, PaymentController, ) -from openg2p_g2p_bridge_example_bank_models.models import Account, FundBlock, InitiatePaymentRequest _logger = logging.getLogger(_config.logging_default_logger_name) @@ -26,6 +30,7 @@ def initialize(self, **kwargs): BlockFundsController().post_init() FundAvailabilityController().post_init() PaymentController().post_init() + AccountStatementController().post_init() def migrate_database(self, args): super().migrate_database(args) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py new file mode 100644 index 0000000..b42255f --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py @@ -0,0 +1,60 @@ +from openg2p_fastapi_common.context import dbengine +from openg2p_fastapi_common.controller import BaseController +from openg2p_g2p_bridge_example_bank_models.models import Account, AccountStatement +from openg2p_g2p_bridge_example_bank_models.schemas import ( + AccountStatementRequest, + AccountStatementResponse, +) +from sqlalchemy.ext.asyncio import async_sessionmaker +from sqlalchemy.future import select + +from ..celery_app import celery_app + + +class AccountStatementController(BaseController): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.router.tags += ["Account Statement"] + + self.router.add_api_route( + "/generate_account_statement", + self.generate_account_statement, + response_model=AccountStatementResponse, + methods=["POST"], + ) + + async def generate_account_statement( + self, account_statement_request: AccountStatementRequest + ) -> AccountStatementResponse: + session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) + async with session_maker() as session: + stmt = select(Account).where( + Account.account_number + == account_statement_request.program_account_number + ) + result = await session.execute(stmt) + account = result.scalars().first() + + if not account: + return AccountStatementResponse( + status="failed", + error_message="Account not found", + ) + + account_statement = AccountStatement( + account_number=account_statement_request.program_account_number, + active=True, + ) + session.add(account_statement) + await session.commit() + + # Create a new task to generate the account statement + celery_app.send_task( + "account_statement_generator", + args=(account_statement.id,), + ) + + return AccountStatementResponse( + status="success", account_statement_id=str(account_statement.id) + ) diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py index 5cc9dc1..48e0ea2 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py @@ -2,8 +2,8 @@ import logging from .config import Settings -_config = Settings.get_config() +_config = Settings.get_config() from celery import Celery from openg2p_fastapi_common.app import Initializer as BaseInitializer @@ -14,6 +14,8 @@ ) from sqlalchemy import create_engine +from .utils import Mt940Writer + _logger = logging.getLogger(_config.logging_default_logger_name) @@ -24,6 +26,7 @@ def initialize(self, **kwargs): BlockFundsController().post_init() FundAvailabilityController().post_init() PaymentController().post_init() + Mt940Writer() def get_engine(): @@ -36,7 +39,7 @@ def get_engine(): "example_bank_celery_tasks", broker="redis://localhost:6379/0", backend="redis://localhost:6379/0", - include=["openg2p_g2p_bridge_example_bank_celery.tasks.process_payment"], + include=["openg2p_g2p_bridge_example_bank_celery.tasks"], ) celery_app.conf.beat_schedule = { diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py index 545b021..c840f90 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py @@ -1 +1,2 @@ +from .account_statement_generator import account_statement_generator from .process_payment import process_payments diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py new file mode 100644 index 0000000..11f4f7a --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py @@ -0,0 +1,122 @@ +from decimal import Decimal + +from mt940_writer import ( + Account as MT940Account, +) +from mt940_writer import Balance, Statement, Transaction, TransactionType +from openg2p_g2p_bridge_example_bank_models.models import ( + Account, + AccountingLog, + AccountStatement, + DebitCreditTypes, +) +from sqlalchemy import select +from sqlalchemy.orm import sessionmaker + +from ..app import celery_app, get_engine +from ..config import Settings +from ..utils import Mt940Writer, TransactionType + +_config = Settings.get_config() +_engine = get_engine() + + +@celery_app.task(name="account_statement_generator") +def account_statement_generator(account_statement_id: int): + session_maker = sessionmaker(bind=_engine, expire_on_commit=False) + with session_maker() as session: + account_statement = ( + session.execute( + select(AccountStatement).where( + AccountStatement.id == account_statement_id + ) + ) + .scalars() + .first() + ) + + if not account_statement: + return + + account = ( + session.execute( + select(Account).where( + Account.account_number == account_statement.account_number + ) + ) + .scalars() + .first() + ) + + if not account: + return + + account_logs = ( + session.execute( + select(AccountingLog).where( + AccountingLog.account_number == account_statement.account_number + ) + ) + .scalars() + .all() + ) + + if not account_logs: + return + + currency = account.account_currency + statement_date = account_statement.account_statement_date + mt940_account = MT940Account(account.account_number, "000001") + mt940_opening_balance = Balance(Decimal("100000000"), statement_date, currency) + mt940_closing_balance = Balance( + Decimal(account.book_balance), statement_date, currency + ) + + + mt940_writer = Mt940Writer.get_component() + transactions = [] + for account_log in account_logs: + transaction_debit_credit = account_log.debit_credit + if account_log.debit_credit == DebitCreditTypes.DEBIT: + transaction_debit_credit = "D" + if account_log.debit_credit == DebitCreditTypes.CREDIT: + transaction_debit_credit = "C" + if ( + account_log.debit_credit == DebitCreditTypes.DEBIT + and account_log.transaction_amount < 0 + ): + transaction_debit_credit = "RD" + if ( + account_log.debit_credit == DebitCreditTypes.CREDIT + and account_log.transaction_amount < 0 + ): + transaction_debit_credit = "RC" + + transactions.append( + mt940_writer.create_transaction( + account_log.transaction_date, + account_log.transaction_date, + transaction_debit_credit, + Decimal(abs(account_log.transaction_amount)), + TransactionType.transfer, + account_log.customer_reference_no, + account_log.reference_no[:16], + "", + "", + f"{account_log.narrative_1}\n{account_log.narrative_2}" + f"\n{account_log.narrative_3}\n{account_log.narrative_4}" + f"\n{account_log.narrative_5}\n{account_log.narrative_6}", + ) + ) + + statement = mt940_writer.create_statement( + account_statement_id, + mt940_account, + "1/1", + mt940_opening_balance, + mt940_closing_balance, + transactions, + ) + + account_statement.account_statement_lob = str(statement) + session.commit() diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/__init__.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/__init__.py new file mode 100644 index 0000000..a3c10d0 --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/__init__.py @@ -0,0 +1,4 @@ +from .mt940_writer import ( + Mt940Writer, + TransactionType, +) diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py index 497521c..48746e0 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py @@ -1,6 +1,7 @@ from .account import ( Account, AccountingLog, + AccountStatement, DebitCreditTypes, FundBlock, InitiatePaymentRequest, diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py index 5e94088..f990ea8 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py @@ -2,7 +2,7 @@ from enum import Enum from openg2p_fastapi_common.models import BaseORMModelWithTimes -from sqlalchemy import DateTime, Float, Integer, String +from sqlalchemy import DateTime, Float, Integer, String, Text from sqlalchemy import Enum as SqlEnum from sqlalchemy.orm import Mapped, mapped_column @@ -31,7 +31,7 @@ class Account(BaseORMModelWithTimes): class FundBlock(BaseORMModelWithTimes): __tablename__ = "fund_blocks" block_reference_no: Mapped[str] = mapped_column(String, index=True, unique=True) - account_no: Mapped[str] = mapped_column(String) + account_number: Mapped[str] = mapped_column(String) currency: Mapped[str] = mapped_column(String) amount: Mapped[float] = mapped_column(Float) amount_released: Mapped[float] = mapped_column(Float, default=0) @@ -104,3 +104,12 @@ class AccountingLog(BaseORMModelWithTimes): narrative_6: Mapped[str] = mapped_column( String, nullable=True ) # beneficiary phone number + + +class AccountStatement(BaseORMModelWithTimes): + __tablename__ = "account_statements" + account_number: Mapped[str] = mapped_column(String, index=True) + account_statement_lob: Mapped[str] = mapped_column(Text, nullable=True) + account_statement_date: Mapped[datetime.date] = mapped_column( + DateTime, default=datetime.date(datetime.utcnow()) + ) diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py index 3b47a75..05ecf21 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py @@ -1,4 +1,6 @@ from .fund_schemas import ( + AccountStatementRequest, + AccountStatementResponse, BlockFundsRequest, BlockFundsResponse, CheckFundRequest, From 429414fd7ea6e3fddafb849800b68e58c09c61be Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:13:20 +0530 Subject: [PATCH 03/16] Pre-commit and minor fixes --- .../controllers/initiate_payment.py | 11 ++++++++--- .../tasks/process_payment.py | 4 +++- .../schemas/fund_schemas.py | 12 +++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py index 797e633..efe415e 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py @@ -2,12 +2,17 @@ from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.controller import BaseController +from openg2p_g2p_bridge_example_bank_models.models import ( + FundBlock, + InitiatePaymentRequest, +) +from openg2p_g2p_bridge_example_bank_models.schemas import ( + InitiatePaymentPayload, + InitiatorPaymentResponse, +) from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select -from openg2p_g2p_bridge_example_bank_models.models import FundBlock, InitiatePaymentRequest -from openg2p_g2p_bridge_example_bank_models.schemas import InitiatePaymentPayload, InitiatorPaymentResponse - class PaymentController(BaseController): def __init__(self, **kwargs): diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py index 232c79e..5f7fbdf 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py @@ -152,7 +152,9 @@ def generate_failures( session.execute( select(FundBlock).where( FundBlock.block_reference_no - == corresponding_fund_blocks[account_log.reference_no].block_reference_no + == corresponding_fund_blocks[ + account_log.reference_no + ].block_reference_no ) ) .scalars() diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py index c623d9c..ab50422 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py @@ -17,7 +17,7 @@ class CheckFundResponse(BaseModel): class BlockFundsRequest(BaseModel): - account_no: str + account_number: str currency: str amount: float @@ -61,3 +61,13 @@ class InitiatePaymentPayload(BaseModel): class InitiatorPaymentResponse(BaseModel): status: str error_message: Optional[str] = None + + +class AccountStatementRequest(BaseModel): + program_account_number: str + + +class AccountStatementResponse(BaseModel): + status: str + account_statement_id: Optional[str] = None + error_message: Optional[str] = None From 9a56964afb458b0db01fa0f85911fadc5dea1842 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:13:26 +0530 Subject: [PATCH 04/16] Pre-commit and minor fixes --- .../controllers/check_available_funds.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py index 37a08c4..b1ea0ca 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py @@ -1,11 +1,13 @@ from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.controller import BaseController +from openg2p_g2p_bridge_example_bank_models.models import Account +from openg2p_g2p_bridge_example_bank_models.schemas import ( + CheckFundRequest, + CheckFundResponse, +) from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select -from openg2p_g2p_bridge_example_bank_models.models import Account -from openg2p_g2p_bridge_example_bank_models.schemas import CheckFundRequest, CheckFundResponse - class FundAvailabilityController(BaseController): def __init__(self, **kwargs): From 53232af9e5af7b5bd0e03dccdb630675abe47e32 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:13:35 +0530 Subject: [PATCH 05/16] Pre-commit and minor fixes --- .../controllers/block_funds.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py index 6030cad..13b7a09 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py @@ -2,13 +2,15 @@ from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.controller import BaseController +from openg2p_g2p_bridge_example_bank_models.models import Account, FundBlock +from openg2p_g2p_bridge_example_bank_models.schemas import ( + BlockFundsRequest, + BlockFundsResponse, +) from sqlalchemy import update from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select -from openg2p_g2p_bridge_example_bank_models.models import Account, FundBlock -from openg2p_g2p_bridge_example_bank_models.schemas import BlockFundsRequest, BlockFundsResponse - class BlockFundsController(BaseController): def __init__(self, **kwargs): @@ -27,7 +29,7 @@ async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse: session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: stmt = select(Account).where( - (Account.account_number == request.account_no) + (Account.account_number == request.account_number) & (Account.account_currency == request.currency) ) result = await session.execute(stmt) @@ -48,7 +50,7 @@ async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse: await session.execute( update(Account) - .where(Account.account_number == request.account_no) + .where(Account.account_number == request.account_number) .values( available_balance=account.book_balance - (account.blocked_amount + request.amount), @@ -59,7 +61,7 @@ async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse: block_reference_no = str(uuid.uuid4()) fund_block = FundBlock( block_reference_no=block_reference_no, - account_no=request.account_no, + account_number=request.account_number, amount=request.amount, currency=request.currency, active=True, From 28ba765a84e0ac4332e26f96027471ce534fc505 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:13:52 +0530 Subject: [PATCH 06/16] Add celery_app.py --- .../celery_app.py | 9 +++++++++ .../controllers/__init__.py | 13 ++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/celery_app.py diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/celery_app.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/celery_app.py new file mode 100644 index 0000000..496f987 --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/celery_app.py @@ -0,0 +1,9 @@ +from celery import Celery + +celery_app = Celery( + "example_bank", + broker="redis://localhost:6379/0", + backend="redis://localhost:6379/0", +) + +celery_app.conf.timezone = "UTC" diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py index 9c545da..f278f0b 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py @@ -1,3 +1,10 @@ -from openg2p_g2p_bridge_example_bank_api.controllers.block_funds import BlockFundsController -from openg2p_g2p_bridge_example_bank_api.controllers.check_available_funds import FundAvailabilityController -from openg2p_g2p_bridge_example_bank_api.controllers.initiate_payment import PaymentController +from .account_statement import AccountStatementController +from .block_funds import ( + BlockFundsController, +) +from .check_available_funds import ( + FundAvailabilityController, +) +from .initiate_payment import ( + PaymentController, +) From 1e580f496733fb480d9ce9f7f25ac615d900c732 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 00:17:28 +0530 Subject: [PATCH 07/16] remove .idea folder --- .gitignore | 2 +- .idea/inspectionProfiles/Project_Default.xml | 13 ------------- .idea/inspectionProfiles/profiles_settings.xml | 6 ------ 3 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml diff --git a/.gitignore b/.gitignore index 82f9275..7b6caf3 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index d4c4a74..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file From 06a3c7b2b0c0ad97a22ddb288bb7e6986134d862 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Tue, 6 Aug 2024 19:42:41 +0530 Subject: [PATCH 08/16] Fix Formating for Mt940 Writer --- .../.gitignore | 3 +- .../celerybeat-schedule.db | Bin 16384 -> 0 bytes .../app.py | 3 +- .../tasks/account_statement_generator.py | 13 ++++---- .../utils/mt940_writer.py | 28 ++++++++++++++---- 5 files changed, 34 insertions(+), 13 deletions(-) delete mode 100644 openg2p-g2p-bridge-example-bank-celery/celerybeat-schedule.db diff --git a/openg2p-g2p-bridge-example-bank-celery/.gitignore b/openg2p-g2p-bridge-example-bank-celery/.gitignore index c633cec..19fc39c 100644 --- a/openg2p-g2p-bridge-example-bank-celery/.gitignore +++ b/openg2p-g2p-bridge-example-bank-celery/.gitignore @@ -78,4 +78,5 @@ docs/_build/ # Ignore secret files and env .secrets.* -../.env +.env +/db \ No newline at end of file diff --git a/openg2p-g2p-bridge-example-bank-celery/celerybeat-schedule.db b/openg2p-g2p-bridge-example-bank-celery/celerybeat-schedule.db deleted file mode 100644 index 0bd077f64ff844859719ba1ab0511a449a3f434d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI%yH3L}6b4`y0!g_vU09i6i5R#%0AfH93|tnLC`}I#5!@ diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py index 48e0ea2..53a7a3a 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py @@ -26,7 +26,6 @@ def initialize(self, **kwargs): BlockFundsController().post_init() FundAvailabilityController().post_init() PaymentController().post_init() - Mt940Writer() def get_engine(): @@ -50,3 +49,5 @@ def get_engine(): } celery_app.conf.timezone = "UTC" +# Initialize the Mt940Writer here +Mt940Writer() diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py index 11f4f7a..532eab4 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py @@ -64,16 +64,17 @@ def account_statement_generator(account_statement_id: int): if not account_logs: return + mt940_writer = Mt940Writer.get_component() currency = account.account_currency statement_date = account_statement.account_statement_date - mt940_account = MT940Account(account.account_number, "000001") - mt940_opening_balance = Balance(Decimal("100000000"), statement_date, currency) - mt940_closing_balance = Balance( + mt940_account = account.account_number + mt940_opening_balance = mt940_writer.create_balance(Decimal("100000000"), statement_date, currency) + mt940_closing_balance = mt940_writer.create_balance( Decimal(account.book_balance), statement_date, currency ) - mt940_writer = Mt940Writer.get_component() + transactions = [] for account_log in account_logs: transaction_debit_credit = account_log.debit_credit @@ -117,6 +118,6 @@ def account_statement_generator(account_statement_id: int): mt940_closing_balance, transactions, ) - - account_statement.account_statement_lob = str(statement) + mt940_statement = mt940_writer.format_statement(statement) + account_statement.account_statement_lob = str(mt940_statement) session.commit() diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py index e2df9fd..afc0311 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py @@ -45,9 +45,9 @@ def format_transaction(self, transaction): transaction["transaction_amount"] ).replace(".", ","), transaction_type=transaction["transaction_type"].value, - customer_reference="{:<17}".format(transaction["customer_reference"]), - bank_reference="{:<17}".format(transaction["bank_reference"]), - supplementary_details="{:<34}".format(transaction["supplementary_details"]), + customer_reference=transaction["customer_reference"], + bank_reference=transaction["bank_reference"], + supplementary_details=transaction["supplementary_details"], ) def create_statement( @@ -74,10 +74,28 @@ def format_statement(self, statement): result.append(f':20:{statement["reference_number"]}') result.append(f':25:{statement["account"]}') result.append(f':28C:{statement["statement_number"]}') - result.append(f':60F:{statement["opening_balance"]}') + result.append(f':60F:{self.format_balance(statement["opening_balance"])}') for transaction in statement["transactions"]: result.append(f":61:{self.format_transaction(transaction)}") if transaction["additional_info"]: result.append(f':86:{transaction["additional_info"]}') - result.append(f':62F:{statement["closing_balance"]}') + result.append(f':62F:{self.format_balance(statement["closing_balance"])}') return "\n".join(result) + + def create_balance(self, amount, date, currency_code): + balance = { + "amount": amount, + "date": date, + "currency_code": currency_code, + } + return balance + + def format_balance(self, balance): + return '{category}{date}{currency_code}{amount}'.format( + category='C' if balance['amount'] >= 0 else 'D', + date=balance['date'].strftime('%y%m%d'), + currency_code=balance['currency_code'], + amount=f'{balance["amount"]:0.2f}'.replace('.', ',').replace('-', ''), + ) + + From 092efa33b799107ab5676026a4bbd5e946fa62dc Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:35:38 +0530 Subject: [PATCH 09/16] Add InitiatePaymentBatchRequest model --- .../models/account.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py index f990ea8..a85ce75 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py @@ -37,12 +37,20 @@ class FundBlock(BaseORMModelWithTimes): amount_released: Mapped[float] = mapped_column(Float, default=0) +class InitiatePaymentBatchRequest(BaseORMModelWithTimes): + batch_id: Mapped[str] = mapped_column(String, index=True, unique=True) + payment_initiate_attempts: Mapped[int] = mapped_column(Integer, default=0) + payment_status: Mapped[PaymentStatus] = mapped_column( + SqlEnum(PaymentStatus), default=PaymentStatus.PENDING + ) + + class InitiatePaymentRequest(BaseORMModelWithTimes): __tablename__ = "initiate_payment_requests" + batch_id: Mapped[str] = mapped_column(String, index=True, unique=False) payment_reference_number = mapped_column( String, index=True, unique=True ) # disbursement id - remitting_account: Mapped[str] = mapped_column(String, nullable=False) remitting_account_currency: Mapped[str] = mapped_column(String, nullable=False) payment_amount: Mapped[float] = mapped_column(Float, nullable=False) @@ -77,15 +85,11 @@ class InitiatePaymentRequest(BaseORMModelWithTimes): narrative_5: Mapped[str] = mapped_column(String, nullable=True) narrative_6: Mapped[str] = mapped_column(String, nullable=True) - payment_initiate_attempts: Mapped[int] = mapped_column(Integer, default=0) - payment_status: Mapped[PaymentStatus] = mapped_column( - SqlEnum(PaymentStatus), default=PaymentStatus.PENDING - ) - class AccountingLog(BaseORMModelWithTimes): __tablename__ = "accounting_logs" reference_no: Mapped[str] = mapped_column(String, index=True, unique=True) + corresponding_block_reference_no: Mapped[str] = mapped_column(String, nullable=True) customer_reference_no: Mapped[str] = mapped_column(String, index=True) debit_credit: Mapped[DebitCreditTypes] = mapped_column(SqlEnum(DebitCreditTypes)) account_number: Mapped[str] = mapped_column(String, index=True) From dc0624c3331b667494aff50e56e9b263a50c7cd1 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:36:23 +0530 Subject: [PATCH 10/16] Initiate payments in batches --- .../controllers/initiate_payment.py | 15 +- .../tasks/process_payment.py | 163 ++++++++++-------- 2 files changed, 108 insertions(+), 70 deletions(-) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py index efe415e..88dbf38 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py @@ -1,9 +1,11 @@ +import uuid from typing import List from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.controller import BaseController from openg2p_g2p_bridge_example_bank_models.models import ( FundBlock, + InitiatePaymentBatchRequest, InitiatePaymentRequest, ) from openg2p_g2p_bridge_example_bank_models.schemas import ( @@ -32,6 +34,13 @@ async def initiate_payment( ) -> InitiatorPaymentResponse: session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: + batch_id = str(uuid.uuid4()) + initiate_payment_batch_request = InitiatePaymentBatchRequest( + batch_id=batch_id, + active=True, + ) + session.add(initiate_payment_batch_request) + initiate_payment_requests = [] for initiate_payment_payload in initiate_payment_payloads: fund_block_stmt = select(FundBlock).where( FundBlock.block_reference_no @@ -51,7 +60,8 @@ async def initiate_payment( error_message="Invalid funds block reference or mismatch in details", ) - payment = InitiatePaymentRequest( + initiate_payment_request = InitiatePaymentRequest( + batch_id=batch_id, payment_reference_number=initiate_payment_payload.payment_reference_number, remitting_account=initiate_payment_payload.remitting_account, remitting_account_currency=initiate_payment_payload.remitting_account_currency, @@ -76,8 +86,9 @@ async def initiate_payment( narrative_6=initiate_payment_payload.narrative_6, active=True, ) - session.add(payment) + initiate_payment_requests.append(initiate_payment_request) + session.add_all(initiate_payment_requests) await session.commit() return InitiatorPaymentResponse(status="success", error_message="") diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py index 5f7fbdf..e7dcdab 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py @@ -1,3 +1,4 @@ +import logging import random import uuid from datetime import datetime @@ -8,6 +9,7 @@ AccountingLog, DebitCreditTypes, FundBlock, + InitiatePaymentBatchRequest, InitiatePaymentRequest, PaymentStatus, ) @@ -19,18 +21,19 @@ _config = Settings.get_config() _engine = get_engine() +_logger = logging.getLogger(_config.logging_default_logger_name) -@celery_app.task(name="process_payments") -def process_payments(): +@celery_app.task(name="process_payments_beat_producer") +def process_payments_beat_producer(): session_maker = sessionmaker(bind=_engine, expire_on_commit=False) with session_maker() as session: - initiate_payment_requests = ( + initiate_payment_batch_requests = ( session.execute( - select(InitiatePaymentRequest).where( - (InitiatePaymentRequest.payment_status.in_(["PENDING", "FAILED"])) + select(InitiatePaymentBatchRequest).where( + (InitiatePaymentBatchRequest.payment_status.in_(["PENDING"])) & ( - InitiatePaymentRequest.payment_initiate_attempts + InitiatePaymentBatchRequest.payment_initiate_attempts < _config.payment_initiate_attempts ) ) @@ -39,59 +42,83 @@ def process_payments(): .all() ) - failure_logs = [] - corresponding_fund_blocks = {} - for initiate_payment_request in initiate_payment_requests: - account = ( - session.execute( - select(Account).where( - Account.account_number - == initiate_payment_request.remitting_account - ) - ) - .scalars() - .first() + for initiate_payment_batch_request in initiate_payment_batch_requests: + celery_app.send_task( + "process_payments_worker", + args=[initiate_payment_batch_request.batch_id], + queue="g2p_bridge_celery_worker_tasks", ) - fund_block = ( + session.commit() + + +@celery_app.task(name="process_payments_worker") +def process_payments_worker(payment_request_batch_id: str): + session_maker = sessionmaker(bind=_engine, expire_on_commit=False) + with session_maker() as session: + initiate_payment_batch_request = ( + session.execute( + select(InitiatePaymentBatchRequest).where( + InitiatePaymentBatchRequest.batch_id == payment_request_batch_id + ) + ) + .scalars() + .first() + ) + try: + initiate_payment_requests = ( session.execute( - select(FundBlock).where( - FundBlock.block_reference_no - == initiate_payment_request.funds_blocked_reference_number + select(InitiatePaymentRequest).where( + InitiatePaymentRequest.batch_id == payment_request_batch_id ) ) .scalars() - .first() + .all() ) - accounting_log: AccountingLog = construct_accounting_log( - initiate_payment_request - ) + failure_logs = [] + for initiate_payment_request in initiate_payment_requests: + accounting_log: AccountingLog = construct_accounting_log( + initiate_payment_request + ) - update_account(account, initiate_payment_request.payment_amount) - update_fund_block(fund_block, initiate_payment_request.payment_amount) - initiate_payment_request.payment_status = PaymentStatus.SUCCESS - initiate_payment_request.payment_initiate_attempts += 1 + account = update_account( + initiate_payment_request.remitting_account, + initiate_payment_request.payment_amount, + session, + ) + fund_block = update_fund_block( + accounting_log.corresponding_block_reference_no, + initiate_payment_request.payment_amount, + session, + ) + initiate_payment_request.payment_status = PaymentStatus.SUCCESS + initiate_payment_request.payment_initiate_attempts += 1 - failure_random_number = random.randint(1, 100) - if failure_random_number <= 30: - failure_logs.append(accounting_log) - corresponding_fund_blocks[accounting_log.reference_no] = fund_block + failure_random_number = random.randint(1, 100) + if failure_random_number <= 30: + failure_logs.append(accounting_log) - session.add(accounting_log) - session.add(fund_block) - session.add(account) + session.add(accounting_log) + session.add(fund_block) + session.add(account) - # End of loop + # End of loop - generate_failures(failure_logs, corresponding_fund_blocks, session) + generate_failures(failure_logs, session) - session.commit() + session.commit() + except Exception as e: + _logger.error(f"Error processing payment: {e}") + initiate_payment_batch_request.payment_status = PaymentStatus.PENDING + initiate_payment_batch_request.payment_initiate_attempts += 1 + session.commit() def construct_accounting_log(initiate_payment_request: InitiatePaymentRequest): return AccountingLog( reference_no=str(uuid.uuid4()), + corresponding_block_reference_no=initiate_payment_request.funds_blocked_reference_number, customer_reference_no=initiate_payment_request.payment_reference_number, debit_credit=DebitCreditTypes.DEBIT, account_number=initiate_payment_request.remitting_account, @@ -109,9 +136,7 @@ def construct_accounting_log(initiate_payment_request: InitiatePaymentRequest): ) -def generate_failures( - failure_logs: List[AccountingLog], corresponding_fund_blocks: dict, session -): +def generate_failures(failure_logs: List[AccountingLog], session): failure_reasons = [ "ACCOUNT_CLOSED", "ACCOUNT_NOT_FOUND", @@ -136,40 +161,42 @@ def generate_failures( narrative_6=random.choice(failure_reasons), active=True, ) - session.add(account_log) - account = ( - session.execute( - select(Account).where( - Account.account_number == account_log.account_number - ) - ) - .scalars() - .first() + account = update_account( + account_log.account_number, account_log.transaction_amount, session ) - - fund_block = ( - session.execute( - select(FundBlock).where( - FundBlock.block_reference_no - == corresponding_fund_blocks[ - account_log.reference_no - ].block_reference_no - ) - ) - .scalars() - .first() + fund_block = update_fund_block( + failure_log.corresponding_block_reference_no, + account_log.transaction_amount, + session, ) - update_account(account, account_log.transaction_amount) - update_fund_block(fund_block, account_log.transaction_amount) + session.add(account_log) + session.add(account) + session.add(fund_block) -def update_account(account, payment_amount): +def update_account(remitting_account_number, payment_amount, session) -> Account: + account = ( + session.execute( + select(Account).where(Account.account_number == remitting_account_number) + ) + .scalars() + .first() + ) account.book_balance -= payment_amount account.blocked_amount -= payment_amount account.available_balance = account.book_balance - account.blocked_amount + return account -def update_fund_block(fund_block, payment_amount): +def update_fund_block(block_reference_no, payment_amount, session) -> FundBlock: + fund_block = ( + session.execute( + select(FundBlock).where(FundBlock.block_reference_no == block_reference_no) + ) + .scalars() + .first() + ) fund_block.amount_released += payment_amount + return fund_block From e665fde65d4821477c3cbd233801607db6f1409a Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:36:42 +0530 Subject: [PATCH 11/16] Pre-commit fixes --- openg2p-g2p-bridge-example-bank-api/.env.example | 2 ++ openg2p-g2p-bridge-example-bank-celery/.gitignore | 2 +- .../tasks/__init__.py | 2 +- .../tasks/account_statement_generator.py | 10 +++------- .../utils/mt940_writer.py | 12 +++++------- .../models/__init__.py | 1 + 6 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 openg2p-g2p-bridge-example-bank-api/.env.example diff --git a/openg2p-g2p-bridge-example-bank-api/.env.example b/openg2p-g2p-bridge-example-bank-api/.env.example new file mode 100644 index 0000000..c96f531 --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-api/.env.example @@ -0,0 +1,2 @@ +EXAMPLE_BANK_HOST=0.0.0.0 +EXAMPLE_BANK_PORT=8003 diff --git a/openg2p-g2p-bridge-example-bank-celery/.gitignore b/openg2p-g2p-bridge-example-bank-celery/.gitignore index 19fc39c..4e1e2e6 100644 --- a/openg2p-g2p-bridge-example-bank-celery/.gitignore +++ b/openg2p-g2p-bridge-example-bank-celery/.gitignore @@ -79,4 +79,4 @@ docs/_build/ # Ignore secret files and env .secrets.* .env -/db \ No newline at end of file +/db diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py index c840f90..84e60a8 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py @@ -1,2 +1,2 @@ from .account_statement_generator import account_statement_generator -from .process_payment import process_payments +from .process_payment import process_payments_beat_producer, process_payments_worker diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py index 532eab4..77f366d 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py @@ -1,9 +1,5 @@ from decimal import Decimal -from mt940_writer import ( - Account as MT940Account, -) -from mt940_writer import Balance, Statement, Transaction, TransactionType from openg2p_g2p_bridge_example_bank_models.models import ( Account, AccountingLog, @@ -68,13 +64,13 @@ def account_statement_generator(account_statement_id: int): currency = account.account_currency statement_date = account_statement.account_statement_date mt940_account = account.account_number - mt940_opening_balance = mt940_writer.create_balance(Decimal("100000000"), statement_date, currency) + mt940_opening_balance = mt940_writer.create_balance( + Decimal("100000000"), statement_date, currency + ) mt940_closing_balance = mt940_writer.create_balance( Decimal(account.book_balance), statement_date, currency ) - - transactions = [] for account_log in account_logs: transaction_debit_credit = account_log.debit_credit diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py index afc0311..ee432c6 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/utils/mt940_writer.py @@ -91,11 +91,9 @@ def create_balance(self, amount, date, currency_code): return balance def format_balance(self, balance): - return '{category}{date}{currency_code}{amount}'.format( - category='C' if balance['amount'] >= 0 else 'D', - date=balance['date'].strftime('%y%m%d'), - currency_code=balance['currency_code'], - amount=f'{balance["amount"]:0.2f}'.replace('.', ',').replace('-', ''), + return "{category}{date}{currency_code}{amount}".format( + category="C" if balance["amount"] >= 0 else "D", + date=balance["date"].strftime("%y%m%d"), + currency_code=balance["currency_code"], + amount=f'{balance["amount"]:0.2f}'.replace(".", ",").replace("-", ""), ) - - diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py index 48746e0..ad8c14b 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py @@ -4,6 +4,7 @@ AccountStatement, DebitCreditTypes, FundBlock, + InitiatePaymentBatchRequest, InitiatePaymentRequest, PaymentStatus, ) From 3b8eb31b725b996f74fa74195fab3e50eeb510ce Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:43:23 +0530 Subject: [PATCH 12/16] Add logging to all methods --- .../controllers/account_statement.py | 9 +++++++++ .../controllers/block_funds.py | 10 ++++++++++ .../controllers/check_available_funds.py | 11 +++++++++++ .../controllers/initiate_payment.py | 12 +++++++++++- .../tasks/account_statement_generator.py | 9 +++++++++ .../tasks/process_payment.py | 10 ++++++++-- 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py index b42255f..b768409 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/account_statement.py @@ -1,3 +1,5 @@ +import logging + from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.controller import BaseController from openg2p_g2p_bridge_example_bank_models.models import Account, AccountStatement @@ -9,6 +11,10 @@ from sqlalchemy.future import select from ..celery_app import celery_app +from ..config import Settings + +_config = Settings.get_config() +_logger = logging.getLogger(_config.logging_default_logger_name) class AccountStatementController(BaseController): @@ -27,6 +33,7 @@ def __init__(self, **kwargs): async def generate_account_statement( self, account_statement_request: AccountStatementRequest ) -> AccountStatementResponse: + _logger.info("Generating account statement") session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: stmt = select(Account).where( @@ -37,6 +44,7 @@ async def generate_account_statement( account = result.scalars().first() if not account: + _logger.error("Account not found") return AccountStatementResponse( status="failed", error_message="Account not found", @@ -50,6 +58,7 @@ async def generate_account_statement( await session.commit() # Create a new task to generate the account statement + _logger.info("Account statement generation task created") celery_app.send_task( "account_statement_generator", args=(account_statement.id,), diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py index 13b7a09..4b260ec 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py @@ -1,3 +1,4 @@ +import logging import uuid from openg2p_fastapi_common.context import dbengine @@ -11,6 +12,11 @@ from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select +from ..config import Settings + +_config = Settings.get_config() +_logger = logging.getLogger(_config.logging_default_logger_name) + class BlockFundsController(BaseController): def __init__(self, **kwargs): @@ -26,6 +32,7 @@ def __init__(self, **kwargs): ) async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse: + _logger.info("Blocking funds") session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: stmt = select(Account).where( @@ -36,12 +43,14 @@ async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse: account = result.scalars().first() if not account: + _logger.error("Account not found") return BlockFundsResponse( status="failed", block_reference_no="", error_message="Account not found", ) if account.available_balance < request.amount: + _logger.error("Insufficient funds") return BlockFundsResponse( status="failed", block_reference_no="", @@ -69,6 +78,7 @@ async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse: session.add(fund_block) await session.commit() + _logger.info("Funds blocked successfully") return BlockFundsResponse( status="success", block_reference_no=block_reference_no, diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py index b1ea0ca..e827050 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py @@ -1,3 +1,5 @@ +import logging + from openg2p_fastapi_common.context import dbengine from openg2p_fastapi_common.controller import BaseController from openg2p_g2p_bridge_example_bank_models.models import Account @@ -8,6 +10,11 @@ from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select +from ..config import Settings + +_config = Settings.get_config() +_logger = logging.getLogger(_config.logging_default_logger_name) + class FundAvailabilityController(BaseController): def __init__(self, **kwargs): @@ -25,6 +32,7 @@ def __init__(self, **kwargs): async def check_available_funds( self, request: CheckFundRequest ) -> CheckFundResponse: + _logger.info("Checking available funds") session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: stmt = select(Account).where( @@ -34,6 +42,7 @@ async def check_available_funds( account = result.scalars().first() if not account: + _logger.error("Account not found") return CheckFundResponse( status="failed", account_number=request.account_number, @@ -42,6 +51,7 @@ async def check_available_funds( ) if account.available_balance >= request.total_funds_needed: + _logger.info("Sufficient funds") return CheckFundResponse( status="success", account_number=account.account_number, @@ -49,6 +59,7 @@ async def check_available_funds( error_message="", ) else: + _logger.error("Insufficient funds") return CheckFundResponse( status="failed", account_number=account.account_number, diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py index 88dbf38..e5a5138 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py @@ -1,3 +1,4 @@ +import logging import uuid from typing import List @@ -15,6 +16,11 @@ from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select +from ..config import Settings + +_config = Settings.get_config() +_logger = logging.getLogger(_config.logging_default_logger_name) + class PaymentController(BaseController): def __init__(self, **kwargs): @@ -32,6 +38,7 @@ def __init__(self, **kwargs): async def initiate_payment( self, initiate_payment_payloads: List[InitiatePaymentPayload] ) -> InitiatorPaymentResponse: + _logger.info("Initiating payment") session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: batch_id = str(uuid.uuid4()) @@ -55,6 +62,9 @@ async def initiate_payment( or fund_block.currency != initiate_payment_payload.remitting_account_currency ): + _logger.error( + "Invalid funds block reference or mismatch in details" + ) return InitiatorPaymentResponse( status="failed", error_message="Invalid funds block reference or mismatch in details", @@ -90,5 +100,5 @@ async def initiate_payment( session.add_all(initiate_payment_requests) await session.commit() - + _logger.info("Payment initiated successfully") return InitiatorPaymentResponse(status="success", error_message="") diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py index 77f366d..4be1080 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py @@ -16,9 +16,14 @@ _config = Settings.get_config() _engine = get_engine() +import logging + +_logger = logging.getLogger(_config.logging_default_logger_name) + @celery_app.task(name="account_statement_generator") def account_statement_generator(account_statement_id: int): + _logger.info("Generating account statement") session_maker = sessionmaker(bind=_engine, expire_on_commit=False) with session_maker() as session: account_statement = ( @@ -32,6 +37,7 @@ def account_statement_generator(account_statement_id: int): ) if not account_statement: + _logger.error("Account statement not found") return account = ( @@ -45,6 +51,7 @@ def account_statement_generator(account_statement_id: int): ) if not account: + _logger.error("Account not found") return account_logs = ( @@ -58,6 +65,7 @@ def account_statement_generator(account_statement_id: int): ) if not account_logs: + _logger.error("Account logs not found") return mt940_writer = Mt940Writer.get_component() @@ -116,4 +124,5 @@ def account_statement_generator(account_statement_id: int): ) mt940_statement = mt940_writer.format_statement(statement) account_statement.account_statement_lob = str(mt940_statement) + _logger.info("Account statement generated successfully") session.commit() diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py index e7dcdab..2ed0a35 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py @@ -26,6 +26,7 @@ @celery_app.task(name="process_payments_beat_producer") def process_payments_beat_producer(): + _logger.info("Processing payments") session_maker = sessionmaker(bind=_engine, expire_on_commit=False) with session_maker() as session: initiate_payment_batch_requests = ( @@ -43,17 +44,21 @@ def process_payments_beat_producer(): ) for initiate_payment_batch_request in initiate_payment_batch_requests: + _logger.info( + f"Initiating payment processing for batch: {initiate_payment_batch_request.batch_id}" + ) celery_app.send_task( "process_payments_worker", args=[initiate_payment_batch_request.batch_id], queue="g2p_bridge_celery_worker_tasks", ) - + _logger.info("Payments processing initiated") session.commit() @celery_app.task(name="process_payments_worker") def process_payments_worker(payment_request_batch_id: str): + _logger.info(f"Processing payments for batch: {payment_request_batch_id}") session_maker = sessionmaker(bind=_engine, expire_on_commit=False) with session_maker() as session: initiate_payment_batch_request = ( @@ -106,7 +111,7 @@ def process_payments_worker(payment_request_batch_id: str): # End of loop generate_failures(failure_logs, session) - + _logger.info(f"Payments processed for batch: {payment_request_batch_id}") session.commit() except Exception as e: _logger.error(f"Error processing payment: {e}") @@ -137,6 +142,7 @@ def construct_accounting_log(initiate_payment_request: InitiatePaymentRequest): def generate_failures(failure_logs: List[AccountingLog], session): + _logger.info("Generating failures") failure_reasons = [ "ACCOUNT_CLOSED", "ACCOUNT_NOT_FOUND", From 1cdfdd6411beda5dc8a5266b5bbef5c7eb0ed3aa Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:44:11 +0530 Subject: [PATCH 13/16] add example env file --- openg2p-g2p-bridge-example-bank-celery/.env.example | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 openg2p-g2p-bridge-example-bank-celery/.env.example diff --git a/openg2p-g2p-bridge-example-bank-celery/.env.example b/openg2p-g2p-bridge-example-bank-celery/.env.example new file mode 100644 index 0000000..c96f531 --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-celery/.env.example @@ -0,0 +1,2 @@ +EXAMPLE_BANK_HOST=0.0.0.0 +EXAMPLE_BANK_PORT=8003 From 01c92681433137d9093af9fd22bdbb041c65f778 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:45:16 +0530 Subject: [PATCH 14/16] add example env file --- openg2p-g2p-bridge-example-bank-celery/.env.example | 2 ++ .../src/openg2p_g2p_bridge_example_bank_celery/config.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openg2p-g2p-bridge-example-bank-celery/.env.example b/openg2p-g2p-bridge-example-bank-celery/.env.example index c96f531..bb597ee 100644 --- a/openg2p-g2p-bridge-example-bank-celery/.env.example +++ b/openg2p-g2p-bridge-example-bank-celery/.env.example @@ -1,2 +1,4 @@ EXAMPLE_BANK_HOST=0.0.0.0 EXAMPLE_BANK_PORT=8003 +EXAMPLE_BANK_PROCESS_PAYMENT_FREQUENCY=3600 +EXAMPLE_BANK_PAYMENT_INITIATE_ATTEMPTS=3 \ No newline at end of file diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py index d5015c5..dbf5d97 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py @@ -20,5 +20,5 @@ class Settings(BaseSettings): db_dbname: str = "example_bank_db" db_driver: str = "postgresql" - process_payment_frequency: int = 10 + process_payment_frequency: int = 3600 payment_initiate_attempts: int = 3 From 5166bb0aaee0dd0b979c2134b76cf98e8544b1cc Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:52:01 +0530 Subject: [PATCH 15/16] Refactor models --- .../.env.example | 2 +- .../tasks/account_statement_generator.py | 2 +- .../models/__init__.py | 4 + .../models/account.py | 108 +----------------- .../models/account_statement.py | 45 ++++++++ .../models/payment_request.py | 71 ++++++++++++ 6 files changed, 123 insertions(+), 109 deletions(-) create mode 100644 openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account_statement.py create mode 100644 openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/payment_request.py diff --git a/openg2p-g2p-bridge-example-bank-celery/.env.example b/openg2p-g2p-bridge-example-bank-celery/.env.example index bb597ee..be05241 100644 --- a/openg2p-g2p-bridge-example-bank-celery/.env.example +++ b/openg2p-g2p-bridge-example-bank-celery/.env.example @@ -1,4 +1,4 @@ EXAMPLE_BANK_HOST=0.0.0.0 EXAMPLE_BANK_PORT=8003 EXAMPLE_BANK_PROCESS_PAYMENT_FREQUENCY=3600 -EXAMPLE_BANK_PAYMENT_INITIATE_ATTEMPTS=3 \ No newline at end of file +EXAMPLE_BANK_PAYMENT_INITIATE_ATTEMPTS=3 diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py index 4be1080..cdcb960 100644 --- a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py +++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/account_statement_generator.py @@ -1,3 +1,4 @@ +import logging from decimal import Decimal from openg2p_g2p_bridge_example_bank_models.models import ( @@ -16,7 +17,6 @@ _config = Settings.get_config() _engine = get_engine() -import logging _logger = logging.getLogger(_config.logging_default_logger_name) diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py index ad8c14b..bbbeb0c 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py @@ -1,8 +1,12 @@ from .account import ( Account, +) +from .account_statement import ( AccountingLog, AccountStatement, DebitCreditTypes, +) +from .payment_request import ( FundBlock, InitiatePaymentBatchRequest, InitiatePaymentRequest, diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py index a85ce75..096a0cb 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py @@ -1,23 +1,8 @@ -from datetime import datetime -from enum import Enum - from openg2p_fastapi_common.models import BaseORMModelWithTimes -from sqlalchemy import DateTime, Float, Integer, String, Text -from sqlalchemy import Enum as SqlEnum +from sqlalchemy import Float, String from sqlalchemy.orm import Mapped, mapped_column -class PaymentStatus(Enum): - PENDING = "PENDING" - SUCCESS = "SUCCESS" - FAILED = "FAILED" - - -class DebitCreditTypes(Enum): - DEBIT = "debit" - CREDIT = "credit" - - class Account(BaseORMModelWithTimes): __tablename__ = "accounts" account_holder_name: Mapped[str] = mapped_column(String) @@ -26,94 +11,3 @@ class Account(BaseORMModelWithTimes): book_balance: Mapped[float] = mapped_column(Float) available_balance: Mapped[float] = mapped_column(Float) blocked_amount: Mapped[float] = mapped_column(Float, default=0) - - -class FundBlock(BaseORMModelWithTimes): - __tablename__ = "fund_blocks" - block_reference_no: Mapped[str] = mapped_column(String, index=True, unique=True) - account_number: Mapped[str] = mapped_column(String) - currency: Mapped[str] = mapped_column(String) - amount: Mapped[float] = mapped_column(Float) - amount_released: Mapped[float] = mapped_column(Float, default=0) - - -class InitiatePaymentBatchRequest(BaseORMModelWithTimes): - batch_id: Mapped[str] = mapped_column(String, index=True, unique=True) - payment_initiate_attempts: Mapped[int] = mapped_column(Integer, default=0) - payment_status: Mapped[PaymentStatus] = mapped_column( - SqlEnum(PaymentStatus), default=PaymentStatus.PENDING - ) - - -class InitiatePaymentRequest(BaseORMModelWithTimes): - __tablename__ = "initiate_payment_requests" - batch_id: Mapped[str] = mapped_column(String, index=True, unique=False) - payment_reference_number = mapped_column( - String, index=True, unique=True - ) # disbursement id - remitting_account: Mapped[str] = mapped_column(String, nullable=False) - remitting_account_currency: Mapped[str] = mapped_column(String, nullable=False) - payment_amount: Mapped[float] = mapped_column(Float, nullable=False) - payment_date: Mapped[str] = mapped_column(String, nullable=False) - funds_blocked_reference_number: Mapped[str] = mapped_column(String, nullable=False) - - beneficiary_name: Mapped[str] = mapped_column(String) - beneficiary_account: Mapped[str] = mapped_column(String) - beneficiary_account_currency: Mapped[str] = mapped_column(String) - beneficiary_account_type: Mapped[str] = mapped_column(String) - beneficiary_bank_code: Mapped[str] = mapped_column(String) - beneficiary_branch_code: Mapped[str] = mapped_column(String) - - beneficiary_mobile_wallet_provider: Mapped[str] = mapped_column( - String, nullable=True - ) - beneficiary_phone_no: Mapped[str] = mapped_column(String, nullable=True) - - beneficiary_email: Mapped[str] = mapped_column(String, nullable=True) - beneficiary_email_wallet_provider: Mapped[str] = mapped_column( - String, nullable=True - ) - - narrative_1: Mapped[str] = mapped_column( - String, nullable=True - ) # disbursement narrative - narrative_2: Mapped[str] = mapped_column(String, nullable=True) # program pneumonic - narrative_3: Mapped[str] = mapped_column( - String, nullable=True - ) # cycle code pneumonic - narrative_4: Mapped[str] = mapped_column(String, nullable=True) # beneficiary id - narrative_5: Mapped[str] = mapped_column(String, nullable=True) - narrative_6: Mapped[str] = mapped_column(String, nullable=True) - - -class AccountingLog(BaseORMModelWithTimes): - __tablename__ = "accounting_logs" - reference_no: Mapped[str] = mapped_column(String, index=True, unique=True) - corresponding_block_reference_no: Mapped[str] = mapped_column(String, nullable=True) - customer_reference_no: Mapped[str] = mapped_column(String, index=True) - debit_credit: Mapped[DebitCreditTypes] = mapped_column(SqlEnum(DebitCreditTypes)) - account_number: Mapped[str] = mapped_column(String, index=True) - transaction_amount: Mapped[float] = mapped_column(Float) - transaction_date: Mapped[datetime] = mapped_column(DateTime) - transaction_currency: Mapped[str] = mapped_column(String) - transaction_code: Mapped[str] = mapped_column(String, nullable=True) - - narrative_1: Mapped[str] = mapped_column(String, nullable=True) # disbursement id - narrative_2: Mapped[str] = mapped_column(String, nullable=True) # beneficiary id - narrative_3: Mapped[str] = mapped_column(String, nullable=True) # program pneumonic - narrative_4: Mapped[str] = mapped_column( - String, nullable=True - ) # cycle code pneumonic - narrative_5: Mapped[str] = mapped_column(String, nullable=True) # beneficiary email - narrative_6: Mapped[str] = mapped_column( - String, nullable=True - ) # beneficiary phone number - - -class AccountStatement(BaseORMModelWithTimes): - __tablename__ = "account_statements" - account_number: Mapped[str] = mapped_column(String, index=True) - account_statement_lob: Mapped[str] = mapped_column(Text, nullable=True) - account_statement_date: Mapped[datetime.date] = mapped_column( - DateTime, default=datetime.date(datetime.utcnow()) - ) diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account_statement.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account_statement.py new file mode 100644 index 0000000..ffc8aa3 --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account_statement.py @@ -0,0 +1,45 @@ +from datetime import datetime +from enum import Enum + +from openg2p_fastapi_common.models import BaseORMModelWithTimes +from sqlalchemy import DateTime, Float, String, Text +from sqlalchemy import Enum as SqlEnum +from sqlalchemy.orm import Mapped, mapped_column + + +class DebitCreditTypes(Enum): + DEBIT = "debit" + CREDIT = "credit" + + +class AccountStatement(BaseORMModelWithTimes): + __tablename__ = "account_statements" + account_number: Mapped[str] = mapped_column(String, index=True) + account_statement_lob: Mapped[str] = mapped_column(Text, nullable=True) + account_statement_date: Mapped[datetime.date] = mapped_column( + DateTime, default=datetime.date(datetime.utcnow()) + ) + + +class AccountingLog(BaseORMModelWithTimes): + __tablename__ = "accounting_logs" + reference_no: Mapped[str] = mapped_column(String, index=True, unique=True) + corresponding_block_reference_no: Mapped[str] = mapped_column(String, nullable=True) + customer_reference_no: Mapped[str] = mapped_column(String, index=True) + debit_credit: Mapped[DebitCreditTypes] = mapped_column(SqlEnum(DebitCreditTypes)) + account_number: Mapped[str] = mapped_column(String, index=True) + transaction_amount: Mapped[float] = mapped_column(Float) + transaction_date: Mapped[datetime] = mapped_column(DateTime) + transaction_currency: Mapped[str] = mapped_column(String) + transaction_code: Mapped[str] = mapped_column(String, nullable=True) + + narrative_1: Mapped[str] = mapped_column(String, nullable=True) # disbursement id + narrative_2: Mapped[str] = mapped_column(String, nullable=True) # beneficiary id + narrative_3: Mapped[str] = mapped_column(String, nullable=True) # program pneumonic + narrative_4: Mapped[str] = mapped_column( + String, nullable=True + ) # cycle code pneumonic + narrative_5: Mapped[str] = mapped_column(String, nullable=True) # beneficiary email + narrative_6: Mapped[str] = mapped_column( + String, nullable=True + ) # beneficiary phone number diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/payment_request.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/payment_request.py new file mode 100644 index 0000000..c6f1168 --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/payment_request.py @@ -0,0 +1,71 @@ +from enum import Enum + +from openg2p_fastapi_common.models import BaseORMModelWithTimes +from sqlalchemy import Enum as SqlEnum +from sqlalchemy import Float, Integer, String +from sqlalchemy.orm import Mapped, mapped_column + + +class PaymentStatus(Enum): + PENDING = "PENDING" + SUCCESS = "SUCCESS" + FAILED = "FAILED" + + +class FundBlock(BaseORMModelWithTimes): + __tablename__ = "fund_blocks" + block_reference_no: Mapped[str] = mapped_column(String, index=True, unique=True) + account_number: Mapped[str] = mapped_column(String) + currency: Mapped[str] = mapped_column(String) + amount: Mapped[float] = mapped_column(Float) + amount_released: Mapped[float] = mapped_column(Float, default=0) + + +class InitiatePaymentBatchRequest(BaseORMModelWithTimes): + __tablename__ = "initiate_payment_batch_requests" + batch_id: Mapped[str] = mapped_column(String, index=True, unique=True) + payment_initiate_attempts: Mapped[int] = mapped_column(Integer, default=0) + payment_status: Mapped[PaymentStatus] = mapped_column( + SqlEnum(PaymentStatus), default=PaymentStatus.PENDING + ) + + +class InitiatePaymentRequest(BaseORMModelWithTimes): + __tablename__ = "initiate_payment_requests" + batch_id: Mapped[str] = mapped_column(String, index=True, unique=False) + payment_reference_number = mapped_column( + String, index=True, unique=True + ) # disbursement id + remitting_account: Mapped[str] = mapped_column(String, nullable=False) + remitting_account_currency: Mapped[str] = mapped_column(String, nullable=False) + payment_amount: Mapped[float] = mapped_column(Float, nullable=False) + payment_date: Mapped[str] = mapped_column(String, nullable=False) + funds_blocked_reference_number: Mapped[str] = mapped_column(String, nullable=False) + + beneficiary_name: Mapped[str] = mapped_column(String) + beneficiary_account: Mapped[str] = mapped_column(String) + beneficiary_account_currency: Mapped[str] = mapped_column(String) + beneficiary_account_type: Mapped[str] = mapped_column(String) + beneficiary_bank_code: Mapped[str] = mapped_column(String) + beneficiary_branch_code: Mapped[str] = mapped_column(String) + + beneficiary_mobile_wallet_provider: Mapped[str] = mapped_column( + String, nullable=True + ) + beneficiary_phone_no: Mapped[str] = mapped_column(String, nullable=True) + + beneficiary_email: Mapped[str] = mapped_column(String, nullable=True) + beneficiary_email_wallet_provider: Mapped[str] = mapped_column( + String, nullable=True + ) + + narrative_1: Mapped[str] = mapped_column( + String, nullable=True + ) # disbursement narrative + narrative_2: Mapped[str] = mapped_column(String, nullable=True) # program pneumonic + narrative_3: Mapped[str] = mapped_column( + String, nullable=True + ) # cycle code pneumonic + narrative_4: Mapped[str] = mapped_column(String, nullable=True) # beneficiary id + narrative_5: Mapped[str] = mapped_column(String, nullable=True) + narrative_6: Mapped[str] = mapped_column(String, nullable=True) From ec51c5d623019a488f9d53c0fe259c07dfb227c5 Mon Sep 17 00:00:00 2001 From: PSNAppZ Date: Wed, 7 Aug 2024 17:57:42 +0530 Subject: [PATCH 16/16] Refactor schemas --- .../controllers/initiate_payment.py | 10 ++--- .../schemas/__init__.py | 12 ++++-- .../schemas/account.py | 28 ++++++++++++++ .../schemas/account_statement.py | 13 +++++++ .../{fund_schemas.py => payment_request.py} | 37 +------------------ 5 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account.py create mode 100644 openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account_statement.py rename openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/{fund_schemas.py => payment_request.py} (56%) diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py index e5a5138..19b24b2 100644 --- a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py +++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py @@ -11,7 +11,7 @@ ) from openg2p_g2p_bridge_example_bank_models.schemas import ( InitiatePaymentPayload, - InitiatorPaymentResponse, + InitiatePaymentResponse, ) from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.future import select @@ -31,13 +31,13 @@ def __init__(self, **kwargs): self.router.add_api_route( "/initiate_payment", self.initiate_payment, - response_model=InitiatorPaymentResponse, + response_model=InitiatePaymentResponse, methods=["POST"], ) async def initiate_payment( self, initiate_payment_payloads: List[InitiatePaymentPayload] - ) -> InitiatorPaymentResponse: + ) -> InitiatePaymentResponse: _logger.info("Initiating payment") session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False) async with session_maker() as session: @@ -65,7 +65,7 @@ async def initiate_payment( _logger.error( "Invalid funds block reference or mismatch in details" ) - return InitiatorPaymentResponse( + return InitiatePaymentResponse( status="failed", error_message="Invalid funds block reference or mismatch in details", ) @@ -101,4 +101,4 @@ async def initiate_payment( session.add_all(initiate_payment_requests) await session.commit() _logger.info("Payment initiated successfully") - return InitiatorPaymentResponse(status="success", error_message="") + return InitiatePaymentResponse(status="success", error_message="") diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py index 05ecf21..1e4879d 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py @@ -1,10 +1,14 @@ -from .fund_schemas import ( - AccountStatementRequest, - AccountStatementResponse, +from .account import ( BlockFundsRequest, BlockFundsResponse, CheckFundRequest, CheckFundResponse, +) +from .account_statement import ( + AccountStatementRequest, + AccountStatementResponse, +) +from .payment_request import ( InitiatePaymentPayload, - InitiatorPaymentResponse, + InitiatePaymentResponse, ) diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account.py new file mode 100644 index 0000000..a0bd59c --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account.py @@ -0,0 +1,28 @@ +from typing import Optional + +from pydantic import BaseModel + + +class CheckFundRequest(BaseModel): + account_number: str + account_currency: str + total_funds_needed: float + + +class CheckFundResponse(BaseModel): + status: str + account_number: str + has_sufficient_funds: bool + error_message: Optional[str] = None + + +class BlockFundsRequest(BaseModel): + account_number: str + currency: str + amount: float + + +class BlockFundsResponse(BaseModel): + status: str + block_reference_no: str + error_message: Optional[str] = None diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account_statement.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account_statement.py new file mode 100644 index 0000000..0689abe --- /dev/null +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/account_statement.py @@ -0,0 +1,13 @@ +from typing import Optional + +from pydantic import BaseModel + + +class AccountStatementRequest(BaseModel): + program_account_number: str + + +class AccountStatementResponse(BaseModel): + status: str + account_statement_id: Optional[str] = None + error_message: Optional[str] = None diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/payment_request.py similarity index 56% rename from openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py rename to openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/payment_request.py index ab50422..fc14ad8 100644 --- a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py +++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/payment_request.py @@ -3,31 +3,6 @@ from pydantic import BaseModel -class CheckFundRequest(BaseModel): - account_number: str - account_currency: str - total_funds_needed: float - - -class CheckFundResponse(BaseModel): - status: str - account_number: str - has_sufficient_funds: bool - error_message: Optional[str] = None - - -class BlockFundsRequest(BaseModel): - account_number: str - currency: str - amount: float - - -class BlockFundsResponse(BaseModel): - status: str - block_reference_no: str - error_message: Optional[str] = None - - class InitiatePaymentPayload(BaseModel): payment_reference_number: str remitting_account: str @@ -58,16 +33,6 @@ class InitiatePaymentPayload(BaseModel): payment_date: str -class InitiatorPaymentResponse(BaseModel): - status: str - error_message: Optional[str] = None - - -class AccountStatementRequest(BaseModel): - program_account_number: str - - -class AccountStatementResponse(BaseModel): +class InitiatePaymentResponse(BaseModel): status: str - account_statement_id: Optional[str] = None error_message: Optional[str] = None