| models | ||
| report | ||
| static/src/app | ||
| views | ||
| __init__.py | ||
| __manifest__.py | ||
| .gitignore | ||
| README.md | ||
pos_closing_receipt
Odoo 19 custom module — automatically prints a session closing summary via the browser print dialog when a POS session is closed.
Features
- Intercepts the POS session closing flow.
- Reads the real session name from the server (the sequence assigned at session opening, e.g.
Mie Mapan Barata00001). - Collects payment totals per payment method (cash + all non-cash methods).
- Triggers the browser's native print dialog (
window.print()) — no IoT Box or network printer required. - Prints cleanly to thermal receipt printers (≤ 80mm paper) via the browser.
- Fault-tolerant: print failures are caught and logged; they never block the session closing process.
- Reprint: adds a Reprint Closing Summary button on the closed session form in the Odoo backend.
---
Receipt Layout
Mie Mapan Barata00001
SESSION CLOSING SUMMARY
--------------------------------
Cashier Elisa Ayu
Date/Time 2026-05-21 20:25:00
--------------------------------
Cash Rp 0,00
BCA Rp 500.000,00
BTN Rp 250.000,00
GOPAY Rp 100.000,00
================================
TOTAL Rp 850.000,00
--------------------------------
*** Session Closed ***
Requirements
| Dependency | Notes |
|---|---|
point_of_sale |
Core Odoo 19 POS module |
pos_hr |
Required for cashier name lookup via pos.getCashier() |
Installation
- Copy the
pos_closing_receiptfolder to yourcustomaddons/directory. - Add the path to
addons_pathinodoo.confif not already included. - Restart the Odoo server.
- Activate Developer Mode in Odoo settings.
- Go to Apps → search for
pos_closing_receipt→ Install.
Or install via CLI:
python odoo-bin -u pos_closing_receipt --config=odoo.conf -d <your_database> --stop-after-init
How It Works
Sequence Timing
In Odoo, pos.session.name starts as "/" (the default) and gets the real sequence (e.g. Mie Mapan Barata00001) only when the session is opened via set_opening_control(). The frontend JS never receives this updated value, so we perform one server-side read before closing:
const result = await this.pos.data.read("pos.session", [this.pos.session.id], ["name"]);
realSessionName = result[0].name; // → "Mie Mapan Barata00001"
Print Flow
User clicks "Close" in POS closing popup
│
├─ 1. Sync unsynced orders (pushOrdersWithClosingPopup)
├─ 2. Post closing cash details (if cash_control enabled)
├─ 3. Fetch real session name from server (pos.data.read)
├─ 4. Collect payment data from props → _buildReceiptData()
├─ 5. Call close_session_from_ui on server
├─ 6. Mark session.state = "closed" locally
├─ 7. _printClosingReceipt() → browser print dialog
└─ 8. pos.router.close() → redirect away from POS
Why we override
closeSession()instead of wrappingsuper.closeSession(): The original method ends by callingthis.pos.router.close()which doeswindow.location.href = ...— a hard browser redirect that immediately unloads the page. Any code placed afterawait super.closeSession()would never execute. The fix inlines the closing logic so the receipt is printed before the redirect.
Web Print (No IoT Box)
Uses Odoo's built-in printer service with webPrintFallback: true — the same mechanism used by the standard POS order receipt:
- If an IoT Box printer is configured → sends to the hardware printer.
- If no printer is configured → falls back to
window.print()(browser dialog).
Reprint from Backend
When the POS session is already closed, open it from Point of Sale → Sessions, then click Reprint Closing Summary in the form header. The report opens in a new browser tab styled identically to the auto-print receipt and includes a (REPRINT) label. Use the browser print dialog (Ctrl+P) to send it to any printer.
File Structure
pos_closing_receipt/
├── __init__.py
├── __manifest__.py
├── README.md
├── models/
│ ├── __init__.py
│ └── pos_session.py # action_reprint_closing_summary + get_closing_summary_data
├── report/
│ └── pos_closing_summary_report.xml # QWeb template + paper format + report action
├── views/
│ └── pos_session_views.xml # adds Reprint button to session form
└── static/src/app/
├── closing_receipt.xml # OWL template (receipt layout + inline CSS)
└── closing_receipt_patch.js # Patch for ClosePosPopup + ClosingReceipt component
Customization
Change the receipt layout
Edit static/src/app/closing_receipt.xml. The template uses standard HTML table layout with inline CSS for maximum printer compatibility.
Add a company logo or header
Add a <div> above the session name section in the XML template. Use base64-encoded images if needed.
Change paper width
The default max-width is 320px (80mm thermal). For 58mm paper, reduce to 220px:
<div class="pos-receipt" style="... max-width: 220px; ...">
Changelog
v1.2.0
- Feature: Added Reprint Closing Summary button on closed
pos.sessionform view. - Backend QWeb report (
qweb-html) renders identically to the auto-print receipt with (REPRINT) label. - Custom 80mm paper format registered for accurate thermal receipt sizing.
models/pos_session.py:action_reprint_closing_summary()+get_closing_summary_data()aggregate payment totals frompos.paymentrecords.
v1.1.0
- Fix: Closing receipt was never printed because the original implementation
placed the print call after
await super.closeSession(), which ends withwindow.location.href = ...— a hard page redirect that unloads everything before the print can execute. - Solution: override
closeSession()fully (mirroring core logic) and invoke_printClosingReceipt()beforepos.router.close().
v1.0.0
- Initial release for Odoo 19.
- Web print via
window.print()(no IoT Box required). - Reads real session sequence name from server at print time.
- Fault-tolerant: print errors never block session closing.