From d86b7c55373c0a6e72d18ab0f9106649489575d5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 11 Oct 2020 00:37:34 +0530 Subject: [PATCH] fix: Remove repayment type --- .../loan_management/doctype/loan/test_loan.py | 136 ++++++++++++++---- .../loan_interest_accrual.py | 29 +++- .../loan_repayment/loan_repayment.json | 31 ++-- .../doctype/loan_repayment/loan_repayment.py | 116 +++++++++------ 4 files changed, 220 insertions(+), 92 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 5a4a19a0fb..7b653652ea 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -14,7 +14,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_ process_loan_interest_accrual_for_term_loans) from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall -from erpnext.loan_management.doctype.loan.loan import unpledge_security +from erpnext.loan_management.doctype.loan.loan import unpledge_security, request_loan_closure from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount @@ -132,7 +132,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -149,23 +149,23 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), 111118.68) repayment_entry.save() repayment_entry.submit() penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2)) - amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', - 'paid_principal_amount']) + amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount']) loan.load_from_db() - self.assertEquals(amounts[0], repayment_entry.interest_payable) + total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount'] + self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid - - penalty_amount - amounts[0], 2)) + penalty_amount - total_interest_paid, 2)) - def test_loan_closure_repayment(self): + def test_loan_closure(self): pledge = [{ "loan_security": "Test Security 1", "qty": 4000.00 @@ -174,7 +174,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -196,14 +196,16 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), - "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry.submit() amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 2),flt(accrued_interest_amount, 2)) + self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + request_loan_closure(loan.name) loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -230,8 +232,7 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(nowdate(), 5), - "Regular Payment", 89768.75) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(nowdate(), 5), 89768.75) repayment_entry.submit() @@ -281,7 +282,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -299,10 +300,10 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), - "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() + request_loan_closure(loan.name) loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -317,9 +318,9 @@ class TestLoan(unittest.TestCase): self.assertEqual(loan.status, 'Closed') self.assertEquals(sum(pledged_qty.values()), 0) - amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") - self.assertEqual(amounts['pending_principal_amount'], 0) - self.assertEqual(amounts['payable_principal_amount'], 0) + amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertTrue(amounts['pending_principal_amount'] < 0) + self.assertTrue(amounts['payable_principal_amount'] < 0) self.assertEqual(amounts['interest_amount'], 0) def test_disbursal_check_with_shortfall(self): @@ -381,7 +382,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -399,20 +400,102 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), - "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', 'paid_principal_amount']) + request_loan_closure(loan.name) loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") - amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") - self.assertEquals(amounts['pending_principal_amount'], 0.0) + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertTrue(amounts['pending_principal_amount'] < 0.0) + + def test_partial_unaccrued_interest_payment(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + no_of_days = date_diff(last_date, first_date) + 1 + + no_of_days += 5.5 + + # get partial unaccrued interest amount + paid_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + paid_amount) + + repayment_entry.submit() + repayment_entry.load_from_db() + + partial_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 5) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + interest_amount = flt(amounts['interest_amount'] + partial_accrued_interest_amount, 2) + self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0)) + + def test_penalty(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + amounts = calculate_amounts(loan.name, add_days(last_date, 1)) + paid_amount = amounts['interest_amount']/2 + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + paid_amount) + + repayment_entry.submit() + + # 30 days - grace period + penalty_days = 30 - 5 + penalty_applicable_amount = flt(amounts['interest_amount']/2, 2) + penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days)/365, 2) + process = process_loan_interest_accrual_for_demand_loans(posting_date = '2019-11-30') + + calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', + {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount') + + self.assertEquals(calculated_penalty_amount, penalty_amount) + def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): @@ -582,12 +665,11 @@ def create_loan_security_price(loan_security, loan_security_price, uom, from_dat "valid_upto": to_date }).insert(ignore_permissions=True) -def create_repayment_entry(loan, applicant, posting_date, payment_type, paid_amount): +def create_repayment_entry(loan, applicant, posting_date, paid_amount): lr = frappe.get_doc({ "doctype": "Loan Repayment", "against_loan": loan, - "payment_type": payment_type, "company": "_Test Company", "posting_date": posting_date or nowdate(), "applicant": applicant, diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 2d959bf3be..4517de0c59 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -22,7 +22,6 @@ class LoanInterestAccrual(AccountsController): if not self.interest_amount and not self.payable_principal_amount: frappe.throw(_("Interest Amount or Principal Amount is mandatory")) - def on_submit(self): self.make_gl_entries() @@ -79,8 +78,11 @@ class LoanInterestAccrual(AccountsController): # For Eg: If Loan disbursement date is '01-09-2019' and disbursed amount is 1000000 and # rate of interest is 13.5 then first loan interest accural will be on '01-10-2019' # which means interest will be accrued for 30 days which should be equal to 11095.89 -def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest): +def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type): + from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts + no_of_days = get_no_of_days_for_interest_accural(loan, posting_date) + precision = cint(frappe.db.get_default("currency_precision")) or 2 if no_of_days <= 0: return @@ -91,7 +93,7 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i else: pending_principal_amount = loan.disbursed_amount - interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100) + interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date) payable_interest = interest_per_day * no_of_days args = frappe._dict({ @@ -102,13 +104,16 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i 'loan_account': loan.loan_account, 'pending_principal_amount': pending_principal_amount, 'interest_amount': payable_interest, + 'penalty_amount': calculate_amounts(loan.name, posting_date)['penalty_amount'], 'process_loan_interest': process_loan_interest, - 'posting_date': posting_date + 'posting_date': posting_date, + 'accrual_type': accrual_type }) - make_loan_interest_accrual_entry(args) + if flt(payable_interest, precision) > 0.0: + make_loan_interest_accrual_entry(args) -def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None): +def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"): query_filters = { "status": ('in', ['Disbursed', 'Partially Disbursed']), "docstatus": 1 @@ -127,7 +132,7 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte filters=query_filters) for loan in open_loans: - calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest) + calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type) def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_interest, term_loan=None, loan_type=None): curr_date = posting_date or add_days(nowdate(), 1) @@ -192,10 +197,12 @@ def make_loan_interest_accrual_entry(args): loan_interest_accrual.loan_account = args.loan_account loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision) loan_interest_accrual.interest_amount = flt(args.interest_amount, precision) + loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision) loan_interest_accrual.posting_date = args.posting_date or nowdate() loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name loan_interest_accrual.payable_principal_amount = args.payable_principal + loan_interest_accrual.accrual_type = args.accrual_type loan_interest_accrual.save() loan_interest_accrual.submit() @@ -226,3 +233,11 @@ def days_in_year(year): return days +def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None): + if not posting_date: + posting_date = getdate() + + precision = cint(frappe.db.get_default("currency_precision")) or 2 + + return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100), 2) + diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 5942455919..60b20369dc 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -10,11 +10,11 @@ "applicant_type", "applicant", "loan_type", - "payment_type", "column_break_3", "company", "posting_date", "is_term_loan", + "rate_of_interest", "payment_details_section", "due_date", "pending_principal_amount", @@ -31,6 +31,7 @@ "column_break_21", "reference_date", "principal_amount_paid", + "total_interest_paid", "repayment_details", "amended_from" ], @@ -95,15 +96,6 @@ "fieldname": "column_break_9", "fieldtype": "Column Break" }, - { - "default": "Regular Payment", - "fieldname": "payment_type", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Payment Type", - "options": "\nRegular Payment\nLoan Closure", - "reqd": 1 - }, { "fieldname": "payable_amount", "fieldtype": "Currency", @@ -195,6 +187,7 @@ "fieldtype": "Currency", "hidden": 1, "label": "Principal Amount Paid", + "options": "Company:company:default_currency", "read_only": 1 }, { @@ -217,11 +210,27 @@ "hidden": 1, "label": "Repayment Details", "options": "Loan Repayment Detail" + }, + { + "fieldname": "total_interest_paid", + "fieldtype": "Currency", + "hidden": 1, + "label": "Total Interest Paid", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fetch_from": "loan_type.rate_of_interest", + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "label": "Rate Of Interest", + "read_only": 1 } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-05-16 09:40:15.581165", + "modified": "2020-10-10 03:49:01.827593", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 97dbc44bf1..c8344d4667 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -14,19 +14,18 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest class LoanRepayment(AccountsController): def validate(self): - amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type) + amounts = calculate_amounts(self.against_loan, self.posting_date) self.set_missing_values(amounts) self.validate_amount() - self.allocate_amounts(amounts['pending_accrual_entries']) - - def before_submit(self): - self.book_unaccrued_interest() + self.allocate_amounts(amounts) def on_submit(self): + self.book_unaccrued_interest() self.update_paid_amount() self.make_gl_entries() @@ -72,29 +71,35 @@ class LoanRepayment(AccountsController): msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount) frappe.throw(msg) - if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision): - msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount) - frappe.throw(msg) - def book_unaccrued_interest(self): - if self.payment_type == 'Loan Closure': - total_interest_paid = 0 - for payment in self.repayment_details: - total_interest_paid += payment.paid_interest_amount + precision = cint(frappe.db.get_default("currency_precision")) or 2 + if self.total_interest_paid > self.interest_payable: + if not self.is_term_loan: + # get last loan interest accrual date + last_accrual_date = frappe.get_value('Loan Interest Accrual', {'loan': self.against_loan}, 'MAX(posting_date)') - if total_interest_paid < self.interest_payable: - if not self.is_term_loan: - process = process_loan_interest_accrual_for_demand_loans(posting_date=self.posting_date, - loan=self.against_loan) + # get posting date upto which interest has to be accrued + per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date), 2) - lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': - process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1) + no_of_days = flt(flt(self.total_interest_paid - self.interest_payable, + precision)/per_day_interest, 0) - self.append('repayment_details', { - 'loan_interest_accrual': lia.name, - 'paid_interest_amount': lia.interest_amount, - 'paid_principal_amount': lia.payable_principal_amount - }) + posting_date = add_days(last_accrual_date, no_of_days) + + # book excess interest paid + process = process_loan_interest_accrual_for_demand_loans(posting_date=posting_date, + loan=self.against_loan, accrual_type="Repayment") + + # get loan interest accrual to update paid amount + lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': + process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1) + + self.append('repayment_details', { + 'loan_interest_accrual': lia.name, + 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), + 'paid_principal_amount': 0.0 + }) def update_paid_amount(self): precision = cint(frappe.db.get_default("currency_precision")) or 2 @@ -108,12 +113,6 @@ class LoanRepayment(AccountsController): WHERE name = %s""", (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual)) - if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision): - if loan.is_secured_loan: - frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") - else: - frappe.db.set_value("Loan", self.against_loan, "status", "Closed") - frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid + self.amount_paid, loan.total_principal_paid + self.principal_amount_paid, self.against_loan)) @@ -137,15 +136,17 @@ class LoanRepayment(AccountsController): if loan.status == "Loan Closure Requested": frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed") - def allocate_amounts(self, paid_entries): + def allocate_amounts(self, repayment_details): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + self.set('repayment_details', []) self.principal_amount_paid = 0 total_interest_paid = 0 interest_paid = self.amount_paid - self.penalty_amount - if self.amount_paid - self.penalty_amount > 0 and paid_entries: + if self.amount_paid - self.penalty_amount > 0: interest_paid = self.amount_paid - self.penalty_amount - for lia, amounts in iteritems(paid_entries): + for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])): if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid: interest_amount = amounts['interest_amount'] paid_principal = amounts['payable_principal_amount'] @@ -169,9 +170,24 @@ class LoanRepayment(AccountsController): 'paid_principal_amount': paid_principal }) - if self.payment_type == 'Loan Closure' and total_interest_paid < self.interest_payable: - unaccrued_interest = self.interest_payable - total_interest_paid - interest_paid -= unaccrued_interest + if repayment_details['unaccrued_interest'] and interest_paid: + # no of days for which to accrue interest + # Interest can only be accrued for an entire day and not partial + if interest_paid > repayment_details['unaccrued_interest']: + per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date), precision) + interest_paid -= repayment_details['unaccrued_interest'] + total_interest_paid += repayment_details['unaccrued_interest'] + else: + # get no of days for which interest can be paid + per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date), precision) + + no_of_days = cint(interest_paid/per_day_interest) + total_interest_paid += no_of_days * per_day_interest + interest_paid -= no_of_days * per_day_interest + + self.total_interest_paid = total_interest_paid if interest_paid: self.principal_amount_paid += interest_paid @@ -289,7 +305,7 @@ def get_accrued_interest_entries(against_loan): # This function returns the amounts that are payable at the time of loan repayment based on posting date # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable -def get_amounts(amounts, against_loan, posting_date, payment_type): +def get_amounts(amounts, against_loan, posting_date): precision = cint(frappe.db.get_default("currency_precision")) or 2 against_loan_doc = frappe.get_doc("Loan", against_loan) @@ -332,15 +348,16 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): else: pending_principal_amount = against_loan_doc.disbursed_amount - if payment_type == "Loan Closure": - if due_date: - pending_days = date_diff(posting_date, due_date) + 1 - else: - pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 + unaccrued_interest = 0 + if due_date: + pending_days = date_diff(posting_date, due_date) + 1 + else: + pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 - payable_principal_amount = pending_principal_amount - per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365 - total_pending_interest += (pending_days * per_day_interest) + if pending_days > 0: + payable_principal_amount = flt(pending_principal_amount, precision) + per_day_interest = get_per_day_interest(payable_principal_amount, loan_type_details.rate_of_interest, posting_date) + unaccrued_interest += (pending_days * flt(per_day_interest, precision)) amounts["pending_principal_amount"] = flt(pending_principal_amount, precision) amounts["payable_principal_amount"] = flt(payable_principal_amount, precision) @@ -348,6 +365,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): amounts["penalty_amount"] = flt(penalty_amount, precision) amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision) amounts["pending_accrual_entries"] = pending_accrual_entries + amounts["unaccrued_interest"] = unaccrued_interest if final_due_date: amounts["due_date"] = final_due_date @@ -355,7 +373,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): return amounts @frappe.whitelist() -def calculate_amounts(against_loan, posting_date, payment_type): +def calculate_amounts(against_loan, posting_date, payment_type=''): amounts = { 'penalty_amount': 0.0, @@ -363,10 +381,14 @@ def calculate_amounts(against_loan, posting_date, payment_type): 'pending_principal_amount': 0.0, 'payable_principal_amount': 0.0, 'payable_amount': 0.0, + 'unaccrued_interest': 0.0, 'due_date': '' } - amounts = get_amounts(amounts, against_loan, posting_date, payment_type) + amounts = get_amounts(amounts, against_loan, posting_date) + + if payment_type == 'Loan Closure': + amounts['payable_amount'] += amounts['unaccrued_interest'] return amounts