[add]: loan charges and offers. Fix RACCheck

This commit is contained in:
VivianDee
2025-04-16 21:36:26 +01:00
parent 359621dc9d
commit 93ed8b3d17
12 changed files with 161 additions and 71 deletions
+5 -6
View File
@@ -36,6 +36,7 @@ class SimbrellaIntegration:
} }
logger.error(f"This is PayLoad: {str(payload)}",exc_info=True) logger.error(f"This is PayLoad: {str(payload)}",exc_info=True)
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'x-api-key': f'{settings.VALID_API_KEY}', 'x-api-key': f'{settings.VALID_API_KEY}',
@@ -44,12 +45,10 @@ class SimbrellaIntegration:
try: try:
response = requests.post(url, json=payload, timeout=10, headers=headers) response = requests.post(url, json=payload, timeout=10, headers=headers)
logger.error(f"This is Response: {str(response)}", exc_info=True) logger.error(f"This is Response: {str(response)}", exc_info=True)
# Raise an error for non-200 responses
if response.status_code != 200:
response.raise_for_status()
return response.json() return response.json()
except requests.exceptions.RequestException as err: except Exception as e:
logger.error(f"RACCheck API call failed: {str(err)}", exc_info=True) logger.error(f"RACCheck API call failed: {str(e)}", exc_info=True)
return {"error": "RACCheck API error"} raise Exception(f"RACCheck API call failed: {str(e)}")
+1 -1
View File
@@ -12,5 +12,5 @@ class ProvideLoanSchema(Schema):
# lienAmount = fields.Float(required=True) # lienAmount = fields.Float(required=True)
requestedAmount = fields.Float(required=True) requestedAmount = fields.Float(required=True)
collectionType = fields.Int(required=True) collectionType = fields.Int(required=True)
offerId = fields.Int(required=True) offerId = fields.Str(required=True)
channel = fields.Str(required=True) channel = fields.Str(required=True)
+3 -19
View File
@@ -58,26 +58,10 @@ class EligibilityCheckService(BaseService):
logger.error(f"This is Response Returned ****** : {str(response)}") logger.error(f"This is Response Returned ****** : {str(response)}")
# this chck for error is not valid # this chck for error is not valid
logger.error(f"Check for ERROR is not valid ****** FIX THIS !!!!!") if response.get("status") != 200:
#if "error" in response or response.get("status") != 200: return jsonify({"message": "RACCheck failed"}), 400
# return jsonify({"message": "RACCheck failed"}), 400
offers = [ offers = [offer.to_dict() for offer in Offer.get_all_offers()]
{
"offerId": "SAL90",
"productId": "2030",
"minAmount": 5000,
"maxAmount": 100000,
"tenor": 30
},
{
"offerId": "SAL30",
"productId": "2090",
"minAmount": 5000,
"maxAmount": 500000,
"tenor": 90
}
]
# Simulate processing # Simulate processing
response_data = { response_data = {
+59 -34
View File
@@ -3,10 +3,12 @@ from marshmallow import ValidationError
from app.api.integrations.kafka import KafkaIntegration from app.api.integrations.kafka import KafkaIntegration
from app.api.services.base_service import BaseService from app.api.services.base_service import BaseService
from app.api.enums import TransactionType from app.api.enums import TransactionType
from app.models.customer import Customer
from app.models.loan_charge import LoanCharge
from app.utils.logger import logger from app.utils.logger import logger
from app.api.schemas.provide_loan import ProvideLoanSchema from app.api.schemas.provide_loan import ProvideLoanSchema
from threading import Thread from threading import Thread
from app.models.loan import Loan from app.models import Loan, Offer
from app.api.enums import LoanStatus from app.api.enums import LoanStatus
from app.extensions import db from app.extensions import db
@@ -34,13 +36,20 @@ class ProvideLoanService(BaseService):
collection_type = validated_data.get('collectionType') collection_type = validated_data.get('collectionType')
transaction_id = validated_data.get('transactionId') transaction_id = validated_data.get('transactionId')
offer_id = validated_data.get('offerId') offer_id = validated_data.get('offerId')
product_id = validated_data.get('productrId')
customer = Customer.is_valid_customer(customer_id)
if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)): if (ProvideLoanService.validate_account_ownership(account_id = account_id, customer_id = customer_id)):
offer = Offer.is_valid_offer(offer_id)
if not offer:
logger.error(f"Invalid Offer")
return jsonify({
"message": "Invalid Offer."
}), 400
# Log Transaction # Log Transaction
transaction = ProvideLoanService.log_transaction(validated_data=validated_data) transaction = ProvideLoanService.log_transaction(validated_data=validated_data)
@@ -56,52 +65,68 @@ class ProvideLoanService(BaseService):
customer_id = customer_id, customer_id = customer_id,
account_id = account_id, account_id = account_id,
offer_id = offer_id, offer_id = offer_id,
product_id = product_id, product_id = offer.product_id,
collection_type = collection_type, collection_type = collection_type,
transaction_id = validated_data.get('transactionId'), transaction_id = validated_data.get('transactionId'),
initial_loan_amount = validated_data.get('requestedAmount'), initial_loan_amount = validated_data.get('requestedAmount'),
status= LoanStatus.ACTIVE status= LoanStatus.ACTIVE
) )
db.session.flush()
if not loan: if not loan:
logger.error(f"Failed to save loan details") logger.error(f"Failed to save loan details")
return jsonify({ return jsonify({
"message": "Failed to save loan details." "message": "Failed to save loan details."
}), 400 }), 400
logger.error(f"********* We need to develop the fee array here")
loan_def = { charges = [
"offers": [
{
"offerId": "SAL90",
"productId": "2030",
"minAmount": 5000,
"maxAmount": 100000,
"tenor": 30
},
{
"offerId": "SAL30",
"productId": "2090",
"minAmount": 3000,
"maxAmount": 500000,
"tenor": 90
}
],
"loan_fee": {
"SAL30": [
{"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 000"},
{"code": "MGTFEE", "percent": 2.5, "due": 0, "description": "This is fee 001"},
{"code": "INSURANCE", "percent": 3.5, "due": 0, "description": "This is fee 001"},
{"code": "VAT", "percent": 1.0, "due": 0, "description": "This is fee 001"},
],
"SAL90": [
{"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 9000"}, {"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 9000"},
{"code": "MGTFEE", "percent": 1.5, "due": 0, "description": "This is fee 90002"}, {"code": "MGTFEE", "percent": 1.5, "due": 0, "description": "This is fee 90002"},
{"code": "INSURANCE", "percent": 1.5, "due": 30, "description": "This is fee 90003"}, {"code": "INSURANCE", "percent": 1.5, "due": 30, "description": "This is fee 90003"},
{"code": "VAT", "percent": 1.5, "due": 60, "description": "This is fee 90004"}, {"code": "VAT", "percent": 1.5, "due": 60, "description": "This is fee 90004"},
] ]
} loan_id = loan.id
}
loan_charges = LoanCharge.create_charges_for_loan(loan_id = loan_id, charges = charges)
# logger.error(f"********* We need to develop the fee array here")
# loan_def = {
# "offers": [
# {
# "offerId": "SAL90",
# "productId": "2030",
# "minAmount": 5000,
# "maxAmount": 100000,
# "tenor": 30
# },
# {
# "offerId": "SAL30",
# "productId": "2090",
# "minAmount": 3000,
# "maxAmount": 500000,
# "tenor": 90
# }
# ],
# "loan_fee": {
# "SAL30": [
# {"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 000"},
# {"code": "MGTFEE", "percent": 2.5, "due": 0, "description": "This is fee 001"},
# {"code": "INSURANCE", "percent": 3.5, "due": 0, "description": "This is fee 001"},
# {"code": "VAT", "percent": 1.0, "due": 0, "description": "This is fee 001"},
# ],
# "SAL90": [
# {"code": "INTEREST", "percent": 1.1, "due": 0, "description": "This is fee 9000"},
# {"code": "MGTFEE", "percent": 1.5, "due": 0, "description": "This is fee 90002"},
# {"code": "INSURANCE", "percent": 1.5, "due": 30, "description": "This is fee 90003"},
# {"code": "VAT", "percent": 1.5, "due": 60, "description": "This is fee 90004"},
# ]
# }
# }
# Log Transaction # Log Transaction
@@ -124,7 +149,7 @@ class ProvideLoanService(BaseService):
"transactionId": transaction_id, "transactionId": transaction_id,
"customerId": customer_id, "customerId": customer_id,
"accountId": account_id, "accountId": account_id,
"msisdn": "3451342", "msisdn": customer.msisdn,
"resultCode": "00", "resultCode": "00",
"resultDescription": "Successful" "resultDescription": "Successful"
} }
+1 -1
View File
@@ -44,7 +44,7 @@ class SelectOfferService(BaseService):
offers = [ offers = [
{ {
"offerId": "14451", "offerId": "SAL90",
"productId": "2030", "productId": "2030",
"amount": 10000.0, "amount": 10000.0,
"upfrontPayment": 1000.0, "upfrontPayment": 1000.0,
+1 -1
View File
@@ -42,7 +42,7 @@ class Account(db.Model):
return False return False
if account.lien_amount > 0: if account.lien_amount > 0:
return False return False
return True return account
def __repr__(self): def __repr__(self):
return f'<Account {self.id}>' return f'<Account {self.id}>'
+1 -1
View File
@@ -32,7 +32,7 @@ class Customer(db.Model):
customer = cls.query.filter_by(id=customer_id).first() customer = cls.query.filter_by(id=customer_id).first()
if not customer: if not customer:
return False return False
return True return customer
@classmethod @classmethod
def create_customer(cls, id, msisdn, country_code, account_id, account_type='savings'): def create_customer(cls, id, msisdn, country_code, account_id, account_type='savings'):
+2 -2
View File
@@ -48,8 +48,8 @@ class Loan(db.Model):
def create_loan(cls, customer_id, account_id, offer_id, product_id, initial_loan_amount, collection_type, transaction_id, status='pending'): def create_loan(cls, customer_id, account_id, offer_id, product_id, initial_loan_amount, collection_type, transaction_id, status='pending'):
# Check if customer exists # Check if customer exists
is_valid = Customer.is_valid_customer(customer_id) customer = Customer.is_valid_customer(customer_id)
if not is_valid: if not customer:
raise ValueError("Customer does not exist") raise ValueError("Customer does not exist")
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
+31 -1
View File
@@ -8,7 +8,6 @@ class LoanCharge(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
loan_id = db.Column(db.Integer, nullable=False) loan_id = db.Column(db.Integer, nullable=False)
transaction_id = db.Column(db.String(50), nullable=True)
code = db.Column(db.String(50), nullable=False) code = db.Column(db.String(50), nullable=False)
amount = db.Column(db.Float, default=0.0) amount = db.Column(db.Float, default=0.0)
percent = db.Column(db.Float, default=0.0) percent = db.Column(db.Float, default=0.0)
@@ -24,6 +23,37 @@ class LoanCharge(db.Model):
back_populates="loan_charges", back_populates="loan_charges",
) )
@classmethod
def create_charges_for_loan(cls, loan_id, charges):
"""
Create loan charges for a given loan.
Args:
loan_id (int): ID of the loan to associate charges with.
charges (list): A list of dictionaries with keys:
code (str), amount (float), percent (float), description (str), due (int)
"""
if not charges or not isinstance(charges, list):
raise ValueError("Charges must be a non-empty list of dictionaries")
if loan_id is None:
raise ValueError("loan_id cannot be None")
loan_charges = []
for charge in charges:
charge_obj = cls(
loan_id=loan_id,
code=charge.get("code"),
amount=charge.get("amount", 0.0),
percent=charge.get("percent", 0.0),
description=charge.get("description", ""),
due=charge.get("due", 0)
)
db.session.add(charge_obj)
loan_charges.append(charge_obj)
return loan_charges
def to_dict(self): def to_dict(self):
+20
View File
@@ -12,6 +12,26 @@ class Offer(db.Model):
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc)) created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
@classmethod
def get_all_offers(cls):
"""
Return all offers in dictionary format.
"""
offers = cls.query.all()
if not offers:
raise ValueError(f"No available offers")
return offers
@classmethod
def is_valid_offer(cls, offer_id):
offer = cls.query.filter_by(id=str(offer_id)).first()
if not offer:
return False
return offer
def to_dict(self): def to_dict(self):
return { return {
"offerId": self.id, "offerId": self.id,
@@ -0,0 +1,32 @@
"""Migration on Wed Apr 16 18:35:18 UTC 2025
Revision ID: 287ecb02d3d7
Revises: a4847b997191
Create Date: 2025-04-16 18:36:04.632791
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '287ecb02d3d7'
down_revision = 'a4847b997191'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('loan_charges', schema=None) as batch_op:
batch_op.drop_column('transaction_id')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('loan_charges', schema=None) as batch_op:
batch_op.add_column(sa.Column('transaction_id', sa.VARCHAR(length=50), autoincrement=False, nullable=True))
# ### end Alembic commands ###
+3 -3
View File
@@ -1,8 +1,8 @@
#!/bin/sh #!/bin/sh
echo "Running DB migrations..." # echo "Running DB migrations..."
flask db migrate -m "Migration on $(date)" # flask db migrate -m "Migration on $(date)"
flask db upgrade # flask db upgrade
echo "Starting Gunicorn server..." echo "Starting Gunicorn server..."
exec gunicorn -w 4 -b 0.0.0.0:5000 wsgi:wsgi_app exec gunicorn -w 4 -b 0.0.0.0:5000 wsgi:wsgi_app