[add]: Loan charge

This commit is contained in:
VivianDee
2025-04-17 15:52:14 +01:00
parent 75f71a807d
commit 57fa4d72d9
6 changed files with 93 additions and 90 deletions
+5 -51
View File
@@ -8,7 +8,7 @@ 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 import Loan, Offer from app.models import Loan, Offer, Charge
from app.api.enums import LoanStatus from app.api.enums import LoanStatus
from app.extensions import db from app.extensions import db
@@ -81,62 +81,16 @@ class ProvideLoanService(BaseService):
"message": "Failed to save loan details." "message": "Failed to save loan details."
}), 400 }), 400
charges = Charge.get_offer_charges(offer.id)
charges = [ logger.error(f"{charges}")
{"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"},
]
loan_id = loan.id loan_id = loan.id
loan_charges = LoanCharge.create_charges_for_loan(loan_id = loan_id, transaction_id = transaction_id, referenced_amount = 800, charges = charges) loan_charges = LoanCharge.create_charges_for_loan(loan_id = loan_id, transaction_id = transaction_id, referenced_amount = 800, 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
# transaction = ProvideLoanService.log_transaction(validated_data = validated_data)
#
# if not transaction:
# logger.error(f"Failed to log transaction")
# return jsonify({
# "message": "Failed to log transaction."
# }), 400
else: else:
return jsonify({ return jsonify({
+42 -32
View File
@@ -1,16 +1,14 @@
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from app.extensions import db from app.extensions import db
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.models.offer import Offer
class Charge(db.Model): class Charge(db.Model):
__tablename__ = 'charges' __tablename__ = 'charges'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
offer_id = db.Column(db.Integer, nullable=False) offer_id = db.Column(db.String(50), nullable=False)
code = db.Column(db.String(50), nullable=False) code = db.Column(db.String(50), nullable=False)
amount = db.Column(db.Float, default=0.0)
percent = db.Column(db.Float, default=0.0) percent = db.Column(db.Float, default=0.0)
description = db.Column(db.Text, nullable=True) description = db.Column(db.Text, nullable=True)
due = db.Column(db.Integer, nullable=False) due = db.Column(db.Integer, nullable=False)
@@ -25,61 +23,73 @@ class Charge(db.Model):
) )
@classmethod @classmethod
def create_charges_for_loan(cls, loan_id, transaction_id, charges, referenced_amount = 0.0): def add_charges(cls, offer_id, charges):
""" """
Create loan charges for a given loan. Add charges to an offer.
Args: Args:
loan_id (int): ID of the loan to associate charges with. offer_id (int): ID of the offer to associate charges with.
charges (list): A list of dictionaries with keys: charges (list): A list of dictionaries with keys:
code (str), amount (float), percent (float), description (str), due (int) code (str), amount (float), percent (float), description (str), due (int)
""" """
if not charges or not isinstance(charges, list): if not charges or not isinstance(charges, list):
raise ValueError("Charges must be a non-empty list of dictionaries") 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 = [] if offer_id is None:
now = datetime.now(timezone.utc) raise ValueError("offer_id cannot be None")
offer_charges = []
for charge in charges: for charge in charges:
due_days = charge.get("due", 0) code = charge.get("code")
amount = charge.get("amount", 0.0)
percent = charge.get("percent", 0.0) percent = charge.get("percent", 0.0)
description = charge.get("description", "")
due_days = charge.get("due", 0)
if amount == 0.0: existing = cls.query.filter_by(offer_id=offer_id, code=code).first()
amount = (percent / 100.0) * referenced_amount
if existing:
continue
charge_obj = cls( charge_obj = cls(
loan_id = loan_id, offer_id = offer_id,
transaction_id = transaction_id, code = code,
code = charge.get("code"),
amount = amount,
percent = percent, percent = percent,
description = charge.get("description", ""), description = description,
due = due_days, due = due_days
due_date = now + timedelta(days=due_days)
) )
db.session.add(charge_obj) db.session.add(charge_obj)
loan_charges.append(charge_obj) offer_charges.append(charge_obj)
return loan_charges
return offer_charges
@classmethod
def get_offer_charges(cls, offer_id):
"""
Get all charges for a particular offer as a dictionary
Args:
offer_id (str): The offer ID.
"""
if not offer_id:
raise ValueError("offer_id not found")
charges = cls.query.filter_by(offer_id=offer_id).all()
return charges
def to_dict(self): def to_dict(self):
return { return {
'id': self.id, 'id': self.id,
'loanId': self.loan_id, 'offerId': self.offer_id,
'transactionId': self.transaction_id,
'code': self.code, 'code': self.code,
'amount': self.amount,
'percent': self.percent, 'percent': self.percent,
'description': self.description, 'description': self.description,
'due': self.due, 'due': self.due
} }
def __repr__(self): def __repr__(self):
return f"<LoanCharge {self.id} - Loan {self.loan_id} - {self.code}>" return f"<Charge {self.id} - Offer {self.offer_id} - {self.code}>"
+5 -5
View File
@@ -45,9 +45,9 @@ class LoanCharge(db.Model):
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
for charge in charges: for charge in charges:
due_days = charge.get("due", 0) due_days = getattr(charge, "due", 0)
amount = charge.get("amount", 0.0) amount = getattr(charge, "amount", 0.0)
percent = charge.get("percent", 0.0) percent = getattr(charge, "percent", 0.0)
if amount == 0.0: if amount == 0.0:
amount = (percent / 100.0) * referenced_amount amount = (percent / 100.0) * referenced_amount
@@ -55,10 +55,10 @@ class LoanCharge(db.Model):
charge_obj = cls( charge_obj = cls(
loan_id = loan_id, loan_id = loan_id,
transaction_id = transaction_id, transaction_id = transaction_id,
code = charge.get("code"), code = getattr(charge, "code"),
amount = amount, amount = amount,
percent = percent, percent = percent,
description = charge.get("description", ""), description = getattr(charge, "description", ""),
due = due_days, due = due_days,
due_date = now + timedelta(days=due_days) due_date = now + timedelta(days=due_days)
) )
+1
View File
@@ -1,6 +1,7 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from app.extensions import db from app.extensions import db
from app.models.charge import Charge from app.models.charge import Charge
from sqlalchemy.orm import relationship
class Offer(db.Model): class Offer(db.Model):
__tablename__ = 'offers' __tablename__ = 'offers'
@@ -0,0 +1,38 @@
"""Migration on Thu Apr 17 14:15:36 UTC 2025
Revision ID: de9ad96ba34e
Revises: ec8d97f9b584
Create Date: 2025-04-17 14:16:16.537466
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'de9ad96ba34e'
down_revision = 'ec8d97f9b584'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('charges',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('offer_id', sa.String(length=50), nullable=False),
sa.Column('code', sa.String(length=50), nullable=False),
sa.Column('percent', sa.Float(), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('due', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('charges')
# ### end Alembic commands ###
+2 -2
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