Information about bug
Payroll Entry fails to submit when a Salary Slip recovers an Employee Advance: "Against Journal Entry X is already adjusted against some other voucher"
Description
Since the advance-payment-ledger changes in v15, submitting a Payroll Entry fails whenever any
salary slip in the run recovers an Employee Advance (via an Additional Salary linked to the
advance).
The payroll accrual Journal Entry is rolled back, so the Journal Entry named in the error message
does not exist — which makes the message actively misleading. Users hunt for a document that
was never committed.
The same payroll configuration worked on the previous release. This is a regression.
Steps to reproduce
- Create an Employee Advance for an employee, submit and pay it, with
repay_unclaimed_amount_from_salary = 1.
- Create an Additional Salary with a Deduction component, and set
ref_doctype = Employee Advance, ref_docname = <the advance>, with payroll_date
inside the payroll period.
- Create a Payroll Entry covering that employee and period, create the salary slips.
- Click Submit Salary Slip.
Expected
Salary slips submit, the accrual Journal Entry posts, and the Employee Advance moves to
Returned (return_amount == paid_amount).
Actual
Submission fails with:
Against Journal Entry ACC-JV-2026-02014 is already adjusted against some other voucher
ACC-JV-2026-02014 does not exist anywhere in the database — the transaction rolled back and the
naming-series counter rolled back with it. Salary slips stay in draft and the Payroll Entry is
marked Failed.
Root cause
1 — HRMS builds the advance-recovery row as an advance reference.
hrms/payroll/doctype/payroll_entry/payroll_entry.py:482
payable_amount = self.get_accounting_entries_and_payable_amount(
...
entry_type="credit",
party=entry.get("employee"),
reference_type="Employee Advance",
reference_name=entry.get("reference_name"),
is_advance="Yes",
)
2 — Employee Advance is in get_advance_payment_doctypes(), so
erpnext/accounts/doctype/journal_entry/journal_entry.py:1074 rewrites the GL row to point at the
Journal Entry itself:
if d.reference_type in advance_doctypes:
row.update(
{
"against_voucher_type": self.doctype, # "Journal Entry"
"against_voucher": self.name, # the JE being created
"advance_voucher_type": d.reference_type,
"advance_voucher_no": d.reference_name,
}
)
This is correct when a Journal Entry pays an advance — the JE carries the outstanding balance.
It is wrong when payroll recovers an advance: the recovery row is the only row on the advance
account, so after the rewrite every row on that account in that JE has a non-empty
against_voucher.
3 — the balance check then finds nothing unallocated.
erpnext/accounts/doctype/gl_entry/gl_entry.py:358
elif against_voucher_type == "Journal Entry":
against_voucher_amount = flt(
frappe.db.sql(
f"""
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
and account = %s and (against_voucher is null or against_voucher='') {party_condition}""",
(against_voucher, account),
)[0][0]
)
if not against_voucher_amount:
frappe.throw(
_("Against Journal Entry {0} is already adjusted against some other voucher").format(
against_voucher
)
)
against_voucher_amount is 0, so it throws — the Journal Entry is being validated against
itself.
Secondary issue: undeclared fields on GL Entry
The same block writes advance_voucher_type and advance_voucher_no to the GL Entry row, but
erpnext/accounts/doctype/gl_entry/gl_entry.json does not declare either field:
$ grep -c advance_voucher erpnext/accounts/doctype/gl_entry/gl_entry.json
0
Only journal_entry_account.json and payment_entry_reference.json declare them. Frappe silently
drops both values, so the advance link they were meant to record is lost. This looks unintended —
either the fields should exist on GL Entry, or they should not be written there.
Suggested fix
Distinguish paying an advance from recovering one. A recovery reduces the advance
(credit on the advance account) and should keep pointing at the advance document, not at the JE:
is_advance_recovery = d.reference_type == "Employee Advance" and flt(d.credit) > 0
if d.reference_type in advance_doctypes and not is_advance_recovery:
row.update({...})
A more general form would be to base the branch on whether the row increases or decreases the
advance balance, rather than special-casing the doctype.
Workaround
Applying the guard above locally restores correct behaviour. Verified on a 38-employee payroll run:
all slips submitted, accrual JE posted, and all three Employee Advances correctly moved to
Returned with GL rows showing
against_voucher_type = Employee Advance, against_voucher = <advance>.
Impact
Any ERPNext + HRMS user who recovers employee advances through payroll cannot run payroll at all.
It blocks the entire run, not just the affected employees. Because the Journal Entry in the error
message never exists, the cause is very hard to identify from the message alone.
Versions
|
|
| ERPNext |
v15.115.0 |
| HRMS |
v15.62.1 |
| Frappe |
v15.113.4 |
| Database |
MariaDB 10.6 |
| OS |
Ubuntu 22.04 |
| Install |
bench, production (supervisor + nginx) |
The identical payroll configuration submitted successfully on the previous release; the first run
after upgrading to the versions above failed.
Module
HR
Version
Frappe version - v15.113.4
ERPNext version - v15.115.0
HRMS version - v15.62.1
Installation method
manual install
Relevant log output / Stack trace / Full Error Message.
Traceback (most recent call last):
File "apps/hrms/hrms/payroll/doctype/payroll_entry/payroll_entry.py", line 1595, in submit_salary_slips_for_employees
payroll_entry.make_accrual_jv_entry(submitted)
File "apps/hrms/hrms/payroll/doctype/payroll_entry/payroll_entry.py", line 621, in make_accrual_jv_entry
self.make_journal_entry(
File "apps/hrms/hrms/payroll/doctype/payroll_entry/payroll_entry.py", line 666, in make_journal_entry
journal_entry.submit()
File "apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py", line 196, in on_submit
self.make_gl_entries()
File "apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py", line 1109, in make_gl_entries
make_gl_entries(
File "apps/erpnext/erpnext/accounts/general_ledger.py", line 49, in make_gl_entries
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
File "apps/erpnext/erpnext/accounts/general_ledger.py", line 406, in save_entries
make_entry(entry, adv_adj, update_outstanding, from_repost)
File "apps/erpnext/erpnext/accounts/general_ledger.py", line 417, in make_entry
gle.submit()
File "apps/erpnext/erpnext/accounts/doctype/gl_entry/gl_entry.py", line 371, in update_outstanding_amt
frappe.throw(
frappe.exceptions.ValidationError: Against Journal Entry ACC-JV-2026-XXXXX is already adjusted against some other voucher
Information about bug
Payroll Entry fails to submit when a Salary Slip recovers an Employee Advance: "Against Journal Entry X is already adjusted against some other voucher"
Description
Since the advance-payment-ledger changes in v15, submitting a Payroll Entry fails whenever any
salary slip in the run recovers an Employee Advance (via an Additional Salary linked to the
advance).
The payroll accrual Journal Entry is rolled back, so the Journal Entry named in the error message
does not exist — which makes the message actively misleading. Users hunt for a document that
was never committed.
The same payroll configuration worked on the previous release. This is a regression.
Steps to reproduce
repay_unclaimed_amount_from_salary = 1.ref_doctype = Employee Advance,ref_docname = <the advance>, withpayroll_dateinside the payroll period.
Expected
Salary slips submit, the accrual Journal Entry posts, and the Employee Advance moves to
Returned (
return_amount == paid_amount).Actual
Submission fails with:
ACC-JV-2026-02014does not exist anywhere in the database — the transaction rolled back and thenaming-series counter rolled back with it. Salary slips stay in draft and the Payroll Entry is
marked
Failed.Root cause
1 — HRMS builds the advance-recovery row as an advance reference.
hrms/payroll/doctype/payroll_entry/payroll_entry.py:4822 —
Employee Advanceis inget_advance_payment_doctypes(), soerpnext/accounts/doctype/journal_entry/journal_entry.py:1074rewrites the GL row to point at theJournal Entry itself:
This is correct when a Journal Entry pays an advance — the JE carries the outstanding balance.
It is wrong when payroll recovers an advance: the recovery row is the only row on the advance
account, so after the rewrite every row on that account in that JE has a non-empty
against_voucher.3 — the balance check then finds nothing unallocated.
erpnext/accounts/doctype/gl_entry/gl_entry.py:358against_voucher_amountis0, so it throws — the Journal Entry is being validated againstitself.
Secondary issue: undeclared fields on GL Entry
The same block writes
advance_voucher_typeandadvance_voucher_noto the GL Entry row, buterpnext/accounts/doctype/gl_entry/gl_entry.jsondoes not declare either field:Only
journal_entry_account.jsonandpayment_entry_reference.jsondeclare them. Frappe silentlydrops both values, so the advance link they were meant to record is lost. This looks unintended —
either the fields should exist on GL Entry, or they should not be written there.
Suggested fix
Distinguish paying an advance from recovering one. A recovery reduces the advance
(credit on the advance account) and should keep pointing at the advance document, not at the JE:
A more general form would be to base the branch on whether the row increases or decreases the
advance balance, rather than special-casing the doctype.
Workaround
Applying the guard above locally restores correct behaviour. Verified on a 38-employee payroll run:
all slips submitted, accrual JE posted, and all three Employee Advances correctly moved to
Returned with GL rows showing
against_voucher_type = Employee Advance,against_voucher = <advance>.Impact
Any ERPNext + HRMS user who recovers employee advances through payroll cannot run payroll at all.
It blocks the entire run, not just the affected employees. Because the Journal Entry in the error
message never exists, the cause is very hard to identify from the message alone.
Versions
The identical payroll configuration submitted successfully on the previous release; the first run
after upgrading to the versions above failed.
Module
HR
Version
Frappe version - v15.113.4
ERPNext version - v15.115.0
HRMS version - v15.62.1
Installation method
manual install
Relevant log output / Stack trace / Full Error Message.