Reworked the fee calculations structure

This commit was merged in pull request #19.
This commit is contained in:
2025-04-26 14:31:06 -04:00
parent 7de4e3651f
commit 1b92ede296
5 changed files with 97 additions and 43 deletions
+48 -26
View File
@@ -79,11 +79,11 @@ class BaseService:
return {"error": "No charges found for the offer"} return {"error": "No charges found for the offer"}
loan_charges = offer.charges loan_charges = offer.charges
tenor = offer.tenor // 30 # Convert to months tenor = offer.schedule # offer.tenor // 30 # Convert to months
interest = cls.get_charge_detail(charges = loan_charges, code = "INTEREST", amount = amount) interest = cls.get_charge_detail(rates = offer.interest_rate, charges = loan_charges, code = "INTEREST", amount = amount)
management = cls.get_charge_detail(charges = loan_charges, code = "MGTFEE", amount = amount) management = cls.get_charge_detail(rates = offer.management_rate, charges = loan_charges, code = "MGTFEE", amount = amount)
insurance = cls.get_charge_detail(charges = loan_charges, code = "INSURANCE", amount = amount) insurance = cls.get_charge_detail(rates = offer.insurance_rate, charges = loan_charges, code = "INSURANCE", amount = amount)
vat = cls.get_charge_detail(charges = loan_charges, code = "VAT", amount = amount, management_fee = management["fee"]) vat = cls.get_charge_detail(rates = offer.vat_rate, charges = loan_charges, code = "VAT", amount = amount, management_fee = management["fee"])
# Separate fees into upfront and postpaid # Separate fees into upfront and postpaid
upfront_fees = [ upfront_fees = [
@@ -97,18 +97,29 @@ class BaseService:
for fee in [interest, management, insurance, vat] for fee in [interest, management, insurance, vat]
if fee["due_days"] != 0 if fee["due_days"] != 0
] ]
vat_test = vat["fee"]
logger.info(f"VAT fee == *************** : {vat_test}")
# Up-front payment: (only those fees due immediately i.e due_days == 0) # Up-front payment: (only those fees due immediately i.e due_days == 0)
upfront_payment = sum(upfront_fees) # upfront_payment = sum(upfront_fees)
if offer.schedule == 1:
upfront_payment = vat["fee"] + management["fee"] + insurance["fee"] + interest["fee"]
interest_amount = interest["fee"]
repayment_amount = amount
else:
upfront_payment = vat["fee"] + insurance["fee"]+management["fee"]
interest_amount = interest["fee"]*offer.schedule
repayment_amount = amount + interest_amount
# Repayment amount: (principal + only those fees not due immediately i.e due_days != 0) # Repayment amount: (principal + only those fees not due immediately i.e due_days != 0)
repayment_amount = amount + (sum(postpaid_fees) * tenor) # repayment_amount = amount + (sum(postpaid_fees) * tenor)
# Total amount: (upfront_payment + repayment_amount) # Total amount: (upfront_payment + repayment_amount)
total_amount = upfront_payment + repayment_amount total_amount = upfront_payment + repayment_amount
# Calculate the installment amount # Calculate the installment amount
installment_amount = repayment_amount / tenor installment_amount = repayment_amount / offer.schedule
return { return {
"interest": interest, "interest": interest,
@@ -123,28 +134,39 @@ class BaseService:
@classmethod @classmethod
def get_charge_detail(cls, charges, code, amount, management_fee=None): def get_charge_detail(cls, rates, charges, code, amount, management_fee= 0.0):
""" """
Get details for a specific charge code from a list of loan charges. Get details for a specific charge code from a list of loan charges.
Returns default values if not found. Returns default values if not found.
""" """
fee = 0.0
for charge in charges: if code == "VAT" and management_fee > 0:
if charge.code == code: fee = management_fee * rates / 100
fee = ( else:
management_fee * charge.percent / 100 fee = amount * rates / 100
if code == "VAT" and management_fee is not None
else amount * charge.percent / 100 return {
) "rate": rates,
"fee": round(fee, 2),
return { "due_days": 30,
"rate": charge.percent, "code": code,
"fee": round(fee, 2), "description" : "have no idea how to get this yet"
"due_days": charge.due }
}
# if charge.code == code:
return {"rate": 0, "fee": 0, "due_days": 0} # if code == "VAT" and management_fee > 0:
# fee = management_fee * rates / 100
# else:
# fee = amount * rates / 100
#
# return {
# "rate": rates,
# "fee": round(fee, 2),
# "due_days": charge.due
# }
# return {"rate": 0, "fee": 0, "due_days": 0}
+11 -3
View File
@@ -70,9 +70,17 @@ class ProvideLoanService(BaseService):
charges = ProvideLoanService.calculate_charges(offer, amount) charges = ProvideLoanService.calculate_charges(offer, amount)
upfront_fee = charges["upfront_payment"] upfront_fee = charges["upfront_payment"]
repayment_amount = charges["repayment_amount"] repayment_amount = charges["repayment_amount"]
installment_amount = charges["installment_amount"] #installment_amount = charges["installment_amount"]
tenor = offer.tenor // 30 # Convert to months tenor = offer.tenor // 30 # Convert to months
upfront_payment = charges["upfront_payment"]
total_amount = charges["total_amount"]
installment_amount = charges["installment_amount"]
interest = charges["interest"]
management = charges["management"]
insurance = charges["insurance"]
vat = charges["vat"]
# Save the loan details # Save the loan details
@@ -107,9 +115,9 @@ class ProvideLoanService(BaseService):
"message": "Failed to generate loan repayment schedule." "message": "Failed to generate loan repayment schedule."
}), 400 }), 400
charges = Charge.get_offer_charges(offer.id) # charges = Charge.get_offer_charges(offer.id)
# logger.error(f"{charges}") logger.error(f"{charges}")
loan_id = loan.id loan_id = loan.id
+22 -5
View File
@@ -61,6 +61,7 @@ class SelectOfferService(BaseService):
management = charges["management"] management = charges["management"]
insurance = charges["insurance"] insurance = charges["insurance"]
vat = charges["vat"] vat = charges["vat"]
repayment_amount = charges["repayment_amount"]
# Calculate the repayment dates # Calculate the repayment dates
@@ -68,7 +69,7 @@ class SelectOfferService(BaseService):
start_date = date.today() start_date = date.today()
# Convert tenor to months # Convert tenor to months
months = tenor // 30 months = offer.schedule # tenor // 30
recommended_repayment_dates = [ recommended_repayment_dates = [
(start_date + relativedelta(months=i + 1)).isoformat() (start_date + relativedelta(months=i + 1)).isoformat()
@@ -83,19 +84,35 @@ class SelectOfferService(BaseService):
"productId": product_id, "productId": product_id,
"amount": amount, "amount": amount,
"upfrontPayment": upfront_payment, "upfrontPayment": upfront_payment,
"interestRate": interest["rate"], "interestRate": offer.interest_rate,
"managementRate": management["rate"], "managementRate": offer.management_rate,
"managementFee": management["fee"], "managementFee": management["fee"],
"insuranceRate": insurance["rate"], "insuranceRate": offer.insurance_rate,
"insuranceFee": insurance["fee"], "insuranceFee": insurance["fee"],
"VATRate": vat["rate"], "VATRate": offer.vat_rate,
"VATAmount": vat["fee"], "VATAmount": vat["fee"],
"recommendedRepaymentDates": recommended_repayment_dates, "recommendedRepaymentDates": recommended_repayment_dates,
"repaymentAmount": repayment_amount,
"installmentAmount": installment_amount, "installmentAmount": installment_amount,
"totalRepaymentAmount": total_amount, "totalRepaymentAmount": total_amount,
} }
] ]
# "offerId": offer.id,
# "productId": product_id,
# "amount": amount,
# "upfrontPayment": upfront_payment,
# "interestRate": interest["rate"],
# "managementRate": management["rate"],
# "managementFee": management["fee"],
# "insuranceRate": insurance["rate"],
# "insuranceFee": insurance["fee"],
# "VATRate": vat["rate"],
# "VATAmount": vat["fee"],
# "recommendedRepaymentDates": recommended_repayment_dates,
# "installmentAmount": installment_amount,
# "totalRepaymentAmount": total_amount,
#
# Business logic - selecting an offer # Business logic - selecting an offer
response_data = { response_data = {
"outstandingDebtAmount": 0, "outstandingDebtAmount": 0,
+13 -10
View File
@@ -1,6 +1,7 @@
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.utils.logger import logger
class LoanCharge(db.Model): class LoanCharge(db.Model):
@@ -35,8 +36,8 @@ class LoanCharge(db.Model):
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: if loan_id is None:
raise ValueError("loan_id cannot be None") raise ValueError("loan_id cannot be None")
@@ -44,21 +45,23 @@ class LoanCharge(db.Model):
loan_charges = [] loan_charges = []
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
for charge in charges:
due_days = getattr(charge, "due", 0)
amount = getattr(charge, "amount", 0.0)
percent = getattr(charge, "percent", 0.0)
if amount == 0.0: subset_keys = ['interest', 'management', 'insurance', 'vat']
amount = (percent / 100.0) * referenced_amount for item in subset_keys:
charge = charges[item]
due_days = charge['due_days'] # getattr(charge, "due_days", 0)
amount = charge['fee'] # getattr(charge, "fee", 0.0)
percent = charge['rate'] # getattr(charge, "rate", 0.0)
code = charge['code'] # getattr(charge, "code","")
description = charge['description'] # getattr(charge, "description", "")
charge_obj = cls( charge_obj = cls(
loan_id = loan_id, loan_id = loan_id,
transaction_id = transaction_id, transaction_id = transaction_id,
code = getattr(charge, "code"), code = code,
amount = round(amount, 2), amount = round(amount, 2),
percent = percent, percent = percent,
description = getattr(charge, "description", ""), description = description,
due = due_days, due = due_days,
due_date = now + timedelta(days=due_days) due_date = now + timedelta(days=due_days)
) )
+5 -1
View File
@@ -75,7 +75,11 @@ class Offer(db.Model):
"productId": self.product_id, "productId": self.product_id,
"minAmount": self.min_amount, "minAmount": self.min_amount,
"maxAmount": self.max_amount, "maxAmount": self.max_amount,
"tenor": self.tenor "tenor": self.tenor,
"interest_rate": self.interest_rate,
"management_rate": self.management_rate,
"insurance_rate": self.insurance_rate,
"vat_rate": self.vat_rate
} }
def __repr__(self): def __repr__(self):