Compare commits

..

1 Commits

Author SHA1 Message Date
VivianDee d3ceda7245 [update]: offer analysis log 2025-07-14 11:20:12 +01:00
33 changed files with 574 additions and 630 deletions
-1
View File
@@ -12,7 +12,6 @@ DATABASE_PASSWORD=FirstAdvance!
DATABASE_HOST=dev-data.simbrellang.net
DATABASE_PORT=10532
DATABASE_NAME=firstadvancedev
#SQLALCHEMY_DATABASE_URI_FULL="oracle+oracledb://FIRSTADVSTG:Pchanged_56789@10.2.110.30:1521/?service_name=firstadv"
# DATABASE_HOST=10.20.30.60
# DATABASE_USER=firstadvance
-15
View File
@@ -1,15 +0,0 @@
from flask import Flask
from app.extensions import mail
from app.utils.mail import send_report_email, get_report_data
from app.config import settings
app = Flask(__name__)
app.config.from_object(settings)
mail.init_app(app)
with app.app_context():
report_data = get_report_data()
recipients = ["vdagbue@gmail.com"]
result = send_report_email(report_data, recipients)
print(result)
+1 -5
View File
@@ -1,5 +1,4 @@
from flask import Flask
from flask_mail import Mail
import os
from flask_swagger_ui import get_swaggerui_blueprint
from flask_cors import CORS
@@ -8,7 +7,7 @@ from app.api.routes import api
from app.errors import register_error_handlers
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from app.extensions import db, migrate, mail
from app.extensions import db, migrate
from flask_jwt_extended import (
JWTManager,
jwt_required,
@@ -48,9 +47,6 @@ def create_app():
from . import models
# Initialize Flask-Mail
mail.init_app(app)
# Database and Migrations
db.init_app(app)
+1 -2
View File
@@ -1,3 +1,2 @@
from .simbrella import SimbrellaIntegration
from .kafka import KafkaIntegration
from .events_service import EventServiceIntegration
from .kafka import KafkaIntegration
-49
View File
@@ -1,49 +0,0 @@
import httpx
from app.utils.logger import logger
from app.config import settings
class EventServiceIntegration:
BASE_URL = settings.SIMBRELLA_BASE_URL
ENDPOINT_DIRECT_LOAN = settings.ENDPOINT_DIRECT_LOAN
ENDPOINT_DIRECT_REPAYMENT = settings.ENDPOINT_DIRECT_REPAYMENT
@staticmethod
def direct_loan(transaction_id: str):
"""
Calls the Direct Loan endpoint
"""
url = f"{EventServiceIntegration.BASE_URL}{EventServiceIntegration.ENDPOINT_DIRECT_LOAN}"
payload = {"transactionId": str(transaction_id)}
headers = {
"Content-Type": "application/json"
}
try:
response = httpx.post(url, json=payload, headers=headers, timeout=10.0)
logger.info(f"Loan Response: {response.text}")
return response
except Exception as e:
logger.error(f"Direct Loan API call failed: {str(e)}", exc_info=True)
raise
@staticmethod
def direct_repayment(transaction_id: str):
"""
Calls the Direct Repayment endpoint
"""
url = f"{EventServiceIntegration.BASE_URL}{EventServiceIntegration.ENDPOINT_DIRECT_REPAYMENT}"
payload = {"transactionId": str(transaction_id)}
headers = {
"Content-Type": "application/json",
}
try:
response = httpx.post(url, json=payload, headers=headers, timeout=10.0)
logger.info(f"Repayment Response: {response.text}")
return response
except Exception as e:
logger.error(f"Direct Repayment API call failed: {str(e)}", exc_info=True)
raise
-4
View File
@@ -4,7 +4,6 @@ from flask import jsonify
from marshmallow import ValidationError
import logging
from app.api.integrations import KafkaIntegration
from app.utils.mail import send_report_email, get_report_data
logger = logging.getLogger(__name__)
@@ -174,6 +173,3 @@ class BaseService:
# return {"rate": 0, "fee": 0, "due_days": 0}
@classmethod
def send_mail(cls, report_data, recipients):
send_report_email(report_data, recipients)
+7 -4
View File
@@ -87,10 +87,13 @@ class OfferAnalysis:
if failed_true_rules or failed_false_rules or not salaries:
logger.warning(f"Failed TRUE rules: {failed_true_rules}")
logger.warning(f"Failed FALSE rules: {failed_false_rules}")
logger.warning("No salary records found in RAC response.")
raise ValueError(f"RAC analysis failed")
if failed_true_rules:
logger.warning(f"RAC analysis failed: TRUE rules failed: {failed_true_rules}")
if failed_false_rules:
logger.warning(f"RAC analysis failed: FALSE rules failed: {failed_false_rules}")
if not salaries:
logger.warning("RAC analysis failed: No salary records found in RAC response.")
raise ValueError("RAC analysis failed")
+3 -15
View File
@@ -13,7 +13,6 @@ from app.api.enums import LoanStatus
from app.extensions import db
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
from app.api.integrations import EventServiceIntegration
from app.models import LoanRepaymentSchedule
from app.api.services.offer_analysis import OfferAnalysis
from app.api.helpers.response_helper import ResponseHelper
@@ -173,15 +172,11 @@ class ProvideLoanService(BaseService):
"accountId": account_id,
"msisdn": customer.msisdn
}
event_thread = Thread(target=ProvideLoanService.trigger_loan_disbursement, args=(transaction_id,))
event_thread.start()
# KafkaIntegration.send_loan_request(loan_data = response_data, request_id = request_id)
# Call Kafka in a background thread
kafka_thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id, "PROCESS_PAYMENT"))
kafka_thread.start()
thread = Thread(target=ProvideLoanService.async_send_to_kafka, args=(response_data, request_id, "PROCESS_PAYMENT"))
thread.start()
db.session.commit()
return ResponseHelper.success(data=response_data)
@@ -200,11 +195,4 @@ class ProvideLoanService(BaseService):
except Exception as e:
logger.error(f"An error occurred: {str(e)}", exc_info=True)
db.session.rollback()
return ResponseHelper.internal_server_error()
@classmethod
def trigger_loan_disbursement(cls, transaction_id: str):
response = EventServiceIntegration.direct_loan(transaction_id=transaction_id)
return response
return ResponseHelper.internal_server_error()
+2 -13
View File
@@ -11,7 +11,6 @@ from app.api.services.base_service import BaseService
from app.api.enums import TransactionType
from threading import Thread
from app.extensions import db
from app.api.integrations import EventServiceIntegration
class RepaymentService(BaseService):
TRANSACTION_TYPE = TransactionType.REPAYMENT
@@ -80,12 +79,9 @@ class RepaymentService(BaseService):
"debtId": loan_id
}
event_thread = Thread(target=RepaymentService.trigger_loan_repayment, args=(transaction_id,))
event_thread.start()
# Call Kafka in a background thread
kafka_thread = Thread(target=RepaymentService.async_send_to_kafka, args=(response_data, request_id, "LOAN_REPAYMENT"))
kafka_thread.start()
thread = Thread(target=RepaymentService.async_send_to_kafka, args=(response_data, request_id, "LOAN_REPAYMENT"))
thread.start()
db.session.commit()
return ResponseHelper.success(data=response_data)
@@ -105,10 +101,3 @@ class RepaymentService(BaseService):
logger.error(f"An error occurred: {str(e)}", exc_info=True)
db.session.rollback()
return ResponseHelper.internal_server_error()
@classmethod
def trigger_loan_repayment(cls, transaction_id: str):
response = EventServiceIntegration.direct_repayment(transaction_id=transaction_id)
return response
+2 -17
View File
@@ -24,10 +24,7 @@ class Config:
# Database Connection
# SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
SQLALCHEMY_DATABASE_URI_INTERNAL = (f"oracle+oracledb://{DATABASE_USER}:{DATABASE_PASSWORD}@{DNS}")
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI_FULL", SQLALCHEMY_DATABASE_URI_INTERNAL)
#SQLALCHEMY_DATABASE_URI_FULL = 'oracle+oracledb://FIRSTADVSTG:Pchanged_56789@10.2.110.30:1521/?service_name=firstadv'
SQLALCHEMY_DATABASE_URI = (f"oracle+oracledb://{DATABASE_USER}:{DATABASE_PASSWORD}@{DNS}")
SQLALCHEMY_TRACK_MODIFICATIONS = False
@@ -46,10 +43,6 @@ class Config:
VALID_API_KEY = os.getenv("SIMBRELLA_API_KEY", "test-api-key-12345")
SIMBRELLA_BASE_URL = os.getenv("SIMBRELLA_BASE_URL", "http://127.0.0.1:6337")
SIMBRELLA_ENDPOINT_RAC_CHECKS = os.getenv("SIMBRELLA_ENDPOINT_RAC_CHECKS","api/rac-check")
EVENTS_SERVICE_BASE_URL = os.getenv("EVENTS_SERVICE_BASE_URL","https://event-core.simbrellang.net")
ENDPOINT_DIRECT_LOAN = os.getenv("ENDPOINT_DIRECT_LOAN","/autocall/direct/loan")
ENDPOINT_DIRECT_REPAYMENT = os.getenv("ENDPOINT_DIRECT_REPAYMENT","/autocall/direct/repayment")
RAC_RESULT_accountStatus = os.environ.get("RAC_RESULT_accountStatus", "true")
RAC_RESULT_bvnValidated = os.environ.get("RAC_RESULT_bvnValidated", "true")
@@ -92,15 +85,7 @@ class Config:
"salarypaymenT_6"
]
MAIL_SERVER = os.getenv('MAIL_SERVER','smtp.zoho.com')
MAIL_PORT = 587
MAIL_USERNAME = os.getenv('MAIL_USERNAME', 'firstadvance@dynamikservices.tech')
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')
MAIL_USE_TLS = True
MAIL_USE_SSL = False
MAIL_DEFAULT_SENDER = ('FirstAdvance', 'firstadvance@dynamikservices.tech')
MAIL_RECEIVER= os.getenv('MAIL_RECEIVER', 'vdagbue@gmail.com')
settings = Config()
-2
View File
@@ -1,7 +1,5 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_mail import Mail
mail = Mail()
db = SQLAlchemy()
migrate = Migrate()
-5
View File
@@ -17,11 +17,6 @@ class LoanRepaymentSchedule(db.Model):
total_repayment_amount = db.Column(db.Float, default=0.0)
paid = db.Column(db.Boolean, default=False)
paid_at = db.Column(db.DateTime, nullable=True)
due_process_date = db.Column(db.DateTime, nullable=True)
due_process_count = db.Column(db.Integer, default=0)
paid_status = db.Column(db.String(20), nullable=True)
repay_description = db.Column(db.String(255), nullable=True)
partial_balance = db.Column(db.Float, default=0.0)
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
+14 -5
View File
@@ -20,15 +20,12 @@
{
"url": "http://api.dev.simbrellang.net:4500"
},
{
{
"url": "https://api.dev.simbrellang.net"
},
{
"url": "http://10.2.249.133:4500"
}
],
"tags": [
{
{
"name": "Authorize",
"description": "This feature will be used for authorizing customers.",
"externalDocs": {
@@ -134,6 +131,18 @@
"RepaymentResponse": {
"$ref": "swagger/schemas/RepaymentResponse.json"
},
"CustomerConsentRequest": {
"$ref": "swagger/schemas/CustomerConsentRequest.json"
},
"CustomerConsentResponse": {
"$ref": "swagger/schemas/CustomerConsentResponse.json"
},
"NotificationCallbackRequest": {
"$ref": "swagger/schemas/NotificationCallbackRequest.json"
},
"NotificationCallbackResponse": {
"$ref": "swagger/schemas/NotificationCallbackResponse.json"
},
"ApiResponse": {
"$ref": "swagger/schemas/ApiResponse.json"
},
+56
View File
@@ -0,0 +1,56 @@
{
"post": {
"tags": [
"CustomerConsent"
],
"summary": "Customer Consent Request",
"description": "Customer Consent Request",
"operationId": "CustomerConsent",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/CustomerConsentRequest.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/CustomerConsentRequest.json"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "../schemas/CustomerConsentRequest.json"
}
}
}
},
"responses": {
"200": {
"description": "Successful",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/CustomerConsentResponse.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/CustomerConsentResponse.json"
}
}
}
},
"400": {
"description": "Invalid request"
},
"422": {
"description": "Validation exception"
},
"500": {
"description": "Internal server error"
}
}
}
}
@@ -0,0 +1,57 @@
{
"post": {
"tags": [
"NotificationCallback"
],
"summary": "Loan Information Request ",
"description": "Loan Information Request",
"operationId": "NotificationCallback",
"requestBody": {
"description": "Post JSON to conduct eligibility tests",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/NotificationCallbackRequest.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/NotificationCallbackRequest.json"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "../schemas/NotificationCallbackRequest.json"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/NotificationCallbackResponse.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/NotificationCallbackResponse.json"
}
}
}
},
"400": {
"description": "Invalid request"
},
"422": {
"description": "Validation exception"
},
"500": {
"description": "Internal server error"
}
}
}
}
@@ -0,0 +1,37 @@
{
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "CustomerConsentRequest"
},
"transactionId": {
"type": "string",
"example": "20171209232177"
},
"customerId": {
"type": "string",
"example": "CN621868"
},
"accountId": {
"type": "string",
"example": "ACN8263457"
},
"requestTime": {
"type": "string",
"format": "date-time",
"example": "2019-10-18 14:26:21.063"
},
"consentType": {
"type": "string",
"example": "Revoke"
},
"channel": {
"type": "string",
"example": "USSD"
}
},
"xml": {
"name": "CustomerConsentRequest"
}
}
@@ -0,0 +1,16 @@
{
"type": "object",
"properties": {
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Request is received"
}
},
"xml": {
"name": "CustomerConsentResponse"
}
}
@@ -3,7 +3,7 @@
"properties": {
"transactionId": {
"type": "string",
"example": "TRX201712RK9232P115"
"example": "Tr201712RK9232P115"
},
"countryCode": {
"type": "string",
@@ -11,19 +11,19 @@
},
"customerId": {
"type": "string",
"example": "5268548"
},
"accountId": {
"type": "string",
"example": "4348094226"
"example": "CN621868"
},
"msisdn": {
"type": "string",
"example": "2348093451342"
"example": "8093451342"
},
"channel": {
"type": "string",
"example": "USSD"
"example": "100"
},
"accountId": {
"type": "string",
"example": "ACN8263457"
}
},
"xml": {
@@ -3,19 +3,19 @@
"properties": {
"customerId": {
"type": "string",
"example": "5268548"
"example": "CN621868"
},
"transactionId": {
"type": "string",
"example": "TRX201712RK9232P115"
"example": "TX12345"
},
"countryCode": {
"type": "string",
"example": "NGR"
"example": "NG"
},
"msisdn": {
"type": "string",
"example": "2348093451342"
"example": "3451342"
},
"eligibleOffers": {
"type": "array",
@@ -24,42 +24,42 @@
"properties": {
"offerId": {
"type": "string",
"example": "SAL90000204"
"example": "Offer1"
},
"productId": {
"type": "string",
"example": "3MPC"
"example": "Product1"
},
"minAamount": {
"type": "number",
"format": "decimal",
"example": 20000.00
"example": 100.00
},
"maxAamount": {
"type": "number",
"format": "decimal",
"example": 31257.00
"example": 1000.00
},
"tenor": {
"type": "integer",
"example": 90
"example": 12
}
}
},
"example": [
{
"max_amount": "31257.00",
"min_amount": 20000.0,
"offerId": "SAL90000204",
"product_id": "3MPC",
"tenor": 90
{
"offerId": "Offer1",
"productId": "Product1",
"minAamount": 100.00,
"maxAamount": 1000.00,
"tenor": 12
},
{
"max_amount": "20838.00",
"min_amount": 5000.0,
"offerId": "SAL30000205",
"product_id": "AMPC",
"tenor": 30
"offerId": "Offer2",
"productId": "Product2",
"minAamount": 200.00,
"maxAamount": 2000.00,
"tenor": 24
}
]
},
+4 -4
View File
@@ -3,15 +3,15 @@
"properties": {
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
"example": "Tr201712RK9232P115"
},
"customerId": {
"type": "string",
"example": "ZX48440946"
"example": "CN621868"
},
"msisdn": {
"type": "string",
"example": "2348093451342"
"example": "3451342"
},
"channel": {
"type": "string",
@@ -19,7 +19,7 @@
},
"accountId": {
"type": "string",
"example": "361005323"
"example": "ACN8263457"
}
},
"xml": {
+77 -104
View File
@@ -1,110 +1,83 @@
{
"type": "object",
"properties": {
"customerId": {
"type": "string",
"example": "ZX48440946"
},
"transactionId": {
"type": "string",
"example": "TRCVIC49381378037"
},
"accountId": {
"type": "string",
"example": "361005323"
},
"loans": {
"type": "array",
"items": {
"type": "object",
"properties": {
"debtId": {
"type": "integer",
"example": 80
},
"loanDate": {
"type": "object",
"properties": {
"customerId": {
"type": "string",
"format": "date-time",
"example": "2025-09-12T11:58:58"
},
"dueDate": {
"example": "CN621868"
},
"transactionId": {
"type": "string",
"format": "date-time",
"example": "2025-12-11T11:58:58"
},
"currentLoanAmount": {
"type": "number",
"format": "float",
"example": 30000.0
},
"initialLoanAmount": {
"type": "number",
"format": "float",
"example": 30000.0
},
"defaultPenaltyFee": {
"type": "number",
"format": "float",
"example": 0.0
},
"continuousFee": {
"type": "number",
"format": "float",
"example": 0.0
},
"productId": {
"type": "string",
"example": "3MPC"
},
"installmentAmount": {
"type": "number",
"format": "float",
"example": 10900.0
},
"repaymentAmount": {
"type": "number",
"format": "float",
"example": 32700.0
},
"loanRef": {
"type": "string",
"example": "TRCVIC73089465966USSD3MPC"
},
"status": {
"type": "string",
"example": "active"
},
"tenor": {
"type": "integer",
"example": 90
},
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
},
"upfrontFee": {
"type": "number",
"format": "float",
"example": 622.5
}
"example": "Tr201712RK9232P115"
},
"loans": {
"type": "array",
"items": {
"type": "object",
"properties": {
"debtId": {
"type": "string",
"example": "123456789"
},
"loanDate": {
"type": "string",
"format": "date-time",
"example": "2019-10-18 14:26:21.063"
},
"dueDate": {
"type": "string",
"format": "date-time",
"example": "2019-11-20 14:26:21.063"
},
"currentLoanAmount": {
"type": "integer",
"example": 8500
},
"initialLoanAmount": {
"type": "integer",
"example": 10000
},
"defaultPenaltyFee": {
"type": "integer",
"example": 0
},
"continuousFee": {
"type": "integer",
"example": 0
},
"productId": {
"type": "string",
"example": "101"
},
"installment": {
"type": "array",
"items": {
"type": "object",
"properties": {
"amount": {
"type": "number",
"format": "float",
"example": 10000.0
},
"repaymentDate": {
"type": "string",
"example": "2025-04-24 10:31:"
}
}
}
}
}
}
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
}
}
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
},
"totalDebtAmount": {
"type": "number",
"format": "float",
"example": 30000.0
"xml": {
"name": "LoanStatusResponse"
}
},
"xml": {
"name": "LoanStatusResponse"
}
}
}
@@ -0,0 +1,50 @@
{
"type": "object",
"properties": {
"fbnTransactionId": {
"type": "string",
"example": "123456789"
},
"transactionId": {
"type": "string",
"example": "123456789"
},
"customerId": {
"type": "string",
"example": "CN621868"
},
"accountId": {
"type": "string",
"example": "ACN8263457"
},
"debtId": {
"type": "string",
"example": "987654321"
},
"transactionType": {
"type": "string",
"example": "Disbursement"
},
"amountProvided": {
"type": "number",
"format": "float",
"example": 1000.00
},
"amountCollected": {
"type": "number",
"format": "float",
"example": 0.00
},
"responseCode": {
"type": "string",
"example": "00"
},
"responseDescription": {
"type": "string",
"example": "Successful"
}
},
"xml": {
"name": "NotificationCallbackRequest"
}
}
@@ -0,0 +1,16 @@
{
"type": "object",
"properties": {
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
}
},
"xml": {
"name": "NotificationCallbackResponse"
}
}
+9 -9
View File
@@ -3,40 +3,40 @@
"properties": {
"requestId": {
"type": "string",
"example": "RQID11170001371256908"
"example": "202111170001371256908"
},
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
"example": "Tr201712RK9232P115"
},
"customerId": {
"type": "string",
"example": "ZX48440946"
"example": "CN621868"
},
"accountId": {
"type": "string",
"example": "361005323"
"example": "ACN8263457"
},
"msisdn": {
"type": "string",
"example": "2348093451342"
"example": "3451342"
},
"requestedAmount": {
"type": "number",
"format": "decimal",
"example": 20000
"example": 900
},
"collectionType": {
"type": "integer",
"example": 0
"example": 1
},
"offerId": {
"type": "integer",
"example": "SAL900004543304"
"example": 1127
},
"channel": {
"type": "string",
"example": "USSD"
"example": "100"
}
},
"xml": {
+37 -37
View File
@@ -1,40 +1,40 @@
{
"type": "object",
"properties": {
"requestId": {
"type": "string",
"example": "81757678335583"
"type": "object",
"properties": {
"requestId": {
"type": "string",
"example": "202111170001371256908"
},
"transactionId": {
"type": "string",
"example": "Tr201712RK9232P115"
},
"loanRef": {
"type": "string",
"example": "1620029887USSDAMPC"
},
"customerId": {
"type": "string",
"example": "CN621868"
},
"accountId": {
"type": "string",
"example": "ACN8263457"
},
"msisdn": {
"type": "string",
"example": "3451342"
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
}
},
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
},
"loanRef": {
"type": "string",
"example": "TRCVIC73089465966USSD3MPC"
},
"customerId": {
"type": "string",
"example": "ZX48440946"
},
"accountId": {
"type": "string",
"example": "361005323"
},
"msisdn": {
"type": "string",
"example": "98016510058"
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
"xml": {
"name": "ProvideLoanResponse"
}
},
"xml": {
"name": "ProvideLoanResponse"
}
}
}
+7 -7
View File
@@ -3,27 +3,27 @@
"properties": {
"msisdn": {
"type": "string",
"example": "2348093451342"
"example": "3451342"
},
"debtId": {
"type": "number",
"example": 80
"type": "string",
"example": "10"
},
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
"example": "20171209232115"
},
"customerId": {
"type": "string",
"example": "ZX48440946"
"example": "CID0000025585"
},
"loanRef": {
"type": "string",
"example": "TRCVIC73089465966USSD3MPC"
"example": "Trx5847365252USSD3MPC"
},
"accountId": {
"type": "string",
"example": "361005323"
"example": "ACN8263457"
}
},
"xml": {
+25 -45
View File
@@ -1,48 +1,28 @@
{
"type": "object",
"properties": {
"Id": {
"type": "integer",
"example": 195
"type": "object",
"properties": {
"customerId": {
"type": "string",
"example": "CN621868"
},
"productId": {
"type": "string",
"example": "101"
},
"debtId": {
"type": "string",
"example": "273194670"
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
}
},
"customerId": {
"type": "string",
"example": "ZX48440946"
},
"debtId": {
"type": "string",
"example": "80"
},
"initiated_by": {
"type": "string",
"example": "USER_INITIATED"
},
"loanRef": {
"type": "string",
"example": "TRCVIC73089465966USSD3MPC"
},
"productId": {
"type": "string",
"example": "3MPC"
},
"repayment_id": {
"type": "integer",
"example": 195
},
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
"xml": {
"name": "RepaymentResponse"
}
},
"xml": {
"name": "RepaymentResponse"
}
}
}
+12 -12
View File
@@ -3,40 +3,40 @@
"properties": {
"requestId": {
"type": "string",
"example": "RQID11170001371256908"
"example": "202111170001371256908"
},
"transactionId": {
"type": "string",
"example": "TRX1231231321232"
"example": "1231231321232"
},
"customerId": {
"type": "string",
"example": "CN6215268548868"
},
"accountId": {
"type": "string",
"example": "4348094226"
"example": "CN621868"
},
"msisdn": {
"type": "string",
"example": "2348093451342"
"example": "123456789"
},
"requestedAmount": {
"type": "number",
"example": 20000
"format": "double",
"example": 10000.55
},
"accountId": {
"type": "string",
"example": "ACN8263457"
},
"productId": {
"type": "string",
"example": "3MPC"
},
"offerId": {
"type": "string",
"example": "SAL900004543304"
"example": "101"
},
"channel": {
"type": "string",
"example": "USSD"
"example": ""
}
},
"xml": {
+113 -121
View File
@@ -1,129 +1,121 @@
{
"type": "object",
"properties": {
"requestId": {
"type": "string",
"example": "81757678225025"
},
"transactionId": {
"type": "string",
"example": "TRCVIC73089465966"
},
"customerId": {
"type": "string",
"example": "ZX48440946"
},
"accountId": {
"type": "string",
"example": "361005323"
},
"loan": {
"type": "array",
"items": {
"type": "object",
"properties": {
"offerId": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"example": "SAL90000204"
},
"productId": {
"example": "202111170001371256908"
},
"transactionId": {
"type": "string",
"example": "3MPC"
},
"amount": {
"type": "number",
"format": "float",
"example": 30000.0
},
"upfrontPayment": {
"type": "number",
"format": "float",
"example": 622.5
},
"interestRate": {
"type": "number",
"format": "float",
"example": 3.0
},
"interestFee": {
"type": "number",
"format": "float",
"example": 2700.0
},
"managementRate": {
"type": "number",
"format": "float",
"example": 1.0
},
"managementFee": {
"type": "number",
"format": "float",
"example": 300.0
},
"insuranceRate": {
"type": "number",
"format": "float",
"example": 1.0
},
"insuranceFee": {
"type": "number",
"format": "float",
"example": 300.0
},
"VATRate": {
"type": "number",
"format": "float",
"example": 7.5
},
"VATAmount": {
"type": "number",
"format": "float",
"example": 22.5
},
"recommendedRepaymentDates": {
"example": "1231231321232"
},
"customerId": {
"type": "string",
"example": "1256907"
},
"accountId": {
"type": "string",
"example": "5948306019"
},
"loan": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"2025-10-12",
"2025-11-12",
"2025-12-12"
]
},
"installmentAmount": {
"type": "number",
"format": "float",
"example": 10900.0
},
"repaymentAmount": {
"type": "number",
"format": "float",
"example": 32700.0
},
"totalRepaymentAmount": {
"type": "number",
"format": "float",
"example": 33322.5
}
"type": "object",
"properties": {
"offerId": {
"type": "string",
"example": "14451"
},
"productId": {
"type": "string",
"example": "3MPC"
},
"amount": {
"type": "number",
"format": "float",
"example": 10000.0
},
"dueDate": {
"type": "string",
"example": "2025-04-24 10:31:"
},
"upfrontPayment": {
"type": "number",
"format": "float",
"example": 1000.0
},
"interestRate": {
"type": "number",
"format": "float",
"example": 3.0
},
"interestFee": {
"type": "number",
"format": "float",
"example": 3000.00
},
"ManagementRate": {
"type": "number",
"format": "float",
"example": 1.0
},
"ManagementFee": {
"type": "number",
"format": "float",
"example": 1.0
},
"InsuranceRate": {
"type": "number",
"format": "float",
"example": 1.0
},
"InsuranceFee": {
"type": "number",
"format": "float",
"example": 100.0
},
"VATRate": {
"type": "number",
"format": "float",
"example": 7.5
},
"VATamount": {
"type": "number",
"format": "float",
"example": 100.0
},
"installmentRepaymentDates": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"2022-11-30"
]
},
"installmentAmount": {
"type": "number",
"format": "float",
"example": 11000.0
},
"totalRepaymentAmount": {
"type": "number",
"format": "float",
"example": 11000.0
}
}
}
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
}
}
},
"outstandingDebtAmount": {
"type": "number",
"format": "float",
"example": 0
},
"resultCode": {
"type": "string",
"example": "00"
},
"resultDescription": {
"type": "string",
"example": "Successful"
"xml": {
"name": "SelectOffersResponse"
}
},
"xml": {
"name": "SelectOffersResponse"
}
}
}
-40
View File
@@ -1,40 +0,0 @@
from flask_mail import Message
from flask import current_app
from app.extensions import mail
import pandas as pd
from io import BytesIO
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"
-45
View File
@@ -1,45 +0,0 @@
"""empty message
Revision ID: 30b45df851fa
Revises: d59bfb9ead82
Create Date: 2025-08-26 13:48:27.458593
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '30b45df851fa'
down_revision = 'd59bfb9ead82'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
'loan_repayment_schedules',
sa.Column('paid_status', sa.String(length=20), nullable=True),
)
op.add_column(
'loan_repayment_schedules',
sa.Column('repay_description', sa.String(length=255), nullable=True),
)
op.add_column(
'loan_repayment_schedules',
sa.Column('partial_balance', sa.Float(), nullable=True),
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('loan_repayment_schedules', 'partial_balance')
op.drop_column('loan_repayment_schedules', 'repay_description')
op.drop_column('loan_repayment_schedules', 'paid_status')
# ### end Alembic commands ###
-38
View File
@@ -1,38 +0,0 @@
"""empty message
Revision ID: d59bfb9ead82
Revises: 05b5494ad406
Create Date: 2025-08-21 14:22:19.220158
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd59bfb9ead82'
down_revision = '05b5494ad406'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
'loan_repayment_schedules',
sa.Column('due_process_date', sa.DateTime(), nullable=True)
)
# Add due_process_count column
op.add_column(
'loan_repayment_schedules',
sa.Column('due_process_count', sa.Integer(), nullable=True)
)
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('loan_repayment_schedules', 'due_process_date')
op.drop_column('loan_repayment_schedules', 'due_process_count')
# ### end Alembic commands ###
-3
View File
@@ -40,6 +40,3 @@ confluent-kafka==1.9.2
python-dateutil
Flask-Mail==0.10.0
pandas==2.1.3
openpyxl==3.1.5