diff --git a/app/integrations/simbrella.py b/app/integrations/simbrella.py index 8bbf5b7..678cc0a 100644 --- a/app/integrations/simbrella.py +++ b/app/integrations/simbrella.py @@ -7,6 +7,7 @@ from app.utils.extras import preprocess_loan_charges_data import random import random import string +from app.extensions import db from app.utils.logger import logger from flask import jsonify, current_app from app.services.transactions import TransactionService @@ -18,6 +19,8 @@ from app.enums.loan_status import LoanStatus from decimal import Decimal, ROUND_HALF_UP from requests.exceptions import SSLError, RequestException,Timeout import sys +from requests.exceptions import ReadTimeout, ConnectTimeout +import socket class SimbrellaClient: @@ -273,11 +276,11 @@ class SimbrellaClient: try: logger.info(f"Sending CollectLoan request............ {collect_loan_data}") - response = requests.post(api_url, json=collect_loan_data, timeout=30, headers=get_headers()) - + response = requests.post(api_url, json=collect_loan_data, timeout=90, headers=get_headers()) logger.info(f"HTTP response object: {response}") if response.status_code == 404: + db.session.rollback() RepaymentService.set_repay_result( repayment_data['Id'], '404', @@ -326,6 +329,7 @@ class SimbrellaClient: updated_loan = LoanService.update_loan_balance(int(loan_data['debtId']), amount_collected) logger.info(f"Updated loan: {updated_loan}") except Exception as ex: + db.session.rollback() logger.error(f"Error updating loan balance for loan ID {loan.id}: {ex}") return ResponseHelper.error("Error updating loan balance") @@ -342,28 +346,55 @@ class SimbrellaClient: partial = LoanService.update_status(updated_loan['debtId'], LoanStatus.ACTIVE_PARTIAL) logger.info(f'Updated loan with partial status: {partial}') except Exception as e: + db.session.rollback() logger.error(f"Error while updating loan status for debtId {updated_loan['debtId']}: {e}") return ResponseHelper.success(result, "Successful") except SSLError as ssl_err: + db.session.rollback() logger.exception(f"SSL error while calling Simbrella endpoint: {ssl_err}") return ResponseHelper.error("SSL handshake failed with Simbrella", status_code=502, error=str(ssl_err)) - except Timeout as timeout_err: + except (Timeout, ReadTimeout, ConnectTimeout, socket.timeout, TimeoutError) as timeout_err: + db.session.rollback() logger.exception(f"Timeout while calling Simbrella: {timeout_err}") + RepaymentService.set_repay_result( + repayment_data['Id'], + '500', + 'There was a timeout while calling Simbrella' + ) return ResponseHelper.error("Connection to Simbrella timed out", status_code=504, error=str(timeout_err)) - except RequestException as req_err: + db.session.rollback() logger.exception(f"RequestException while calling Simbrella: {req_err}") + RepaymentService.set_repay_result( + repayment_data['Id'], + '500', + 'There was a request error while calling Simbrella' + ) return ResponseHelper.error("Connection to Simbrella failed", status_code=503, error=str(req_err)) except SystemExit as sys_exit: + db.session.rollback() logger.error(f"SystemExit was triggered: {sys_exit}") + RepaymentService.set_repay_result( + repayment_data['Id'], + '500', + 'There was a system error while calling Simbrella' + + + ) return ResponseHelper.error("Unexpected shutdown detected", status_code=500, error=str(sys_exit)) except Exception as e: + db.session.rollback() logger.exception(f"Unexpected error occurred while calling CollectLoan: {e}") + RepaymentService.set_repay_result( + repayment_data['Id'], + '500', + 'Unexpected error while processing loan collection' + ) return ResponseHelper.error("Unexpected error while processing loan collection", status_code=500, error=str(e)) diff --git a/app/models/loan.py b/app/models/loan.py index aa0c276..8719d78 100644 --- a/app/models/loan.py +++ b/app/models/loan.py @@ -1,5 +1,4 @@ from datetime import datetime, timezone -from app.extensions import db from sqlalchemy.orm import relationship from dateutil.relativedelta import relativedelta from datetime import timedelta @@ -92,8 +91,7 @@ class Loan(db.Model): 'disburseVerify': self.disburse_verify.isoformat() if self.disburse_verify else None, 'reference': self.reference, 'balance': self.balance, - 'tenor': self.tenor, - + 'tenor': self.tenor, } @classmethod diff --git a/app/models/repayment.py b/app/models/repayment.py index 451be6e..4bb2636 100644 --- a/app/models/repayment.py +++ b/app/models/repayment.py @@ -150,10 +150,11 @@ class Repayment(db.Model): try: logger.info(f"Updating repay date for repayment ID {repayment_id} to {current_time}") db.session.commit() + return repayment.to_dict() except Exception as e: db.session.rollback() logger.error(f"Failed to update repay date: {e}") - raise + raise e @classmethod def set_repay_verify_date(cls, repayment_id, customer_id): """ @@ -181,11 +182,12 @@ class Repayment(db.Model): except Exception as e: db.session.rollback() logger.error(f"Failed to update repay verify date: {e}") - raise + raise e @classmethod def set_repay_result(cls, repayment_id, result, description): + logger.info("repay result called") """ Update the repayment result and description of the repayment with the given repayment_id. """ diff --git a/app/models/repayments_data.py b/app/models/repayments_data.py index 488a314..a61d8b8 100644 --- a/app/models/repayments_data.py +++ b/app/models/repayments_data.py @@ -25,7 +25,7 @@ class RepaymentsData(db.Model): "response_code": self.response_code, "response_descr": self.response_descr, "customerId": self.customer_id, - "accountId": self.customer_id, + "accountId": self.account_id, "fbnTransactionId": self.fbn_transaction_id, "repaymentAmount": self.repayment_amount, "amountCollected": self.amount_collected, diff --git a/app/routes/autocall.py b/app/routes/autocall.py index d944b25..679608f 100644 --- a/app/routes/autocall.py +++ b/app/routes/autocall.py @@ -1,5 +1,6 @@ from flask import Blueprint, request, jsonify, current_app import requests +from app.extensions import db from app.config import settings from app.helpers.response_helper import ResponseHelper from app.utils.auth import get_headers @@ -167,6 +168,7 @@ def process_salary_list(): try: SalaryService.update_status(pending_salary.id, "PROCESSING") except Exception as e: + db.session.rollback() logger.warning(f"Could not update status for salary ID {pending_salary.id}: {e}") continue @@ -177,6 +179,7 @@ def process_salary_list(): logger.warning(f"No loans found for customer ID: {pending_salary.customer_id}") continue except Exception as e: + db.session.rollback() logger.error(f"Error fetching loans for customer ID {pending_salary.customer_id}: {e}") continue @@ -197,40 +200,41 @@ def process_salary_list(): logger.info(f"Creating repayment with data: {repayment_data}") repayment = RepaymentService.create_repayment(repayment_data) - if not repayment: - logger.error(f"Repayment creation failed for loan ID {loan.id}") + 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 - # Update loan status to START_REPAY 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 5: Call Simbrella + try: + simbrella_response = SimbrellaClient.collect_loan_user_salary_detect(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 - # Step 5: Call Simbrella to collect loan - - try: - simbrella_response = SimbrellaClient.collect_loan_user_salary_detect(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): - status = simbrella_response.get("status") - if status != "success": - logger.warning(f"Simbrella call failed for repayment ID {repayment.id}: {simbrella_response}") - else: - logger.warning(f"Unexpected Simbrella response type: {type(simbrella_response)}") - except Exception as e: - logger.error(f"Failed to call Simbrella for repayment ID {repayment.id}: {e}") - - logger.info(f"Finished processing salary ID: {pending_salary.id}") return ResponseHelper.success([], "Processed all pending salaries")