From 03d75ae4aa8eae1ee3edffc91ba3fe4d9f3041a7 Mon Sep 17 00:00:00 2001 From: nareshkannasln Date: Mon, 25 May 2026 22:36:07 +0530 Subject: [PATCH 1/5] feat: add partially paid status in employee advance --- .../src/components/EmployeeAdvanceItem.vue | 1 + frontend/src/views/employee_advance/List.vue | 3 ++- hrms/api/__init__.py | 2 +- .../employee_advance/employee_advance.js | 4 ++- .../employee_advance/employee_advance.json | 6 ++++- .../employee_advance/employee_advance.py | 14 ++++++++-- .../employee_advance/test_employee_advance.py | 26 ++++++++++++++----- .../employee_advance_summary.js | 2 +- hrms/patches.txt | 1 + ..._partially_paid_employee_advance_status.py | 17 ++++++++++++ hrms/public/js/erpnext/payment_entry.js | 9 ++++--- 11 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 hrms/patches/v16_0/update_partially_paid_employee_advance_status.py diff --git a/frontend/src/components/EmployeeAdvanceItem.vue b/frontend/src/components/EmployeeAdvanceItem.vue index 5349601c58..8578bb263d 100644 --- a/frontend/src/components/EmployeeAdvanceItem.vue +++ b/frontend/src/components/EmployeeAdvanceItem.vue @@ -60,6 +60,7 @@ const props = defineProps({ const colorMap = { Paid: "green", + "Partially Paid": "orange", Unpaid: "orange", Claimed: "blue", Returned: "gray", diff --git a/frontend/src/views/employee_advance/List.vue b/frontend/src/views/employee_advance/List.vue index fdb30ce774..9a370eb0b4 100644 --- a/frontend/src/views/employee_advance/List.vue +++ b/frontend/src/views/employee_advance/List.vue @@ -34,12 +34,13 @@ const EMPLOYEE_ADVANCE_FIELDS = [ const STATUS_FILTER_OPTIONS = [ "Draft", "Paid", + "Partially Paid", "Unpaid", "Claimed", "Returned", "Partly Claimed and Returned", "Cancelled", -] // __("Draft"), __("Paid"), __("Unpaid"), __("Claimed"), __("Returned"), __("Partly Claimed and Returned"), __("Cancelled") +] // __("Draft"), __("Paid"), __("Partially Paid"), __("Unpaid"), __("Claimed"), __("Returned"), __("Partly Claimed and Returned"), __("Cancelled") const FILTER_CONFIG = [ { fieldname: "status", diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index f20af0a265..6ea7f1bf70 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -660,7 +660,7 @@ def get_employee_advance_balance() -> list[dict]: & (Advance.paid_amount) & (Advance.employee == employee) # don't need claimed & returned advances, only partly or completely paid ones - & (Advance.status.isin(["Paid", "Unpaid"])) + & (Advance.status.isin(["Paid", "Partially Paid", "Unpaid"])) ) .orderby(Advance.posting_date, order=Order.desc) ).run(as_dict=True) diff --git a/hrms/hr/doctype/employee_advance/employee_advance.js b/hrms/hr/doctype/employee_advance/employee_advance.js index 07408a1b67..86b461f70c 100644 --- a/hrms/hr/doctype/employee_advance/employee_advance.js +++ b/hrms/hr/doctype/employee_advance/employee_advance.js @@ -41,7 +41,9 @@ frappe.ui.form.on("Employee Advance", { }, __("Create"), ); - } else if ( + } + + if ( frm.doc.docstatus === 1 && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) && frappe.model.can_create("Expense Claim") diff --git a/hrms/hr/doctype/employee_advance/employee_advance.json b/hrms/hr/doctype/employee_advance/employee_advance.json index 504d7bdb99..b41148e4d7 100644 --- a/hrms/hr/doctype/employee_advance/employee_advance.json +++ b/hrms/hr/doctype/employee_advance/employee_advance.json @@ -122,7 +122,7 @@ "fieldtype": "Select", "label": "Status", "no_copy": 1, - "options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled", + "options": "Draft\nPaid\nPartially Paid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled", "read_only": 1 }, { @@ -291,6 +291,10 @@ "color": "Green", "title": "Paid" }, + { + "color": "Blue", + "title": "Partially Paid" + }, { "color": "Orange", "title": "Unpaid" diff --git a/hrms/hr/doctype/employee_advance/employee_advance.py b/hrms/hr/doctype/employee_advance/employee_advance.py index b382af2472..fe5d792a64 100644 --- a/hrms/hr/doctype/employee_advance/employee_advance.py +++ b/hrms/hr/doctype/employee_advance/employee_advance.py @@ -47,7 +47,14 @@ class EmployeeAdvance(Document): repay_unclaimed_amount_from_salary: DF.Check return_amount: DF.Currency status: DF.Literal[ - "Draft", "Paid", "Unpaid", "Claimed", "Returned", "Partly Claimed and Returned", "Cancelled" + "Draft", + "Paid", + "Partially Paid", + "Unpaid", + "Claimed", + "Returned", + "Partly Claimed and Returned", + "Cancelled", ] # end: auto-generated types @@ -154,6 +161,8 @@ def set_status(self, update=False): or (self.paid_amount and self.repay_unclaimed_amount_from_salary) ): status = "Paid" + elif flt(self.paid_amount) > 0: + status = "Partially Paid" else: status = "Unpaid" elif self.docstatus == 2: @@ -233,6 +242,7 @@ def set_total_advance_paid(self): self.db_set("paid_amount", paid_amount) self.db_set("return_amount", return_amount) self.set_status(update=True) + self.set_pending_amount() base_paid_amount = aple_paid_amount.get("base_paid_amount") or 0 self.db_set("base_paid_amount", base_paid_amount) @@ -266,7 +276,7 @@ def set_pending_amount(self): (Advance.employee == self.employee) & (Advance.docstatus == 1) & (Advance.posting_date <= self.posting_date) - & (Advance.status == "Unpaid") + & (Advance.status.isin(["Unpaid", "Partially Paid"])) ) ).run()[0][0] or 0.0 diff --git a/hrms/hr/doctype/employee_advance/test_employee_advance.py b/hrms/hr/doctype/employee_advance/test_employee_advance.py index b2a04145b2..dd3b0bc101 100644 --- a/hrms/hr/doctype/employee_advance/test_employee_advance.py +++ b/hrms/hr/doctype/employee_advance/test_employee_advance.py @@ -14,7 +14,7 @@ get_same_currency_bank_cash_account, make_return_entry, ) -from hrms.hr.doctype.expense_claim.expense_claim import get_advances +from hrms.hr.doctype.expense_claim.expense_claim import get_advances, get_expense_claim from hrms.hr.doctype.expense_claim.test_expense_claim import ( get_payable_account, make_expense_claim, @@ -206,7 +206,7 @@ def test_payment_entry_against_advance(self): pe = make_payment_entry(advance, 700) advance.reload() - self.assertEqual(advance.status, "Unpaid") + self.assertEqual(advance.status, "Partially Paid") self.assertEqual(advance.paid_amount, 700) pe = make_payment_entry(advance, 300) @@ -216,9 +216,23 @@ def test_payment_entry_against_advance(self): pe.cancel() advance.reload() - self.assertEqual(advance.status, "Unpaid") + self.assertEqual(advance.status, "Partially Paid") self.assertEqual(advance.paid_amount, 700) + def test_expense_claim_against_partially_paid_advance(self): + employee_name = make_employee("_T@employee.advance", "_Test Company") + advance = make_employee_advance(employee_name) + make_payment_entry(advance, 700) + advance.reload() + + self.assertEqual(advance.status, "Partially Paid") + + claim = get_expense_claim(advance.name, payment_via_journal_entry=0) + self.assertEqual(len(claim.advances), 1) + self.assertEqual(claim.advances[0].employee_advance, advance.name) + self.assertEqual(claim.advances[0].advance_paid, 700) + self.assertEqual(claim.advances[0].allocated_amount, 700) + def test_precision(self): employee_name = make_employee("_T@employee.advance", "_Test Company") advance = make_employee_advance(employee_name) @@ -261,6 +275,8 @@ def test_pending_amount(self): advance1 = make_employee_advance(employee_name) make_payment_entry(advance1, 500) + advance1.reload() + self.assertEqual(advance1.status, "Partially Paid") advance2 = make_employee_advance(employee_name) # 1000 - 500 @@ -412,12 +428,10 @@ def manual_journal_entry_for_advance(advance) -> dict: def make_payment_entry(advance, amount=None): from hrms.overrides.employee_payment_entry import get_payment_entry_for_employee - payment_entry = get_payment_entry_for_employee(advance.doctype, advance.name) + payment_entry = get_payment_entry_for_employee(advance.doctype, advance.name, party_amount=amount) payment_entry.reference_no = "1" payment_entry.reference_date = nowdate() - if amount: - payment_entry.references[0].allocated_amount = amount payment_entry.submit() return payment_entry diff --git a/hrms/hr/report/employee_advance_summary/employee_advance_summary.js b/hrms/hr/report/employee_advance_summary/employee_advance_summary.js index 75b783143a..a55a1dd1af 100644 --- a/hrms/hr/report/employee_advance_summary/employee_advance_summary.js +++ b/hrms/hr/report/employee_advance_summary/employee_advance_summary.js @@ -36,7 +36,7 @@ frappe.query_reports["Employee Advance Summary"] = { fieldname: "status", label: __("Status"), fieldtype: "Select", - options: "\nDraft\nPaid\nUnpaid\nClaimed\nCancelled", + options: "\nDraft\nPaid\nPartially Paid\nUnpaid\nClaimed\nCancelled", }, ], }; diff --git a/hrms/patches.txt b/hrms/patches.txt index 9426844f12..ea0d9ec166 100644 --- a/hrms/patches.txt +++ b/hrms/patches.txt @@ -47,3 +47,4 @@ hrms.patches.v16_0.set_currency_and_base_fields_in_expense_claim hrms.patches.v16_0.remove_ess_user_type_limit hrms.patches.v16_0.add_default_hr_role_permissions #2026-05-28 hrms.patches.v16_0.set_reference_fields_in_expense_claim_advance +hrms.patches.v16_0.update_partially_paid_employee_advance_status diff --git a/hrms/patches/v16_0/update_partially_paid_employee_advance_status.py b/hrms/patches/v16_0/update_partially_paid_employee_advance_status.py new file mode 100644 index 0000000000..094e742d63 --- /dev/null +++ b/hrms/patches/v16_0/update_partially_paid_employee_advance_status.py @@ -0,0 +1,17 @@ +import frappe + + +def execute(): + frappe.reload_doc("hr", "doctype", "employee_advance") + + advance = frappe.qb.DocType("Employee Advance") + ( + frappe.qb.update(advance) + .set(advance.status, "Partially Paid") + .where( + (advance.docstatus == 1) + & (advance.paid_amount > 0) + & (advance.paid_amount < advance.advance_amount) + & (advance.status == "Unpaid") + ) + ).run() diff --git a/hrms/public/js/erpnext/payment_entry.js b/hrms/public/js/erpnext/payment_entry.js index 35fd82b673..996f0f8af4 100644 --- a/hrms/public/js/erpnext/payment_entry.js +++ b/hrms/public/js/erpnext/payment_entry.js @@ -47,10 +47,11 @@ frappe.ui.form.on("Payment Entry", { filters["is_paid"] = 0; } - if ( - child.reference_doctype == "Employee Advance" || - child.reference_doctype == "Leave Encashment" - ) { + if (child.reference_doctype == "Employee Advance") { + filters["status"] = ["in", ["Unpaid", "Partially Paid"]]; + } + + if (child.reference_doctype == "Leave Encashment") { filters["status"] = "Unpaid"; } From ba72088f5fb08aed73f9f15749ec23a856e8cf62 Mon Sep 17 00:00:00 2001 From: nareshkannasln Date: Tue, 26 May 2026 12:19:10 +0530 Subject: [PATCH 2/5] chore: remove unwanted argument --- hrms/hr/doctype/employee_advance/test_employee_advance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/hr/doctype/employee_advance/test_employee_advance.py b/hrms/hr/doctype/employee_advance/test_employee_advance.py index dd3b0bc101..faf12d7a21 100644 --- a/hrms/hr/doctype/employee_advance/test_employee_advance.py +++ b/hrms/hr/doctype/employee_advance/test_employee_advance.py @@ -227,7 +227,7 @@ def test_expense_claim_against_partially_paid_advance(self): self.assertEqual(advance.status, "Partially Paid") - claim = get_expense_claim(advance.name, payment_via_journal_entry=0) + claim = get_expense_claim(advance.name) self.assertEqual(len(claim.advances), 1) self.assertEqual(claim.advances[0].employee_advance, advance.name) self.assertEqual(claim.advances[0].advance_paid, 700) From 1c11337ca23fdca156b40920f3dbb13668cff683 Mon Sep 17 00:00:00 2001 From: nareshkannasln Date: Tue, 26 May 2026 12:25:06 +0530 Subject: [PATCH 3/5] fix: update pending amount in employee advance --- hrms/hr/doctype/employee_advance/employee_advance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hrms/hr/doctype/employee_advance/employee_advance.py b/hrms/hr/doctype/employee_advance/employee_advance.py index fe5d792a64..859ccefb7c 100644 --- a/hrms/hr/doctype/employee_advance/employee_advance.py +++ b/hrms/hr/doctype/employee_advance/employee_advance.py @@ -243,6 +243,7 @@ def set_total_advance_paid(self): self.db_set("return_amount", return_amount) self.set_status(update=True) self.set_pending_amount() + self.db_set("pending_amount", self.pending_amount) base_paid_amount = aple_paid_amount.get("base_paid_amount") or 0 self.db_set("base_paid_amount", base_paid_amount) From 55a2a7a1e490a143b7e3dfe7ba9f834afa3e138c Mon Sep 17 00:00:00 2001 From: nareshkannasln Date: Wed, 10 Jun 2026 13:02:28 +0530 Subject: [PATCH 4/5] fix: update partially paid status, color and pending amount handling --- frontend/src/components/EmployeeAdvanceItem.vue | 2 +- .../employee_advance/employee_advance.py | 17 ++++++++++------- .../employee_advance/test_employee_advance.py | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/EmployeeAdvanceItem.vue b/frontend/src/components/EmployeeAdvanceItem.vue index 8578bb263d..7c60e45f73 100644 --- a/frontend/src/components/EmployeeAdvanceItem.vue +++ b/frontend/src/components/EmployeeAdvanceItem.vue @@ -60,7 +60,7 @@ const props = defineProps({ const colorMap = { Paid: "green", - "Partially Paid": "orange", + "Partially Paid": "blue", Unpaid: "orange", Claimed: "blue", Returned: "gray", diff --git a/hrms/hr/doctype/employee_advance/employee_advance.py b/hrms/hr/doctype/employee_advance/employee_advance.py index 859ccefb7c..9890a15c52 100644 --- a/hrms/hr/doctype/employee_advance/employee_advance.py +++ b/hrms/hr/doctype/employee_advance/employee_advance.py @@ -156,9 +156,8 @@ def set_status(self, update=False): 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) - or (self.paid_amount and self.repay_unclaimed_amount_from_salary) + elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt( + self.paid_amount, precision ): status = "Paid" elif flt(self.paid_amount) > 0: @@ -242,8 +241,7 @@ def set_total_advance_paid(self): self.db_set("paid_amount", paid_amount) self.db_set("return_amount", return_amount) self.set_status(update=True) - self.set_pending_amount() - self.db_set("pending_amount", self.pending_amount) + self.set_pending_amount(update=True) base_paid_amount = aple_paid_amount.get("base_paid_amount") or 0 self.db_set("base_paid_amount", base_paid_amount) @@ -268,9 +266,9 @@ def update_claimed_amount(self): self.reload() self.set_status(update=True) - def set_pending_amount(self): + def set_pending_amount(self, update=False): Advance = frappe.qb.DocType("Employee Advance") - self.pending_amount = ( + pending_amount = ( frappe.qb.from_(Advance) .select(Sum(Advance.advance_amount - Advance.paid_amount)) .where( @@ -281,6 +279,11 @@ def set_pending_amount(self): ) ).run()[0][0] or 0.0 + if update: + self.db_set("pending_amount", pending_amount) + else: + self.pending_amount = pending_amount + def check_linked_payment_entry(self): from erpnext.accounts.utils import ( remove_ref_doc_link_from_pe, diff --git a/hrms/hr/doctype/employee_advance/test_employee_advance.py b/hrms/hr/doctype/employee_advance/test_employee_advance.py index faf12d7a21..9b763abcda 100644 --- a/hrms/hr/doctype/employee_advance/test_employee_advance.py +++ b/hrms/hr/doctype/employee_advance/test_employee_advance.py @@ -228,6 +228,8 @@ def test_expense_claim_against_partially_paid_advance(self): self.assertEqual(advance.status, "Partially Paid") claim = get_expense_claim(advance.name) + claim.save() + self.assertEqual(len(claim.advances), 1) self.assertEqual(claim.advances[0].employee_advance, advance.name) self.assertEqual(claim.advances[0].advance_paid, 700) From e73f20524c234e2c8e4b27c5d914cb55a7f1cc80 Mon Sep 17 00:00:00 2001 From: nareshkannasln Date: Thu, 11 Jun 2026 17:04:51 +0530 Subject: [PATCH 5/5] chore: update partial advance claim test to submit before assertion --- .../employee_advance/test_employee_advance.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/hrms/hr/doctype/employee_advance/test_employee_advance.py b/hrms/hr/doctype/employee_advance/test_employee_advance.py index 9b763abcda..02d8f5ba5d 100644 --- a/hrms/hr/doctype/employee_advance/test_employee_advance.py +++ b/hrms/hr/doctype/employee_advance/test_employee_advance.py @@ -227,13 +227,41 @@ def test_expense_claim_against_partially_paid_advance(self): self.assertEqual(advance.status, "Partially Paid") - claim = get_expense_claim(advance.name) + currency, cost_center = frappe.db.get_value( + "Company", "_Test Company", ["default_currency", "cost_center"] + ) + claim = get_expense_claim(advance.name) # create claim from employee advance form + claim.update( + { + "payable_account": get_payable_account("_Test Company"), + "currency": currency, + "exchange_rate": 1, + "approval_status": "Approved", + } + ) + claim.append( + "expenses", + { + "expense_type": "Travel", + "default_account": "Travel Expenses - _TC", + "amount": 1000, + "sanctioned_amount": 1000, + "cost_center": cost_center, + }, + ) claim.save() + claim.submit() self.assertEqual(len(claim.advances), 1) self.assertEqual(claim.advances[0].employee_advance, advance.name) self.assertEqual(claim.advances[0].advance_paid, 700) self.assertEqual(claim.advances[0].allocated_amount, 700) + self.assertEqual(claim.total_claimed_amount, 1000) + self.assertEqual(claim.grand_total, 300) + + advance.reload() + self.assertEqual(advance.claimed_amount, 700) + self.assertEqual(advance.status, "Claimed") def test_precision(self): employee_name = make_employee("_T@employee.advance", "_Test Company")