8000
Skip to content

Payroll Entry fails to submit when a Salary Slip recovers an Employee Advance: "Against Journal Entry X is already adjusted against some other voucher" #5137

Description

@asifashi

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

  1. Create an Employee Advance for an employee, submit and pay it, with
    repay_unclaimed_amount_from_salary = 1.
  2. 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.
  3. Create a Payroll Entry covering that employee and period, create the salary slips.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    0