Merge pull request #26108 from deepeshgarg007/loan_repay_via_salary_v13

fix: Sanctioned loan amount limit check
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 69d11a8..ff7fbbd 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -60,8 +60,9 @@
 			self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
 
 	def check_sanctioned_amount_limit(self):
-		total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 		sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+		if sanctioned_amount_limit:
+			total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 
 		if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
 			frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
@@ -155,9 +156,29 @@
 	frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
 
 def get_total_loan_amount(applicant_type, applicant, company):
-	return frappe.db.get_value('Loan',
-		{'applicant_type': applicant_type, 'company': company, 'applicant': applicant, 'docstatus': 1},
-		'sum(loan_amount)')
+	pending_amount = 0
+	loan_details = frappe.db.get_all("Loan",
+		filters={"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1,
+			"status": ("!=", "Closed")},
+		fields=["status", "total_payment", "disbursed_amount", "total_interest_payable", "total_principal_paid",
+			"written_off_amount"])
+
+	interest_amount = flt(frappe.db.get_value("Loan Interest Accrual", {"applicant_type": applicant_type,
+		"company": company, "applicant": applicant, "docstatus": 1}, "sum(interest_amount - paid_interest_amount)"))
+
+	for loan in loan_details:
+		if loan.status in ("Disbursed", "Loan Closure Requested"):
+			pending_amount += flt(loan.total_payment) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+		elif loan.status == "Partially Disbursed":
+			pending_amount += flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+		elif loan.status == "Sanctioned":
+			pending_amount += flt(loan.total_payment)
+
+	pending_amount += interest_amount
+
+	return pending_amount
 
 def get_sanctioned_amount_limit(applicant_type, applicant, company):
 	return frappe.db.get_value('Sanctioned Loan Amount',
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index fa4707c..314f58d 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -49,7 +49,11 @@
 		if not frappe.db.exists("Customer", "_Test Loan Customer"):
 			frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
 
-		self.applicant2 = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
+		if not frappe.db.exists("Customer", "_Test Loan Customer 1"):
+			frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True)
+
+		self.applicant2 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
+		self.applicant3 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer 1"}, "name")
 
 		create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20)
 
@@ -125,6 +129,38 @@
 		self.assertTrue(gl_entries1)
 		self.assertTrue(gl_entries2)
 
+	def test_sanctioned_amount_limit(self):
+		# Clear loan docs before checking
+		frappe.db.sql("DELETE FROM `tabLoan` where applicant = '_Test Loan Customer 1'")
+		frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'")
+		frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'")
+
+		if not frappe.db.get_value("Sanctioned Loan Amount", filters={"applicant_type": "Customer",
+			"applicant": "_Test Loan Customer 1", "company": "_Test Company"}):
+			frappe.get_doc({
+				"doctype": "Sanctioned Loan Amount",
+				"applicant_type": "Customer",
+				"applicant": "_Test Loan Customer 1",
+				"sanctioned_amount_limit": 1500000,
+				"company": "_Test Company"
+			}).insert(ignore_permissions=True)
+
+		# Make First Loan
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+		loan = create_demand_loan(self.applicant3, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		# Make second loan greater than the sanctioned amount
+		loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge,
+			do_not_save=True)
+		self.assertRaises(frappe.ValidationError, loan_application.save)
+
 	def test_regular_loan_repayment(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -367,7 +403,7 @@
 		unpledge_request.load_from_db()
 		self.assertEqual(unpledge_request.docstatus, 1)
 
-	def test_santined_loan_security_unpledge(self):
+	def test_sanctioned_loan_security_unpledge(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
 			"qty": 4000.00
@@ -858,7 +894,7 @@
 	return lr
 
 def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
-	repayment_periods=None, posting_date=None):
+	repayment_periods=None, posting_date=None, do_not_save=False):
 	loan_application = frappe.new_doc('Loan Application')
 	loan_application.applicant_type = 'Customer'
 	loan_application.company = company
@@ -874,6 +910,9 @@
 	for pledge in proposed_pledges:
 		loan_application.append('proposed_pledges', pledge)
 
+	if do_not_save:
+		return loan_application
+
 	loan_application.save()
 	loan_application.submit()
 
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index 9c0147e..d8f3577 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -46,9 +46,11 @@
 			frappe.throw(_("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(self.maximum_loan_amount))
 
 	def check_sanctioned_amount_limit(self):
-		total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 		sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
 
+		if sanctioned_amount_limit:
+			total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
+
 		if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
 			frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
 
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 3d99b1f..b8b1a40 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -235,70 +235,71 @@
 		else:
 			remarks = _("Repayment against Loan: ") + self.against_loan
 
-		if self.total_penalty_paid:
+		if not loan_details.repay_from_salary:
+			if self.total_penalty_paid:
+				gle_map.append(
+					self.get_gl_dict({
+						"account": loan_details.loan_account,
+						"against": loan_details.payment_account,
+						"debit": self.total_penalty_paid,
+						"debit_in_account_currency": self.total_penalty_paid,
+						"against_voucher_type": "Loan",
+						"against_voucher": self.against_loan,
+						"remarks": _("Penalty against loan:") + self.against_loan,
+						"cost_center": self.cost_center,
+						"party_type": self.applicant_type,
+						"party": self.applicant,
+						"posting_date": getdate(self.posting_date)
+					})
+				)
+
+				gle_map.append(
+					self.get_gl_dict({
+						"account": loan_details.penalty_income_account,
+						"against": loan_details.payment_account,
+						"credit": self.total_penalty_paid,
+						"credit_in_account_currency": self.total_penalty_paid,
+						"against_voucher_type": "Loan",
+						"against_voucher": self.against_loan,
+						"remarks": _("Penalty against loan:") + self.against_loan,
+						"cost_center": self.cost_center,
+						"posting_date": getdate(self.posting_date)
+					})
+				)
+
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.loan_account,
-					"against": loan_details.payment_account,
-					"debit": self.total_penalty_paid,
-					"debit_in_account_currency": self.total_penalty_paid,
+					"account": loan_details.payment_account,
+					"against": loan_details.loan_account + ", " + loan_details.interest_income_account
+							+ ", " + loan_details.penalty_income_account,
+					"debit": self.amount_paid,
+					"debit_in_account_currency": self.amount_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": _("Penalty against loan:") + self.against_loan,
+					"remarks": remarks,
 					"cost_center": self.cost_center,
-					"party_type": self.applicant_type,
-					"party": self.applicant,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.penalty_income_account,
+					"account": loan_details.loan_account,
+					"party_type": loan_details.applicant_type,
+					"party": loan_details.applicant,
 					"against": loan_details.payment_account,
-					"credit": self.total_penalty_paid,
-					"credit_in_account_currency": self.total_penalty_paid,
+					"credit": self.amount_paid,
+					"credit_in_account_currency": self.amount_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": _("Penalty against loan:") + self.against_loan,
+					"remarks": remarks,
 					"cost_center": self.cost_center,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
-		gle_map.append(
-			self.get_gl_dict({
-				"account": loan_details.payment_account,
-				"against": loan_details.loan_account + ", " + loan_details.interest_income_account
-						+ ", " + loan_details.penalty_income_account,
-				"debit": self.amount_paid,
-				"debit_in_account_currency": self.amount_paid,
-				"against_voucher_type": "Loan",
-				"against_voucher": self.against_loan,
-				"remarks": remarks,
-				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
-			})
-		)
-
-		gle_map.append(
-			self.get_gl_dict({
-				"account": loan_details.loan_account,
-				"party_type": loan_details.applicant_type,
-				"party": loan_details.applicant,
-				"against": loan_details.payment_account,
-				"credit": self.amount_paid,
-				"credit_in_account_currency": self.amount_paid,
-				"against_voucher_type": "Loan",
-				"against_voucher": self.against_loan,
-				"remarks": remarks,
-				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
-			})
-		)
-
-		if gle_map:
-			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
+			if gle_map:
+				make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
 
 def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
 	payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None):