From a987b5f2fb6d1b50992c2110edbc5337aa07c97d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Dec 2023 10:13:40 +0530 Subject: [PATCH] feat: broken period interest calculation --- .../loan_disbursement/loan_disbursement.json | 9 +- .../loan_disbursement/loan_disbursement.py | 125 +++++++----------- .../loan_repayment_schedule.json | 9 +- .../loan_repayment_schedule.py | 21 ++- 4 files changed, 77 insertions(+), 87 deletions(-) diff --git a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.json b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.json index be9f6abf..4120129e 100644 --- a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.json +++ b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.json @@ -28,6 +28,7 @@ "clearance_date", "column_break_8", "disbursed_amount", + "broken_period_interest", "accounting_dimensions_section", "cost_center", "charges_section", @@ -279,12 +280,18 @@ "fieldtype": "Currency", "label": "Current Disbursed Amount", "read_only": 1 + }, + { + "fieldname": "broken_period_interest", + "fieldtype": "Currency", + "label": "Broken Period Interest", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-12-07 10:43:07.860874", + "modified": "2023-12-10 18:58:36.390130", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Disbursement", diff --git a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py index 5122f87c..c631ce9b 100644 --- a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -56,7 +56,6 @@ def get_schedule_details(self): "repayment_method": self.repayment_method, "repayment_start_date": self.repayment_start_date, "repayment_periods": self.tenure, - "monthly_repayment_amount": self.monthly_repayment_amount, "posting_date": self.disbursement_date, "repayment_frequency": self.repayment_frequency, "disbursed_amount": disbursed_amount, @@ -79,7 +78,9 @@ def make_draft_schedule(self): already_accrued_months = self.get_already_accrued_months() self.tenure = loan_details.repayment_periods - already_accrued_months - frappe.get_doc(self.get_schedule_details()).insert() + schedule = frappe.get_doc(self.get_schedule_details()).insert() + self.monthly_repayment_amount = schedule.monthly_repayment_amount + self.broken_period_interest = schedule.broken_period_interest def get_already_accrued_months(self): already_accrued_months = 0 @@ -129,6 +130,9 @@ def update_draft_schedule(self): schedule.update(self.get_schedule_details()) schedule.save() + self.broken_period_interest = schedule.broken_period_interest + self.monthly_repayment_amount = schedule.monthly_repayment_amount + def on_submit(self): if self.is_term_loan: self.update_current_repayment_schedule() @@ -370,19 +374,17 @@ def get_values_on_submit(self, loan_details): return disbursed_amount, status, total_payment - def make_gl_entries(self, cancel=0, adv_adj=0): - gle_map = [] - - gle_map.append( + def add_gl_entry(self, gl_entries, account, against_account, amount, remarks=None): + gl_entries.append( self.get_gl_dict( { - "account": self.loan_account, - "against": self.disbursement_account, - "debit": self.disbursed_amount, - "debit_in_account_currency": self.disbursed_amount, + "account": account, + "against": against_account, + "debit": amount, + "debit_in_account_currency": amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Disbursement against loan:") + self.against_loan, + "remarks": remarks, "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, @@ -391,95 +393,58 @@ def make_gl_entries(self, cancel=0, adv_adj=0): ) ) - gle_map.append( + gl_entries.append( self.get_gl_dict( { - "account": self.disbursement_account, - "against": self.loan_account, - "credit": self.disbursed_amount, - "credit_in_account_currency": self.disbursed_amount, + "account": against_account, + "against": account, + "debit": -1 * amount, + "debit_in_account_currency": amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Disbursement against loan:") + self.against_loan, + "remarks": remarks, "cost_center": self.cost_center, "posting_date": self.disbursement_date, } ) ) + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + remarks = _("Disbursement against loan:") + self.against_loan + + self.add_gl_entry( + gle_map, self.loan_account, self.disbursement_account, self.disbursed_amount, remarks + ) + if self.withhold_security_deposit: security_deposit_account = frappe.db.get_value( "Loan Product", self.loan_product, "security_deposit_account" ) - gle_map.append( - self.get_gl_dict( - { - "account": security_deposit_account, - "against": self.disbursement_account, - "credit": self.monthly_repayment_amount, - "credit_in_account_currency": self.monthly_repayment_amount, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("Disbursement against loan:") + self.against_loan, - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.disbursement_date, - } - ) - ) - gle_map.append( - self.get_gl_dict( - { - "account": self.disbursement_account, - "against": self.loan_account, - "credit": -1 * self.monthly_repayment_amount, - "credit_in_account_currency": -1 * self.monthly_repayment_amount, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("Disbursement against loan:") + self.against_loan, - "cost_center": self.cost_center, - "posting_date": self.disbursement_date, - } - ) + self.add_gl_entry( + gle_map, + security_deposit_account, + self.disbursement_account, + -1 * self.monthly_repayment_amount, + remarks, ) - for charge in self.get("loan_disbursement_charges"): - gle_map.append( - self.get_gl_dict( - { - "account": charge.account, - "against": self.disbursement_account, - "credit": charge.amount, - "credit_in_account_currency": charge.amount, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("Disbursement against loan:") + self.against_loan, - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.disbursement_date, - } - ) + if self.broken_period_interest: + broken_period_interest_account = frappe.db.get_value( + "Loan Product", self.loan_product, "broken_period_interest_recovery_account" ) - - gle_map.append( - self.get_gl_dict( - { - "account": self.disbursement_account, - "against": self.loan_account, - "credit": -1 * charge.amount, - "credit_in_account_currency": -1 * charge.amount, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("Disbursement against loan:") + self.against_loan, - "cost_center": self.cost_center, - "posting_date": self.disbursement_date, - } - ) + self.add_gl_entry( + gle_map, + broken_period_interest_account, + self.disbursement_account, + -1 * self.broken_period_interest, + remarks, ) + for charge in self.get("loan_disbursement_charges"): + self.add_gl_entry(gle_map, charge.account, self.disbursement_account, charge.amount, remarks) + if gle_map: make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) diff --git a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json index 73cc03b5..e6ce1136 100644 --- a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json +++ b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json @@ -16,6 +16,7 @@ "rate_of_interest", "posting_date", "adjusted_interest", + "broken_period_interest", "column_break_n6iy", "loan_product", "repayment_frequency", @@ -188,12 +189,18 @@ "fieldtype": "Link", "label": "Loan Disbursement", "options": "Loan Disbursement" + }, + { + "fieldname": "broken_period_interest", + "fieldtype": "Currency", + "label": "Broken Period Interest", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-12-05 22:50:03.030846", + "modified": "2023-12-10 18:53:50.681242", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment Schedule", diff --git a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py index fd99d9de..35b6fd51 100644 --- a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py +++ b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py @@ -37,7 +37,7 @@ def make_repayment_schedule(self): self.repayment_schedule = [] payment_date = self.repayment_start_date balance_amount = self.disbursed_amount or self.loan_amount - broken_period_interest_days = date_diff(add_months(payment_date, -1), self.posting_date) + broken_period_interest_days = date_diff(add_months(payment_date, -1), self.posting_date) + 1 carry_forward_interest = self.adjusted_interest moratorium_interest = 0 @@ -140,7 +140,7 @@ def get_amounts( additional_days, carry_forward_interest=0, ): - days, months = self.get_days_and_months(payment_date, additional_days) + days, months = self.get_days_and_months(payment_date, additional_days, balance_amount) interest_amount = flt(balance_amount * flt(self.rate_of_interest) * days / (months * 100)) principal_amount = self.monthly_repayment_amount - flt(interest_amount) balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount) @@ -155,7 +155,7 @@ def get_amounts( return interest_amount, principal_amount, balance_amount, total_payment, days - def get_days_and_months(self, payment_date, additional_days): + def get_days_and_months(self, payment_date, additional_days, balance_amount): months = 365 if self.repayment_frequency == "Monthly": if self.repayment_schedule_type == "Monthly as per repayment start date": @@ -173,7 +173,7 @@ def get_days_and_months(self, payment_date, additional_days): additional_days = 0 if additional_days: - days += additional_days + self.add_broken_period_interest(balance_amount, additional_days, payment_date) additional_days = 0 elif expected_payment_date == payment_date: # using 30 days for calculating interest for all full months @@ -182,7 +182,7 @@ def get_days_and_months(self, payment_date, additional_days): days = date_diff(get_last_day(payment_date), payment_date) else: if payment_date == self.repayment_start_date: - days = date_diff(payment_date, self.posting_date) + 1 + days = date_diff(payment_date, self.posting_date) elif self.repayment_frequency == "Weekly": days = 7 elif self.repayment_frequency == "Daily": @@ -194,6 +194,17 @@ def get_days_and_months(self, payment_date, additional_days): return days, months + def add_broken_period_interest(self, balance_amount, additional_days, payment_date): + interest_amount = flt( + balance_amount * flt(self.rate_of_interest) * additional_days / (365 * 100) + ) + payment_date = add_months(payment_date, -1) + self.add_repayment_schedule_row( + payment_date, 0, interest_amount, interest_amount, balance_amount, additional_days + ) + + self.broken_period_interest = interest_amount + def add_repayment_schedule_row( self, payment_date, principal_amount, interest_amount, total_payment, balance_loan_amount, days ):