diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index d55af703c0..0c9c97f60f 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -12,7 +12,6 @@ from frappe.utils import ( add_months, date_diff, flt, - get_first_day, get_last_day, getdate, now_datetime, @@ -117,36 +116,51 @@ class Loan(AccountsController): if not self.repayment_start_date: frappe.throw(_("Repayment Start Date is mandatory for term loans")) - schedule_type = frappe.db.get_value("Loan Type", self.loan_type, "repayment_schedule_type") + schedule_type_details = frappe.db.get_value( + "Loan Type", self.loan_type, ["repayment_schedule_type", "repayment_date_on"], as_dict=1 + ) + self.repayment_schedule = [] payment_date = self.repayment_start_date balance_amount = self.loan_amount while balance_amount > 0: interest_amount, principal_amount, balance_amount, total_payment = self.get_amounts( - payment_date, balance_amount, schedule_type + payment_date, + balance_amount, + schedule_type_details.repayment_schedule_type, + schedule_type_details.repayment_date_on, ) - if schedule_type == "Pro-rated calendar months": - next_payment_date = add_days(get_last_day(payment_date), 1) + if schedule_type_details.repayment_schedule_type == "Pro-rated calendar months": + next_payment_date = get_last_day(payment_date) + if schedule_type_details.repayment_date_on == "Start of the next month": + next_payment_date = add_days(next_payment_date, 1) + payment_date = next_payment_date self.add_repayment_schedule_row( payment_date, principal_amount, interest_amount, total_payment, balance_amount ) - if schedule_type == "Monthly as per repayment start date": + if ( + schedule_type_details.repayment_schedule_type == "Monthly as per repayment start date" + or schedule_type_details.repayment_date_on == "End of the current month" + ): next_payment_date = add_single_month(payment_date) payment_date = next_payment_date - def get_amounts(self, payment_date, balance_amount, schedule_type): - first_day_of_month = get_first_day(payment_date) - + def get_amounts(self, payment_date, balance_amount, schedule_type, repayment_date_on): if schedule_type == "Monthly as per repayment start date": days = 1 months = 12 else: - if first_day_of_month == payment_date: + expected_payment_date = get_last_day(payment_date) + if repayment_date_on == "Start of the next month": + expected_payment_date = add_days(expected_payment_date, 1) + + if expected_payment_date == payment_date: + # using 30 days for calculating interest for all full months days = 30 months = 365 else: diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 2608e42e08..0e61161601 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -4,7 +4,16 @@ import unittest import frappe -from frappe.utils import add_days, add_months, add_to_date, date_diff, flt, get_datetime, nowdate +from frappe.utils import ( + add_days, + add_months, + add_to_date, + date_diff, + flt, + format_date, + get_datetime, + nowdate, +) from erpnext.loan_management.doctype.loan.loan import ( make_loan_write_off, @@ -49,6 +58,50 @@ class TestLoan(unittest.TestCase): penalty_income_account="Penalty Income Account - _TC", ) + create_loan_type( + "Term Loan Type 1", + 12000, + 7.5, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Monthly as per repayment start date", + ) + + create_loan_type( + "Term Loan Type 2", + 12000, + 7.5, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Pro-rated calendar months", + repayment_date_on="Start of the next month", + ) + + create_loan_type( + "Term Loan Type 3", + 12000, + 7.5, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Pro-rated calendar months", + repayment_date_on="End of the current month", + ) + create_loan_type( "Stock Loan", 2000000, @@ -902,6 +955,45 @@ class TestLoan(unittest.TestCase): amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(flt(amounts["pending_principal_amount"], 0), 0) + def test_term_loan_schedule_types(self): + loan = create_loan( + self.applicant1, + "Term Loan Type 1", + 12000, + "Repay Over Number of Periods", + 12, + repayment_start_date="2022-10-17", + ) + + # Check for first, second and last installment date + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "17-10-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "17-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "17-09-2023") + + loan.loan_type = "Term Loan Type 2" + loan.save() + + # Check for first, second and last installment date + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "01-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "01-12-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "01-10-2023") + + loan.loan_type = "Term Loan Type 3" + loan.save() + + # Check for first, second and last installment date + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "31-10-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "30-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "30-09-2023") + + loan.repayment_method = "Repay Fixed Amount per Period" + loan.monthly_repayment_amount = 1042 + loan.save() + + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "31-10-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "30-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "30-09-2023") + def create_loan_scenario_for_penalty(doc): pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}] @@ -1033,6 +1125,8 @@ def create_loan_type( penalty_income_account=None, repayment_method=None, repayment_periods=None, + repayment_schedule_type=None, + repayment_date_on=None, ): if not frappe.db.exists("Loan Type", loan_name): @@ -1057,8 +1151,14 @@ def create_loan_type( "repayment_periods": repayment_periods, "write_off_amount": 100, } - ).insert() + ) + if loan_type.is_term_loan: + loan_type.repayment_schedule_type = repayment_schedule_type + if loan_type.repayment_schedule_type != "Monthly as per repayment start date": + loan_type.repayment_date_on = repayment_date_on + + loan_type.insert() loan_type.submit() diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index e1ed3caf8f..5cc9464585 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -17,6 +17,7 @@ "is_term_loan", "disabled", "repayment_schedule_type", + "repayment_date_on", "description", "account_details_section", "mode_of_payment", @@ -161,17 +162,27 @@ }, { "depends_on": "is_term_loan", + "description": "The schedule type that will be used for generating the term loan schedules (will affect the payment date and monthly repayment amount)", "fieldname": "repayment_schedule_type", "fieldtype": "Select", "label": "Repayment Schedule Type", "mandatory_depends_on": "is_term_loan", "options": "\nMonthly as per repayment start date\nPro-rated calendar months" + }, + { + "depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"", + "description": "Select whether the repayment date should be the end of the current month or start of the upcoming month", + "fieldname": "repayment_date_on", + "fieldtype": "Select", + "label": "Repayment Date On", + "mandatory_depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"", + "options": "\nStart of the next month\nEnd of the current month" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-09-28 21:31:01.278941", + "modified": "2022-10-22 17:43:03.954201", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type",