diff --git a/app/integrations/simbrella.py b/app/integrations/simbrella.py index dfbc2e1..d1541a6 100644 --- a/app/integrations/simbrella.py +++ b/app/integrations/simbrella.py @@ -226,9 +226,16 @@ class SimbrellaClient: @staticmethod def collect_loan_user_due_payment(data): # InitiatedBy = REPAYMENT_DUE - return SimbrellaClient._collect_loan(data,"3") + try: + return SimbrellaClient._collect_loan(data,"3") + except Exception as e: + logger.error(f"Error in collect_loan_user_due_payment: {e}") + return ResponseHelper.error( + message="Failed to collect loan for due payment", + status_code=500, + error=str(e) + ) - @staticmethod def _collect_loan(data, collectionMethod: str): api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}{SimbrellaClient.BANK_CALL_COLLECT_LOAN_ENDPOINT}" diff --git a/app/models/loan.py b/app/models/loan.py index 8719d78..4017f7b 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -335,4 +335,25 @@ class Loan(db.Model): except Exception as e: db.session.rollback() logger.error(f"Error updating loan balance: {e}") - raise Exception(f"Error updating loan balance: {str(e)}") \ No newline at end of file + raise Exception(f"Error updating loan balance: {str(e)}") + + @classmethod + def get_overdue_loans(cls): + """ + Get all overdue loans. + """ + try: + overdue_loans = cls.query.filter( + cls.due_date < datetime.now(timezone.utc), + cls.status != 'repaid' + ).all() + + if not overdue_loans: + logger.info("No overdue loans found.") + return [] + + logger.info(f"Found {len(overdue_loans)} overdue loans.") + return overdue_loans + except Exception as e: + logger.error(f"Error fetching overdue loans: {e}") + return [] diff --git a/app/routes/autocall.py b/app/routes/autocall.py index 279826a..34168ba 100644 --- a/app/routes/autocall.py +++ b/app/routes/autocall.py @@ -256,3 +256,69 @@ def report(): except Exception as e: logger.error(f"Error generating or sending report: {e}") return ResponseHelper.error("Failed to send report", status_code=500, error=str(e)) + +@autocall_bp.route("/overdue-loans", methods=["GET"]) +def overdue_loans(): + try: + # Step 1: Get all overdue loans + loans = LoanService.get_overdue_loans() + logger.info(f"Found {len(loans)} overdue loans.") + if not loans: + return ResponseHelper.success(message="No overdue loans found", status_code=200) + # Step 2: Create repayments for each loan + for loan in loans: + logger.info(f"Processing Loan ID: {loan.id}") + try: + repayment_data = { + "customerId": loan.customer_id, + "loanId": loan.id, + "productId": loan.product_id, + "transactionId": loan.transaction_id, + "initiatedBy": "SYSTEM", #To be reviewed + "salaryAmount": 0, + "LoanStatus": loan.status, + } + + logger.info(f"Creating repayment with data: {repayment_data}") + repayment = RepaymentService.create_repayment(repayment_data) + + if not repayment or isinstance(repayment, dict) and "error" in repayment: + db.session.rollback() # important in case create_repayment failed mid-way + logger.error(f"Repayment creation failed for loan ID {loan.id}: {repayment}") + continue + + try: + LoanService.update_status(loan_id=loan.id, status=LoanStatus.START_REPAY) + except Exception as e: + db.session.rollback() + logger.error(f"Failed to update loan status for loan ID {loan.id}: {e}") + + logger.info(f"Created repayment ID: {repayment.id}") + + # Step 3: Call Simbrella + try: + simbrella_response = SimbrellaClient.collect_loan_user_due_payment(repayment.to_dict()) + + if isinstance(simbrella_response, tuple): + simbrella_response, status_code = simbrella_response + logger.warning(f"Simbrella returned tuple: status={status_code}, response={simbrella_response}") + + if isinstance(simbrella_response, dict): + if simbrella_response.get("status") != "success": + logger.warning(f"Simbrella failed for repayment ID {repayment.id}: {simbrella_response}") + else: + logger.warning(f"Unexpected Simbrella response: {type(simbrella_response)}") + + except Exception as e: + logger.error(f"Failed to call Simbrella for repayment ID {repayment.id}: {e}") + + except Exception as e: + db.session.rollback() + logger.error(f"Error creating repayment for loan ID {loan.id}: {e}") + continue + + logger.info(f"Finished processing loan ID: {loan.id}") + return ResponseHelper.success(message="Processed overdue loans successfully", status_code=200) + except Exception as e: + logger.error(f"Error fetching overdue loans: {e}") + return ResponseHelper.error("Failed to fetch overdue loans", status_code=500, error=str(e)) diff --git a/app/services/loan.py b/app/services/loan.py index 3cf927a..bb4e2d8 100644 --- a/app/services/loan.py +++ b/app/services/loan.py @@ -99,3 +99,10 @@ class LoanService: update the loan balance after successful repayment """ return Loan.update_loan_balance(loan_id,amount_collected) + + @classmethod + def get_overdue_loans(cls): + """ + Get all overdue loans. + """ + return Loan.get_overdue_loans() diff --git a/openapi.yml b/openapi.yml index 9b03e59..eb3d2de 100644 --- a/openapi.yml +++ b/openapi.yml @@ -208,4 +208,9 @@ paths: responses: 200: description: A successful response - \ No newline at end of file + /autocall/overdue-loans: + get: + summary: Get all overdue loans + responses: + 200: + description: A successful response \ No newline at end of file