From 09b57d81a24754f70100980e2b1242bf0f6bc4a2 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 05:14:53 -0400 Subject: [PATCH 01/22] Moved offer decide --- app/api/services/eligibility_check.py | 75 ++++++++++++++++----------- app/api/services/offer_analysis.py | 40 ++++++++++++++ 2 files changed, 85 insertions(+), 30 deletions(-) diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py index 6fd8470..b27b9b7 100644 --- a/app/api/services/eligibility_check.py +++ b/app/api/services/eligibility_check.py @@ -8,6 +8,8 @@ from app.api.enums import TransactionType from app.api.integrations import SimbrellaIntegration from app.extensions import db from app.models import Offer, RACCheck +from app.api.services.offer_analysis import OfferAnalysis + import random @@ -78,39 +80,52 @@ class EligibilityCheckService(BaseService): return jsonify({ "message": "Failed to save RACCheck." }), 400 - - offers = Offer.get_all_offers() - - eligible_offers = [] - - for offer in offers: - # Determine an approved amount - random_float = random.random() # temporary to play data - approved_amount = min(offer.max_amount, offer.max_amount * random_float) #temporary for now - approved_amount = round(approved_amount, 2) - - transaction_offer = TransactionOffer.create_transaction_offer( - customer_id = customer.id, - transaction_id = transaction.transaction_id, - offer_id = offer.id, - min_amount = offer.min_amount, - max_amount = offer.max_amount, - eligible_amount = approved_amount, - product_id = offer.product_id, - tenor = offer.tenor +# -----------------TIME FOR ANALYSIS TO REGISTER OFFER ---------------------- + # eligible_offers = [] + try: + eligible_offers = OfferAnalysis.decide_offer( + transaction_id=transaction_id, + rac_check=rac_check, + validated_data=validated_data ) + except ValueError as ve: + logger.error(str(ve)) + return jsonify({ + "message": str(ve) + }), 400 +# ----------------------------------------------------------------------- +# s = Offer.get_all_offers() - # Visible offer ID: offer_id + padded(transaction_offer.id) - padded_id = str(transaction_offer.id).zfill(6) - public_offer_id = f"{offer.id}{padded_id}" + # eligible_offers = [] - eligible_offers.append({ - "offerId": public_offer_id, - "product_id": offer.product_id, - "min_amount": offer.min_amount, - "max_amount": approved_amount, - "tenor": offer.tenor - }) + # for offer in offers: + # # Determine an approved amount + # random_float = random.random() # temporary to play data + # approved_amount = min(offer.max_amount, offer.max_amount * random_float) #temporary for now + # approved_amount = round(approved_amount, 2) + # + # transaction_offer = TransactionOffer.create_transaction_offer( + # customer_id = customer.id, + # transaction_id = transaction.transaction_id, + # offer_id = offer.id, + # min_amount = offer.min_amount, + # max_amount = offer.max_amount, + # eligible_amount = approved_amount, + # product_id = offer.product_id, + # tenor = offer.tenor + # ) + # + # # Visible offer ID: offer_id + padded(transaction_offer.id) + # padded_id = str(transaction_offer.id).zfill(6) + # public_offer_id = f"{offer.id}{padded_id}" + # + # eligible_offers.append({ + # "offerId": public_offer_id, + # "product_id": offer.product_id, + # "min_amount": offer.min_amount, + # "max_amount": approved_amount, + # "tenor": offer.tenor + # }) # Simulate processing response_data = { diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index b1a021a..435d6cd 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -35,3 +35,43 @@ class OfferAnalysis: return transaction_offer, offer, eligible_amount, original_transaction + + @staticmethod + def decide_offer(transaction_id, rac_check, validated_data): + # if we have active offers - we have to feed off it + + + + # Do this if no active loan or registered offers + offers = Offer.get_all_offers() + eligible_offers = [] + for offer in offers: + # Determine an approved amount + random_float = random.random() # temporary to play data + approved_amount = min(offer.max_amount, offer.max_amount * random_float) # temporary for now + approved_amount = round(approved_amount, 2) + + transaction_offer = TransactionOffer.create_transaction_offer( + customer_id=customer.id, + transaction_id=transaction.transaction_id, + offer_id=offer.id, + min_amount=offer.min_amount, + max_amount=offer.max_amount, + eligible_amount=approved_amount, + product_id=offer.product_id, + tenor=offer.tenor + ) + + # Visible offer ID: offer_id + padded(transaction_offer.id) + padded_id = str(transaction_offer.id).zfill(6) + public_offer_id = f"{offer.id}{padded_id}" + + eligible_offers.append({ + "offerId": public_offer_id, + "product_id": offer.product_id, + "min_amount": offer.min_amount, + "max_amount": approved_amount, + "tenor": offer.tenor + }) + + return eligible_offers \ No newline at end of file From f573d5e6432ce7aded861959a2f6a8d2bf7bfcba Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 05:17:28 -0400 Subject: [PATCH 02/22] transaction_id --- app/api/services/offer_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 435d6cd..02dc2c9 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -53,7 +53,7 @@ class OfferAnalysis: transaction_offer = TransactionOffer.create_transaction_offer( customer_id=customer.id, - transaction_id=transaction.transaction_id, + transaction_id=transaction_id, offer_id=offer.id, min_amount=offer.min_amount, max_amount=offer.max_amount, From cc3cd5b72b649ea82b851fed667c61fb2eea5f07 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 05:33:04 -0400 Subject: [PATCH 03/22] Customer id fix --- app/api/services/eligibility_check.py | 5 +++-- app/api/services/offer_analysis.py | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py index b27b9b7..65b1c5b 100644 --- a/app/api/services/eligibility_check.py +++ b/app/api/services/eligibility_check.py @@ -84,9 +84,10 @@ class EligibilityCheckService(BaseService): # eligible_offers = [] try: eligible_offers = OfferAnalysis.decide_offer( - transaction_id=transaction_id, + transaction_id=transactionId, rac_check=rac_check, - validated_data=validated_data + validated_data=validated_data, + customer=customer ) except ValueError as ve: logger.error(str(ve)) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 02dc2c9..66df567 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -1,6 +1,6 @@ from app.models import Offer, TransactionOffer from app.models.loan import Loan - +import random import logging logger = logging.getLogger(__name__) @@ -37,11 +37,15 @@ class OfferAnalysis: return transaction_offer, offer, eligible_amount, original_transaction @staticmethod - def decide_offer(transaction_id, rac_check, validated_data): + def decide_offer(transaction_id, rac_check, validated_data,customer): # if we have active offers - we have to feed off it + + + + # Do this if no active loan or registered offers offers = Offer.get_all_offers() eligible_offers = [] From 89b621b9a8b74c7ceb5d324d0fcc25f1fc63632c Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 05:53:32 -0400 Subject: [PATCH 04/22] Original Transaction id on offers --- app/api/services/offer_analysis.py | 1 + app/models/transaction_offers.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 66df567..9a2d56a 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -58,6 +58,7 @@ class OfferAnalysis: transaction_offer = TransactionOffer.create_transaction_offer( customer_id=customer.id, transaction_id=transaction_id, + original_transaction=transaction_id, offer_id=offer.id, min_amount=offer.min_amount, max_amount=offer.max_amount, diff --git a/app/models/transaction_offers.py b/app/models/transaction_offers.py index 9ee764a..5884095 100644 --- a/app/models/transaction_offers.py +++ b/app/models/transaction_offers.py @@ -8,6 +8,7 @@ class TransactionOffer(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) customer_id = db.Column(db.String(50), nullable=False) transaction_id = db.Column(db.String(50), nullable=False) + original_transaction = db.Column(db.String(50), nullable=True) offer_id = db.Column(db.String(20), nullable=False) product_id = db.Column(db.String(20), nullable=True) min_amount = db.Column(db.Float, nullable=False) @@ -39,13 +40,14 @@ class TransactionOffer(db.Model): return transaction_offer @classmethod - def create_transaction_offer(cls, customer_id, transaction_id, offer_id, min_amount, max_amount, eligible_amount=None, product_id=None, tenor=None): + def create_transaction_offer(cls, customer_id, transaction_id, original_transaction, offer_id, min_amount, max_amount, eligible_amount=None, product_id=None, tenor=None): """ Class method to create and save a TransactionOffer. """ transaction_offer = cls( customer_id=customer_id, transaction_id=transaction_id, + original_transaction=original_transaction, offer_id=offer_id, min_amount=min_amount, max_amount=max_amount, From b7ae0e6baa36468a0d7e0960740080376b2d0b76 Mon Sep 17 00:00:00 2001 From: ameye Date: Sat, 10 May 2025 05:55:53 -0400 Subject: [PATCH 05/22] New original transaction on offer --- ...9_migration_on_sat_may_10_09_54_34_utc_.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 migrations/versions/173ea45db189_migration_on_sat_may_10_09_54_34_utc_.py diff --git a/migrations/versions/173ea45db189_migration_on_sat_may_10_09_54_34_utc_.py b/migrations/versions/173ea45db189_migration_on_sat_may_10_09_54_34_utc_.py new file mode 100644 index 0000000..0c3686b --- /dev/null +++ b/migrations/versions/173ea45db189_migration_on_sat_may_10_09_54_34_utc_.py @@ -0,0 +1,32 @@ +"""Migration on Sat May 10 09:54:34 UTC 2025 + +Revision ID: 173ea45db189 +Revises: 3105abd795d4 +Create Date: 2025-05-10 09:54:39.380499 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '173ea45db189' +down_revision = '3105abd795d4' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('transaction_offers', schema=None) as batch_op: + batch_op.add_column(sa.Column('original_transaction', sa.String(length=50), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('transaction_offers', schema=None) as batch_op: + batch_op.drop_column('original_transaction') + + # ### end Alembic commands ### From 40158b1c54f6f1aaee9b538534c6d800f4bda2c1 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 06:20:03 -0400 Subject: [PATCH 06/22] Analysis steps --- app/api/services/offer_analysis.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 9a2d56a..44b2472 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -40,9 +40,25 @@ class OfferAnalysis: def decide_offer(transaction_id, rac_check, validated_data,customer): # if we have active offers - we have to feed off it + """ + Find the last loan - it will have original_transaction + This loan is part of the original approval where transaction_id = original_transaction for this account + This will give you the eligible_amount = REAL_ELIGIBLIBLE_AMOUNT + Sum all loans amount with same original_transaction together = Sum_active loans. = SUM_OF_ACTIVE_LOAN + + The new eligible amount will be = REAL_ELIGIBLIBLE_AMOUNT - SUM_OF_ACTIVE_LOAN + + Construct eligible_offers[] + + Save eligible_offers + + Return the eligible_amount + + No need for fresh construct + """ From 334cb0f2d6338b08fb4517e0be02462daa8cb58d Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 06:58:17 -0400 Subject: [PATCH 07/22] Staered --- app/api/services/offer_analysis.py | 8 +++++--- app/api/services/provide_loan.py | 2 +- app/models/loan.py | 8 ++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 44b2472..17736e7 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -30,9 +30,6 @@ class OfferAnalysis: if not offer: raise ValueError("Invalid Offer.") original_transaction = transaction_id - # we can now find the origin transactions - customer_loan = Loan.get_customer_current_active_loan(customer_id) - return transaction_offer, offer, eligible_amount, original_transaction @@ -40,6 +37,10 @@ class OfferAnalysis: def decide_offer(transaction_id, rac_check, validated_data,customer): # if we have active offers - we have to feed off it + # we can now find the origin transactions + last_customer_loan = Loan.get_customer_last_loan(customer.id) #Find the last loan - it will have original_transaction + logger.info(f"{last_customer_loan}") + """ Find the last loan - it will have original_transaction @@ -48,6 +49,7 @@ class OfferAnalysis: This will give you the eligible_amount = REAL_ELIGIBLIBLE_AMOUNT Sum all loans amount with same original_transaction together = Sum_active loans. = SUM_OF_ACTIVE_LOAN + [*** we will come back to this action to make sure w dont count already paid loans *******] The new eligible amount will be = REAL_ELIGIBLIBLE_AMOUNT - SUM_OF_ACTIVE_LOAN diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py index fb9178b..88db200 100644 --- a/app/api/services/provide_loan.py +++ b/app/api/services/provide_loan.py @@ -146,7 +146,7 @@ class ProvideLoanService(BaseService): # charges = Charge.get_offer_charges(offer.id) - logger.info(f"{charges}") + # logger.info(f"{charges}") loan_id = loan.id diff --git a/app/models/loan.py b/app/models/loan.py index 9a43d8e..8143944 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -6,6 +6,8 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import relationship from dateutil.relativedelta import relativedelta from datetime import timedelta +import logging +logger = logging.getLogger(__name__) class Loan(db.Model): @@ -134,11 +136,13 @@ class Loan(db.Model): return loan @classmethod - def get_customer_current_active_loan(cls, customer_id): + def get_customer_last_loan(cls, customer_id): """ Get customer's active loans. """ + logger.info(f"Find last loan for [customer_id] ==>>>> {customer_id}") loan = cls.query.filter_by( customer_id = customer_id).first() + logger.info(f" Active Loan ==>>>> RESULT************************ AMEYE") if not loan: loan = { "eligible_amount": 0, @@ -147,7 +151,7 @@ class Loan(db.Model): "transaction_id": "", "resultDescription": "No Active Loan" } - + logger.info(f" Active Loan ==>>>> RESULT*********************") logger.info(f" Active Loan ==>>>> {loan}") return loan From 976fb14614ed2fbce010dfb84f1861572bcf0c7a Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 07:53:35 -0400 Subject: [PATCH 08/22] This ensures the progragation of original transaction id --- app/api/services/provide_loan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py index 88db200..6e39cfb 100644 --- a/app/api/services/provide_loan.py +++ b/app/api/services/provide_loan.py @@ -107,7 +107,6 @@ class ProvideLoanService(BaseService): vat = charges["vat"] - # Save the loan details loan = Loan.create_loan( customer_id = customer_id, @@ -116,7 +115,7 @@ class ProvideLoanService(BaseService): product_id = offer.product_id, collection_type = collection_type, transaction_id = validated_data.get('transactionId'), - original_transaction = validated_data.get('transactionId'), + original_transaction = transaction_offer.original_transaction, initial_loan_amount = validated_data.get('requestedAmount'), upfront_fee = upfront_fee, repayment_amount = repayment_amount, @@ -124,7 +123,6 @@ class ProvideLoanService(BaseService): eligible_amount=eligible_amount, status = LoanStatus.ACTIVE, tenor = offer.tenor, - ) if not loan: From c330c3f0e7f4198d07ef37439e880b78369334f4 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 08:54:08 -0400 Subject: [PATCH 09/22] disburse_date --- app/models/loan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/loan.py b/app/models/loan.py index 8143944..e8188f2 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -38,6 +38,8 @@ 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)) eligible_amount = db.Column(db.Float, nullable=True, default=0.0) + disburse_date = db.Column(db.DateTime, nullable=True) + disburse_verify = db.Column(db.DateTime, nullable=True) customer = relationship( "Customer", From e9c50f75b12a06137c81d45f80dd162db22115d4 Mon Sep 17 00:00:00 2001 From: ameye Date: Sat, 10 May 2025 08:55:38 -0400 Subject: [PATCH 10/22] disburse migration --- ...e_migration_on_sat_may_10_12_54_52_utc_.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 migrations/versions/565bc3d0ba6e_migration_on_sat_may_10_12_54_52_utc_.py diff --git a/migrations/versions/565bc3d0ba6e_migration_on_sat_may_10_12_54_52_utc_.py b/migrations/versions/565bc3d0ba6e_migration_on_sat_may_10_12_54_52_utc_.py new file mode 100644 index 0000000..91895f2 --- /dev/null +++ b/migrations/versions/565bc3d0ba6e_migration_on_sat_may_10_12_54_52_utc_.py @@ -0,0 +1,34 @@ +"""Migration on Sat May 10 12:54:52 UTC 2025 + +Revision ID: 565bc3d0ba6e +Revises: 173ea45db189 +Create Date: 2025-05-10 12:54:56.683215 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '565bc3d0ba6e' +down_revision = '173ea45db189' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('loans', schema=None) as batch_op: + batch_op.add_column(sa.Column('disburse_date', sa.DateTime(), nullable=True)) + batch_op.add_column(sa.Column('disburse_verify', sa.DateTime(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('loans', schema=None) as batch_op: + batch_op.drop_column('disburse_verify') + batch_op.drop_column('disburse_date') + + # ### end Alembic commands ### From bbdb7214d1fe0c7d461248be6c793f3a38381ae4 Mon Sep 17 00:00:00 2001 From: Vivian Dee <> Date: Sat, 10 May 2025 14:24:05 +0100 Subject: [PATCH 11/22] [add]: define offers update --- app/api/services/offer_analysis.py | 37 +++++++++++++++++++++--------- app/models/loan.py | 14 +++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 17736e7..212546d 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -34,14 +34,8 @@ class OfferAnalysis: return transaction_offer, offer, eligible_amount, original_transaction @staticmethod - def decide_offer(transaction_id, rac_check, validated_data,customer): - # if we have active offers - we have to feed off it - - # we can now find the origin transactions - last_customer_loan = Loan.get_customer_last_loan(customer.id) #Find the last loan - it will have original_transaction - logger.info(f"{last_customer_loan}") - - """ + def decide_offer(transaction_id, rac_check, validated_data, customer): + """ Find the last loan - it will have original_transaction This loan is part of the original approval where transaction_id = original_transaction for this account @@ -61,16 +55,37 @@ class OfferAnalysis: No need for fresh construct """ + # if we have active offers - we have to feed off it + # we can now find the origin transactions + last_customer_loan = Loan.get_customer_last_loan(customer.id) #Find the last loan - it will have original_transaction + logger.info(f"{last_customer_loan}") + new_eligible_amount = 0 + + if last_customer_loan: + original_transaction = last_customer_loan.original_transaction or last_customer_loan.transaction_id + + real_eligible_amount = last_customer_loan.eligible_amount + + active_loans = Loan.get_active_loans_by_original_transaction(original_transaction) + + sum_active_loans = sum(loan.current_loan_amount for loan in active_loans) + + new_eligible_amount = max(real_eligible_amount - sum_active_loans, 0) + + logger.info(f"Real eligible: {real_eligible_amount}, Sum of active: {sum_active_loans}, New eligible: {new_eligible_amount}") + + # Construct eligible_offers - # Do this if no active loan or registered offers offers = Offer.get_all_offers() eligible_offers = [] + for offer in offers: - # Determine an approved amount + # Get approved amount random_float = random.random() # temporary to play data - approved_amount = min(offer.max_amount, offer.max_amount * random_float) # temporary for now + + approved_amount = new_eligible_amount if new_eligible_amount > 0 else min(offer.max_amount, offer.max_amount * random_float) approved_amount = round(approved_amount, 2) transaction_offer = TransactionOffer.create_transaction_offer( diff --git a/app/models/loan.py b/app/models/loan.py index 8143944..257d3ef 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -156,6 +156,20 @@ class Loan(db.Model): return loan + @classmethod + def get_active_loans_by_original_transaction(cls, original_transaction_id): + """ + Get all active loans with the same original_transaction ID. + """ + + active_loans = cls.query.filter_by( + original_transaction=original_transaction_id, + # status='active' + ).all() + + return active_loans + + @classmethod def update_status(cls, loan_id, status): """ From e377858c478aa12e2b444ffb1163148abd65860e Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 09:43:21 -0400 Subject: [PATCH 12/22] removed comments --- app/api/services/offer_analysis.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 212546d..0162088 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -35,30 +35,12 @@ class OfferAnalysis: @staticmethod def decide_offer(transaction_id, rac_check, validated_data, customer): - """ - Find the last loan - it will have original_transaction - This loan is part of the original approval where transaction_id = original_transaction for this account - - This will give you the eligible_amount = REAL_ELIGIBLIBLE_AMOUNT - - Sum all loans amount with same original_transaction together = Sum_active loans. = SUM_OF_ACTIVE_LOAN - [*** we will come back to this action to make sure w dont count already paid loans *******] - - The new eligible amount will be = REAL_ELIGIBLIBLE_AMOUNT - SUM_OF_ACTIVE_LOAN - - Construct eligible_offers[] - - Save eligible_offers - - Return the eligible_amount - - No need for fresh construct - """ # if we have active offers - we have to feed off it # we can now find the origin transactions - last_customer_loan = Loan.get_customer_last_loan(customer.id) #Find the last loan - it will have original_transaction + # Find the last loan - it will have original_transaction + last_customer_loan = Loan.get_customer_last_loan(customer.id) logger.info(f"{last_customer_loan}") new_eligible_amount = 0 From 332c344efaa5da0f8ead6ca64e828de4bf5df879 Mon Sep 17 00:00:00 2001 From: Vivian Dee <> Date: Sat, 10 May 2025 14:47:52 +0100 Subject: [PATCH 13/22] [fix]: Indentation --- app/api/services/offer_analysis.py | 2 +- app/models/loan.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 212546d..3ebdd31 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -35,7 +35,7 @@ class OfferAnalysis: @staticmethod def decide_offer(transaction_id, rac_check, validated_data, customer): - """ + """ Find the last loan - it will have original_transaction This loan is part of the original approval where transaction_id = original_transaction for this account diff --git a/app/models/loan.py b/app/models/loan.py index 257d3ef..dddf5ae 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -156,18 +156,18 @@ class Loan(db.Model): return loan - @classmethod - def get_active_loans_by_original_transaction(cls, original_transaction_id): - """ - Get all active loans with the same original_transaction ID. - """ - - active_loans = cls.query.filter_by( - original_transaction=original_transaction_id, - # status='active' - ).all() + @classmethod + def get_active_loans_by_original_transaction(cls, original_transaction_id): + """ + Get all active loans with the same original_transaction ID. + """ + + active_loans = cls.query.filter_by( + original_transaction=original_transaction_id, + # status='active' + ).all() - return active_loans + return active_loans @classmethod From b86bd3dece44da30ce6cbc1deebe9794a69e398e Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 10:06:14 -0400 Subject: [PATCH 14/22] Fix ident --- app/models/loan.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/models/loan.py b/app/models/loan.py index 9b77415..8d3613d 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -158,18 +158,18 @@ class Loan(db.Model): return loan - @classmethod - def get_active_loans_by_original_transaction(cls, original_transaction_id): - """ - Get all active loans with the same original_transaction ID. - """ - - active_loans = cls.query.filter_by( - original_transaction=original_transaction_id, - # status='active' - ).all() + @classmethod + def get_active_loans_by_original_transaction(cls, original_transaction_id): + """ + Get all active loans with the same original_transaction ID. + """ - return active_loans + active_loans = cls.query.filter_by( + original_transaction=original_transaction_id, + # status='active' + ).all() + + return active_loans @classmethod From 4bcaa3d13d2128e5a3ec07e0476a46df8a8943f0 Mon Sep 17 00:00:00 2001 From: Vivian Dee <> Date: Sat, 10 May 2025 15:30:15 +0100 Subject: [PATCH 15/22] Update loan.py --- app/models/loan.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/loan.py b/app/models/loan.py index ac42b43..9c8345c 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -146,6 +146,9 @@ class Loan(db.Model): loan = cls.query.filter_by( customer_id = customer_id).first() logger.info(f" Active Loan ==>>>> RESULT************************ AMEYE") if not loan: + + return None + loan = { "eligible_amount": 0, "loan_amount": 0, From feb97c3fa87502e339324125843dabe43fa83d40 Mon Sep 17 00:00:00 2001 From: Vivian Dee <> Date: Sat, 10 May 2025 15:32:11 +0100 Subject: [PATCH 16/22] Update loan.py --- app/models/loan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/loan.py b/app/models/loan.py index 9c8345c..8942996 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -146,7 +146,6 @@ class Loan(db.Model): loan = cls.query.filter_by( customer_id = customer_id).first() logger.info(f" Active Loan ==>>>> RESULT************************ AMEYE") if not loan: - return None loan = { From 4718c9c50b7d4f361887ada5652a149c9fd158db Mon Sep 17 00:00:00 2001 From: Vivian Dee <> Date: Sat, 10 May 2025 15:34:43 +0100 Subject: [PATCH 17/22] Update loan.py --- app/models/loan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/loan.py b/app/models/loan.py index 8942996..6de1522 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -155,8 +155,8 @@ class Loan(db.Model): "transaction_id": "", "resultDescription": "No Active Loan" } - logger.info(f" Active Loan ==>>>> RESULT*********************") - logger.info(f" Active Loan ==>>>> {loan}") + logger.info(f" Active Loan ==>>>> RESULT*********************") + logger.info(f" Active Loan ==>>>> {loan}") return loan From 6d743ea09ba973292274455f489d59c4576feec6 Mon Sep 17 00:00:00 2001 From: Vivian Dee <> Date: Sat, 10 May 2025 15:42:09 +0100 Subject: [PATCH 18/22] Update offer_analysis.py --- app/api/services/offer_analysis.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index 0162088..ad417f1 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -40,23 +40,23 @@ class OfferAnalysis: # we can now find the origin transactions # Find the last loan - it will have original_transaction - last_customer_loan = Loan.get_customer_last_loan(customer.id) - logger.info(f"{last_customer_loan}") + # last_customer_loan = Loan.get_customer_last_loan(customer.id) + # logger.info(f"{last_customer_loan}") new_eligible_amount = 0 - if last_customer_loan: - original_transaction = last_customer_loan.original_transaction or last_customer_loan.transaction_id + # if last_customer_loan: + # original_transaction = last_customer_loan.original_transaction or last_customer_loan.transaction_id - real_eligible_amount = last_customer_loan.eligible_amount + # real_eligible_amount = last_customer_loan.eligible_amount - active_loans = Loan.get_active_loans_by_original_transaction(original_transaction) + # active_loans = Loan.get_active_loans_by_original_transaction(original_transaction) - sum_active_loans = sum(loan.current_loan_amount for loan in active_loans) + # sum_active_loans = sum(loan.current_loan_amount for loan in active_loans) - new_eligible_amount = max(real_eligible_amount - sum_active_loans, 0) + # new_eligible_amount = max(real_eligible_amount - sum_active_loans, 0) - logger.info(f"Real eligible: {real_eligible_amount}, Sum of active: {sum_active_loans}, New eligible: {new_eligible_amount}") + # logger.info(f"Real eligible: {real_eligible_amount}, Sum of active: {sum_active_loans}, New eligible: {new_eligible_amount}") # Construct eligible_offers From 11a239c67aa87cd5ee4ae525f3e788d8ae874e84 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 10 May 2025 20:08:27 -0400 Subject: [PATCH 19/22] Linked loan design --- app/api/integrations/simbrella.py | 2 +- app/api/services/eligibility_check.py | 2 +- app/api/services/offer_analysis.py | 57 ++++++++++++++++++++------- app/models/loan.py | 25 +++++++++--- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/app/api/integrations/simbrella.py b/app/api/integrations/simbrella.py index 13c7922..e46210d 100644 --- a/app/api/integrations/simbrella.py +++ b/app/api/integrations/simbrella.py @@ -35,7 +35,7 @@ class SimbrellaIntegration: ], } - logger.info(f"This is PayLoad: {str(payload)}", exc_info=True) + # logger.info(f"This is PayLoad: {str(payload)}", exc_info=True) headers = { "Content-Type": "application/json", diff --git a/app/api/services/eligibility_check.py b/app/api/services/eligibility_check.py index 65b1c5b..263d9cb 100644 --- a/app/api/services/eligibility_check.py +++ b/app/api/services/eligibility_check.py @@ -87,7 +87,7 @@ class EligibilityCheckService(BaseService): transaction_id=transactionId, rac_check=rac_check, validated_data=validated_data, - customer=customer + customer_id=customer_id ) except ValueError as ve: logger.error(str(ve)) diff --git a/app/api/services/offer_analysis.py b/app/api/services/offer_analysis.py index ad417f1..d97cc7e 100644 --- a/app/api/services/offer_analysis.py +++ b/app/api/services/offer_analysis.py @@ -34,34 +34,63 @@ class OfferAnalysis: return transaction_offer, offer, eligible_amount, original_transaction @staticmethod - def decide_offer(transaction_id, rac_check, validated_data, customer): - + def decide_offer(transaction_id, rac_check, validated_data, customer_id): + eligible_offers = [] # if we have active offers - we have to feed off it + logger.info(f"LOOOOOOOOOOOOOOOOOO** {customer_id}") # we can now find the origin transactions # Find the last loan - it will have original_transaction - # last_customer_loan = Loan.get_customer_last_loan(customer.id) + last_customer_loan = Loan.get_customer_last_loan(customer_id) # logger.info(f"{last_customer_loan}") new_eligible_amount = 0 - # if last_customer_loan: - # original_transaction = last_customer_loan.original_transaction or last_customer_loan.transaction_id + if last_customer_loan: + original_transaction = last_customer_loan.original_transaction or last_customer_loan.transaction_id + logger.info(f"transaction_id |-| original_transaction === > {transaction_id} {original_transaction}") + original_loan = Loan.get_customer_original_loan(customer_id, original_transaction) + if original_loan is not None: + logger.info(f"original_loan === > {original_loan}") + logger.info(f"loan_offer_id === > {original_loan.offer_id}") - # real_eligible_amount = last_customer_loan.eligible_amount + original_offer_id = str(original_loan.offer_id[:5]) # The last part is str + transaction_offer_id = int(original_loan.offer_id[5:]) # The last part is int + original_transaction_offer = TransactionOffer.is_valid_transaction_offer(transaction_offer_id, customer_id, original_loan.product_id) - # active_loans = Loan.get_active_loans_by_original_transaction(original_transaction) + active_loans = Loan.get_active_loans_by_original_transaction(original_transaction) + sum_active_loans = sum(loan.current_loan_amount for loan in active_loans) + logger.info(f"sum_active_loans === > {sum_active_loans}") + real_eligible_amount = original_loan.eligible_amount - sum_active_loans - # sum_active_loans = sum(loan.current_loan_amount for loan in active_loans) - - # new_eligible_amount = max(real_eligible_amount - sum_active_loans, 0) + transaction_offer = TransactionOffer.create_transaction_offer( + customer_id=customer_id, + transaction_id=transaction_id, + original_transaction=original_transaction, + offer_id=original_offer_id, + min_amount=original_transaction_offer.min_amount, + max_amount=original_transaction_offer.max_amount, + eligible_amount=real_eligible_amount, + product_id=original_loan.product_id, + tenor=original_loan.tenor + ) - # logger.info(f"Real eligible: {real_eligible_amount}, Sum of active: {sum_active_loans}, New eligible: {new_eligible_amount}") + # Visible offer ID: offer_id + padded(transaction_offer.id) + padded_id = str(transaction_offer.id).zfill(6) + public_offer_id = f"{original_offer_id}{padded_id}" + + eligible_offers.append({ + "offerId": public_offer_id, + "product_id": original_transaction_offer.product_id, + "min_amount": original_transaction_offer.min_amount, + "max_amount": real_eligible_amount, + "tenor": original_loan.tenor + }) + return eligible_offers - # Construct eligible_offers offers = Offer.get_all_offers() - eligible_offers = [] + for offer in offers: # Get approved amount @@ -71,7 +100,7 @@ class OfferAnalysis: approved_amount = round(approved_amount, 2) transaction_offer = TransactionOffer.create_transaction_offer( - customer_id=customer.id, + customer_id=customer_id, transaction_id=transaction_id, original_transaction=transaction_id, offer_id=offer.id, diff --git a/app/models/loan.py b/app/models/loan.py index 8d3613d..9309ae6 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -7,6 +7,8 @@ from sqlalchemy.orm import relationship from dateutil.relativedelta import relativedelta from datetime import timedelta import logging +from sqlalchemy import and_, or_, not_ + logger = logging.getLogger(__name__) @@ -137,14 +139,27 @@ class Loan(db.Model): raise ValueError(f"Loan with ID {loan_id} does not exist or does not belong to customer {customer_id}.") return loan + @classmethod + def get_customer_original_loan(cls, customer_id, original_transaction): + """ + Get customer's original loan offer. + """ + original_loan = cls.query.filter(and_( cls.customer_id ==customer_id, cls.original_transaction==original_transaction, cls.transaction_id==original_transaction )).first() + if not original_loan: + return None + + logger.info(f" get_customer_original_loan ==>>>> {original_loan}") + return original_loan + @classmethod def get_customer_last_loan(cls, customer_id): """ Get customer's active loans. """ - logger.info(f"Find last loan for [customer_id] ==>>>> {customer_id}") - loan = cls.query.filter_by( customer_id = customer_id).first() - logger.info(f" Active Loan ==>>>> RESULT************************ AMEYE") + logger.info(f"get_customer_last_loan [customer_id] ==>>>> {customer_id}") + # loan = cls.query.filter_by( cls.customer_id == customer_id).first() + loan = cls.query.filter(and_( cls.customer_id ==customer_id, cls.status=='active')).first() + if not loan: loan = { "eligible_amount": 0, @@ -153,9 +168,7 @@ class Loan(db.Model): "transaction_id": "", "resultDescription": "No Active Loan" } - logger.info(f" Active Loan ==>>>> RESULT*********************") - logger.info(f" Active Loan ==>>>> {loan}") - + logger.info(f" get_customer_last_loan ==>>>> {loan}") return loan @classmethod From eeacffad9a5aee98d5aa36e44c64b742ad46f10c Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sun, 11 May 2025 16:05:36 -0400 Subject: [PATCH 20/22] Loan Reference added --- app/api/services/provide_loan.py | 8 ++++++-- app/swagger/schemas/ProvideLoanResponse.json | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/api/services/provide_loan.py b/app/api/services/provide_loan.py index 6e39cfb..46dd4a8 100644 --- a/app/api/services/provide_loan.py +++ b/app/api/services/provide_loan.py @@ -42,6 +42,7 @@ class ProvideLoanService(BaseService): offer_id = validated_data.get('offerId') amount = validated_data.get("requestedAmount") product_id = validated_data.get("productId") + channel = validated_data.get('channel') customer = Customer.is_valid_customer(customer_id) @@ -132,7 +133,7 @@ class ProvideLoanService(BaseService): }), 400 db.session.flush() - + current_product_id = offer.product_id schedule = LoanRepaymentSchedule.add_repayment_schedule(loan = loan, num_schedules = num_schedules, transaction_id = transaction_id) @@ -156,7 +157,9 @@ class ProvideLoanService(BaseService): return jsonify({ "message": "Invalid Customer or Account" }), 400 - + + padded_loan_id = str(loan_id).zfill(9) + loanRef = f"LID{padded_loan_id}{channel}{current_product_id}" response_data = { "requestId": request_id, @@ -164,6 +167,7 @@ class ProvideLoanService(BaseService): "customerId": customer_id, "accountId": account_id, "msisdn": customer.msisdn, + "loanRef": loanRef, "resultCode": "00", "resultDescription": "Successful" } diff --git a/app/swagger/schemas/ProvideLoanResponse.json b/app/swagger/schemas/ProvideLoanResponse.json index d7ddb3c..1da4cb2 100644 --- a/app/swagger/schemas/ProvideLoanResponse.json +++ b/app/swagger/schemas/ProvideLoanResponse.json @@ -9,6 +9,10 @@ "type": "string", "example": "Tr201712RK9232P115" }, + "loanRef": { + "type": "string", + "example": "1620029887USSDAMPC" + }, "customerId": { "type": "string", "example": "CN621868" From 3d81322515f45c46749251358d5ec42267ee83eb Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sun, 11 May 2025 20:04:49 -0400 Subject: [PATCH 21/22] Fix Intetrest Fee --- app/api/services/select_offer.py | 2 +- app/swagger/schemas/SelectOfferResponse.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/services/select_offer.py b/app/api/services/select_offer.py index 11338b3..db7a7a6 100644 --- a/app/api/services/select_offer.py +++ b/app/api/services/select_offer.py @@ -91,7 +91,7 @@ class SelectOfferService(BaseService): "amount": amount, "upfrontPayment": upfront_payment, "interestRate": offer.interest_rate, - "interestAmount": interest_amount, + "interestFee": interest_amount, "managementRate": offer.management_rate, "managementFee": management["fee"], "insuranceRate": offer.insurance_rate, diff --git a/app/swagger/schemas/SelectOfferResponse.json b/app/swagger/schemas/SelectOfferResponse.json index 41b60d4..6fbc6ea 100644 --- a/app/swagger/schemas/SelectOfferResponse.json +++ b/app/swagger/schemas/SelectOfferResponse.json @@ -49,7 +49,7 @@ "format": "float", "example": 3.0 }, - "interestAmount": { + "interestFee": { "type": "number", "format": "float", "example": 3000.00 From 746ca486da49e7d6e0616c6be215ccd4a592fd6d Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sun, 11 May 2025 23:44:36 -0400 Subject: [PATCH 22/22] Fix data --- app/models/loan.py | 16 +++++++++------- app/swagger/schemas/SelectOfferRequest.json | 2 +- app/swagger/schemas/SelectOfferResponse.json | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/models/loan.py b/app/models/loan.py index 9309ae6..2af5711 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -161,13 +161,15 @@ class Loan(db.Model): loan = cls.query.filter(and_( cls.customer_id ==customer_id, cls.status=='active')).first() if not loan: - loan = { - "eligible_amount": 0, - "loan_amount": 0, - "customer_id": customer_id, - "transaction_id": "", - "resultDescription": "No Active Loan" - } + return None + # loan = { + # "original_transaction":"", + # "eligible_amount": 0, + # "loan_amount": 0, + # "customer_id": customer_id, + # "transaction_id": "", + # "resultDescription": "No Active Loan" + # } logger.info(f" get_customer_last_loan ==>>>> {loan}") return loan diff --git a/app/swagger/schemas/SelectOfferRequest.json b/app/swagger/schemas/SelectOfferRequest.json index e51bb99..0540a8e 100644 --- a/app/swagger/schemas/SelectOfferRequest.json +++ b/app/swagger/schemas/SelectOfferRequest.json @@ -28,7 +28,7 @@ }, "productId": { "type": "string", - "example": "2090" + "example": "3MPC" }, "offerId": { "type": "string", diff --git a/app/swagger/schemas/SelectOfferResponse.json b/app/swagger/schemas/SelectOfferResponse.json index 6fbc6ea..f32d39a 100644 --- a/app/swagger/schemas/SelectOfferResponse.json +++ b/app/swagger/schemas/SelectOfferResponse.json @@ -28,7 +28,7 @@ }, "productId": { "type": "string", - "example": "2030" + "example": "3MPC" }, "amount": { "type": "number",