overdue loans
This commit was merged in pull request #45.
This commit is contained in:
@@ -4,6 +4,9 @@ from app.services.repayment import RepaymentService
|
|||||||
from app.services.loan import LoanService
|
from app.services.loan import LoanService
|
||||||
from app.helpers.response_helper import ResponseHelper
|
from app.helpers.response_helper import ResponseHelper
|
||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
|
from decimal import Decimal, ROUND_HALF_UP
|
||||||
|
from app.services.loan_repayment_schedule import LoanRepaymentScheduleService
|
||||||
|
from app.enums.loan_status import LoanStatus
|
||||||
|
|
||||||
class CollectLoanHelper:
|
class CollectLoanHelper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -46,3 +49,5 @@ class CollectLoanHelper:
|
|||||||
"countryId": "NG",
|
"countryId": "NG",
|
||||||
"comment": "COLLECT LOAN"
|
"comment": "COLLECT LOAN"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -308,48 +308,8 @@ class SimbrellaClient:
|
|||||||
amount_collected = Decimal(str(result.get('amountCollected', 0))).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
amount_collected = Decimal(str(result.get('amountCollected', 0))).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
||||||
logger.info(f"Amount collected: {amount_collected}")
|
logger.info(f"Amount collected: {amount_collected}")
|
||||||
|
|
||||||
if loan.balance is None or loan.balance <= 0:
|
updated_loan = LoanService._update_loan_after_collection(
|
||||||
logger.warning(f"Loan ID {loan.id} has no balance. Skipping loan update.")
|
loan, loan_data, updated_loan, amount_collected, data, response_message=response_message
|
||||||
updated_loan = loan.to_dict()
|
|
||||||
#return ResponseHelper.error("Loan has no balance. Skipping.")
|
|
||||||
else:
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"Updating loan balance for loan ID {loan_data['debtId']} with amount collected: {amount_collected}")
|
|
||||||
updated_loan = LoanService.update_loan_balance(int(loan_data['debtId']), amount_collected)
|
|
||||||
logger.info(f"Updated loan: {updated_loan}")
|
|
||||||
except Exception as ex:
|
|
||||||
db.session.rollback()
|
|
||||||
logger.error(f"Error updating loan balance for loan ID {loan.id}: {ex}")
|
|
||||||
return ResponseHelper.error("Error updating loan balance")
|
|
||||||
|
|
||||||
try:
|
|
||||||
updated_balance = Decimal(str(updated_loan['balance'])).quantize(Decimal('0.01'))
|
|
||||||
logger.info(f"Updated balance: {updated_balance}")
|
|
||||||
|
|
||||||
if updated_balance <= Decimal('0.00'):
|
|
||||||
logger.info('Loan fully repaid')
|
|
||||||
repaid = LoanService.update_status(updated_loan['debtId'], LoanStatus.REPAID)
|
|
||||||
logger.info(f'Updated loan with repaid status: {repaid}')
|
|
||||||
updated_loan = repaid
|
|
||||||
else:
|
|
||||||
logger.info('Loan partially repaid')
|
|
||||||
partial = LoanService.update_status(updated_loan['debtId'], LoanStatus.ACTIVE_PARTIAL)
|
|
||||||
logger.info(f'Updated loan with partial status: {partial}')
|
|
||||||
updated_loan = partial
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
db.session.rollback()
|
|
||||||
logger.error(f"Error while updating loan status for debtId {updated_loan['debtId']}: {e}")
|
|
||||||
# lets update the loan repayment schedule when full payment has been made
|
|
||||||
# get the repayment schedule by loan_id
|
|
||||||
logger.info(f"Updated loan status: {updated_loan.get('status')}")
|
|
||||||
LoanRepaymentScheduleService.handle_schedule_updates(
|
|
||||||
updated_loan=updated_loan,
|
|
||||||
data=data,
|
|
||||||
amount_collected=amount_collected,
|
|
||||||
message=response_message,
|
|
||||||
loan_data=loan_data
|
|
||||||
)
|
)
|
||||||
return ResponseHelper.success(result, "Successful")
|
return ResponseHelper.success(result, "Successful")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
from app.models import Loan, LoanCharge
|
from app.models import Loan, LoanCharge
|
||||||
|
from app.utils.logger import logger
|
||||||
|
from app.enums.loan_status import LoanStatus
|
||||||
|
from decimal import Decimal, ROUND_HALF_UP
|
||||||
|
from app.services.loan_repayment_schedule import LoanRepaymentScheduleService
|
||||||
|
|
||||||
class LoanService:
|
class LoanService:
|
||||||
|
|
||||||
@@ -106,5 +110,29 @@ class LoanService:
|
|||||||
Get all overdue loans.
|
Get all overdue loans.
|
||||||
"""
|
"""
|
||||||
return Loan.get_overdue_loans()
|
return Loan.get_overdue_loans()
|
||||||
|
@staticmethod
|
||||||
|
def _update_loan_after_collection(loan, loan_data, updated_loan, amount_collected, data, response_message):
|
||||||
|
if loan.balance is None or loan.balance <= 0:
|
||||||
|
logger.warning(f"Loan ID {loan.id} has no balance. Skipping loan update.")
|
||||||
|
updated_loan = loan.to_dict()
|
||||||
|
else:
|
||||||
|
updated_loan = LoanService.update_loan_balance(int(loan_data['debtId']), amount_collected)
|
||||||
|
updated_balance = Decimal(str(updated_loan['balance'])).quantize(Decimal('0.01'))
|
||||||
|
|
||||||
|
if updated_balance <= Decimal('0.00'):
|
||||||
|
updated_loan = LoanService.update_status(updated_loan['debtId'], LoanStatus.REPAID)
|
||||||
|
else:
|
||||||
|
updated_loan = LoanService.update_status(updated_loan['debtId'], LoanStatus.ACTIVE_PARTIAL)
|
||||||
|
logger.info(f"Updated loan status: {updated_loan.get('status')}")
|
||||||
|
# lets update the loan repayment schedule
|
||||||
|
LoanRepaymentScheduleService.handle_schedule_updates(
|
||||||
|
updated_loan=updated_loan,
|
||||||
|
data=data,
|
||||||
|
amount_collected=amount_collected,
|
||||||
|
message=response_message,
|
||||||
|
loan_data=loan_data
|
||||||
|
)
|
||||||
|
return updated_loan
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from app.models.loan_repayment_schedule import LoanRepaymentSchedule
|
from app.models.loan_repayment_schedule import LoanRepaymentSchedule
|
||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
from app.enums.loan_status import LoanStatus
|
from app.enums.loan_status import LoanStatus
|
||||||
|
from decimal import Decimal, ROUND_HALF_UP
|
||||||
|
|
||||||
|
|
||||||
class LoanRepaymentScheduleService:
|
class LoanRepaymentScheduleService:
|
||||||
@@ -40,6 +41,7 @@ class LoanRepaymentScheduleService:
|
|||||||
"""
|
"""
|
||||||
return LoanRepaymentSchedule.update_repayment_schedule_description(schedule_id, description)
|
return LoanRepaymentSchedule.update_repayment_schedule_description(schedule_id, description)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_schedule_updates(updated_loan, data, amount_collected, message, loan_data):
|
def handle_schedule_updates(updated_loan, data, amount_collected, message, loan_data):
|
||||||
"""
|
"""
|
||||||
@@ -68,12 +70,12 @@ class LoanRepaymentScheduleService:
|
|||||||
logger.error(f"Failed to update installment {installment.id}: {e}")
|
logger.error(f"Failed to update installment {installment.id}: {e}")
|
||||||
logger.info('All installments processed')
|
logger.info('All installments processed')
|
||||||
|
|
||||||
# Case 2: Partial repayment without overdueLoanScheduleId
|
# 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'):
|
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.")
|
logger.info("Partial repayment detected, but no overdue schedule ID provided.")
|
||||||
# TODO: implement proportional installment updates
|
# TODO: implement proportional installment updates
|
||||||
|
|
||||||
# Case 3: Overdue schedule repayment → update balance & description
|
# Case 3: when we are processing Overdue schedule repayment → update balance & description
|
||||||
elif data.get('overdueLoanScheduleId') is not None:
|
elif data.get('overdueLoanScheduleId') is not None:
|
||||||
logger.info(f"Overdue loan schedule ID: {data['overdueLoanScheduleId']}")
|
logger.info(f"Overdue loan schedule ID: {data['overdueLoanScheduleId']}")
|
||||||
try:
|
try:
|
||||||
@@ -102,4 +104,7 @@ class LoanRepaymentScheduleService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Unexpected error while handling schedule updates: {e}")
|
logger.error(f"Unexpected error while handling schedule updates: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user