Reworked the fee calculations structure
This commit was merged in pull request #19.
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
if code == "VAT" and management_fee > 0:
|
||||||
for charge in charges:
|
fee = management_fee * rates / 100
|
||||||
if charge.code == code:
|
else:
|
||||||
fee = (
|
fee = amount * rates / 100
|
||||||
management_fee * charge.percent / 100
|
|
||||||
if code == "VAT" and management_fee is not None
|
return {
|
||||||
else amount * charge.percent / 100
|
"rate": rates,
|
||||||
)
|
"fee": round(fee, 2),
|
||||||
|
"due_days": 30,
|
||||||
|
"code": code,
|
||||||
|
"description" : "have no idea how to get this yet"
|
||||||
|
}
|
||||||
|
|
||||||
|
# if charge.code == code:
|
||||||
|
# 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 {
|
# return {"rate": 0, "fee": 0, "due_days": 0}
|
||||||
"rate": charge.percent,
|
|
||||||
"fee": round(fee, 2),
|
|
||||||
"due_days": charge.due
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"rate": 0, "fee": 0, "due_days": 0}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,16 @@ 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"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user