[add]: Lona repayment schedule
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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}>'
|
||||
Reference in New Issue
Block a user