Merge pull request #24252 from deepeshgarg007/partial_security_unpledge

fix: Partial loan security unpledging
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index cd40a66..2e0a4d1 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -6,6 +6,7 @@
 import frappe, math, json
 import erpnext
 from frappe import _
+from six import string_types
 from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
 from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
 from erpnext.controllers.accounts_controller import AccountsController
@@ -280,10 +281,13 @@
 		return write_off
 
 @frappe.whitelist()
-def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
-	# if loan is passed it will be considered as full unpledge
+def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
+	# if no security_map is passed it will be considered as full unpledge
+	if security_map and isinstance(security_map, string_types):
+		security_map = json.loads(security_map)
+
 	if loan:
-		pledge_qty_map = get_pledged_security_qty(loan)
+		pledge_qty_map = security_map or get_pledged_security_qty(loan)
 		loan_doc = frappe.get_doc('Loan', loan)
 		unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
 			loan_doc.applicant_type, loan_doc.applicant)
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 1d831ae..8b1f9a2 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -325,6 +325,43 @@
 		self.assertEquals(amounts['payable_principal_amount'], 0.0)
 		self.assertEqual(amounts['interest_amount'], 0)
 
+	def test_partial_loan_security_unpledge(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 2000.00
+		},
+		{
+			"loan_security": "Test Security 2",
+			"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)
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000)
+		repayment_entry.submit()
+
+		unpledge_map = {'Test Security 2': 2000}
+
+		unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+		unpledge_request.submit()
+		unpledge_request.status = 'Approved'
+		unpledge_request.save()
+		unpledge_request.submit()
+		unpledge_request.load_from_db()
+		self.assertEqual(unpledge_request.docstatus, 1)
+
 	def test_disbursal_check_with_shortfall(self):
 		pledges = [{
 			"loan_security": "Test Security 2",
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 8ec0bfb..6469806 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
@@ -81,7 +81,6 @@
 				process_loan_security_shortfall)
 
 def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
-
 	existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
 
 	if existing_shortfall:
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 c29f325..61c418d 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
@@ -30,6 +30,8 @@
 					d.idx, frappe.bold(d.loan_security)))
 
 	def validate_unpledge_qty(self):
+		from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio
+
 		pledge_qty_map = get_pledged_security_qty(self.loan)
 
 		ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
@@ -47,6 +49,8 @@
 
 		pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
 		security_value = 0
+		unpledge_qty_map = {}
+		ltv_ratio = 0
 
 		for security in self.securities:
 			pledged_qty = pledge_qty_map.get(security.loan_security, 0)
@@ -57,13 +61,15 @@
 				msg += _("You are trying to unpledge more.")
 				frappe.throw(msg, title=_("Loan Security Unpledge Error"))
 
-			qty_after_unpledge = pledged_qty - security.qty
-			ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
+			unpledge_qty_map.setdefault(security.loan_security, 0)
+			unpledge_qty_map[security.loan_security] += security.qty
 
-			current_price = loan_security_price_map.get(security.loan_security)
-			if not current_price:
-				frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security)))
+		for security in pledge_qty_map:
+			if not ltv_ratio:
+				ltv_ratio = get_ltv_ratio(security)
 
+			qty_after_unpledge = pledge_qty_map.get(security, 0) - unpledge_qty_map.get(security, 0)
+			current_price = loan_security_price_map.get(security)
 			security_value += qty_after_unpledge * current_price
 
 		if not security_value and flt(pending_principal_amount, 2) > 0: