9 Commits

Author SHA1 Message Date
Chinenye Nmoh ca3ca1cac3 added eco integration 2025-07-25 11:53:05 +01:00
Chinenye Nmoh c50b69e852 added mail values in the env file 2025-07-17 12:42:22 +01:00
ameye cef23778d5 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-07-16 11:10:15 +00:00
Chinenye Nmoh 7ac9b8c061 added mail 2025-07-15 20:25:49 +01:00
ameye ccdb44f0d5 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-07-14 14:14:13 +00:00
Chinenye Nmoh 22ea12b3c4 resolved method not found error 2025-07-14 07:59:37 +01:00
ameye 71608091ab Merge branch 'oracle_migration' of DigiFi/digifi-EventManager into master 2025-07-11 10:27:08 +00:00
ameye 16758c3ae9 Merge branch 'test' of DigiFi/digifi-EventManager into master 2025-07-11 10:27:02 +00:00
Chinenye Nmoh 23574a433a corrected commit error 2025-07-08 10:49:04 +01:00
13 changed files with 187 additions and 64 deletions
+10 -10
View File
@@ -3,14 +3,14 @@ KAFKA_TIMEOUT=1000.0
KAFKA_BROKER="10.20.30.50:9092" KAFKA_BROKER="10.20.30.50:9092"
KAFKA_TOPICS=PROCESS_PAYMENT,LOAN_REPAYMENT KAFKA_TOPICS=PROCESS_PAYMENT,LOAN_REPAYMENT
# DATABASE_USER=firstadvance DATABASE_USER=firstadvance
# DATABASE_PASSWORD=FirstAdvance! DATABASE_PASSWORD=FirstAdvance!
# DATABASE_HOST=10.20.30.60 DATABASE_HOST=10.20.30.60
# DATABASE_PORT=5432 DATABASE_PORT=5432
# DATABASE_NAME=firstadvancedev DATABASE_NAME=firstadvancedev
DATABASE_USER=system # DATABASE_USER=system
DATABASE_PASSWORD=FIRSTADV_PASS #DATABASE_PASSWORD=FIRSTADV_PASS
DATABASE_HOST=10.10.33.65 #DATABASE_HOST=10.10.33.65
DATABASE_PORT=1521 #DATABASE_PORT=1521
DATABASE_SID=FREE #DATABASE_SID=FREE
+10 -10
View File
@@ -3,14 +3,14 @@ KAFKA_TIMEOUT=1000.0
KAFKA_BROKER="dev-events.simbrellang.net:9085" KAFKA_BROKER="dev-events.simbrellang.net:9085"
KAFKA_TOPICS=PROCESS_PAYMENT,LOAN_REPAYMENT KAFKA_TOPICS=PROCESS_PAYMENT,LOAN_REPAYMENT
# DATABASE_USER=firstadvance DATABASE_USER=firstadvance
# DATABASE_PASSWORD=FirstAdvance! DATABASE_PASSWORD=FirstAdvance!
# DATABASE_HOST=dev-data.simbrellang.net DATABASE_HOST=10.20.30.60
# DATABASE_PORT=10532 DATABASE_PORT=5432
# DATABASE_NAME=firstadvancedev DATABASE_NAME=firstadvancedev
DATABASE_USER=system # DATABASE_USER=system
DATABASE_PASSWORD=FIRSTADV_PASS #DATABASE_PASSWORD=FIRSTADV_PASS
DATABASE_HOST=10.10.33.65 #DATABASE_HOST=10.10.33.65
DATABASE_PORT=1521 #DATABASE_PORT=1521
DATABASE_SID=FREE #DATABASE_SID=FREE
+5 -1
View File
@@ -1,9 +1,10 @@
from flask import Flask from flask import Flask
from flask_mail import Mail
from flask_cors import CORS from flask_cors import CORS
from app.config import Config from app.config import Config
from app.routes import auth_bp, autocall_bp from app.routes import auth_bp, autocall_bp
from app.response import (method_not_allowed, unsupported_media_type, not_found, bad_request) from app.response import (method_not_allowed, unsupported_media_type, not_found, bad_request)
from app.extensions import db from app.extensions import db, mail
def create_app(): def create_app():
@@ -16,6 +17,9 @@ def create_app():
# Setup CORS # Setup CORS
CORS(app) CORS(app)
# Initialize Flask-Mail
mail.init_app(app)
# Register blueprints # Register blueprints
app.register_blueprint(auth_bp) app.register_blueprint(auth_bp)
app.register_blueprint(autocall_bp, url_prefix="/autocall") app.register_blueprint(autocall_bp, url_prefix="/autocall")
+12 -3
View File
@@ -33,16 +33,25 @@ class Config:
DATABASE_NAME = os.getenv("DATABASE_NAME") DATABASE_NAME = os.getenv("DATABASE_NAME")
DATABASE_PORT = os.getenv("DATABASE_PORT", 10532) DATABASE_PORT = os.getenv("DATABASE_PORT", 10532)
DATABASE_SID = os.environ.get("DATABASE_SID", "FREE") DATABASE_SID = os.environ.get("DATABASE_SID", "FREE")
DNS = f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={DATABASE_HOST})(PORT={DATABASE_PORT}))(CONNECT_DATA=(SID={DATABASE_SID})))" #DNS = f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={DATABASE_HOST})(PORT={DATABASE_PORT}))(CONNECT_DATA=(SID={DATABASE_SID})))"
SQLALCHEMY_DATABASE_URI = (f"oracle+oracledb://{DATABASE_USER}:{DATABASE_PASSWORD}@{DNS}") #SQLALCHEMY_DATABASE_URI = (f"oracle+oracledb://{DATABASE_USER}:{DATABASE_PASSWORD}@{DNS}")
# SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}" SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
# SQLALCHEMY_ECHO = True # SQLALCHEMY_ECHO = True
MAIL_SERVER = os.getenv('MAIL_SERVER','smtp.zoho.com')
MAIL_PORT = os.getenv('MAIL_PORT', 587)
MAIL_USERNAME = os.getenv('MAIL_USERNAME', 'firstadvance@dynamikservices.tech')
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')
MAIL_USE_TLS = os.getenv('MAIL_USE_TLS', 'True').lower() in ('true', '1', 'yes')
MAIL_USE_SSL = os.getenv('MAIL_USE_SSL', 'False').lower() in ('true', '1', 'yes')
MAIL_DEFAULT_SENDER = ('FirstAdvance', 'firstadvance@dynamikservices.tech')
MAIL_RECEIVER= os.getenv('MAIL_RECEIVER', 'chinenyeumeaku@gmail.com')
BANK_CALL_BASE_URL = os.getenv("BANK_CALL_BASE_URL", "https://bank-emulator.dev.simbrellang.net/api") BANK_CALL_BASE_URL = os.getenv("BANK_CALL_BASE_URL", "https://bank-emulator.dev.simbrellang.net/api")
BANK_CALL_SMS_BASE_URL= os.getenv("BANK_CALL_SMS_BASE_URL","https://first-advance-middleware-develop.fbn-devops-dev-asenv.appserviceenvironment.net/SMS") BANK_CALL_SMS_BASE_URL= os.getenv("BANK_CALL_SMS_BASE_URL","https://first-advance-middleware-develop.fbn-devops-dev-asenv.appserviceenvironment.net/SMS")
BANK_CALL_DISBURSE_LOAN_ENDPOINT = os.getenv("BANK_CALL_DISBURSE_LOAN_ENDPOINT","/DisburseLoan") BANK_CALL_DISBURSE_LOAN_ENDPOINT = os.getenv("BANK_CALL_DISBURSE_LOAN_ENDPOINT","/DisburseLoan")
+2
View File
@@ -1,3 +1,5 @@
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
mail = Mail()
db = SQLAlchemy() db = SQLAlchemy()
+42 -9
View File
@@ -7,6 +7,7 @@ from app.utils.extras import preprocess_loan_charges_data
import random import random
import random import random
import string import string
from app.extensions import db
from app.utils.logger import logger from app.utils.logger import logger
from flask import jsonify, current_app from flask import jsonify, current_app
from app.services.transactions import TransactionService 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 decimal import Decimal, ROUND_HALF_UP
from requests.exceptions import SSLError, RequestException,Timeout from requests.exceptions import SSLError, RequestException,Timeout
import sys import sys
from requests.exceptions import ReadTimeout, ConnectTimeout
import socket
class SimbrellaClient: class SimbrellaClient:
@@ -30,7 +33,7 @@ class SimbrellaClient:
@staticmethod @staticmethod
def disburse_loan(data): def disburse_loan(data):
api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}/{SimbrellaClient.BANK_CALL_DISBURSE_LOAN_ENDPOINT}" api_url = f"{SimbrellaClient.BANK_CALL_BASE_URL}{SimbrellaClient.BANK_CALL_DISBURSE_LOAN_ENDPOINT}"
logger.info(f"Calling DisburseLoan api_url==> : {api_url}") logger.info(f"Calling DisburseLoan api_url==> : {api_url}")
logger.info(f"Calling DisburseLoan endpoint with data: {data}") logger.info(f"Calling DisburseLoan endpoint with data: {data}")
@@ -75,9 +78,10 @@ class SimbrellaClient:
insurance_fee = loan_charges.get("INSURANCE")['amount'] insurance_fee = loan_charges.get("INSURANCE")['amount']
debtId = str(loan_data.get('debtId', "")).strip().zfill(6) debtId = str(loan_data.get('debtId', "")).strip().zfill(6)
t_id = ''.join(random.choices(string.ascii_uppercase, k=22))
disbursement_data = { disbursement_data = {
"transactionId": loan_data.get('transactionId'), "transactionId": t_id,
"FbnTransactionId": loan_data.get('transactionId'), "FbnTransactionId": loan_data.get('transactionId'),
"debtId": debtId, "debtId": debtId,
"customerId": loan_data.get('customerId'), "customerId": loan_data.get('customerId'),
@@ -94,11 +98,12 @@ class SimbrellaClient:
try: try:
logger.info(f"Here is your Disbursement Request data ****** : {disbursement_data}") logger.info(f"Here is your Disbursement Request data ****** : {disbursement_data}")
response = requests.post(api_url, json=disbursement_data, timeout=10, headers=get_headers()) response = requests.post(api_url, json=disbursement_data, timeout=90, headers=get_headers())
if response.status_code == 404: if response.status_code == 404:
logger.error("Received 404 from external service") logger.error("Received 404 from external service")
return ResponseHelper.error("Disbursement Service url not found (404)", status_code=404) return ResponseHelper.error("Disbursement Service url not found (404)", status_code=404)
logger.info(f"Disbursement response: {response.json()}") logger.info(f"Disbursement response: {response.json()}")
result = response.json() result = response.json()
LoanService.set_disbursement_result(loan_data['debtId'],result.get('responseCode', ''), result.get('responseMessage', '')) LoanService.set_disbursement_result(loan_data['debtId'],result.get('responseCode', ''), result.get('responseMessage', ''))
return ResponseHelper.success(response.json(), "Successful") return ResponseHelper.success(response.json(), "Successful")
@@ -174,7 +179,7 @@ class SimbrellaClient:
"unicode": True "unicode": True
} }
try: try:
sms_response = requests.post(sms_url, json=sms_data, timeout=10, headers=get_headers()) sms_response = requests.post(sms_url, json=sms_data, timeout=90, headers=get_headers())
sms_response.raise_for_status() # Raise an exception for 4xx or 5xx status codes sms_response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
result = sms_response.json() result = sms_response.json()
@@ -190,7 +195,7 @@ class SimbrellaClient:
return 0 return 0
except Exception as e: except Exception as e:
logger.info(f"Failed to call TransactionVerify endpoint: {e}") logger.info(f"Failed to call TransactionVerify endpoint: {e}")
return 0 return ResponseHelper.error("Unexpected error while processing loan disbursement", status_code=500, error=str(e))
@staticmethod @staticmethod
def collect_loan_user_initiated(data): def collect_loan_user_initiated(data):
@@ -273,11 +278,11 @@ class SimbrellaClient:
try: try:
logger.info(f"Sending CollectLoan request............ {collect_loan_data}") 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}") logger.info(f"HTTP response object: {response}")
if response.status_code == 404: if response.status_code == 404:
db.session.rollback()
RepaymentService.set_repay_result( RepaymentService.set_repay_result(
repayment_data['Id'], repayment_data['Id'],
'404', '404',
@@ -326,6 +331,7 @@ class SimbrellaClient:
updated_loan = LoanService.update_loan_balance(int(loan_data['debtId']), amount_collected) updated_loan = LoanService.update_loan_balance(int(loan_data['debtId']), amount_collected)
logger.info(f"Updated loan: {updated_loan}") logger.info(f"Updated loan: {updated_loan}")
except Exception as ex: except Exception as ex:
db.session.rollback()
logger.error(f"Error updating loan balance for loan ID {loan.id}: {ex}") logger.error(f"Error updating loan balance for loan ID {loan.id}: {ex}")
return ResponseHelper.error("Error updating loan balance") return ResponseHelper.error("Error updating loan balance")
@@ -342,28 +348,55 @@ class SimbrellaClient:
partial = LoanService.update_status(updated_loan['debtId'], LoanStatus.ACTIVE_PARTIAL) partial = LoanService.update_status(updated_loan['debtId'], LoanStatus.ACTIVE_PARTIAL)
logger.info(f'Updated loan with partial status: {partial}') logger.info(f'Updated loan with partial status: {partial}')
except Exception as e: except Exception as e:
db.session.rollback()
logger.error(f"Error while updating loan status for debtId {updated_loan['debtId']}: {e}") logger.error(f"Error while updating loan status for debtId {updated_loan['debtId']}: {e}")
return ResponseHelper.success(result, "Successful") return ResponseHelper.success(result, "Successful")
except SSLError as ssl_err: except SSLError as ssl_err:
db.session.rollback()
logger.exception(f"SSL error while calling Simbrella endpoint: {ssl_err}") 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)) 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}") 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)) return ResponseHelper.error("Connection to Simbrella timed out", status_code=504, error=str(timeout_err))
except RequestException as req_err: except RequestException as req_err:
db.session.rollback()
logger.exception(f"RequestException while calling Simbrella: {req_err}") 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)) return ResponseHelper.error("Connection to Simbrella failed", status_code=503, error=str(req_err))
except SystemExit as sys_exit: except SystemExit as sys_exit:
db.session.rollback()
logger.error(f"SystemExit was triggered: {sys_exit}") 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)) return ResponseHelper.error("Unexpected shutdown detected", status_code=500, error=str(sys_exit))
except Exception as e: except Exception as e:
db.session.rollback()
logger.exception(f"Unexpected error occurred while calling CollectLoan: {e}") 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)) return ResponseHelper.error("Unexpected error while processing loan collection", status_code=500, error=str(e))
-2
View File
@@ -1,5 +1,4 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from app.extensions import db
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from datetime import timedelta from datetime import timedelta
@@ -93,7 +92,6 @@ class Loan(db.Model):
'reference': self.reference, 'reference': self.reference,
'balance': self.balance, 'balance': self.balance,
'tenor': self.tenor, 'tenor': self.tenor,
} }
@classmethod @classmethod
+4 -2
View File
@@ -150,10 +150,11 @@ class Repayment(db.Model):
try: try:
logger.info(f"Updating repay date for repayment ID {repayment_id} to {current_time}") logger.info(f"Updating repay date for repayment ID {repayment_id} to {current_time}")
db.session.commit() db.session.commit()
return repayment.to_dict()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
logger.error(f"Failed to update repay date: {e}") logger.error(f"Failed to update repay date: {e}")
raise raise e
@classmethod @classmethod
def set_repay_verify_date(cls, repayment_id, customer_id): def set_repay_verify_date(cls, repayment_id, customer_id):
""" """
@@ -181,11 +182,12 @@ class Repayment(db.Model):
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
logger.error(f"Failed to update repay verify date: {e}") logger.error(f"Failed to update repay verify date: {e}")
raise raise e
@classmethod @classmethod
def set_repay_result(cls, repayment_id, result, description): 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. Update the repayment result and description of the repayment with the given repayment_id.
""" """
+1 -1
View File
@@ -25,7 +25,7 @@ class RepaymentsData(db.Model):
"response_code": self.response_code, "response_code": self.response_code,
"response_descr": self.response_descr, "response_descr": self.response_descr,
"customerId": self.customer_id, "customerId": self.customer_id,
"accountId": self.customer_id, "accountId": self.account_id,
"fbnTransactionId": self.fbn_transaction_id, "fbnTransactionId": self.fbn_transaction_id,
"repaymentAmount": self.repayment_amount, "repaymentAmount": self.repayment_amount,
"amountCollected": self.amount_collected, "amountCollected": self.amount_collected,
+44 -22
View File
@@ -1,5 +1,6 @@
from flask import Blueprint, request, jsonify, current_app from flask import Blueprint, request, jsonify, current_app
import requests import requests
from app.extensions import db
from app.config import settings from app.config import settings
from app.helpers.response_helper import ResponseHelper from app.helpers.response_helper import ResponseHelper
from app.utils.auth import get_headers from app.utils.auth import get_headers
@@ -9,6 +10,8 @@ from app.services.loan import LoanService
from app.services.repayment import RepaymentService from app.services.repayment import RepaymentService
from app.services.salary import SalaryService from app.services.salary import SalaryService
from app.enums.loan_status import LoanStatus from app.enums.loan_status import LoanStatus
from app.utils.mail import send_report_email, get_report_data
from app.config import settings
autocall_bp = Blueprint("autocall", __name__) autocall_bp = Blueprint("autocall", __name__)
@@ -167,6 +170,7 @@ def process_salary_list():
try: try:
SalaryService.update_status(pending_salary.id, "PROCESSING") SalaryService.update_status(pending_salary.id, "PROCESSING")
except Exception as e: except Exception as e:
db.session.rollback()
logger.warning(f"Could not update status for salary ID {pending_salary.id}: {e}") logger.warning(f"Could not update status for salary ID {pending_salary.id}: {e}")
continue continue
@@ -177,6 +181,7 @@ def process_salary_list():
logger.warning(f"No loans found for customer ID: {pending_salary.customer_id}") logger.warning(f"No loans found for customer ID: {pending_salary.customer_id}")
continue continue
except Exception as e: except Exception as e:
db.session.rollback()
logger.error(f"Error fetching loans for customer ID {pending_salary.customer_id}: {e}") logger.error(f"Error fetching loans for customer ID {pending_salary.customer_id}: {e}")
continue continue
@@ -197,40 +202,57 @@ def process_salary_list():
logger.info(f"Creating repayment with data: {repayment_data}") logger.info(f"Creating repayment with data: {repayment_data}")
repayment = RepaymentService.create_repayment(repayment_data) repayment = RepaymentService.create_repayment(repayment_data)
if not repayment: if not repayment or isinstance(repayment, dict) and "error" in repayment:
logger.error(f"Repayment creation failed for loan ID {loan.id}") db.session.rollback() # important in case create_repayment failed mid-way
logger.error(f"Repayment creation failed for loan ID {loan.id}: {repayment}")
continue continue
# Update loan status to START_REPAY
try: try:
LoanService.update_status(loan_id=loan.id, status=LoanStatus.START_REPAY) LoanService.update_status(loan_id=loan.id, status=LoanStatus.START_REPAY)
except Exception as e: except Exception as e:
db.session.rollback()
logger.error(f"Failed to update loan status for loan ID {loan.id}: {e}") logger.error(f"Failed to update loan status for loan ID {loan.id}: {e}")
logger.info(f"Created repayment ID: {repayment.id}") 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: except Exception as e:
db.session.rollback()
logger.error(f"Error creating repayment for loan ID {loan.id}: {e}") logger.error(f"Error creating repayment for loan ID {loan.id}: {e}")
continue 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}") logger.info(f"Finished processing salary ID: {pending_salary.id}")
return ResponseHelper.success([], "Processed all pending salaries") return ResponseHelper.success([], "Processed all pending salaries")
@autocall_bp.route("/report", methods=["GET"])
def report():
try:
report_data = get_report_data()
logger.info(f"Generated report data: {report_data}")
send_report_email(
report_data,
recipients = [email.strip() for email in settings.MAIL_RECEIVER.split(",")])
logger.info(f"Report sent successfully")
return ResponseHelper.success(message="Report sent successfully",status_code=200)
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))
+42
View File
@@ -0,0 +1,42 @@
from flask_mail import Message
from flask import current_app
from app.extensions import mail
import pandas as pd
from io import BytesIO
from app.utils.logger import logger
def get_report_data():
"""
Fetch and return loan summary data.
"""
return [
{"Type": "Disbursement", "Count": 45},
{"Type": "Repayment", "Count": 32},
]
def send_report_email(report_data: list, recipients: list):
"""
Sends an HTML + Excel report to the given email recipients.
"""
df = pd.DataFrame(report_data)
output = BytesIO()
df.to_excel(output, index=False)
output.seek(0)
html_table = df.to_html(index=False, border=1)
msg = Message(
subject="Loan Report Summary",
recipients=recipients,
html=f"<h3>Loan Report Summary</h3>{html_table}",
)
msg.attach(
"loan_report.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
output.read()
)
with current_app.app_context():
mail.send(msg)
return "Report email sent"
+10 -2
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: Event Manager API title: Eco Integration Event Manager API
description: The documentation for Event Manager API description: The documentation for Eco Event Manager API
version: 1.0.0 version: 1.0.0
contact: contact:
name: API Support name: API Support
@@ -201,3 +201,11 @@ paths:
responses: responses:
200: 200:
description: A successful response description: A successful response
/autocall/report:
get:
summary: Generate and send a report
responses:
200:
description: A successful response
+3
View File
@@ -11,3 +11,6 @@ psycopg2-binary
alembic alembic
python-dateutil python-dateutil
oracledb oracledb
Flask-Mail==0.10.0
pandas==2.1.3
openpyxl==3.1.5