From a196d4d3c4384b74d315a065adf78bf151f8dcd8 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:06:51 +0100 Subject: [PATCH 1/6] [add]: ref_id and ref_model for transactions. And db session and rollback for transactions --- app/api/services/base_service.py | 11 +++-- app/api/services/customer_consent.py | 16 ++++--- app/api/services/eligibility_check.py | 14 +++--- app/api/services/loan_status.py | 16 ++++--- app/api/services/notification_callback.py | 3 +- app/api/services/provide_loan.py | 47 ++++++++++--------- app/api/services/repayment.py | 30 ++++++------ app/api/services/select_offer.py | 14 +++--- app/models/account.py | 2 - app/models/customer.py | 2 - app/models/loan.py | 5 +- app/models/repayment.py | 2 - app/models/transaction.py | 17 ++++--- ...9_migration_on_thu_apr_10_21_50_01_utc_.py | 32 +++++++++++++ 14 files changed, 123 insertions(+), 88 deletions(-) create mode 100644 migrations/versions/1340e7e578b9_migration_on_thu_apr_10_21_50_01_utc_.py diff --git a/app/api/services/base_service.py b/app/api/services/base_service.py index aa678e0..55d03c3 100644 --- a/app/api/services/base_service.py +++ b/app/api/services/base_service.py @@ -44,15 +44,16 @@ class BaseService: return is_valid @classmethod - def log_transaction(cls, validated_data): + def log_transaction(cls, validated_data,): """ Create a new transaction. """ return Transaction.create_transaction( - transaction_id =validated_data.get("transactionId"), - account_id=validated_data.get("accountId"), - type=cls.TRANSACTION_TYPE, - channel=validated_data.get("channel"), + transaction_id = validated_data.get("transactionId"), + ref_id = validated_data.get("refId") or validated_data.get("accountId"), + ref_model = validated_data.get("refModel", "Account"), + type = cls.TRANSACTION_TYPE, + channel = validated_data.get("channel"), ) @classmethod diff --git a/app/api/services/customer_consent.py b/app/api/services/customer_consent.py index 2d1048a..da50864 100644 --- a/app/api/services/customer_consent.py +++ b/app/api/services/customer_consent.py @@ -4,7 +4,8 @@ from marshmallow import ValidationError from app.utils.logger import logger from app.api.schemas.customer_consent import CustomerConsentSchema from app.api.services.base_service import BaseService -from app.api.enums import TransactionType +from app.api.enums import TransactionType +from app.extensions import db class CustomerConsentService(BaseService): @@ -28,13 +29,14 @@ class CustomerConsentService(BaseService): customer_id = validated_data.get('customerId') if(CustomerConsentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - transaction = CustomerConsentService.log_transaction(validated_data = validated_data) + with db.session.begin(): + transaction = CustomerConsentService.log_transaction(validated_data = validated_data) - if not transaction: - logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 else: return jsonify({ "message": "Invalid Customer or Account" diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py index d7dff72..840424b 100644 --- a/app/api/services/eligibility_check.py +++ b/app/api/services/eligibility_check.py @@ -5,6 +5,7 @@ from app.api.schemas.eligibility_check import EligibilityCheckSchema from marshmallow import ValidationError from app.api.enums import TransactionType from app.api.integrations import SimbrellaIntegration +from app.extensions import db class EligibilityCheckService(BaseService): TRANSACTION_TYPE = TransactionType.ELIGIBILITY_CHECK @@ -31,13 +32,14 @@ class EligibilityCheckService(BaseService): customer = EligibilityCheckService.get_or_create_customer(validated_data = validated_data) if (EligibilityCheckService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - transaction = EligibilityCheckService.log_transaction(validated_data = validated_data) + with db.session.begin(): + transaction = EligibilityCheckService.log_transaction(validated_data = validated_data) - if not transaction: - logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 else: return jsonify({ "message": "Invalid Customer or Account" diff --git a/app/api/services/loan_status.py b/app/api/services/loan_status.py index cdd161c..2e03b29 100644 --- a/app/api/services/loan_status.py +++ b/app/api/services/loan_status.py @@ -3,7 +3,8 @@ from marshmallow import ValidationError from app.utils.logger import logger from app.api.schemas.loan_status import LoanStatusSchema from app.api.services.base_service import BaseService -from app.api.enums import TransactionType +from app.api.enums import TransactionType +from app.extensions import db class LoanStatusService(BaseService): @@ -27,13 +28,14 @@ class LoanStatusService(BaseService): account = customer.accounts[0] if (LoanStatusService.validate_account_ownership(account_id = account.id, customer_id = customer_id)): - transaction = LoanStatusService.log_transaction(validated_data = validated_data) + with db.session.begin(): + transaction = LoanStatusService.log_transaction(validated_data = validated_data) - if not transaction: - logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 else: return jsonify({ "message": "Invalid Customer or Account" diff --git a/app/api/services/notification_callback.py b/app/api/services/notification_callback.py index b3a1c8b..6257b2b 100644 --- a/app/api/services/notification_callback.py +++ b/app/api/services/notification_callback.py @@ -3,7 +3,8 @@ from marshmallow import ValidationError from app.api.services.base_service import BaseService from app.api.enums import TransactionType from app.utils.logger import logger -from app.api.schemas.notification_callback import NotificationCallbackSchema +from app.api.schemas.notification_callback import NotificationCallbackSchema +from app.extensions import db class NotificationCallbackService(BaseService): TRANSACTION_TYPE = TransactionType.NOTIFICATION_CALLBACK diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py index e268a92..e62fa04 100644 --- a/app/api/services/provide_loan.py +++ b/app/api/services/provide_loan.py @@ -8,6 +8,7 @@ from app.api.schemas.provide_loan import ProvideLoanSchema from threading import Thread from app.models.loan import Loan from app.api.enums import LoanStatus +from app.extensions import db class ProvideLoanService(BaseService): TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN @@ -33,29 +34,33 @@ class ProvideLoanService(BaseService): if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - # Save the loan details - loan = Loan.create_loan( - customer_id=customer_id, - account_id=account_id, - offer_id=validated_data.get('offerId'), - principal_amount=validated_data.get('requestedAmount'), - status=LoanStatus.ACTIVE - ) + with db.session.begin(): + # Save the loan details + loan = Loan.create_loan( + customer_id=customer_id, + account_id=account_id, + offer_id=validated_data.get('offerId'), + principal_amount=validated_data.get('requestedAmount'), + status=LoanStatus.ACTIVE + ) - if not loan: - logger.error(f"Failed to save loan details") - return jsonify({ - "message": "Failed to save loan details." - }), 400 - - # Log Transaction - transaction = ProvideLoanService.log_transaction(validated_data = validated_data) + if not loan: + logger.error(f"Failed to save loan details") + return jsonify({ + "message": "Failed to save loan details." + }), 400 + + validated_data['refId'] = loan.id + validated_data['refModel'] = "loan" + + # Log Transaction + transaction = ProvideLoanService.log_transaction(validated_data = validated_data) - if not transaction: - logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 diff --git a/app/api/services/repayment.py b/app/api/services/repayment.py index d92f8b8..f914310 100644 --- a/app/api/services/repayment.py +++ b/app/api/services/repayment.py @@ -7,7 +7,8 @@ from app.utils.logger import logger from app.api.schemas.repayment import RepaymentSchema from app.api.services.base_service import BaseService from app.api.enums import TransactionType -from threading import Thread +from threading import Thread +from app.extensions import db class RepaymentService(BaseService): TRANSACTION_TYPE = TransactionType.REPAYMENT @@ -26,20 +27,17 @@ class RepaymentService(BaseService): try: validated_data = RepaymentService.validate_data(data, RepaymentSchema()) customer_id = validated_data.get('customerId') - customer = RepaymentService.get_or_create_customer(validated_data) - account = customer.accounts[0] - validated_data['accountId'] = account.id request_id = validated_data.get('requestId') loan_id = validated_data.get('debtId') + product_id = validated_data.get('productId') - - if (RepaymentService.validate_account_ownership(account_id = account.id, customer_id = customer_id)): - - # Save the repayment details + + with db.session.begin(): + # Save the repayment details repayment = Repayment.create_repayment( customer_id = customer_id, loan_id = loan_id, - product_id = validated_data.get('productId') + product_id = product_id ) @@ -49,6 +47,9 @@ class RepaymentService(BaseService): "message": "Failed to save repayment details." }), 400 + validated_data['refId'] = repayment.id + validated_data['refModel'] = "repayment" + #Update Loan status Loan.update_status(loan_id = loan_id, status = LoanStatus.REPAID) @@ -59,16 +60,13 @@ class RepaymentService(BaseService): return jsonify({ "message": "Failed to log transaction." }), 400 - else: - return jsonify({ - "message": "Invalid Customer or Account" - }), 400 + # Simulated processing logic response_data = { - "customerId": "CN621868", - "productId": "101", - "debtId": "273194670", + "customerId": customer_id, + "productId": product_id, + "debtId": loan_id, "resultCode": "00", "resultDescription": "Successful" } diff --git a/app/api/services/select_offer.py b/app/api/services/select_offer.py index 1f07cd5..4b4c4bb 100644 --- a/app/api/services/select_offer.py +++ b/app/api/services/select_offer.py @@ -4,6 +4,7 @@ from app.api.services.base_service import BaseService from app.api.enums import TransactionType from app.utils.logger import logger from app.api.schemas.select_offer import SelectOfferSchema +from app.extensions import db class SelectOfferService(BaseService): TRANSACTION_TYPE = TransactionType.SELECT_OFFER @@ -25,13 +26,14 @@ class SelectOfferService(BaseService): customer_id = validated_data.get('customerId') if (SelectOfferService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - transaction = SelectOfferService.log_transaction(validated_data = validated_data) + with db.session.begin(): + transaction = SelectOfferService.log_transaction(validated_data = validated_data) - if not transaction: - logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." + }), 400 else: return jsonify({ "message": "Invalid Customer or Account" diff --git a/app/models/account.py b/app/models/account.py index af4501e..e8a90ce 100644 --- a/app/models/account.py +++ b/app/models/account.py @@ -31,9 +31,7 @@ class Account(db.Model): try: db.session.add(account) - db.session.commit() except IntegrityError as err: - db.session.rollback() raise ValueError(f"Database integrity error: {err}") return account diff --git a/app/models/customer.py b/app/models/customer.py index e0fb316..c36a11a 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -44,9 +44,7 @@ class Customer(db.Model): account_type=account_type ) - db.session.commit() except IntegrityError as err: - db.session.rollback() raise ValueError(f"Database integrity error: {err}") return customer diff --git a/app/models/loan.py b/app/models/loan.py index 0fec3d2..252fe74 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -47,9 +47,7 @@ class Loan(db.Model): try: db.session.add(loan) - db.session.commit() except IntegrityError as err: - db.session.rollback() raise ValueError(f"Database integrity error: {err}") return loan @@ -92,8 +90,7 @@ class Loan(db.Model): # Update loan status and the updated_at timestamp loan.status = status - - db.session.commit() + def __repr__(self): return f'' \ No newline at end of file diff --git a/app/models/repayment.py b/app/models/repayment.py index 06b872c..f58e1a8 100644 --- a/app/models/repayment.py +++ b/app/models/repayment.py @@ -44,9 +44,7 @@ class Repayment(db.Model): try: db.session.add(repayment) - db.session.commit() except IntegrityError as err: - db.session.rollback() raise ValueError(f"Database integrity error: {err}") return repayment diff --git a/app/models/transaction.py b/app/models/transaction.py index 5bde2db..1775d2c 100644 --- a/app/models/transaction.py +++ b/app/models/transaction.py @@ -10,9 +10,9 @@ class Transaction(db.Model): primary_key=True, autoincrement=True, ) - #id = db.Column(db.Int, primary_key=True) transaction_id = db.Column(db.String(50), nullable=False) - account_id = db.Column(db.String(50), nullable=False) + ref_id = db.Column(db.String(50), nullable=False) + ref_model = db.Column(db.String(50), nullable=True, default='account') type = db.Column(db.String(50), nullable=False) channel = db.Column(db.String(50), nullable=False) created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) @@ -22,7 +22,7 @@ class Transaction(db.Model): return f'' @classmethod - def create_transaction(cls, transaction_id, account_id, type, channel): + def create_transaction(cls, transaction_id, ref_id, ref_model, type, channel): # if cls.query.filter_by(transaction_id=transaction_id).first(): # raise ValueError("Duplicate Transaction") @@ -33,17 +33,16 @@ class Transaction(db.Model): transaction = cls( - transaction_id=transaction_id, - account_id=account_id, - type=type, - channel=channel + transaction_id = transaction_id, + ref_id = ref_id, + ref_model = ref_model, + type = type, + channel = channel ) try: db.session.add(transaction) - db.session.commit() except IntegrityError as err: - db.session.rollback() raise ValueError(f"Database integrity error: {err}") return transaction diff --git a/migrations/versions/1340e7e578b9_migration_on_thu_apr_10_21_50_01_utc_.py b/migrations/versions/1340e7e578b9_migration_on_thu_apr_10_21_50_01_utc_.py new file mode 100644 index 0000000..92e0eef --- /dev/null +++ b/migrations/versions/1340e7e578b9_migration_on_thu_apr_10_21_50_01_utc_.py @@ -0,0 +1,32 @@ +"""Migration on Thu Apr 10 21:50:01 UTC 2025 + +Revision ID: 1340e7e578b9 +Revises: b8f6fd76ead8 +Create Date: 2025-04-10 21:50:32.113149 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1340e7e578b9' +down_revision = 'b8f6fd76ead8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('transactions', schema=None) as batch_op: + batch_op.add_column(sa.Column('ref_model', sa.String(length=50), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('transactions', schema=None) as batch_op: + batch_op.drop_column('ref_model') + + # ### end Alembic commands ### From d397c834f4fbaf1aa02717ec3cb445889aa4ed23 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:45:40 +0100 Subject: [PATCH 2/6] [add]: db sessions --- app/api/services/customer_consent.py | 40 +++++----- app/api/services/eligibility_check.py | 107 ++++++++++++------------- app/api/services/loan_status.py | 76 +++++++++--------- app/api/services/provide_loan.py | 62 ++++++++------- app/api/services/repayment.py | 53 +++++++------ app/api/services/select_offer.py | 108 +++++++++++++------------- app/models/repayment.py | 2 +- 7 files changed, 231 insertions(+), 217 deletions(-) diff --git a/app/api/services/customer_consent.py b/app/api/services/customer_consent.py index da50864..a6b4f07 100644 --- a/app/api/services/customer_consent.py +++ b/app/api/services/customer_consent.py @@ -23,13 +23,13 @@ class CustomerConsentService(BaseService): dict: A standardized response. """ try: + with db.session.begin(): + validated_data = CustomerConsentService.validate_data(data, CustomerConsentSchema()) + account_id = validated_data.get('accountId') + customer_id = validated_data.get('customerId') - validated_data = CustomerConsentService.validate_data(data, CustomerConsentSchema()) - account_id = validated_data.get('accountId') - customer_id = validated_data.get('customerId') - - if(CustomerConsentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - with db.session.begin(): + if(CustomerConsentService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): + transaction = CustomerConsentService.log_transaction(validated_data = validated_data) if not transaction: @@ -37,23 +37,25 @@ class CustomerConsentService(BaseService): return jsonify({ "message": "Failed to log transaction." }), 400 - else: - return jsonify({ - "message": "Invalid Customer or Account" - }), 400 + else: + return jsonify({ + "message": "Invalid Customer or Account" + }), 400 - - # Simulated processing logic - response_data = { - "resultCode": "00", - "resultDescription": "Request is received" - } + + # Simulated processing logic + response_data = { + "resultCode": "00", + "resultDescription": "Request is received" + } - return response_data - + db.session.commit() + return response_data + except ValidationError as err: logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": "Validation exception" @@ -61,6 +63,7 @@ class CustomerConsentService(BaseService): except ValueError as err: logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": str(err) @@ -68,6 +71,7 @@ class CustomerConsentService(BaseService): except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() return jsonify({ "message": "Internal Server Error" }) , 500 \ No newline at end of file diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py index 840424b..256ddf3 100644 --- a/app/api/services/eligibility_check.py +++ b/app/api/services/eligibility_check.py @@ -22,17 +22,18 @@ class EligibilityCheckService(BaseService): dict: A standardized response. """ try: + with db.session.begin(): - validated_data = EligibilityCheckService.validate_data(data, EligibilityCheckSchema()) - account_id = validated_data.get('accountId') - customer_id = validated_data.get('customerId') - transactionId = validated_data.get('transactionId') - msisdn = validated_data.get('msisdn') + validated_data = EligibilityCheckService.validate_data(data, EligibilityCheckSchema()) + account_id = validated_data.get('accountId') + customer_id = validated_data.get('customerId') + transactionId = validated_data.get('transactionId') + msisdn = validated_data.get('msisdn') - customer = EligibilityCheckService.get_or_create_customer(validated_data = validated_data) + customer = EligibilityCheckService.get_or_create_customer(validated_data = validated_data) - if (EligibilityCheckService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - with db.session.begin(): + if (EligibilityCheckService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): + transaction = EligibilityCheckService.log_transaction(validated_data = validated_data) if not transaction: @@ -40,54 +41,56 @@ class EligibilityCheckService(BaseService): return jsonify({ "message": "Failed to log transaction." }), 400 - else: - return jsonify({ - "message": "Invalid Customer or Account" - }), 400 - - # Call RACCheck - response = SimbrellaIntegration.rac_check( - customer_id = customer_id, - account_id = account_id, - transaction_id = transaction.id, - ) - logger.error(f"This is Response Returned ****** : {str(response)}") + + else: + return jsonify({ + "message": "Invalid Customer or Account" + }), 400 + + # Call RACCheck + response = SimbrellaIntegration.rac_check( + customer_id = customer_id, + account_id = account_id, + transaction_id = transaction.id, + ) + logger.error(f"This is Response Returned ****** : {str(response)}") - # this chck for error is not valid - logger.error(f"Check for ERROR is not valid ****** FIX THIS !!!!!") - #if "error" in response or response.get("status") != 200: - # return jsonify({"message": "RACCheck failed"}), 400 + # this chck for error is not valid + logger.error(f"Check for ERROR is not valid ****** FIX THIS !!!!!") + #if "error" in response or response.get("status") != 200: + # return jsonify({"message": "RACCheck failed"}), 400 - offers = [ - { - "offerId": "SAL90", - "productId": "2030", - "minAmount": 5000, - "maxAmount": 100000, - "tenor": 30 - }, - { - "offerId": "SAL30", - "productId": "2090", - "minAmount": 3000, - "maxAmount": 500000, - "tenor": 90 - } - ] + offers = [ + { + "offerId": "SAL90", + "productId": "2030", + "minAmount": 5000, + "maxAmount": 100000, + "tenor": 30 + }, + { + "offerId": "SAL30", + "productId": "2090", + "minAmount": 3000, + "maxAmount": 500000, + "tenor": 90 + } + ] - # Simulate processing - response_data = { - "customerId": customer_id, - "transactionId": transactionId, - "countryCode": "NG", - "msisdn": msisdn, - "eligibleOffers": offers, - "resultDescription": "Successful", - "resultCode": "00", - "accountId": account_id - } + # Simulate processing + response_data = { + "customerId": customer_id, + "transactionId": transactionId, + "countryCode": "NG", + "msisdn": msisdn, + "eligibleOffers": offers, + "resultDescription": "Successful", + "resultCode": "00", + "accountId": account_id + } - return response_data + return response_data + except ValidationError as err: logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") diff --git a/app/api/services/loan_status.py b/app/api/services/loan_status.py index 2e03b29..bfe8b68 100644 --- a/app/api/services/loan_status.py +++ b/app/api/services/loan_status.py @@ -22,55 +22,49 @@ class LoanStatusService(BaseService): dict: A standardized response. """ try: - validated_data = LoanStatusService.validate_data(data, LoanStatusSchema()) - customer_id = validated_data.get('customerId') - customer = LoanStatusService.get_or_create_customer(validated_data) - account = customer.accounts[0] + with db.session.begin(): + validated_data = LoanStatusService.validate_data(data, LoanStatusSchema()) + customer_id = validated_data.get('customerId') + + transaction = LoanStatusService.log_transaction(validated_data = validated_data) - if (LoanStatusService.validate_account_ownership(account_id = account.id, customer_id = customer_id)): - with db.session.begin(): - transaction = LoanStatusService.log_transaction(validated_data = validated_data) - - if not transaction: - logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 - else: - return jsonify({ - "message": "Invalid Customer or Account" + if not transaction: + logger.error(f"Failed to log transaction") + return jsonify({ + "message": "Failed to log transaction." }), 400 - + - loans = [ - { - "debtId": "123456789", - "loanDate": "2019-10-18 14:26:21.063", - "dueDate": "2019-11-20 14:26:21.063", - "currentLoanAmount": 8500, - "initialLoanAmount": 10000, - "defaultPenaltyFee": 0, - "continuousFee": 0, - "productId": "101" + loans = [ + { + "debtId": "123456789", + "loanDate": "2019-10-18 14:26:21.063", + "dueDate": "2019-11-20 14:26:21.063", + "currentLoanAmount": 8500, + "initialLoanAmount": 10000, + "defaultPenaltyFee": 0, + "continuousFee": 0, + "productId": "101" + } + ] + + # Simulated processing logic + response_data = { + "customerId": "CN621868", + "transactionId": "Tr201712RK9232P115", + "loans": loans, + "totalDebtAmount": 8500, + "resultCode": "00", + "resultDescription": "Successful" } - ] - # Simulated processing logic - response_data = { - "customerId": "CN621868", - "transactionId": "Tr201712RK9232P115", - "loans": loans, - "totalDebtAmount": 8500, - "resultCode": "00", - "resultDescription": "Successful" - } - - - return response_data + db.session.commit() + return response_data except ValidationError as err: logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": "Validation exception" @@ -78,6 +72,7 @@ class LoanStatusService(BaseService): except ValueError as err: logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": str(err) @@ -85,6 +80,7 @@ class LoanStatusService(BaseService): except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() return jsonify({ "message": "Internal Server Error" }) , 500 \ No newline at end of file diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py index e62fa04..b496265 100644 --- a/app/api/services/provide_loan.py +++ b/app/api/services/provide_loan.py @@ -26,15 +26,16 @@ class ProvideLoanService(BaseService): dict: A standardized response. """ try: - validated_data = ProvideLoanService.validate_data(data, ProvideLoanSchema()) - account_id = validated_data.get('accountId') - customer_id = validated_data.get('customerId') - request_id = validated_data.get('requestId') - transaction_id = validated_data.get('transactionId') + with db.session.begin(): + validated_data = ProvideLoanService.validate_data(data, ProvideLoanSchema()) + account_id = validated_data.get('accountId') + customer_id = validated_data.get('customerId') + request_id = validated_data.get('requestId') + transaction_id = validated_data.get('transactionId') - if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): + if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - with db.session.begin(): + # Save the loan details loan = Loan.create_loan( customer_id=customer_id, @@ -50,6 +51,7 @@ class ProvideLoanService(BaseService): "message": "Failed to save loan details." }), 400 + db.session.flush() validated_data['refId'] = loan.id validated_data['refModel'] = "loan" @@ -63,34 +65,36 @@ class ProvideLoanService(BaseService): }), 400 - - else: - return jsonify({ - "message": "Invalid Customer or Account" - }), 400 - - - response_data = { - "requestId": request_id, - "transactionId": transaction_id, - "customerId": customer_id, - "accountId": account_id, - "msisdn": "3451342", - "resultCode": "00", - "resultDescription": "Successful" - } + + else: + return jsonify({ + "message": "Invalid Customer or Account" + }), 400 + + + response_data = { + "requestId": request_id, + "transactionId": transaction_id, + "customerId": customer_id, + "accountId": account_id, + "msisdn": "3451342", + "resultCode": "00", + "resultDescription": "Successful" + } - # KafkaIntegration.send_loan_request(loan_data = response_data, request_id = request_id) - # Call Kafka in a background thread - thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id, "PROCESS_PAYMENT")) - thread.start() + # KafkaIntegration.send_loan_request(loan_data = response_data, request_id = request_id) + # Call Kafka in a background thread + thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id, "PROCESS_PAYMENT")) + thread.start() - return response_data + db.session.commit() + return response_data except ValidationError as err: logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": "Validation exception" @@ -98,6 +102,7 @@ class ProvideLoanService(BaseService): except ValueError as err: logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": str(err) @@ -105,6 +110,7 @@ class ProvideLoanService(BaseService): except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() return jsonify({ "message": "Internal Server Error" }) , 500 diff --git a/app/api/services/repayment.py b/app/api/services/repayment.py index f914310..33be586 100644 --- a/app/api/services/repayment.py +++ b/app/api/services/repayment.py @@ -25,14 +25,15 @@ class RepaymentService(BaseService): dict: A standardized response. """ try: - validated_data = RepaymentService.validate_data(data, RepaymentSchema()) - customer_id = validated_data.get('customerId') - request_id = validated_data.get('requestId') - loan_id = validated_data.get('debtId') - product_id = validated_data.get('productId') - - with db.session.begin(): + validated_data = RepaymentService.validate_data(data, RepaymentSchema()) + customer_id = validated_data.get('customerId') + request_id = validated_data.get('requestId') + loan_id = validated_data.get('debtId') + product_id = validated_data.get('productId') + + + # Save the repayment details repayment = Repayment.create_repayment( customer_id = customer_id, @@ -47,6 +48,8 @@ class RepaymentService(BaseService): "message": "Failed to save repayment details." }), 400 + db.session.flush() + validated_data['refId'] = repayment.id validated_data['refModel'] = "repayment" @@ -62,29 +65,31 @@ class RepaymentService(BaseService): }), 400 - # Simulated processing logic - response_data = { - "customerId": customer_id, - "productId": product_id, - "debtId": loan_id, - "resultCode": "00", - "resultDescription": "Successful" - } + # Simulated processing logic + response_data = { + "customerId": customer_id, + "productId": product_id, + "debtId": loan_id, + "resultCode": "00", + "resultDescription": "Successful" + } - # return ResponseHelper.success( - # data=response_data, - # message="Repayment processed successfully" - # ) + # return ResponseHelper.success( + # data=response_data, + # message="Repayment processed successfully" + # ) - # Call Kafka in a background thread - thread = Thread(target=RepaymentService.async_send_to_kafka, args=(response_data, request_id, "LOAN_REPAYMENT")) - thread.start() + # Call Kafka in a background thread + thread = Thread(target=RepaymentService.async_send_to_kafka, args=(response_data, request_id, "LOAN_REPAYMENT")) + thread.start() - return response_data + db.session.commit() + return response_data except ValidationError as err: logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": "Validation exception" @@ -92,6 +97,7 @@ class RepaymentService(BaseService): except ValueError as err: logger.error(f"{getattr(err, 'messages', str(err))}") + db.session.rollback() return jsonify({ "message": str(err) @@ -99,6 +105,7 @@ class RepaymentService(BaseService): except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) + db.session.rollback() return jsonify({ "message": "Internal Server Error" }) , 500 diff --git a/app/api/services/select_offer.py b/app/api/services/select_offer.py index 4b4c4bb..6a0a914 100644 --- a/app/api/services/select_offer.py +++ b/app/api/services/select_offer.py @@ -1,13 +1,14 @@ from flask import request, jsonify from marshmallow import ValidationError from app.api.services.base_service import BaseService -from app.api.enums import TransactionType +from app.api.enums import TransactionType from app.utils.logger import logger from app.api.schemas.select_offer import SelectOfferSchema from app.extensions import db + class SelectOfferService(BaseService): - TRANSACTION_TYPE = TransactionType.SELECT_OFFER + TRANSACTION_TYPE = TransactionType.SELECT_OFFER @staticmethod def process_request(data): @@ -21,75 +22,72 @@ class SelectOfferService(BaseService): dict: A standardized response. """ try: - validated_data = SelectOfferService.validate_data(data, SelectOfferSchema()) - account_id = validated_data.get('accountId') - customer_id = validated_data.get('customerId') + with db.session.begin(): + validated_data = SelectOfferService.validate_data( + data, SelectOfferSchema() + ) + account_id = validated_data.get("accountId") + customer_id = validated_data.get("customerId") - if (SelectOfferService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): - with db.session.begin(): - transaction = SelectOfferService.log_transaction(validated_data = validated_data) + if SelectOfferService.validate_account_ownership( + account_id=account_id, customer_id=customer_id + ): + transaction = SelectOfferService.log_transaction( + validated_data=validated_data + ) if not transaction: logger.error(f"Failed to log transaction") - return jsonify({ - "message": "Failed to log transaction." - }), 400 - else: - return jsonify({ - "message": "Invalid Customer or Account" - }), 400 - - offers = [ + return jsonify({"message": "Failed to log transaction."}), 400 + else: + return jsonify({"message": "Invalid Customer or Account"}), 400 + + offers = [ { - "offerId": "14451", - "productId": "2030", - "amount": 10000.0, - "upfrontPayment": 1000.0, - "interestRate": 3.0, - "managementRate": 1.0, - "managementFee": 1.0, - "insuranceRate": 1.0, - "insuranceFee": 100.0, - "VATRate": 7.5, - "VATAmount": 100.0, - "recommendedRepaymentDates": ["2022-11-30"], - "installmentAmount": 11000.0, - "totalRepaymentAmount": 11000.0 + "offerId": "14451", + "productId": "2030", + "amount": 10000.0, + "upfrontPayment": 1000.0, + "interestRate": 3.0, + "managementRate": 1.0, + "managementFee": 1.0, + "insuranceRate": 1.0, + "insuranceFee": 100.0, + "VATRate": 7.5, + "VATAmount": 100.0, + "recommendedRepaymentDates": ["2022-11-30"], + "installmentAmount": 11000.0, + "totalRepaymentAmount": 11000.0, } ] - # Business logic - selecting an offer - response_data = { - "outstandingDebtAmount": 0, - "requestId": "202111170001371256908", - "transactionId": transaction.id, - "customerId": customer_id, - "accountId": account_id, - "loan": offers, - "resultCode": "00", - "resultDescription": "Successful" + # Business logic - selecting an offer + response_data = { + "outstandingDebtAmount": 0, + "requestId": "202111170001371256908", + "transactionId": transaction.id, + "customerId": customer_id, + "accountId": account_id, + "loan": offers, + "resultCode": "00", + "resultDescription": "Successful", } - - return response_data + db.session.commit() + return response_data except ValidationError as err: logger.error(f"Validation Error: {getattr(err, 'messages', str(err))}") + db.session.rollback() + return jsonify({"message": "Validation exception"}), 422 - return jsonify({ - "message": "Validation exception" - }) , 422 - - except ValueError as err: + except ValueError as err: logger.error(f"{getattr(err, 'messages', str(err))}") - - return jsonify({ - "message": str(err) - }) , 400 + db.session.rollback() + return jsonify({"message": str(err)}), 400 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return jsonify({ - "message": "Internal Server Error" - }) , 500 \ No newline at end of file + db.session.rollback() + return jsonify({"message": "Internal Server Error"}), 500 diff --git a/app/models/repayment.py b/app/models/repayment.py index f58e1a8..c313131 100644 --- a/app/models/repayment.py +++ b/app/models/repayment.py @@ -38,7 +38,7 @@ class Repayment(db.Model): repayment = cls( customer_id=customer_id, - loan_id=loan.id, + loan_id=loan_id, product_id=product_id, ) From 9f9512b060f5256690be44e09ec6b15e59413dc8 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:02:35 +0100 Subject: [PATCH 3/6] [add]: loan status --- app/api/services/loan_status.py | 38 +++++++++++++++++++++------------ app/models/customer.py | 18 ++++++++++++++++ app/models/loan.py | 8 +++++++ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/api/services/loan_status.py b/app/api/services/loan_status.py index bfe8b68..08d2c2a 100644 --- a/app/api/services/loan_status.py +++ b/app/api/services/loan_status.py @@ -1,5 +1,6 @@ from flask import request, jsonify from marshmallow import ValidationError +from app.models import Customer from app.utils.logger import logger from app.api.schemas.loan_status import LoanStatusSchema from app.api.services.base_service import BaseService @@ -25,6 +26,15 @@ class LoanStatusService(BaseService): with db.session.begin(): validated_data = LoanStatusService.validate_data(data, LoanStatusSchema()) customer_id = validated_data.get('customerId') + customer = Customer.get_customer(customer_id) + transactionId = validated_data.get('transactionId') + + loans = customer.loans + + db.session.flush() + + validated_data['refId'] = customer.id + validated_data['refModel'] = "customer" transaction = LoanStatusService.log_transaction(validated_data = validated_data) @@ -35,23 +45,23 @@ class LoanStatusService(BaseService): }), 400 - loans = [ - { - "debtId": "123456789", - "loanDate": "2019-10-18 14:26:21.063", - "dueDate": "2019-11-20 14:26:21.063", - "currentLoanAmount": 8500, - "initialLoanAmount": 10000, - "defaultPenaltyFee": 0, - "continuousFee": 0, - "productId": "101" - } - ] + # loans = [ + # { + # "debtId": "123456789", + # "loanDate": "2019-10-18 14:26:21.063", + # "dueDate": "2019-11-20 14:26:21.063", + # "currentLoanAmount": 8500, + # "initialLoanAmount": 10000, + # "defaultPenaltyFee": 0, + # "continuousFee": 0, + # "productId": "101" + # } + # ] # Simulated processing logic response_data = { - "customerId": "CN621868", - "transactionId": "Tr201712RK9232P115", + "customerId": customer_id, + "transactionId": transactionId, "loans": loans, "totalDebtAmount": 8500, "resultCode": "00", diff --git a/app/models/customer.py b/app/models/customer.py index c36a11a..7692f5a 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -20,6 +20,13 @@ class Customer(db.Model): back_populates="customer", ) + loans = relationship( + "Loan", + primaryjoin="Customer.id == Loan.customer_id", + foreign_keys="Loan.customer_id", + back_populates="customer", + ) + @classmethod def is_valid_customer(cls, customer_id): customer = cls.query.filter_by(id=customer_id).first() @@ -47,6 +54,17 @@ class Customer(db.Model): except IntegrityError as err: raise ValueError(f"Database integrity error: {err}") return customer + + @classmethod + def get_customer(cls, customer_id): + """ + Get customer by ID. + """ + customer = cls.query.filter_by(id=customer_id).first() + + if not customer: + raise ValueError(f"Customer does not exist") + return customer def __repr__(self): return f'' diff --git a/app/models/loan.py b/app/models/loan.py index 252fe74..5f8f1fc 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -3,6 +3,8 @@ from app.extensions import db from app.models.customer import Customer from app.models.account import Account from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import relationship +from app.models import Customer class Loan(db.Model): @@ -21,6 +23,12 @@ class Loan(db.Model): created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) + customer = relationship( + "Customer", + primaryjoin="Customer.id == Loan.customer_id", + foreign_keys=[customer_id], + back_populates="loans", + ) @classmethod def create_loan(cls, customer_id, account_id, offer_id, principal_amount, status='pending'): From 1d97304f4e688b60d6c6000ef73c286c725ec852 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:14:02 +0100 Subject: [PATCH 4/6] [update]: Loan status --- app/api/services/loan_status.py | 10 +++++++--- app/models/loan.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/api/services/loan_status.py b/app/api/services/loan_status.py index 08d2c2a..f80b7f2 100644 --- a/app/api/services/loan_status.py +++ b/app/api/services/loan_status.py @@ -24,18 +24,22 @@ class LoanStatusService(BaseService): """ try: with db.session.begin(): + # Validate data validated_data = LoanStatusService.validate_data(data, LoanStatusSchema()) + + customer_id = validated_data.get('customerId') customer = Customer.get_customer(customer_id) transactionId = validated_data.get('transactionId') - loans = customer.loans + # Get loans + loans = [loan.to_dict() for loan in customer.loans] + - db.session.flush() - validated_data['refId'] = customer.id validated_data['refModel'] = "customer" + transaction = LoanStatusService.log_transaction(validated_data = validated_data) if not transaction: diff --git a/app/models/loan.py b/app/models/loan.py index 5f8f1fc..33256e4 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -99,6 +99,20 @@ class Loan(db.Model): # Update loan status and the updated_at timestamp loan.status = status + def to_dict(self): + """ + Convert the Loan object to a dictionary format for JSON serialization. + """ + return { + 'id': self.id, + 'customer_id': self.customer_id, + 'account_id': self.account_id, + 'offer_id': self.offer_id, + 'principal_amount': self.principal_amount, + 'status': self.status, + 'created_at': self.created_at.isoformat() if self.created_at else None, + 'updated_at': self.updated_at.isoformat() if self.updated_at else None + } def __repr__(self): return f'' \ No newline at end of file From 3031251519428360a28dbaa0bd27bd161766f4b5 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:17:38 +0100 Subject: [PATCH 5/6] Update base_service.py --- app/api/services/base_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/services/base_service.py b/app/api/services/base_service.py index 55d03c3..e3e18fc 100644 --- a/app/api/services/base_service.py +++ b/app/api/services/base_service.py @@ -51,7 +51,7 @@ class BaseService: return Transaction.create_transaction( transaction_id = validated_data.get("transactionId"), ref_id = validated_data.get("refId") or validated_data.get("accountId"), - ref_model = validated_data.get("refModel", "Account"), + ref_model = validated_data.get("refModel", "account"), type = cls.TRANSACTION_TYPE, channel = validated_data.get("channel"), ) From 8a018545ec97219f92faa53397b20aafe8d4480e Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:17:59 +0100 Subject: [PATCH 6/6] Update base_service.py --- app/api/services/base_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/services/base_service.py b/app/api/services/base_service.py index e3e18fc..8616fd8 100644 --- a/app/api/services/base_service.py +++ b/app/api/services/base_service.py @@ -44,7 +44,7 @@ class BaseService: return is_valid @classmethod - def log_transaction(cls, validated_data,): + def log_transaction(cls, validated_data): """ Create a new transaction. """