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:
- Marks invoices as consolidated by setting the
consolidated_invoicefield on each POS Invoice - Links invoices to the closing entry via
update_sales_invoices_closing_entry, which writes the closing entry name to thepos_closing_entryfield on each Sales Invoice - 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_submitmarks invoices as processed and links them to the closing entry viaupdate_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →