Repository: frappe/erpnext
Pull request: fix: Loan Repayment entry cancellation on salary slip cancel
Closes: https://github.com/frappe/erpnext/issues/24543

Problem statement:
Canceling and deleting a draft salary slip should reset loan repayments-v13-beta
To replicate:

- Create an employee loan, disburse, then run loan interest accrual.
- Create a salary slip via payroll entry, or manually. The salary slip will pick the loan.
- Now, delete the salary slip.
- On creating the salary slip again, it won't pick the load repayment.

Hints from issue discussion:

Commit message: fix: Prevent non sequential repayment entries

Patch to review:
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index bac06c4e9e63..72e3050b54b7 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -21,6 +21,7 @@ class LoanRepayment(AccountsController):
 	def validate(self):
 		amounts = calculate_amounts(self.against_loan, self.posting_date)
 		self.set_missing_values(amounts)
+		self.check_future_entries()
 		self.validate_amount()
 		self.allocate_amounts(amounts)
 
@@ -30,6 +31,7 @@ def before_submit(self):
 	def on_submit(self):
 		self.update_paid_amount()
 		self.make_gl_entries()
+		#self.repost_future_loan_interest_accruals()
 
 	def on_cancel(self):
 		self.mark_as_unpaid()
@@ -63,6 +65,13 @@ def set_missing_values(self, amounts):
 		if amounts.get('due_date'):
 			self.due_date = amounts.get('due_date')
 
+	def check_future_entries(self):
+		future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
+			"docstatus": 1}, 'posting_date')
+
+		if future_repayment_date:
+			frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date)))
+
 	def validate_amount(self):
 		precision = cint(frappe.db.get_default("currency_precision")) or 2
 
@@ -265,6 +274,10 @@ def make_gl_entries(self, cancel=0, adv_adj=0):
 		if gle_map:
 			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
 
+	# def repost_future_loan_interest_accruals(self):
+	# 	future_lias = frappe.db.get_all("Loan Interest Accrual", {"docstatus": 1, "posting_date": (">", self.posting_date)})
+	# 	if future_lias:
+
 def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
 	payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None):
 
@@ -284,8 +297,7 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
 
 	return lr
 
-def get_accrued_interest_entries(against_loan):
-
+def get_accrued_interest_entries(against_loan, posting_date):
 	unpaid_accrued_entries = frappe.db.sql(
 		"""
 			SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount,
@@ -295,12 +307,13 @@ def get_accrued_interest_entries(against_loan):
 				`tabLoan Interest Accrual`
 			WHERE
 				loan = %s
+			AND posting_date <= %s
 			AND (interest_amount - paid_interest_amount > 0 OR
 				payable_principal_amount - paid_principal_amount > 0)
 			AND
 				docstatus = 1
 			ORDER BY posting_date
-		""", (against_loan), as_dict=1)
+		""", (against_loan, posting_date), as_dict=1)
 
 	return unpaid_accrued_entries
 
diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
index 2f4fe2494564..3d0708121523 100644
--- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
+++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
@@ -70,7 +70,9 @@
   {
    "fieldname": "loan_repayment_entry",
    "fieldtype": "Link",
+   "hidden": 1,
    "label": "Loan Repayment Entry",
+   "no_copy": 1,
    "options": "Loan Repayment",
    "read_only": 1
   },
@@ -83,9 +85,10 @@
    "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-04-16 13:17:04.798335",
+ "modified": "2021-03-14 20:47:11.725818",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Salary Slip Loan",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 595d6974fd59..a7e53cc5aaf0 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -1050,7 +1050,7 @@ def make_loan_repayment_entry(self):
 			repayment_entry.save()
 			repayment_entry.submit()
 
-			loan.loan_repayment_entry = repayment_entry.name
+			frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
 
 	def cancel_loan_repayment_entry(self):
 		for loan in self.loans:


Code review comments:
diff_hunk: @@ -30,6 +31,7 @@ def before_submit(self):
 	def on_submit(self):
 		self.update_paid_amount()
 		self.make_gl_entries()
+		#self.repost_future_loan_interest_accruals()
path: erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
text: @user1:
Commented code.

diff_hunk: @@ -265,6 +274,10 @@ def make_gl_entries(self, cancel=0, adv_adj=0):
 		if gle_map:
 			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
 
+	# def repost_future_loan_interest_accruals(self):
+	# 	future_lias = frappe.db.get_all("Loan Interest Accrual", {"docstatus": 1, "posting_date": (">", self.posting_date)})
+	# 	if future_lias:
+
path: erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
text: @user1:
Please remove commented code.

@author:
I just added this to check for test cases, this PR is not complete yet, will uncomment and complete those functions. Added a dont-merge lable