From eaa4529f4436fff5ba95e47b8cade8f2bfaac226 Mon Sep 17 00:00:00 2001 From: Chinenye Nmoh Date: Thu, 11 Sep 2025 08:38:22 +0100 Subject: [PATCH] added direct loan and repayment endpoint --- app/models/loan.py | 2 +- app/routes/autocall.py | 138 ++++++++++++++++++++++++++++++++++++++++- openapi.yml | 37 ++++++++++- 3 files changed, 174 insertions(+), 3 deletions(-) diff --git a/app/models/loan.py b/app/models/loan.py index 37aed8c..8a4b9a7 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -100,7 +100,7 @@ class Loan(db.Model): @classmethod def get_loan_by_loan_id(cls, loan_id): return cls.query.filter_by(id=loan_id).first() - + @classmethod def set_disbursement_date(cls, loan_id, customer_id): """ diff --git a/app/routes/autocall.py b/app/routes/autocall.py index 86fcd8f..05c0ecc 100644 --- a/app/routes/autocall.py +++ b/app/routes/autocall.py @@ -64,6 +64,136 @@ def disbursement(): return response + +@autocall_bp.route("/direct/loan", methods=["POST"]) +def direct_loan(): + data = request.get_json() + logger.info(f"Data received: {data}") + + REQUIRED_KEYS = [ + "transactionId" + ] + + # Check for missing keys + missing_keys = [key for key in REQUIRED_KEYS if key not in data or data[key] is None] + if missing_keys: + logger.warning(f"Missing required keys: {missing_keys}") + return jsonify({ + "status": "error", + "message": f"Missing required fields: {', '.join(missing_keys)}" + }), 400 + + # Check if the loan exists + loan = LoanService.get_loan_by_transaction_id(transaction_id=data['transactionId']) + if not loan: + return jsonify({ + "status": "error", + "message": f"Loan with transaction id {data['transactionId']} does not exist" + }), 400 + + loan_data = loan.to_dict() + + # Prevent double disbursement + if loan_data.get('disburseDate') is not None: + return jsonify({ + "status": "error", + "message": f"Loan with transaction id {data['transactionId']} has already been processed" + }), 400 + + data_to_process = { + "transactionId": loan_data.get('transactionId'), + "FbnTransactionId": loan_data.get('transactionId'), + "debtId": str(loan_data.get('debtId')), + "customerId": loan_data.get('customerId'), + "accountId": loan_data.get('accountId'), + "productId": str(loan_data.get('productId', "")), + "provideAmount": loan_data.get('currentLoanAmount'), + } + response = SimbrellaClient.disburse_loan(data_to_process) + return response + +@autocall_bp.route("/direct/repayment", methods=["POST"]) +def direct_repayment(): + data = request.get_json() + logger.info(f"Data received: {data}") + + REQUIRED_KEYS = ["transactionId"] + + # Check for missing keys + missing_keys = [key for key in REQUIRED_KEYS if key not in data or data[key] is None] + if missing_keys: + logger.warning(f"Missing required keys: {missing_keys}") + return jsonify({ + "status": "error", + "message": f"Missing required fields: {', '.join(missing_keys)}" + }), 400 + + # Check if the loan exists + loan = LoanService.get_loan_by_transaction_id(transaction_id=data['transactionId']) + if not loan: + return jsonify({ + "status": "error", + "message": f"Loan with transaction id {data['transactionId']} does not exist" + }), 400 + + loan_data = loan.to_dict() + + # check if loan has been repaid + if loan_data.get("status") == LoanStatus.REPAID and loan_data.get("balance") <= 0: + return jsonify({ + "status": "error", + "message": f"loan with Id {loan_data.get('debtId')} has been repaid" + }), 400 + + + repayment_data = { + "customerId": loan_data.get("customerId"), + "loanId": loan_data.get("debtId"), + "productId": loan_data.get("productId"), + "transactionId": loan_data.get("transactionId"), + "initiatedBy": "USER INITIATED", + "salaryAmount": 0, + "LoanStatus": loan_data.get("status"), + } + + logger.info(f"Creating repayment with data: {repayment_data}") + + try: + repayment = RepaymentService.create_repayment(repayment_data) + except Exception as e: + db.session.rollback() + logger.error(f"Repayment creation raised exception: {e}") + return jsonify({ + "status": "error", + "message": "Failed to create repayment" + }), 500 + + if not repayment or (isinstance(repayment, dict) and "error" in repayment): + db.session.rollback() + logger.error(f"Repayment creation failed for loan ID {loan_data.get('debtId')}: {repayment}") + + try: + if loan_data.get('status') == LoanStatus.ACTIVE: + LoanService.update_status(loan_id=loan_data.get('debtId'), status=LoanStatus.START_REPAY) + except Exception as e: + db.session.rollback() + logger.error(f"Failed to update loan status for loan ID {loan_data.get('debtId')}: {e}") + repayment_data_dict = repayment.to_dict() + + data_to_process = { + "transactionId": repayment_data_dict['transactionId'], + "debtId": repayment_data_dict['loanId'], + "customerId": repayment_data_dict['customerId'], + "productId": repayment_data_dict['productId'], + "Id":repayment_data_dict['Id'] + } + + response = SimbrellaClient.collect_loan_user_initiated(data_to_process) + return response + + + + @autocall_bp.route("/refresh-verify-collection", methods=["GET"]) def refresh_verify_collection(): data = request.get_json() @@ -190,6 +320,11 @@ def process_salary_list(): # Step 4: Create repayments for each loan for loan in loans: logger.info(f"Processing Loan ID: {loan.id}") + #check if the loan has been repaid + if loan.status in [LoanStatus.REPAID] and loan.balance <= 0: + logger.info(f"Skipping loan ID {loan.id} because it is already repaid/closed") + continue + try: repayment_data = { "customerId": loan.customer_id, @@ -210,7 +345,8 @@ def process_salary_list(): continue try: - LoanService.update_status(loan_id=loan.id, status=LoanStatus.START_REPAY) + if loan.status == LoanStatus.ACTIVE: + 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}") diff --git a/openapi.yml b/openapi.yml index eb3d2de..ac23bb9 100644 --- a/openapi.yml +++ b/openapi.yml @@ -213,4 +213,39 @@ paths: summary: Get all overdue loans responses: 200: - description: A successful response \ No newline at end of file + description: A successful response + /autocall/direct/loan: + post: + summary: Direct call for loan disbursement + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + transactionId: + type: string + example: "TXN123456" + responses: + 200: + description: A successful response + /autocall/direct/repayment: + post: + summary: Direct call for loan repayment + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + transactionId: + type: string + example: "TXN123456" + + responses: + 200: + description: A successful response + + \ No newline at end of file -- 2.34.1