How POS Closing Entry Works and Handles Transaction Balancing in ERPNext

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 (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:


# 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.

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

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


# 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


# 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 (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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →