# Understanding the Payment Ledger in ERPNext and Troubleshooting Reconciliation Issues

> Master ERPNext's Payment Ledger. Learn to troubleshoot reconciliation issues by checking delinked entries, filters, and using the Repost utility for accurate financial data. Resolve outstanding balances efficiently.

- Repository: [Frappe/erpnext](https://github.com/frappe/erpnext)
- Tags: how-to-guide
- Published: 2026-05-20

---

**The Payment Ledger is a financial report that aggregates Payment Ledger Entry (PLE) records to display payment-related postings and outstanding balances per voucher or party, while reconciliation issues are typically resolved by verifying the `delinked` status of entries, validating filter configurations, and using the Repost Payment Ledger utility to regenerate data from source documents.**

The **Payment Ledger** in ERPNext provides a granular view of payment-related financial postings by querying the Payment Ledger Entry DocType within the `frappe/erpnext` repository. Unlike the General Ledger, this report specifically focuses on voucher-level payment movements, grouping transactions by party or against specific vouchers to calculate outstanding balances. When amounts appear incorrect or transactions seem absent, understanding the underlying architecture in [`erpnext/accounts/report/payment_ledger/payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/report/payment_ledger/payment_ledger.py) is essential for effective troubleshooting.

## What Is the Payment Ledger?

The **Payment Ledger** is a specialized financial report that displays every payment-related posting—both debits and credits—for a given set of vouchers. It aggregates rows from the **Payment Ledger Entry** DocType and groups them either by the original voucher or by party, depending on the **Group Party** filter setting. The report calculates an "Outstanding" balance row by summing all amounts within each group, providing a clear view of remaining financial obligations.

This ledger differs from the General Ledger by focusing exclusively on payment-type documents such as Payment Entries and Journal Entries, maintaining a dedicated table that tracks the relationship between payments and the vouchers they reference.

## Payment Ledger Architecture and Key Components

Understanding the three core components of the Payment Ledger system is critical for diagnosing reconciliation failures.

### Payment Ledger Entry (PLE) DocType

The **Payment Ledger Entry** (PLE) is the foundational single-row table defined in [`erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py). Each record stores the posting date, account, party, voucher details, amount, and optionally the amount in the account currency. When a payment-type document is submitted, the system creates corresponding PLE rows that serve as the immutable source of truth for the Payment Ledger report.

A critical field in this DocType is **`delinked`**, which indicates whether a payment has been removed from its associated voucher (set to `1`) or remains active (set to `0`). The report explicitly filters out delinked entries when calculating voucher amounts.

### Report Execution and Grouping Logic

The report class located in [`erpnext/accounts/report/payment_ledger/payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/report/payment_ledger/payment_ledger.py) handles data retrieval and presentation. The **`execute`** function serves as the entry point, returning a tuple of column definitions and prepared data rows.

Key methods include:

- **`build_conditions`**: Transforms user filters (company, account, period, party, voucher numbers) into query-builder criteria for SQL generation.
- **`init_voucher_dict`**: Creates an ordered dictionary keyed by `(against_voucher_type, against_voucher_no, party)` or by `(party_type, party)` when **Group Party** is enabled. Positive amounts populate the `"increase"` list while negatives populate `"decrease"`.
- **`build_data`**: Merges the increase and decrease lists, calculates the running total for the **Outstanding** balance row, and appends spacer rows for readability.

### The Repost Payment Ledger Tool

Reconciliation is not performed automatically by the ledger itself; instead, the **Repost Payment Ledger** tool in [`erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.py) handles regeneration of PLE records. This utility deletes existing Payment Ledger Entry rows for specified vouchers and recreates them from the source documents. It is essential after cancellations, amendments, or when unlinking payments from invoices.

## Common Reconciliation Issues

Reconciliation problems in the Payment Ledger typically manifest as missing data or incorrect balances, with root causes traceable to specific code behaviors:

- **Missing rows for a voucher**: Occurs when `delinked = 1` in the Payment Ledger Entry table, often after a payment entry cancellation. The `voucher_amount` query in [`payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/payment_ledger.py) (lines 31‑35) filters for `ple.delinked == 0`, excluding these records from the report.
- **Incorrect outstanding balance**: The balance row calculation in `build_data` (lines 70‑80) sums all amounts in the group. If a PLE contains a zero value or incorrect amount due to validation failures during original transaction processing, the resulting balance will be inaccurate.
- **Currency mismatch**: When the **Include Account Currency** flag is enabled but PLE rows lack the `amount_in_account_currency` field, the report displays zero values. The `init_voucher_dict` method only includes this field when the flag is set (line 52).
- **Duplicate rows after cancellation**: If the **Repost Payment Ledger** tool was not executed after cancelling a voucher, "old" rows persist with `delinked = 0` while new rows are created, resulting in duplicates.
- **Empty result sets**: Typos in filter values (such as company names) or date ranges excluding all records cause `build_conditions` to generate criteria that match no entries.

## Troubleshooting Payment Ledger Problems

Follow this systematic approach to resolve reconciliation discrepancies using direct database queries and utility functions.

**1. Verify Underlying PLE Records**

Check if Payment Ledger Entries exist for the specific voucher and are not delinked:

```python
ple = frappe.qb.DocType("Payment Ledger Entry")
entries = (
    frappe.qb.from_(ple)
    .select(ple.star)
    .where(ple.voucher_no == "PAY-00001")
    .where(ple.delinked == 0)
    .run(as_dict=True)
)
frappe.msgprint(str(entries))

```

If the list is empty, the payment entry never created a PLE or it was delinked and requires reposting.

**2. Validate Report Filter Values**

Open the Payment Ledger report UI and verify that **Company**, **Account**, **Period**, **Group Party**, and **Voucher** filters match your expected data. Mismatches in `build_conditions` parameters are a common source of empty reports.

**3. Rebuild the Payment Ledger**

Execute the Repost Payment Ledger function via bench command to regenerate entries:

```bash
bench --site yoursite execute erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.repost_payment_ledger

```

This invokes the server-side function that clears and recreates PLE rows from source documents.

**4. Inspect Balance Calculations**

If the **Outstanding** amount appears incorrect, add temporary logging inside `build_data` (lines 70‑82) to output the intermediate `total` variable and each `x.amount` value during execution.

**5. Run Unit Tests**

Verify ledger integrity using the built-in test suite:

```bash
bench run-tests --module erpnext.accounts.report.payment_ledger.test_payment_ledger

```

This module covers grouping logic, balance calculation, and currency handling; failing tests indicate regressions in the core logic.

## Practical Code Examples for Diagnostics

These scripts provide deeper visibility into Payment Ledger data for advanced troubleshooting.

### Querying Raw Payment Ledger Entries

To examine the raw data feeding the report, including the `delinked` status and account currency amounts:

```python
import frappe

ple = frappe.qb.DocType("Payment Ledger Entry")
entries = (
    frappe.qb.from_(ple)
    .select(ple.name, ple.voucher_no, ple.amount, 
            ple.amount_in_account_currency, ple.delinked)
    .where(ple.party == "Customer-001")
    .where(ple.delinked == 0)
    .run(as_dict=True)
)

for entry in entries:
    frappe.msgprint(f"{entry.voucher_no}: {entry.amount}")

```

### Executing the Report Programmatically

Generate Payment Ledger data directly without the UI for custom integrations or automated checks:

```python
import frappe
from erpnext.accounts.report.payment_ledger.payment_ledger import execute

filters = {
    "company": "My Company",
    "account": ["Cash", "Bank"],
    "period_start_date": "2024-01-01",
    "period_end_date": "2024-12-31",
    "group_party": 1,
    "include_account_currency": 1,
}
columns, data = execute(filters)

frappe.msgprint(f"Found {len(data)} rows")

```

### Reposting a Single Voucher

Target specific vouchers for ledger rebuilding when you identify individual inconsistencies:

```python
import frappe
from erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger import repost_payment_ledger

repost_payment_ledger(voucher_type="Sales Invoice", voucher_no="SI-00045")
frappe.msgprint("Payment Ledger rebuilt for SI-00045")

```

### Detecting Currency Mismatches

Identify rows where currency conversion may have failed, resulting in reconciliation anomalies:

```python
import frappe

def find_mismatches():
    ple = frappe.qb.DocType("Payment Ledger Entry")
    mismatched = (
        frappe.qb.from_(ple)
        .select(ple.voucher_no, ple.amount, ple.amount_in_account_currency)
        .where(ple.amount != ple.amount_in_account_currency)
        .where(ple.delinked == 0)
        .run(as_dict=True)
    )
    return mismatched

print(find_mismatches())

```

## Summary

- The **Payment Ledger** is a read-only report that aggregates data from the **Payment Ledger Entry** DocType ([`payment_ledger_entry.py`](https://github.com/frappe/erpnext/blob/main/payment_ledger_entry.py)), grouping transactions by voucher or party.
- Reconciliation issues typically involve the **`delinked`** field being set to `1`, missing execution of the **Repost Payment Ledger** tool, or currency mismatches when the **Include Account Currency** filter is active.
- Troubleshooting requires inspecting raw PLE records in the database, validating filter configurations in `build_conditions`, and using [`repost_payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/repost_payment_ledger.py) to rebuild entries from source documents.
- The report's balance calculation occurs in `build_data` within [`payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/payment_ledger.py), where positive and negative amounts are summed to produce the **Outstanding** row.
- Unit tests in [`test_payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/test_payment_ledger.py) provide a baseline for verifying ledger integrity after code changes or data migrations.

## Frequently Asked Questions

### What is the difference between the Payment Ledger and the General Ledger?

The **Payment Ledger** specifically tracks payment-related postings between payments and their target vouchers, maintaining a dedicated **Payment Ledger Entry** table that links payments to invoices or orders. The **General Ledger** records all financial transactions across every account in the chart of accounts. While the General Ledger shows the complete financial position, the Payment Ledger provides a focused view of payment allocation and Outstanding amounts against specific parties and vouchers.

### Why are payment entries missing from my Payment Ledger report?

Missing entries typically indicate that the **Payment Ledger Entry** records have been **delinked** (`delinked = 1`) after a cancellation or amendment, or the **Repost Payment Ledger** tool was not run after a voucher change. Since [`payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/payment_ledger.py) filters explicitly for `ple.delinked == 0` in the `voucher_amount` query (lines 31‑35), any delinked records are excluded from the report. Run the repost utility to regenerate the ledger entries from source documents.

### How do I fix incorrect outstanding balances in the Payment Ledger?

Incorrect balances usually stem from stale data or partial reposts. First, verify the raw PLE amounts using the **Querying Raw Payment Ledger Entries** script to ensure no zero-value or duplicate rows exist. Then, execute the **Repost Payment Ledger** function for the affected voucher type and number to rebuild the entries from scratch. If the issue persists, inspect the `total` calculation in the `build_data` method (lines 70‑80) by adding debug logging to trace the intermediate sums.

### When should I use the Repost Payment Ledger tool?

Use the **Repost Payment Ledger** tool whenever you cancel or amend a payment entry, unlink a payment from an invoice, or restore a cancelled document. The tool, defined in [`repost_payment_ledger.py`](https://github.com/frappe/erpnext/blob/main/repost_payment_ledger.py), clears existing **Payment Ledger Entry** records for the affected vouchers and recreates them based on the current state of the source documents. This is necessary because the Payment Ledger does not update automatically when underlying vouchers change state.