From dd340c15de2daec6e21dcc5c35db362bee1d419c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 7 May 2020 15:35:54 +0530 Subject: [PATCH] fix: Loan Security Unpledge fixes --- erpnext/loan_management/doctype/loan/loan.py | 3 +- .../loan_management/doctype/loan/test_loan.py | 12 +- .../doctype/loan_repayment/loan_repayment.py | 9 +- .../loan_security_shortfall.py | 2 +- .../loan_security_unpledge.js | 8 +- .../loan_security_unpledge.py | 138 +++++++++--------- .../doctype/unpledge/unpledge.json | 13 +- 7 files changed, 92 insertions(+), 93 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index c550d4952d..76e10e5ddd 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -248,8 +248,7 @@ def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_d for loan_security in loan_security_pledge_details: unpledge_request.append('securities', { "loan_security": loan_security.loan_security, - "qty": loan_security.qty, - "against_pledge": loan_security.parent + "qty": loan_security.qty }) if as_dict: diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 77a1fcc574..364e2ffecf 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -15,6 +15,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_ 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 create_loan_security_unpledge +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty class TestLoan(unittest.TestCase): def setUp(self): @@ -152,7 +153,7 @@ class TestLoan(unittest.TestCase): repayment_entry.save() repayment_entry.submit() - penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year)) + 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', @@ -305,7 +306,7 @@ 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, 5), + 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.submit() @@ -319,13 +320,12 @@ class TestLoan(unittest.TestCase): unpledge_request.submit() unpledge_request.status = 'Approved' unpledge_request.save() - - loan_security_pledge.load_from_db() loan.load_from_db() + pledged_qty = get_pledged_security_qty(loan.name) + self.assertEqual(loan.status, 'Closed') - for security in loan_security_pledge.securities: - self.assertEquals(security.qty, 0) + self.assertEquals(sum(pledged_qty.values()), 0) def create_loan_accounts(): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 452c836819..2ab668a0e1 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -264,6 +264,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): penalty_amount = 0 payable_principal_amount = 0 final_due_date = '' + due_date = '' for entry in accrued_interest_entries: # Loan repayment due date is one day after the loan interest is accrued @@ -272,7 +273,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): due_date = add_days(entry.posting_date, 1) no_of_late_days = date_diff(posting_date, - add_days(due_date, loan_type_details.grace_period_in_days)) + 1 + add_days(due_date, loan_type_details.grace_period_in_days)) if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary): penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365 @@ -290,9 +291,9 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable - if payment_type == "Loan Closure" and not payable_principal_amount: - if final_due_date: - pending_days = date_diff(posting_date, final_due_date) + 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 diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 8ca6e3e908..308c4385d3 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -69,7 +69,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100) for loan, value in iteritems(loan_security_map): - if (value["security_value"]/value["loan_amount"]) < ltv_ratio: + if (value["loan_amount"]/value['security_value'] * 100) > ltv_ratio: create_loan_security_shortfall(loan, value, process_loan_security_shortfall) def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js index 72c5f38cf3..8223206277 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js @@ -4,10 +4,8 @@ frappe.ui.form.on('Loan Security Unpledge', { refresh: function(frm) { - frm.set_query("against_pledge", "securities", () => { - return { - filters : [["status", "in", ["Pledged", "Partially Pledged"]]] - }; - }); + if (frm.doc.docstatus == 1 && frm.doc.status == 'Approved') { + frm.set_df_property('status', 'read_only', 1); + } } }); diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index b2bb22a3ce..5e9d82aa91 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -8,12 +8,13 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import get_datetime, flt import json +from six import iteritems from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price class LoanSecurityUnpledge(Document): def validate(self): - self.validate_pledges() self.validate_duplicate_securities() + self.validate_unpledge_qty() def on_cancel(self): self.update_loan_security_pledge(cancel=1) @@ -23,80 +24,52 @@ class LoanSecurityUnpledge(Document): def validate_duplicate_securities(self): security_list = [] for d in self.securities: - security = [d.loan_security, d.against_pledge] - if security not in security_list: - security_list.append(security) + if d.loan_security not in security_list: + security_list.append(d.loan_security) else: - frappe.throw(_("Row {0}: Loan Security {1} against Loan Security Pledge {2} added multiple times").format( - d.idx, frappe.bold(d.loan_security), frappe.bold(d.against_pledge))) + frappe.throw(_("Row {0}: Loan Security {1} added multiple times").format( + d.idx, frappe.bold(d.loan_security))) - def validate_pledges(self): - pledge_qty_map = self.get_pledge_details() - loan = frappe.get_doc("Loan", self.loan) + def validate_unpledge_qty(self): + pledge_qty_map = get_pledged_security_qty(self.loan) - remaining_qty = 0 - unpledge_value = 0 + ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type", + fields=["name", "loan_to_value_ratio"], as_list=1)) + + loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price", + fields=["loan_security", "loan_security_price"], + filters = { + "valid_from": ("<=", get_datetime()), + "valid_upto": (">=", get_datetime()) + }, as_list=1)) + + loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid']) + pending_principal_amount = loan_amount - principal_paid + security_value = 0 for security in self.securities: - pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0) - if not pledged_qty: - frappe.throw(_("Zero qty of {0} pledged against loan {1}").format(frappe.bold(security.loan_security), - frappe.bold(self.loan))) + pledged_qty = pledge_qty_map.get(security.loan_security) - unpledge_qty = pledged_qty - security.qty - security_price = security.qty * get_loan_security_price(security.loan_security) + if security.qty > pledged_qty: + frappe.throw(_("""Row {0}: {1} {2} of {3} is pledged against Loan {4}. + You are trying to unpledge more""").format(security.idx, pledged_qty, security.uom, + frappe.bold(security.loan_security), frappe.bold(self.loan))) - if unpledge_qty < 0: - frappe.throw(_("""Row {0}: Cannot unpledge more than {1} qty of {2} against - Loan Security Pledge {3}""").format(security.idx, frappe.bold(pledged_qty), - frappe.bold(security.loan_security), frappe.bold(security.against_pledge))) + qty_after_unpledge = pledged_qty - security.qty + ltv_ratio = ltv_ratio_map.get(security.loan_security_type) - remaining_qty += unpledge_qty - unpledge_value += security_price - flt(security_price * security.haircut/100) + security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security) - if unpledge_value > loan.total_principal_paid: - frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount")) + if not security_value and pending_principal_amount > 0: + frappe.throw("Cannot Unpledge, loan to value ratio is breaching") - def get_pledge_details(self): - pledge_qty_map = {} - - pledge_details = frappe.db.sql(""" - SELECT p.parent, p.loan_security, p.qty FROM - `tabLoan Security Pledge` lsp, - `tabPledge` p - WHERE - p.parent = lsp.name - AND lsp.loan = %s - AND lsp.docstatus = 1 - AND lsp.status in ('Pledged', 'Partially Pledged') - """, (self.loan), as_dict=1) - - for pledge in pledge_details: - pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty) - - return pledge_qty_map + if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio: + frappe.throw("Cannot Unpledge, loan to value ratio is breaching") def on_update_after_submit(self): if self.status == "Approved": - self.update_loan_security_pledge() self.update_loan_status() - - def update_loan_security_pledge(self, cancel=0): - if cancel: - new_qty = 'p.qty + u.qty' - else: - new_qty = 'p.qty - u.qty' - - frappe.db.sql(""" - UPDATE - `tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, `tabLoan Security Unpledge` lsu - SET p.qty = {new_qty} - WHERE - lsp.loan = %s - AND p.parent = u.against_pledge - AND p.parent = lsp.name - AND lsp.docstatus = 1 - AND p.loan_security = u.loan_security""".format(new_qty=new_qty),(self.loan)) + self.db_set('unpledge_time', get_datetime()) def update_loan_status(self, cancel=0): if cancel: @@ -104,10 +77,45 @@ class LoanSecurityUnpledge(Document): if loan_status == 'Closed': frappe.db.set_value('Loan', self.loan, 'status', 'Loan Closure Requested') else: - pledge_qty = frappe.db.sql("""SELECT SUM(c.qty) - FROM `tabLoan Security Pledge` p, `tabPledge` c - WHERE p.loan = %s AND c.parent = p.name""", (self.loan))[0][0] + pledged_qty = 0 + current_pledges = get_pledged_security_qty(self.loan) - if not pledge_qty: + for security, qty in iteritems(current_pledges): + pledged_qty += qty + + if not pledged_qty: frappe.db.set_value('Loan', self.loan, 'status', 'Closed') +@frappe.whitelist() +def get_pledged_security_qty(loan): + + current_pledges = {} + + unpledges = frappe._dict(frappe.db.sql(""" + SELECT u.loan_security, sum(u.qty) as qty + FROM `tabLoan Security Unpledge` up, `tabUnpledge` u + WHERE up.loan = %s + AND u.parent = up.name + AND up.status = 'Approved' + GROUP BY u.loan_security + """, (loan))) + + pledges = frappe._dict(frappe.db.sql(""" + SELECT p.loan_security, sum(p.qty) as qty + FROM `tabLoan Security Pledge` lp, `tabPledge`p + WHERE lp.loan = %s + AND p.parent = lp.name + AND lp.status = 'Pledged' + GROUP BY p.loan_security + """, (loan))) + + for security, qty in iteritems(pledges): + current_pledges.setdefault(security, qty) + current_pledges[security] -= unpledges.get(security, 0.0) + + return current_pledges + + + + + diff --git a/erpnext/loan_management/doctype/unpledge/unpledge.json b/erpnext/loan_management/doctype/unpledge/unpledge.json index 9e6277d5f8..ee192d7377 100644 --- a/erpnext/loan_management/doctype/unpledge/unpledge.json +++ b/erpnext/loan_management/doctype/unpledge/unpledge.json @@ -1,11 +1,11 @@ { + "actions": [], "creation": "2019-09-21 13:22:19.793797", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "loan_security", - "against_pledge", "loan_security_type", "loan_security_code", "haircut", @@ -54,14 +54,6 @@ "label": "Quantity", "reqd": 1 }, - { - "fieldname": "against_pledge", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Against Pledge", - "options": "Loan Security Pledge", - "reqd": 1 - }, { "fetch_from": "loan_security.haircut", "fieldname": "haircut", @@ -71,7 +63,8 @@ } ], "istable": 1, - "modified": "2019-10-02 12:48:18.588236", + "links": [], + "modified": "2020-05-06 10:50:18.448552", "modified_by": "Administrator", "module": "Loan Management", "name": "Unpledge",