[add]: Lona repayment schedule

This commit is contained in:
Vivian Dee
2025-04-25 14:29:13 +01:00
parent 0995f08aea
commit c216c55928
5 changed files with 106 additions and 14 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ class BaseService:
# Up-front payment: (only those fees due immediately i.e due_days == 0)
upfront_payment = sum(upfront_fees)
# 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)
# Total amount: (upfront_payment + repayment_amount)
+18 -11
View File
@@ -11,6 +11,10 @@ from threading import Thread
from app.models import Loan, Offer, Charge
from app.api.enums import LoanStatus
from app.extensions import db
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
from app.models import LoanRepaymentSchedule
class ProvideLoanService(BaseService):
TRANSACTION_TYPE = TransactionType.PROVIDE_LOAN
@@ -85,19 +89,26 @@ class ProvideLoanService(BaseService):
status= LoanStatus.ACTIVE
)
db.session.flush()
if not loan:
logger.error(f"Failed to save loan details")
return jsonify({
"message": "Failed to save loan details."
}), 400
db.session.flush()
schedule = LoanRepaymentSchedule.add_repayment_schedule(loan = loan, Offer = offer, charges = charges)
if not schedule:
logger.error(f"Failed to create repayment schedule for loan ID {loan.id}")
return jsonify({
"message": "Failed to generate loan repayment schedule."
}), 400
charges = Charge.get_offer_charges(offer.id)
logger.error(f"{charges}")
# logger.error(f"{charges}")
loan_id = loan.id
@@ -152,8 +163,4 @@ class ProvideLoanService(BaseService):
db.session.rollback()
return jsonify({
"message": "Internal Server Error"
}) , 500
}) , 500
+2 -1
View File
@@ -7,6 +7,7 @@ from .loan_charge import LoanCharge
from .offer import Offer
from .charge import Charge
from .rac_checks import RACCheck
from .loan_repayment_schedule import LoanRepaymentSchedule
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment', 'LoanCharge', 'Offer', 'Charge', 'RACCheck']
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', 'Repayment', 'LoanCharge', 'Offer', 'Charge', 'RACCheck', 'LoanRepaymentSchedule']
+11 -1
View File
@@ -5,6 +5,9 @@ from app.models.account import Account
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship
from app.models.loan_charge import LoanCharge
from dateutil.relativedelta import relativedelta
from app.models.loan_repayment_schedule import LoanRepaymentSchedule # Make sure this import exists
class Loan(db.Model):
@@ -47,6 +50,13 @@ class Loan(db.Model):
back_populates="loan",
)
loan_repayent_schedules = relationship(
"LoanRepaymentSchedule",
primaryjoin="Loan.id == LoanRepaymentSchedule.loan_id",
foreign_keys="LoanRepaymentSchedule.loan_id",
back_populates="loan",
)
@classmethod
def create_loan(
cls,
@@ -60,6 +70,7 @@ class Loan(db.Model):
upfront_fee,
repayment_amount,
installment_amount,
tenor,
status="pending",
):
# Check if customer exists
@@ -92,7 +103,6 @@ class Loan(db.Model):
raise ValueError(f"Database integrity error: {err}")
return loan
@classmethod
def has_active_loans(cls, customer_id):
active_loans = cls.query.filter_by(
+74
View File
@@ -0,0 +1,74 @@
from datetime import datetime, timezone
from app.extensions import db
from sqlalchemy.orm import relationship
class LoanRepaymentSchedule(db.Model):
__tablename__ = 'loan_repayment_schedules'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
loan_id = db.Column(db.Integer, nullable=False)
installment_number = db.Column(db.Integer, nullable=False)
due_date = db.Column(db.DateTime, nullable=False)
principal_amount = db.Column(db.Float, default=0.0)
interest_amount = db.Column(db.Float, default=0.0)
total_installment = db.Column(db.Float, default=0.0)
paid = db.Column(db.Boolean, default=False)
paid_at = db.Column(db.DateTime, nullable=True)
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))
loan = relationship(
"Loan",
primaryjoin="LoanCharge.loan_id == Loan.id",
foreign_keys=[loan_id],
back_populates="loan_charges",
)
@classmethod
def add_repayment_schedule(cls, loan, Offer, charges):
"""
Add repayment schedules for a given loan.
"""
if not loan.amount or not loan.installment_amount:
raise ValueError("Loan must have amount and installment_amount set.")
now = datetime.now(timezone.utc)
schedules = []
interest_fee = charges["interest"]
tenor = Offer.tenor // 30
principal = loan.amount / tenor
interest = interest_fee["fee"] / tenor
for i in range(tenor):
due_date = now + relativedelta(months=i + 1)
schedule = LoanRepaymentSchedule(
loan_id=loan.id,
installment_number=i + 1,
due_date=due_date,
principal_amount=round(principal, 2),
interest_amount=round(interest, 2),
total_installment=round(loan.installment_amount, 2)
)
db.session.add(schedule)
schedules.append(schedule)
return schedules
def to_dict(self):
return {
'id': self.id,
'loanId': self.loan_id,
'installmentNumber': self.installment_number,
'dueDate': self.due_date.isoformat(),
'principalAmount': self.principal_amount,
'interestAmount': self.interest_amount,
'totalInstallment': self.total_installment,
'paid': self.paid,
'paidAt': self.paid_at.isoformat() if self.paid_at else None
}
def __repr__(self):
return f'<LoanRepaymentSchedule Loan:{self.loan_id} Installment:{self.installment_number}>'