4.3 KiB
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.
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. Fetch real session name from server (pos.data.read)
├─ 2. Collect payment data from props (default_cash_details, non_cash_payment_methods)
├─ 3. Call super.closeSession() — standard Odoo closing
│
└─ 4. Session state === "closed"?
└─ Yes → printer.print(ClosingReceipt, data, { webPrintFallback: true })
└─ Browser print dialog opens
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).
File Structure
pos_closing_receipt/
├── __init__.py
├── __manifest__.py
├── README.md
└── 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.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.