# How POS Closing Entry Works and Handles Transaction Balancing in ERPNext

> Learn how the ERPNext POS Closing Entry balances transactions by consolidating invoices, payments, and taxes. Ensure your cash drawer is accurate with this essential ERPNext feature.

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

---

**The POS Closing Entry finalizes an ERPNext POS session by aggregating invoices, reconciling payments with change adjustments, and consolidating tax liabilities to ensure the cash drawer balances correctly.**

The **POS Closing Entry** is the critical server-side mechanism in ERPNext that reconciles all Point of Sale transactions at the end of a shift. As implemented in `frappe/erpnext`, this document type ensures that every invoice, payment, and tax amount is accurately captured before marking a POS session as complete.

## Creating a POS Closing Entry from an Opening Entry

When an operator clicks **Close** on an active POS session, ERPNext triggers `make_closing_entry_from_opening` in [`erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py) (lines 40‑53). This function instantiates a new POS Closing Entry document using `frappe.new_doc` and copies the opening metadata—period dates, POS profile, user, and company—into the header fields.

The function then retrieves all relevant invoices by calling `get_invoices`, which uses `build_invoice_query` to fetch POS and Sales Invoices (depending on the `invoice_type` setting in POS Settings) belonging to the current user, profile, and time window.

## Gathering Invoice Data for Reconciliation

The `get_invoices` function (lines 62‑79) executes the built query to return invoice fields required for totals, taxes, and payments. ERPNext populates three child tables in the closing entry:

- **Invoices** – The raw transaction records
- **Payments** – Processed via `get_payments` 
- **Taxes** – Aggregated via `get_taxes`

## Payment Reconciliation and Change Adjustment

Inside `get_payments` (lines 86‑112), ERPNext groups transactions by **mode of payment** and sums the amounts. Critically, the function handles cash change by subtracting the total change amount from the summed payment for each account:

```python

# Logic from get_payments in pos_closing_entry.py lines 86-112

# Groups by mode_of_payment, then:

# net_amount = sum(payment_amount) - sum(change_amount)

```

This ensures that the **net cash** figure reflects actual drawer contents (cash received minus cash returned to customers).

## Tax Aggregation Across Invoices

The `get_taxes` function (lines 115‑134) aggregates tax amounts per tax account across all selected invoices. This creates an auditable trail of total tax liability for the session, stored in the **POS Closing Entry Taxes** child table defined in [`erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py).

## Calculating Session Totals

While iterating through fetched invoices, `make_closing_entry_from_opening` updates the closing entry’s header fields: `grand_total`, `net_total`, `total_quantity`, and `total_taxes_and_charges` (lines 93‑97). These aggregations provide the balanced summary that appears on the closing report.

## Submitting and Consolidating Invoices

When the POS Closing Entry is submitted, the `on_submit` method (lines 111‑119) executes `consolidate_pos_invoices(self)`. This process performs three critical actions:

1. **Marks invoices as consolidated** by setting the `consolidated_invoice` field on each POS Invoice
2. **Links invoices to the closing entry** via `update_sales_invoices_closing_entry`, which writes the closing entry name to the `pos_closing_entry` field on each Sales Invoice  
3. **Closes the session** by sending a realtime notification to the POS Opening Entry to update its status to **Closed**

This consolidation prevents invoices from appearing in future closing entries, eliminating double-counting risks.

## Cancellation and Session Reopening

If corrections are needed, cancelling the POS Closing Entry triggers `on_cancel` (lines 122‑130), which calls `unconsolidate_pos_invoices` to remove consolidation flags. The system also clears the `pos_closing_entry` field on linked Sales Invoices.

Before cancellation is permitted, `check_pce_is_cancellable` (lines 144‑152) validates that no other open POS Opening Entry exists for the same profile, preventing orphaned sessions.

## Programmatic Implementation Examples

Here are practical code examples for working with POS Closing Entries in ERPNext.

### Creating and Submitting a Closing Entry Programmatically

```python
import frappe
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening

# Load the open POS session

opening = frappe.get_doc("POS Opening Entry", "POS-OPENING-2023-001")

# Build the closing entry (populates invoices, taxes, payments)

closing = make_closing_entry_from_opening(opening)

# Save and submit - triggers consolidation and balancing

closing.insert()
closing.submit()

```

### Accessing Payment Reconciliation Data

```python

# Assuming `closing` is a submitted POS Closing Entry

payments = closing.payment_reconciliation  # child table POSClosingEntryDetail

for row in payments:
    print(f"{row.mode_of_payment}: expected {row.expected_amount}, opening {row.opening_amount}")

```

### Reopening a Cancelled Closing Entry

```python

# Cancel the entry (unconsolidates invoices automatically)

closing.cancel()

# Optional: retry consolidation after fixing invoice issues

closing.retry()

```

## Summary

- The **POS Closing Entry** finalizes ERPNext POS sessions by consolidating all invoices created during a shift through `make_closing_entry_from_opening`.
- **Payment reconciliation** in `get_payments` (lines 86‑112) automatically deducts change amounts from cash payments to calculate net drawer totals for each mode of payment.
- **Tax aggregation** via `get_taxes` (lines 115‑134) ensures accurate liability reporting per tax account across the entire session.
- The **consolidation** process in `on_submit` marks invoices as processed and links them to the closing entry via `update_sales_invoices_closing_entry`, preventing duplicate processing in future sessions.
- **Cancellation safety checks** via `check_pce_is_cancellable` (lines 144‑152) prevent orphaned sessions when reopening a closed shift.

## Frequently Asked Questions

### What happens to POS invoices after a POS Closing Entry is submitted?

When submitted, the `on_submit` event triggers `consolidate_pos_invoices`, which marks each invoice with a `consolidated_invoice` flag and updates the `pos_closing_entry` field on linked Sales Invoices. This locks the invoices from appearing in future closing entries and creates an audit trail.

### How does ERPNext handle cash change in the POS Closing Entry?

The `get_payments` method in [`erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py) (lines 86‑112) subtracts the total change amount from the summed cash payments for each mode of payment. This ensures the expected drawer balance reflects actual net cash (received minus returned).

### Can a POS Closing Entry be cancelled after submission?

Yes, but only if `check_pce_is_cancellable` confirms no other open POS Opening Entry exists for the same profile. Cancelling triggers `unconsolidate_pos_invoices` to remove consolidation flags and clears the closing entry reference from Sales Invoices, effectively reopening the session.

### What is the relationship between POS Opening Entry and POS Closing Entry?

The **POS Opening Entry** initiates the session with a starting cash balance. The **POS Closing Entry** references this opening entry, captures all transactions within the time window via `get_invoices`, and upon submission sends a realtime notification to mark the opening entry status as Closed, completing the session lifecycle.