Merge pull request #21605 from deepeshgarg007/loan_security_unpledge_fixes
fix: Loan security unpledge fixes
This commit is contained in:
commit
0ff0522500
@ -233,7 +233,7 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as
|
|||||||
return repayment_entry
|
return repayment_entry
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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("""
|
loan_security_pledge_details = frappe.db.sql("""
|
||||||
SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p
|
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
|
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
|
"against_pledge": loan_security.parent
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if as_dict:
|
||||||
return unpledge_request.as_dict()
|
return unpledge_request.as_dict()
|
||||||
|
else:
|
||||||
|
return unpledge_request
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
|
|||||||
process_loan_interest_accrual_for_term_loans)
|
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.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.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):
|
class TestLoan(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -276,6 +277,56 @@ class TestLoan(unittest.TestCase):
|
|||||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||||
where loan_security='Test Security 2'""")
|
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():
|
def create_loan_accounts():
|
||||||
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
||||||
|
@ -78,7 +78,10 @@ class LoanRepayment(AccountsController):
|
|||||||
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
|
(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):
|
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
||||||
|
if loan.is_secured_loan:
|
||||||
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
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
|
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
||||||
WHERE name = %s """, (loan.total_amount_paid + self.amount_paid,
|
WHERE name = %s """, (loan.total_amount_paid + self.amount_paid,
|
||||||
|
@ -13,6 +13,7 @@ from erpnext.loan_management.doctype.loan_security_price.loan_security_price imp
|
|||||||
class LoanSecurityPledge(Document):
|
class LoanSecurityPledge(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_pledge_amount()
|
self.set_pledge_amount()
|
||||||
|
self.validate_duplicate_securities()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.loan:
|
if self.loan:
|
||||||
@ -21,6 +22,15 @@ class LoanSecurityPledge(Document):
|
|||||||
update_shortfall_status(self.loan, self.total_security_value)
|
update_shortfall_status(self.loan, self.total_security_value)
|
||||||
update_loan(self.loan, self.maximum_loan_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):
|
def set_pledge_amount(self):
|
||||||
total_security_value = 0
|
total_security_value = 0
|
||||||
maximum_loan_value = 0
|
maximum_loan_value = 0
|
||||||
|
@ -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
|
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
|
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 = {}
|
loan_security_map = {}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "LSU-.{applicant}.-.#####",
|
"autoname": "LSU-.{applicant}.-.#####",
|
||||||
"creation": "2019-09-21 13:23:16.117028",
|
"creation": "2019-09-21 13:23:16.117028",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -15,7 +16,6 @@
|
|||||||
"status",
|
"status",
|
||||||
"loan_security_details_section",
|
"loan_security_details_section",
|
||||||
"securities",
|
"securities",
|
||||||
"unpledge_type",
|
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -47,6 +47,7 @@
|
|||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"default": "Requested",
|
"default": "Requested",
|
||||||
|
"depends_on": "eval:doc.docstatus == 1",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
@ -80,13 +81,6 @@
|
|||||||
"options": "Unpledge",
|
"options": "Unpledge",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "unpledge_type",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Unpledge Type",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -104,7 +98,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-10-28 07:41:47.084882",
|
"links": [],
|
||||||
|
"modified": "2020-05-05 07:23:18.440058",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Security Unpledge",
|
"name": "Loan Security Unpledge",
|
||||||
|
@ -13,31 +13,43 @@ from erpnext.loan_management.doctype.loan_security_price.loan_security_price imp
|
|||||||
class LoanSecurityUnpledge(Document):
|
class LoanSecurityUnpledge(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_pledges()
|
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):
|
def validate_pledges(self):
|
||||||
pledge_details = self.get_pledge_details()
|
pledge_qty_map = self.get_pledge_details()
|
||||||
|
|
||||||
loan = frappe.get_doc("Loan", self.loan)
|
loan = frappe.get_doc("Loan", self.loan)
|
||||||
|
|
||||||
pledge_qty_map = {}
|
|
||||||
remaining_qty = 0
|
remaining_qty = 0
|
||||||
unpledge_value = 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:
|
for security in self.securities:
|
||||||
pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0)
|
pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0)
|
||||||
if not pledged_qty:
|
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)))
|
frappe.bold(self.loan)))
|
||||||
|
|
||||||
unpledge_qty = pledged_qty - security.qty
|
unpledge_qty = pledged_qty - security.qty
|
||||||
security_price = security.qty * get_loan_security_price(security.loan_security)
|
security_price = security.qty * get_loan_security_price(security.loan_security)
|
||||||
|
|
||||||
if unpledge_qty < 0:
|
if unpledge_qty < 0:
|
||||||
frappe.throw(_("Cannot unpledge more than {0} qty of {0}").format(frappe.bold(pledged_qty),
|
frappe.throw(_("""Row {0}: Cannot unpledge more than {1} qty of {2} against
|
||||||
frappe.bold(security.loan_security)))
|
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
|
remaining_qty += unpledge_qty
|
||||||
unpledge_value += security_price - flt(security_price * security.haircut/100)
|
unpledge_value += security_price - flt(security_price * security.haircut/100)
|
||||||
@ -45,41 +57,57 @@ class LoanSecurityUnpledge(Document):
|
|||||||
if unpledge_value > loan.total_principal_paid:
|
if unpledge_value > loan.total_principal_paid:
|
||||||
frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount"))
|
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):
|
def get_pledge_details(self):
|
||||||
|
pledge_qty_map = {}
|
||||||
|
|
||||||
pledge_details = frappe.db.sql("""
|
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,
|
`tabLoan Security Pledge` lsp,
|
||||||
`tabPledge` p
|
`tabPledge` p
|
||||||
WHERE
|
WHERE
|
||||||
p.parent = lsp.name
|
p.parent = lsp.name
|
||||||
AND lsp.loan = %s
|
AND lsp.loan = %s
|
||||||
AND lsp.docstatus = 1
|
AND lsp.docstatus = 1
|
||||||
AND lsp.status = "Pledged"
|
AND lsp.status in ('Pledged', 'Partially Pledged')
|
||||||
""",(self.loan), as_dict=1)
|
""", (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):
|
def on_update_after_submit(self):
|
||||||
if self.status == "Approved":
|
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("""
|
frappe.db.sql("""
|
||||||
UPDATE
|
UPDATE
|
||||||
`tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp,
|
`tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, `tabLoan Security Unpledge` lsu
|
||||||
`tabLoan Security Unpledge` lsu SET p.qty = (p.qty - u.qty)
|
SET p.qty = {new_qty}
|
||||||
WHERE
|
WHERE
|
||||||
lsp.loan = %s
|
lsp.loan = %s
|
||||||
AND lsu.status = 'Requested'
|
|
||||||
AND u.parent = %s
|
|
||||||
AND p.parent = u.against_pledge
|
AND p.parent = u.against_pledge
|
||||||
AND p.loan_security = u.loan_security""",(self.loan, self.name))
|
AND p.parent = lsp.name
|
||||||
|
AND lsp.docstatus = 1
|
||||||
|
AND p.loan_security = u.loan_security""".format(new_qty=new_qty),(self.loan))
|
||||||
|
|
||||||
frappe.db.sql("""UPDATE `tabLoan Security Pledge`
|
def update_loan_status(self, cancel=0):
|
||||||
SET status = %s WHERE loan = %s""", (self.unpledge_type, self.loan))
|
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')
|
|
||||||
|
Loading…
Reference in New Issue
Block a user