diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index c7a2fba878..c550d4952d 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -233,7 +233,7 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as return repayment_entry @frappe.whitelist() -def create_loan_security_unpledge(loan, applicant_type, applicant, company): +def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1): loan_security_pledge_details = frappe.db.sql(""" SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1 @@ -252,7 +252,10 @@ def create_loan_security_unpledge(loan, applicant_type, applicant, company): "against_pledge": loan_security.parent }) - return unpledge_request.as_dict() + if as_dict: + return unpledge_request.as_dict() + else: + return unpledge_request diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 90b8534bc8..77a1fcc574 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -14,6 +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 create_loan_security_unpledge class TestLoan(unittest.TestCase): def setUp(self): @@ -276,6 +277,56 @@ class TestLoan(unittest.TestCase): frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250 where loan_security='Test Security 2'""") + def test_loan_security_unpledge(self): + pledges = [] + pledges.append({ + "loan_security": "Test Security 1", + "qty": 4000.00, + "haircut": 50 + }) + + loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name, + posting_date=get_first_day(nowdate())) + 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 += 6 + + accrued_interest_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) + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), + "Loan Closure", 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']) + + loan.load_from_db() + self.assertEquals(loan.status, "Loan Closure Requested") + + unpledge_request = create_loan_security_unpledge(loan.name, loan.applicant_type, loan.applicant, loan.company, as_dict=0) + unpledge_request.submit() + unpledge_request.status = 'Approved' + unpledge_request.save() + + loan_security_pledge.load_from_db() + loan.load_from_db() + + self.assertEqual(loan.status, 'Closed') + for security in loan_security_pledge.securities: + self.assertEquals(security.qty, 0) + def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 5979ee3102..452c836819 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -78,7 +78,10 @@ class LoanRepayment(AccountsController): (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual)) if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2): - frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") + 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, diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index eb6135868d..f97e5965a5 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -13,6 +13,7 @@ from erpnext.loan_management.doctype.loan_security_price.loan_security_price imp class LoanSecurityPledge(Document): def validate(self): self.set_pledge_amount() + self.validate_duplicate_securities() def on_submit(self): if self.loan: @@ -21,6 +22,15 @@ class LoanSecurityPledge(Document): update_shortfall_status(self.loan, self.total_security_value) update_loan(self.loan, self.maximum_loan_value) + def validate_duplicate_securities(self): + security_list = [] + for security in self.securities: + if security.loan_security not in security_list: + security_list.append(security.loan_security) + else: + frappe.throw(_('Loan Security {0} added multiple times').format(frappe.bold( + security.loan_security))) + def set_pledge_amount(self): total_security_value = 0 maximum_loan_value = 0 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 ab040f1d33..8ca6e3e908 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 @@ -53,7 +53,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty, lp.loan_security_type FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1 - and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1) + and l.is_secured_loan and l.status = 'Disbursed' and p.status = 'Pledged'""", as_dict=1) loan_security_map = {} diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json index ba94855031..aece46ffda 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "LSU-.{applicant}.-.#####", "creation": "2019-09-21 13:23:16.117028", "doctype": "DocType", @@ -15,7 +16,6 @@ "status", "loan_security_details_section", "securities", - "unpledge_type", "amended_from" ], "fields": [ @@ -47,6 +47,7 @@ { "allow_on_submit": 1, "default": "Requested", + "depends_on": "eval:doc.docstatus == 1", "fieldname": "status", "fieldtype": "Select", "label": "Status", @@ -80,13 +81,6 @@ "options": "Unpledge", "reqd": 1 }, - { - "fieldname": "unpledge_type", - "fieldtype": "Data", - "hidden": 1, - "label": "Unpledge Type", - "read_only": 1 - }, { "fieldname": "company", "fieldtype": "Link", @@ -104,7 +98,8 @@ } ], "is_submittable": 1, - "modified": "2019-10-28 07:41:47.084882", + "links": [], + "modified": "2020-05-05 07:23:18.440058", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Unpledge", 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 02b1ecb4ca..b2bb22a3ce 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 @@ -13,31 +13,43 @@ from erpnext.loan_management.doctype.loan_security_price.loan_security_price imp class LoanSecurityUnpledge(Document): def validate(self): self.validate_pledges() + self.validate_duplicate_securities() + + def on_cancel(self): + self.update_loan_security_pledge(cancel=1) + self.update_loan_status(cancel=1) + self.db_set('status', 'Requested') + + 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) + 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))) def validate_pledges(self): - pledge_details = self.get_pledge_details() - + pledge_qty_map = self.get_pledge_details() loan = frappe.get_doc("Loan", self.loan) - pledge_qty_map = {} remaining_qty = 0 unpledge_value = 0 - for pledge in pledge_details: - pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty) - 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 {0}").format(frappe.bold(security.loan_security), + frappe.throw(_("Zero qty of {0} pledged against loan {1}").format(frappe.bold(security.loan_security), frappe.bold(self.loan))) unpledge_qty = pledged_qty - security.qty security_price = security.qty * get_loan_security_price(security.loan_security) if unpledge_qty < 0: - frappe.throw(_("Cannot unpledge more than {0} qty of {0}").format(frappe.bold(pledged_qty), - frappe.bold(security.loan_security))) + 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))) remaining_qty += unpledge_qty unpledge_value += security_price - flt(security_price * security.haircut/100) @@ -45,41 +57,57 @@ class LoanSecurityUnpledge(Document): if unpledge_value > loan.total_principal_paid: frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount")) - if not remaining_qty: - self.db_set('unpledge_type', 'Unpledged') - else: - self.db_set('unpledge_type', 'Partially Pledged') - - def get_pledge_details(self): + pledge_qty_map = {} + pledge_details = frappe.db.sql(""" - SELECT p.parent, p.loan_security, p.qty as qty FROM + 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 = "Pledged" - """,(self.loan), as_dict=1) + AND lsp.status in ('Pledged', 'Partially Pledged') + """, (self.loan), as_dict=1) - return pledge_details + for pledge in pledge_details: + pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty) + + return pledge_qty_map def on_update_after_submit(self): if self.status == "Approved": - frappe.db.sql(""" - UPDATE - `tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, - `tabLoan Security Unpledge` lsu SET p.qty = (p.qty - u.qty) - WHERE - lsp.loan = %s - AND lsu.status = 'Requested' - AND u.parent = %s - AND p.parent = u.against_pledge - AND p.loan_security = u.loan_security""",(self.loan, self.name)) + self.update_loan_security_pledge() + self.update_loan_status() - frappe.db.sql("""UPDATE `tabLoan Security Pledge` - SET status = %s WHERE loan = %s""", (self.unpledge_type, self.loan)) + 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)) + + def update_loan_status(self, cancel=0): + if cancel: + loan_status = frappe.get_value('Loan', self.loan, 'status') + 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] + + if not pledge_qty: + frappe.db.set_value('Loan', self.loan, 'status', 'Closed') - if self.unpledge_type == 'Unpledged': - frappe.db.set_value("Loan", self.loan, 'status', 'Closed')