Merge pull request #29335 from ruchamahabal/emp-adv-status

diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 0475453..b050183 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -2,7 +2,7 @@
  "actions": [],
  "allow_import": 1,
  "autoname": "naming_series:",
- "creation": "2017-10-09 14:26:29.612365",
+ "creation": "2022-01-17 18:36:51.450395",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -121,7 +121,7 @@
    "fieldtype": "Select",
    "label": "Status",
    "no_copy": 1,
-   "options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled",
+   "options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled",
    "read_only": 1
   },
   {
@@ -200,7 +200,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-11 18:38:38.617478",
+ "modified": "2022-01-17 19:33:52.345823",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Advance",
@@ -237,5 +237,41 @@
  "search_fields": "employee,employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [
+  {
+   "color": "Red",
+   "custom": 1,
+   "title": "Draft"
+  },
+  {
+   "color": "Green",
+   "custom": 1,
+   "title": "Paid"
+  },
+  {
+   "color": "Orange",
+   "custom": 1,
+   "title": "Unpaid"
+  },
+  {
+   "color": "Blue",
+   "custom": 1,
+   "title": "Claimed"
+  },
+  {
+   "color": "Gray",
+   "title": "Returned"
+  },
+  {
+   "color": "Yellow",
+   "title": "Partly Claimed and Returned"
+  },
+  {
+   "color": "Red",
+   "custom": 1,
+   "title": "Cancelled"
+  }
+ ],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 7aac2b6..79d389d 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -27,19 +27,33 @@
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ('GL Entry')
+		self.set_status(update=True)
 
-	def set_status(self):
+	def set_status(self, update=False):
+		precision = self.precision("paid_amount")
+		total_amount = flt(flt(self.claimed_amount) + flt(self.return_amount), precision)
+		status = None
+
 		if self.docstatus == 0:
-			self.status = "Draft"
-		if self.docstatus == 1:
-			if self.claimed_amount and flt(self.claimed_amount) == flt(self.paid_amount):
-				self.status = "Claimed"
-			elif self.paid_amount and self.advance_amount == flt(self.paid_amount):
-				self.status = "Paid"
+			status = "Draft"
+		elif self.docstatus == 1:
+			if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(self.paid_amount, precision):
+				status = "Claimed"
+			elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(self.paid_amount, precision):
+				status = "Returned"
+			elif flt(self.claimed_amount) > 0 and (flt(self.return_amount) > 0) and total_amount == flt(self.paid_amount, precision):
+				status = "Partly Claimed and Returned"
+			elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(self.paid_amount, precision):
+				status = "Paid"
 			else:
-				self.status = "Unpaid"
+				status = "Unpaid"
 		elif self.docstatus == 2:
-			self.status = "Cancelled"
+			status = "Cancelled"
+
+		if update:
+			self.db_set("status", status)
+		else:
+			self.status = status
 
 	def set_total_advance_paid(self):
 		gle = frappe.qb.DocType("GL Entry")
@@ -85,9 +99,7 @@
 
 		self.db_set("paid_amount", paid_amount)
 		self.db_set("return_amount", return_amount)
-		self.set_status()
-		frappe.db.set_value("Employee Advance", self.name , "status", self.status)
-
+		self.set_status(update=True)
 
 	def update_claimed_amount(self):
 		claimed_amount = frappe.db.sql("""
@@ -103,8 +115,8 @@
 
 		frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
 		self.reload()
-		self.set_status()
-		frappe.db.set_value("Employee Advance", self.name, "status", self.status)
+		self.set_status(update=True)
+
 
 @frappe.whitelist()
 def get_pending_amount(employee, posting_date):
@@ -222,7 +234,8 @@
 		'reference_name': employee_advance_name,
 		'party_type': 'Employee',
 		'party': employee,
-		'is_advance': 'Yes'
+		'is_advance': 'Yes',
+		'cost_center': erpnext.get_default_cost_center(company)
 	})
 
 	bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
@@ -233,7 +246,8 @@
 		"debit_in_account_currency": bank_amount,
 		"account_currency": bank_cash_account.account_currency,
 		"account_type": bank_cash_account.account_type,
-		"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
+		"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
+		"cost_center": erpnext.get_default_cost_center(company)
 	})
 
 	return je.as_dict()
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 5f2e720..e3c1487 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -4,7 +4,7 @@
 import unittest
 
 import frappe
-from frappe.utils import nowdate
+from frappe.utils import flt, nowdate
 
 import erpnext
 from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -12,12 +12,21 @@
 	EmployeeAdvanceOverPayment,
 	create_return_through_additional_salary,
 	make_bank_entry,
+	make_return_entry,
+)
+from erpnext.hr.doctype.expense_claim.expense_claim import get_advances
+from erpnext.hr.doctype.expense_claim.test_expense_claim import (
+	get_payable_account,
+	make_expense_claim,
 )
 from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
 from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
 
 class TestEmployeeAdvance(unittest.TestCase):
+	def setUp(self):
+		frappe.db.delete("Employee Advance")
+
 	def test_paid_amount_and_status(self):
 		employee_name = make_employee("_T@employe.advance")
 		advance = make_employee_advance(employee_name)
@@ -52,9 +61,102 @@
 		self.assertEqual(advance.paid_amount, 0)
 		self.assertEqual(advance.status, "Unpaid")
 
+		advance.cancel()
+		advance.reload()
+		self.assertEqual(advance.status, "Cancelled")
+
+	def test_claimed_status(self):
+		# CLAIMED Status check, full amount claimed
+		payable_account = get_payable_account("_Test Company")
+		claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+
+		advance = make_employee_advance(claim.employee)
+		pe = make_payment_entry(advance)
+		pe.submit()
+
+		claim = get_advances_for_claim(claim, advance.name)
+		claim.save()
+		claim.submit()
+
+		advance.reload()
+		self.assertEqual(advance.claimed_amount, 1000)
+		self.assertEqual(advance.status, "Claimed")
+
+		# advance should not be shown in claims
+		advances = get_advances(claim.employee)
+		advances = [entry.name for entry in advances]
+		self.assertTrue(advance.name not in advances)
+
+		# cancel claim; status should be Paid
+		claim.cancel()
+		advance.reload()
+		self.assertEqual(advance.claimed_amount, 0)
+		self.assertEqual(advance.status, "Paid")
+
+	def test_partly_claimed_and_returned_status(self):
+		payable_account = get_payable_account("_Test Company")
+		claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+
+		advance = make_employee_advance(claim.employee)
+		pe = make_payment_entry(advance)
+		pe.submit()
+
+		# PARTLY CLAIMED AND RETURNED status check
+		# 500 Claimed, 500 Returned
+		claim = make_expense_claim(payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+
+		advance = make_employee_advance(claim.employee)
+		pe = make_payment_entry(advance)
+		pe.submit()
+
+		claim = get_advances_for_claim(claim, advance.name, amount=500)
+		claim.save()
+		claim.submit()
+
+		advance.reload()
+		self.assertEqual(advance.claimed_amount, 500)
+		self.assertEqual(advance.status, "Paid")
+
+		entry = make_return_entry(
+			employee=advance.employee,
+			company=advance.company,
+			employee_advance_name=advance.name,
+			return_amount=flt(advance.paid_amount - advance.claimed_amount),
+			advance_account=advance.advance_account,
+			mode_of_payment=advance.mode_of_payment,
+			currency=advance.currency,
+			exchange_rate=advance.exchange_rate
+		)
+
+		entry = frappe.get_doc(entry)
+		entry.insert()
+		entry.submit()
+
+		advance.reload()
+		self.assertEqual(advance.return_amount, 500)
+		self.assertEqual(advance.status, "Partly Claimed and Returned")
+
+		# advance should not be shown in claims
+		advances = get_advances(claim.employee)
+		advances = [entry.name for entry in advances]
+		self.assertTrue(advance.name not in advances)
+
+		# Cancel return entry; status should change to PAID
+		entry.cancel()
+		advance.reload()
+		self.assertEqual(advance.return_amount, 0)
+		self.assertEqual(advance.status, "Paid")
+
+		# advance should be shown in claims
+		advances = get_advances(claim.employee)
+		advances = [entry.name for entry in advances]
+		self.assertTrue(advance.name in advances)
+
 	def test_repay_unclaimed_amount_from_salary(self):
 		employee_name = make_employee("_T@employe.advance")
 		advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
+		pe = make_payment_entry(advance)
+		pe.submit()
 
 		args = {"type": "Deduction"}
 		create_salary_component("Advance Salary - Deduction", **args)
@@ -82,11 +184,13 @@
 
 		advance.reload()
 		self.assertEqual(advance.return_amount, 1000)
+		self.assertEqual(advance.status, "Returned")
 
 		# update advance return amount on additional salary cancellation
 		additional_salary.cancel()
 		advance.reload()
 		self.assertEqual(advance.return_amount, 700)
+		self.assertEqual(advance.status, "Paid")
 
 	def tearDown(self):
 		frappe.db.rollback()
@@ -118,3 +222,24 @@
 	doc.submit()
 
 	return doc
+
+
+def get_advances_for_claim(claim, advance_name, amount=None):
+	advances = get_advances(claim.employee, advance_name)
+
+	for entry in advances:
+		if amount:
+			allocated_amount = amount
+		else:
+			allocated_amount = flt(entry.paid_amount) - flt(entry.claimed_amount)
+
+		claim.append("advances", {
+			"employee_advance": entry.name,
+			"posting_date": entry.posting_date,
+			"advance_account": entry.advance_account,
+			"advance_paid": entry.paid_amount,
+			"unclaimed_amount": allocated_amount,
+			"allocated_amount": allocated_amount
+		})
+
+	return claim
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 0479457..af80b63 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -171,7 +171,7 @@
 					['docstatus', '=', 1],
 					['employee', '=', frm.doc.employee],
 					['paid_amount', '>', 0],
-					['status', '!=', 'Claimed']
+					['status', 'not in', ['Claimed', 'Returned', 'Partly Claimed and Returned']]
 				]
 			};
 		});
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 7e3898b..fe04efb 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -23,10 +23,10 @@
 
 	def validate(self):
 		validate_active_employee(self.employee)
-		self.validate_advances()
+		set_employee_name(self)
 		self.validate_sanctioned_amount()
 		self.calculate_total_amount()
-		set_employee_name(self)
+		self.validate_advances()
 		self.set_expense_account(validate=True)
 		self.set_payable_account()
 		self.set_cost_center()
@@ -341,18 +341,27 @@
 
 @frappe.whitelist()
 def get_advances(employee, advance_id=None):
-	if not advance_id:
-		condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount'.format(frappe.db.escape(employee))
-	else:
-		condition = 'name={0}'.format(frappe.db.escape(advance_id))
+	advance = frappe.qb.DocType("Employee Advance")
 
-	return frappe.db.sql("""
-		select
-			name, posting_date, paid_amount, claimed_amount, advance_account
-		from
-			`tabEmployee Advance`
-		where {0}
-	""".format(condition), as_dict=1)
+	query = (
+		frappe.qb.from_(advance)
+		.select(
+			advance.name, advance.posting_date, advance.paid_amount,
+			advance.claimed_amount, advance.advance_account
+		)
+	)
+
+	if not advance_id:
+		query = query.where(
+			(advance.docstatus == 1)
+			& (advance.employee == employee)
+			& (advance.paid_amount > 0)
+			& (advance.status.notin(["Claimed", "Returned", "Partly Claimed and Returned"]))
+		)
+	else:
+		query = query.where(advance.name == advance_id)
+
+	return query.run(as_dict=True)
 
 
 @frappe.whitelist()
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 12af8f8..13f0e7b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -356,4 +356,5 @@
 erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
 erpnext.patches.v13_0.update_accounts_in_loan_docs
 erpnext.patches.v14_0.update_batch_valuation_flag
-erpnext.patches.v14_0.delete_non_profit_doctypes
\ No newline at end of file
+erpnext.patches.v14_0.delete_non_profit_doctypes
+erpnext.patches.v14_0.update_employee_advance_status
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/update_employee_advance_status.py b/erpnext/patches/v14_0/update_employee_advance_status.py
new file mode 100644
index 0000000..a20e35a
--- /dev/null
+++ b/erpnext/patches/v14_0/update_employee_advance_status.py
@@ -0,0 +1,26 @@
+import frappe
+
+
+def execute():
+	frappe.reload_doc('hr', 'doctype', 'employee_advance')
+
+	advance = frappe.qb.DocType('Employee Advance')
+	(frappe.qb
+		.update(advance)
+		.set(advance.status, 'Returned')
+		.where(
+			(advance.docstatus == 1)
+			& ((advance.return_amount) & (advance.paid_amount == advance.return_amount))
+			& (advance.status == 'Paid')
+		)
+	).run()
+
+	(frappe.qb
+		.update(advance)
+		.set(advance.status, 'Partly Claimed and Returned')
+		.where(
+			(advance.docstatus == 1)
+			& ((advance.claimed_amount & advance.return_amount) & (advance.paid_amount == (advance.return_amount + advance.claimed_amount)))
+			& (advance.status == 'Paid')
+		)
+	).run()
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index bf8bd05..d618568 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -105,6 +105,8 @@
 				return_amount += self.amount
 
 			frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount)
+			advance = frappe.get_doc("Employee Advance", self.ref_docname)
+			advance.set_status(update=True)
 
 	def update_employee_referral(self, cancel=False):
 		if self.ref_doctype == "Employee Referral":