Merge pull request #25053 from deepeshgarg007/delivery_note_gst_category

fix: Add GST category field in Delivery Note
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index acf09f5..4f8ceb0 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -23,6 +23,7 @@
   "rate_of_interest",
   "is_secured_loan",
   "disbursement_date",
+  "closure_date",
   "disbursed_amount",
   "column_break_11",
   "maximum_loan_amount",
@@ -348,12 +349,18 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "read_only": 1
+  },
+  {
+   "fieldname": "closure_date",
+   "fieldtype": "Date",
+   "label": "Closure Date",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-24 12:27:23.208240",
+ "modified": "2021-04-10 09:28:21.946972",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 4b9a894..6f8da31 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -523,33 +523,7 @@
 		self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0))
 
 	def test_penalty(self):
-		pledge = [{
-			"loan_security": "Test Security 1",
-			"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)
-
-		amounts = calculate_amounts(loan.name, add_days(last_date, 1))
-		paid_amount = amounts['interest_amount']/2
-
-		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
-			paid_amount)
-
-		repayment_entry.submit()
-
+		loan, amounts = create_loan_scenario_for_penalty(self)
 		# 30 days - grace period
 		penalty_days = 30 - 4
 		penalty_applicable_amount = flt(amounts['interest_amount']/2)
@@ -559,8 +533,28 @@
 		calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual',
 			{'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount')
 
+		self.assertEquals(loan.loan_amount, 1000000)
 		self.assertEquals(calculated_penalty_amount, penalty_amount)
 
+	def test_penalty_repayment(self):
+		loan, dummy = create_loan_scenario_for_penalty(self)
+		amounts = calculate_amounts(loan.name, '2019-11-30 00:00:00')
+
+		first_penalty = 10000
+		second_penalty = amounts['penalty_amount'] - 10000
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:00', 10000)
+		repayment_entry.submit()
+
+		amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01')
+		self.assertEquals(amounts['penalty_amount'], second_penalty)
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty)
+		repayment_entry.submit()
+
+		amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02')
+		self.assertEquals(amounts['penalty_amount'], 0)
+
 	def test_loan_write_off_limit(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -651,6 +645,32 @@
 		amounts = calculate_amounts(loan.name, add_days(last_date, 5))
 		self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0)
 
+def create_loan_scenario_for_penalty(doc):
+	pledge = [{
+		"loan_security": "Test Security 1",
+		"qty": 4000.00
+	}]
+
+	loan_application = create_loan_application('_Test Company', doc.applicant2, 'Demand Loan', pledge)
+	create_pledge(loan_application)
+	loan = create_demand_loan(doc.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+	loan.submit()
+
+	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)
+
+	amounts = calculate_amounts(loan.name, add_days(last_date, 1))
+	paid_amount = amounts['interest_amount']/2
+
+	repayment_entry = create_repayment_entry(loan.name, doc.applicant2, add_days(last_date, 5),
+		paid_amount)
+
+	repayment_entry.submit()
+
+	return loan, amounts
 
 def create_loan_accounts():
 	if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index cd5df4d..662c626 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -20,6 +20,10 @@
   "cost_center",
   "customer_details_section",
   "bank_account",
+  "disbursement_references_section",
+  "reference_date",
+  "column_break_17",
+  "reference_number",
   "amended_from"
  ],
  "fields": [
@@ -126,12 +130,31 @@
   {
    "fieldname": "column_break_8",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "disbursement_references_section",
+   "fieldtype": "Section Break",
+   "label": "Disbursement References"
+  },
+  {
+   "fieldname": "reference_date",
+   "fieldtype": "Date",
+   "label": "Reference Date"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "reference_number",
+   "fieldtype": "Data",
+   "label": "Reference Number"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-06 10:04:30.882322",
+ "modified": "2021-04-10 10:03:41.502210",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Disbursement",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 86ea59d..8fbf233 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -239,14 +239,16 @@
   {
    "fieldname": "total_penalty_paid",
    "fieldtype": "Currency",
+   "hidden": 1,
    "label": "Total Penalty Paid",
-   "options": "Company:company:default_currency"
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-05 13:45:19.137896",
+ "modified": "2021-04-10 10:00:31.859076",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 5d57ced..728eadf 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -75,7 +75,7 @@
 			"docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
 
 		if future_repayment_date:
-			frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date)))
+			frappe.throw("Repayment already made till date {0}".format(get_datetime(future_repayment_date)))
 
 	def validate_amount(self):
 		precision = cint(frappe.db.get_default("currency_precision")) or 2
@@ -83,10 +83,6 @@
 		if not self.amount_paid:
 			frappe.throw(_("Amount paid cannot be zero"))
 
-		if not self.shortfall_amount and self.amount_paid < self.penalty_amount:
-			msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
-			frappe.throw(msg)
-
 	def book_unaccrued_interest(self):
 		precision = cint(frappe.db.get_default("currency_precision")) or 2
 		if self.total_interest_paid > self.interest_payable:
@@ -231,6 +227,14 @@
 		gle_map = []
 		loan_details = frappe.get_doc("Loan", self.against_loan)
 
+		if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
+			remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount,
+				self.against_loan)
+		elif self.shortfall_amount:
+			remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
+		else:
+			remarks = _("Repayment against Loan: ") + self.against_loan
+
 		if self.total_penalty_paid:
 			gle_map.append(
 				self.get_gl_dict({
@@ -271,7 +275,7 @@
 				"debit_in_account_currency": self.amount_paid,
 				"against_voucher_type": "Loan",
 				"against_voucher": self.against_loan,
-				"remarks": _("Repayment against Loan: ") + self.against_loan,
+				"remarks": remarks,
 				"cost_center": self.cost_center,
 				"posting_date": getdate(self.posting_date)
 			})
@@ -287,7 +291,7 @@
 				"credit_in_account_currency": self.amount_paid,
 				"against_voucher_type": "Loan",
 				"against_voucher": self.against_loan,
-				"remarks": _("Repayment against Loan: ") + self.against_loan,
+				"remarks": remarks,
 				"cost_center": self.cost_center,
 				"posting_date": getdate(self.posting_date)
 			})
@@ -338,6 +342,18 @@
 
 	return unpaid_accrued_entries
 
+def get_penalty_details(against_loan):
+	penalty_details = frappe.db.sql("""
+		SELECT posting_date, (penalty_amount - total_penalty_paid) as pending_penalty_amount
+		FROM `tabLoan Repayment` where posting_date >= (SELECT MAX(posting_date) from `tabLoan Repayment`
+		where against_loan = %s) and docstatus = 1 and against_loan = %s
+	""", (against_loan, against_loan))
+
+	if penalty_details:
+		return penalty_details[0][0], flt(penalty_details[0][1])
+	else:
+		return None, 0
+
 # This function returns the amounts that are payable at the time of loan repayment based on posting date
 # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
 
@@ -348,6 +364,7 @@
 	loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
 	accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date)
 
+	computed_penalty_date, pending_penalty_amount = get_penalty_details(against_loan)
 	pending_accrual_entries = {}
 
 	total_pending_interest = 0
@@ -362,8 +379,13 @@
 		# and if no_of_late days are positive then penalty is levied
 
 		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
+		due_date_after_grace_period = add_days(due_date, loan_type_details.grace_period_in_days)
+
+		# Consider one day after already calculated penalty
+		if computed_penalty_date and getdate(computed_penalty_date) >= due_date_after_grace_period:
+			due_date_after_grace_period = add_days(computed_penalty_date, 1)
+
+		no_of_late_days = date_diff(posting_date, due_date_after_grace_period) + 1
 
 		if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular':
 			penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)
@@ -401,7 +423,7 @@
 	amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
 	amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
 	amounts["interest_amount"] = flt(total_pending_interest, precision)
-	amounts["penalty_amount"] = flt(penalty_amount, precision)
+	amounts["penalty_amount"] = flt(penalty_amount + pending_penalty_amount, precision)
 	amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
 	amounts["pending_accrual_entries"] = pending_accrual_entries
 	amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
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 c4c2d68..b24dc2f 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
@@ -6,7 +6,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import get_datetime, flt
+from frappe.utils import get_datetime, flt, getdate
 import json
 from six import iteritems
 from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price
@@ -113,7 +113,11 @@
 				pledged_qty += qty
 
 			if not pledged_qty:
-				frappe.db.set_value('Loan', self.loan, 'status', 'Closed')
+				frappe.db.set_value('Loan', self.loan,
+					{
+						'status': 'Closed',
+						'closure_date': getdate()
+					})
 
 @frappe.whitelist()
 def get_pledged_security_qty(loan):