added failed loans endpoint

This commit was merged in pull request #70.
This commit is contained in:
Chinenye Nmoh
2026-04-05 16:53:02 +01:00
parent f1db12c7f2
commit c32c2502cc
7 changed files with 183 additions and 2 deletions
+111
View File
@@ -26,6 +26,12 @@ autocall_bp = Blueprint("autocall", __name__)
#
@autocall_bp.route("/refresh-verify-disbursement", methods=["GET"])
def verify_transaction():
"""
Get the latest loan with disbursement date and no verification date.
Then call the verify transaction endpoint with the loan data.
This is called after disbursement to ensure the loan was actually disbursed
Then a verification date is set
"""
logger.info(f"Calling VerifyTransaction Components")
loan = LoanService.get_latest_loan_with_disburse_date()
@@ -49,6 +55,11 @@ def verify_transaction():
@autocall_bp.route("/refresh-disbursement", methods=["GET"])
def disbursement():
"""
Get the latest loan without disbursement date.
Then call the disburse loan endpoint with the loan data.
"""
# data = request.json()
logger.info(f"Calling Disbursement Components")
loan = LoanService.get_latest_loan_without_disburse_date()
@@ -72,6 +83,14 @@ def disbursement():
@autocall_bp.route("/retry-disbursement", methods=["POST"])
def retry_disbursement():
"""
This takes in a transaction id as input and
retries the disbursement for the loan with that transaction id.
This is to be used in cases where the disbursement failed due to network issues
or other transient issues. It will call the disbursement endpoint
with the loan data for the loan with the given transaction id.
"""
try:
data = request.get_json()
logger.info(f"Retry Transaction ID Data Received for :::: {data}")
@@ -103,8 +122,64 @@ def retry_disbursement():
except Exception as e:
logger.error(f"Failed to call retry disbursement {data}: {e}")
@autocall_bp.route("/retry-failed-disbursements", methods=["GET"])
def retry_failed_disbursements():
"""
This endpoint is for retrying failed disbursements.
It will get the list of failed disbursements and call the disbursement endpoint for each of them.
This is to be used in cases where the disbursement failed due to network issues or other transient issues. It will call the disbursement endpoint
with the loan data for the loan with the given transaction id.
"""
try:
logger.info(f"Calling Retry Failed Disbursements Components")
failed_disbursements = LoanService.get_failed_disbursements()
if not failed_disbursements:
logger.info(f"No failed disbursements found")
return ResponseHelper.success(message="No failed disbursements found", status_code=200)
logger.info(f"Found {len(failed_disbursements)} failed disbursements to retry")
#get batch size from settings
# Safe config values
batch_size = max(1, settings.FAILED_DISBURSEMENT_BATCH_SIZE or 1)
delay_seconds = max(0, settings.FAILED_DISBURSEMENT_DELAY_SECONDS or 0)
batch_delay = max(0, settings.FAILED_DISBURSEMENT_BATCH_DELAY_SECONDS or 0)
for i in range(0, len(failed_disbursements), batch_size):
batch = failed_disbursements[i:i + batch_size]
for loan in batch:
logger.info(f"Retrying disbursement for loan ID {loan.id}")
loan_data = loan.to_dict()
data = {
"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)
logger.info(f"Retry Disbursement Result Received for loan ID {loan.id}: {response}")
time_module.sleep(delay_seconds)
time_module.sleep(batch_delay)
return ResponseHelper.success(message="Retry Failed Disbursements Request Sent Successfully", status_code=200)
except Exception as e:
logger.error(f"Failed to retry disbursements: {e}")
return ResponseHelper.error("Failed to retry disbursements", status_code=500, error=str(e))
@autocall_bp.route("/direct/loan", methods=["POST"])
def direct_loan():
"""
This endpoint is for directly calling the disbursement endpoint with a transaction id.
This is to be used in cases where the disbursement failed due to network issues
or other transient issues. It will call the disbursement endpoint
with the loan data for the loan with the given transaction id.
"""
data = request.get_json()
logger.info(f"Data received: {data}")
@@ -156,6 +231,10 @@ def direct_loan():
@autocall_bp.route("/direct/repayment", methods=["POST"])
def direct_repayment():
"""
This endpoint is for directly calling the collect loan endpoint with a transaction id.
"""
data = request.get_json()
logger.info(f"Data received: {data}")
@@ -239,6 +318,9 @@ def direct_repayment():
@autocall_bp.route("/refresh-verify-collection", methods=["GET"])
def refresh_verify_collection():
"""
This endpoint is for directly calling the verify collection endpoint.
"""
data = request.get_json()
logger.info(f"Calling Verify Collection")
@@ -248,6 +330,11 @@ def refresh_verify_collection():
@autocall_bp.route("/refresh-collection", methods=["GET"])
def refresh_collection():
"""
This endpoint is for directly calling the collect loan endpoint.
It will get the latest repayment with no repay date and call the collect loan endpoint with that repayment data.
This is to be used in cases where the collection failed due to network issues or other transient issues. It will call the collection endpoint
"""
#data = request.get_json()
logger.info(f"Calling Collection ")
#grab the last repayments with repay date is none
@@ -283,7 +370,11 @@ def payment_callback():
return response
@autocall_bp.route("/penal-charge", methods=["POST"])
def penal_charge():
"""
This endpoint is for directly calling the penal charge endpoint.
"""
data = request.get_json()
logger.info(f"Calling Penal Charge Endpoint")
@@ -296,6 +387,13 @@ def penal_charge():
@autocall_bp.route("/analytic-salary-detect", methods=["POST"])
def salary_detect():
"""
Creates new salary data
Gets the salary list not yet processed
then, gets the loan list for each salary and
creates repayments for each loan with the salary data,
then calls the collect loan endpoint for each repayment created.
"""
payload = request.get_json()
logger.info("Calling Salary Detect endpoint")
@@ -421,6 +519,7 @@ def process_salary_list():
@autocall_bp.route("/report", methods=["GET"])
def report():
"""This endpoint is for generating the report and sending the email."""
try:
report_data = get_report_data()
logger.info(f"Generated report data: {report_data}")
@@ -435,6 +534,12 @@ def report():
@autocall_bp.route("/process-penal-charges", methods=["GET"])
def process_penal_charges():
"""
This endpoint is for processing penal charges for overdue loans schedule with grace period.
It will check for overdue loans, calculate the penal charge,
create a new penal charge record, update the loan and repayment schedule with the new penal charge, and call the Simbrella endpoint to update the penal charge on their system.
"""
try:
OVERDUE_GRACE_PERIOD_DAYS = settings.OVERDUE_GRACE_PERIOD_DAYS
OVERDUE_PROCESSING_LIST_LIMIT = settings.OVERDUE_PROCESSING_LIST_LIMIT
@@ -549,6 +654,12 @@ def process_penal_charges():
@autocall_bp.route("/overdue-loans", methods=["GET"])
def overdue_loans():
"""
This endpoint is for processing overdue loans.
It will get all active overdue loan schedules,
and then for each loan schedule, it will create a repayment, update the loan status, and call the Simbrella endpoint to collect the loan.
"""
try:
# Step 1: Get all active overdue loans
overdue_loans = LoanRepaymentScheduleService.get_active_overdue_repayment_schedule()