137 lines
6.5 KiB
Python
137 lines
6.5 KiB
Python
from app.models.loan_repayment_schedule import LoanRepaymentSchedule
|
|
from app.utils.logger import logger
|
|
from app.enums.loan_status import LoanStatus
|
|
from decimal import Decimal, ROUND_HALF_UP
|
|
|
|
|
|
class LoanRepaymentScheduleService:
|
|
|
|
@classmethod
|
|
def get_repayment_schedule_by_loan_id(cls, loan_id, include_paid=True):
|
|
return LoanRepaymentSchedule.get_repayment_schedule_by_loan_id(loan_id, include_paid=include_paid)
|
|
@classmethod
|
|
def get_overdue_repayment_schedule(cls):
|
|
return LoanRepaymentSchedule.get_overdue_repayment_schedule()
|
|
@classmethod
|
|
def get_active_overdue_repayment_schedule(cls):
|
|
return LoanRepaymentSchedule.get_active_overdue_repayment_schedule()
|
|
@classmethod
|
|
def get_partially_paid_overdue_repayment_schedule(cls):
|
|
return LoanRepaymentSchedule.get_partially_paid_overdue_repayment_schedule()
|
|
@classmethod
|
|
def get_repayment_schedule_by_id_and_transaction_id(cls, id, transaction_id):
|
|
return LoanRepaymentSchedule.get_repayment_schedule_by_id_and_transaction_id(id, transaction_id)
|
|
|
|
@classmethod
|
|
def get_repayment_schedule_by_transaction_id(cls, transaction_id):
|
|
return LoanRepaymentSchedule.get_repayment_schedule_by_transaction_id(transaction_id)
|
|
|
|
@classmethod
|
|
def update_repayment_schedule_status(cls, schedule_id):
|
|
"""
|
|
Update repayment schedule status.
|
|
"""
|
|
return LoanRepaymentSchedule.update_repayment_schedule_status(schedule_id)
|
|
@classmethod
|
|
def update_repayment_schedule_status_to_active(cls, schedule_id):
|
|
"""
|
|
Update repayment schedule status.
|
|
"""
|
|
return LoanRepaymentSchedule.update_repayment_schedule_status_to_active(schedule_id)
|
|
@classmethod
|
|
def update_repayment_schedule_balance(cls, schedule_id, amount_collected):
|
|
"""
|
|
Update repayment schedule balance.
|
|
"""
|
|
return LoanRepaymentSchedule.update_repayment_schedule_balance(schedule_id, amount_collected)
|
|
@classmethod
|
|
def calculate_penal_charge(cls, schedule):
|
|
"""
|
|
Calculate penal charge for a repayment schedule.
|
|
"""
|
|
return LoanRepaymentSchedule.calculate_penal_charge(schedule)
|
|
|
|
@classmethod
|
|
def update_repayment_schedule_description(cls, schedule_id, description):
|
|
"""
|
|
Update repayment schedule description.
|
|
"""
|
|
return LoanRepaymentSchedule.update_repayment_schedule_description(schedule_id, description)
|
|
|
|
@classmethod
|
|
def get_overdue_repayment_schedule_with_grace_period(cls, grace_period_days, limit=None):
|
|
return LoanRepaymentSchedule.get_overdue_repayment_schedule_with_grace_period(grace_period_days, limit=limit)
|
|
|
|
@classmethod
|
|
def apply_penal_to_schedule(cls, schedule_id, penal_amount):
|
|
"""
|
|
Apply penal charge to a repayment schedule.
|
|
"""
|
|
return LoanRepaymentSchedule.apply_penal_to_schedule(schedule_id, penal_amount)
|
|
@staticmethod
|
|
def handle_schedule_updates(updated_loan, data, amount_collected, message, loan_data):
|
|
"""
|
|
Handles updating loan repayment schedules depending on loan status
|
|
and overdue schedule data.
|
|
"""
|
|
try:
|
|
# Case 1: Loan fully repaid → mark all schedules paid
|
|
if updated_loan and updated_loan.get('status') == LoanStatus.REPAID:
|
|
repayment_schedule = LoanRepaymentScheduleService.get_repayment_schedule_by_loan_id(
|
|
updated_loan['debtId'], include_paid=False
|
|
)
|
|
logger.info(f'Loan repayment schedule: {repayment_schedule}')
|
|
|
|
if repayment_schedule:
|
|
for installment in repayment_schedule:
|
|
try:
|
|
logger.info(f'Processing installment: {installment}')
|
|
LoanRepaymentScheduleService.update_repayment_schedule_status(installment.id)
|
|
LoanRepaymentScheduleService.update_repayment_schedule_description(
|
|
installment.id,
|
|
message
|
|
)
|
|
logger.info(f'Updated installment {installment.id} as paid')
|
|
except Exception as e:
|
|
logger.error(f"Failed to update installment {installment.id}: {e}")
|
|
logger.info('All installments processed')
|
|
|
|
# Case 2: Partial repayment made on a full loan without overdueLoanScheduleId
|
|
elif updated_loan and updated_loan.get('status') == LoanStatus.ACTIVE_PARTIAL and not data.get('overdueLoanScheduleId'):
|
|
logger.info("Partial repayment detected, but no overdue schedule ID provided.")
|
|
# TODO: implement proportional installment updates
|
|
|
|
# Case 3: when we are processing Overdue schedule repayment → update balance & description
|
|
elif data.get('overdueLoanScheduleId') is not None:
|
|
logger.info(f"Overdue loan schedule ID: {data['overdueLoanScheduleId']}")
|
|
try:
|
|
schedule_to_update = LoanRepaymentScheduleService.get_repayment_schedule_by_id_and_transaction_id(
|
|
data["overdueLoanScheduleId"], data["transactionId"]
|
|
)
|
|
logger.info(f"Schedule to update: {schedule_to_update}")
|
|
|
|
if schedule_to_update is None:
|
|
logger.warning(
|
|
f"Repayment schedule not found for ID {data['overdueLoanScheduleId']} "
|
|
f"and transaction ID {loan_data['transactionId']}"
|
|
)
|
|
else:
|
|
if not schedule_to_update.paid:
|
|
update_schedule_balance = LoanRepaymentScheduleService.update_repayment_schedule_balance(
|
|
schedule_to_update.id, amount_collected
|
|
)
|
|
logger.info(f"Updated loan schedule balance: {update_schedule_balance}")
|
|
LoanRepaymentScheduleService.update_repayment_schedule_description(
|
|
schedule_to_update.id,
|
|
message
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to update repayment schedule installment: {e}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error while handling schedule updates: {e}")
|
|
|
|
|
|
|
|
|