From 0f4455e738ac55498c1731c72891ead06f6bb4d6 Mon Sep 17 00:00:00 2001 From: Chinenye Nmoh Date: Fri, 23 Jan 2026 11:14:17 +0100 Subject: [PATCH 1/3] added a new penal charge endpoint --- app/config.py | 1 + app/models/loan_repayment_schedule.py | 24 ++++++++++++++++++++++-- app/routes/autocall.py | 5 +++-- app/services/loan_repayment_schedule.py | 11 +++++++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/config.py b/app/config.py index 4b5951b..a466d8c 100644 --- a/app/config.py +++ b/app/config.py @@ -61,6 +61,7 @@ class Config: os.getenv("OVERDUE_LOAN_BATCH_DELAY_SECONDS", 5) ) OVERDUE_GRACE_PERIOD_DAYS = int(os.getenv("OVERDUE_GRACE_PERIOD_DAYS", 30)) + OVERDUE_PROCESSING_LIST_LIMIT = int(os.getenv("OVERDUE_PROCESSING_LIST_LIMIT", 100)) BANK_CALL_API_TIME_OUT = os.getenv("BANK_CALL_API_TIME_OUT", 100) diff --git a/app/models/loan_repayment_schedule.py b/app/models/loan_repayment_schedule.py index a6d8369..a38d670 100644 --- a/app/models/loan_repayment_schedule.py +++ b/app/models/loan_repayment_schedule.py @@ -115,7 +115,7 @@ class LoanRepaymentSchedule(db.Model): logger.error(f"Error fetching active overdue repayment schedules: {e}") return [] @classmethod - def get_overdue_repayment_schedule_with_grace_period(cls, grace_period_days): + def get_overdue_repayment_schedule_with_grace_period(cls, grace_period_days, limit=None): """ Get all overdue repayment schedules that are not repaid and beyond the grace period. """ @@ -124,7 +124,7 @@ class LoanRepaymentSchedule(db.Model): return cls.query.filter( cls.due_date < grace_period_date, cls.paid == False - ).order_by(cls.due_date.asc()).all() + ).order_by(cls.due_date.asc()).limit(limit).all() except Exception as e: logger.error(f"Error fetching overdue repayment schedules with grace period: {e}") return [] @@ -274,5 +274,25 @@ class LoanRepaymentSchedule(db.Model): db.session.rollback() logger.error(f"Error applying repayment for schedule {schedule_id}: {e}") raise + @classmethod + def update_due_process_date_and_count(cls, schedule_id): + """ + Update the due process date to now and increment the due process count. + """ + try: + schedule = cls.query.get(schedule_id) + if schedule.due_process_count is None: + schedule.due_process_count = 0 + schedule.due_process_count += 1 + schedule.due_process_date = datetime.now(timezone.utc) + schedule.updated_at = datetime.now(timezone.utc) + + db.session.commit() + logger.info(f"Updated due process date and count for schedule ID {schedule.id}") + + except Exception as e: + db.session.rollback() + logger.error(f"Error updating due process date and count for schedule {schedule.id}: {e}") + raise \ No newline at end of file diff --git a/app/routes/autocall.py b/app/routes/autocall.py index 0c19993..8379c95 100644 --- a/app/routes/autocall.py +++ b/app/routes/autocall.py @@ -445,10 +445,11 @@ def report(): def process_penal_charges(): try: OVERDUE_GRACE_PERIOD_DAYS = settings.OVERDUE_GRACE_PERIOD_DAYS + OVERDUE_PROCESSING_LIST_LIMIT = settings.OVERDUE_PROCESSING_LIST_LIMIT overdue_loans = ( LoanRepaymentScheduleService - .get_overdue_repayment_schedule_with_grace_period(OVERDUE_GRACE_PERIOD_DAYS) + .get_overdue_repayment_schedule_with_grace_period(OVERDUE_GRACE_PERIOD_DAYS,OVERDUE_PROCESSING_LIST_LIMIT) ) logger.info(f"Found {len(overdue_loans)} overdue loans.") @@ -464,7 +465,7 @@ def process_penal_charges(): for loan in overdue_loans: # TODO: apply penal charge logic here # PenalChargeService.apply_penal_charge(loan) - + LoanRepaymentScheduleService.update_due_process_date_and_count(loan.id) processed_loans.append(loan.to_dict()) return ResponseHelper.success( diff --git a/app/services/loan_repayment_schedule.py b/app/services/loan_repayment_schedule.py index bf37117..7aca7df 100644 --- a/app/services/loan_repayment_schedule.py +++ b/app/services/loan_repayment_schedule.py @@ -48,8 +48,15 @@ class LoanRepaymentScheduleService: return LoanRepaymentSchedule.update_repayment_schedule_description(schedule_id, description) @classmethod - def get_overdue_repayment_schedule_with_grace_period(cls, grace_period_days): - return LoanRepaymentSchedule.get_overdue_repayment_schedule_with_grace_period(grace_period_days) + def get_overdue_repayment_schedule_with_grace_period(cls, grace_period_days, limit=None): + return LoanRepaymentSchedule.get_overdue_repayment_schedule_with_grace_period(grace_period_days, limit=limit) + + @classmethod + def update_due_process_date_and_count(cls, schedule_id): + """ + Update due process date and count for a repayment schedule. + """ + return LoanRepaymentSchedule.update_due_process_date_and_count(schedule_id) @staticmethod def handle_schedule_updates(updated_loan, data, amount_collected, message, loan_data): """ From 39ea231fa0cd2aac104bc60ea203db40b5b34694 Mon Sep 17 00:00:00 2001 From: Chinenye Nmoh Date: Fri, 23 Jan 2026 20:38:53 +0100 Subject: [PATCH 2/3] added logic to only send charges for 1 month loan --- app/integrations/simbrella.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/integrations/simbrella.py b/app/integrations/simbrella.py index 61cc544..cdf3206 100644 --- a/app/integrations/simbrella.py +++ b/app/integrations/simbrella.py @@ -94,7 +94,7 @@ class SimbrellaClient: insurance_fee = loan_charges.get("INSURANCE")['amount'] debtId = str(loan_data.get('debtId', "")).strip().zfill(6) - + product_id = str(loan_data.get('productId', '')) disbursement_data = { "transactionId": loan_data.get('transactionId'), "fbnTransactionId": loan_data.get('transactionId'), @@ -103,10 +103,10 @@ class SimbrellaClient: "accountId": loan_data.get('accountId'), "productId": str(loan_data.get('productId', "")), "provideAmount": loan_data.get('currentLoanAmount'), - "collectAmountInterest": interest_fee, - "collectAmountMgtFee": mgt_fee, - "collectAmountInsurance": insurance_fee, - "collectAmountVAT": vat_fee, + "collectAmountInterest": interest_fee if product_id != '3MPC' else 0, + "collectAmountMgtFee": mgt_fee if product_id != '3MPC' else 0, + "collectAmountInsurance": insurance_fee if product_id != '3MPC' else 0, + "collectAmountVAT": vat_fee if product_id != '3MPC' else 0, "countryId": "01", "comment": "Loan Disbursement", } From f6f8e369c4d23b6681c89649e89fb9e616736d85 Mon Sep 17 00:00:00 2001 From: Chinenye Nmoh Date: Fri, 23 Jan 2026 20:42:03 +0100 Subject: [PATCH 3/3] added logic to only send charges for 1 month loan --- app/integrations/simbrella.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/integrations/simbrella.py b/app/integrations/simbrella.py index cdf3206..ccb7620 100644 --- a/app/integrations/simbrella.py +++ b/app/integrations/simbrella.py @@ -103,7 +103,7 @@ class SimbrellaClient: "accountId": loan_data.get('accountId'), "productId": str(loan_data.get('productId', "")), "provideAmount": loan_data.get('currentLoanAmount'), - "collectAmountInterest": interest_fee if product_id != '3MPC' else 0, + "collectAmountInterest": interest_fee if product_id != '3MPC' else 0, #send charges for only 1 month loan "collectAmountMgtFee": mgt_fee if product_id != '3MPC' else 0, "collectAmountInsurance": insurance_fee if product_id != '3MPC' else 0, "collectAmountVAT": vat_fee if product_id != '3MPC' else 0,