# 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 1. Copy the `pos_closing_receipt` folder to your `customaddons/` directory. 2. Add the path to `addons_path` in `odoo.conf` if not already included. 3. Restart the Odoo server. 4. Activate **Developer Mode** in Odoo settings. 5. Go to **Apps** → search for `pos_closing_receipt` → **Install**. Or install via CLI: ```bash python odoo-bin -u pos_closing_receipt --config=odoo.conf -d --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: ```js 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 wrapping `super.closeSession()`:** > The original method ends by calling `this.pos.router.close()` which does > `window.location.href = ...` — a hard browser redirect that immediately > unloads the page. Any code placed **after** `await 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 `
` 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`: ```xml
``` --- ## Changelog ### v1.2.0 - **Feature**: Added **Reprint Closing Summary** button on closed `pos.session` form 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 from `pos.payment` records. ### v1.1.0 - **Fix**: Closing receipt was never printed because the original implementation placed the print call **after** `await super.closeSession()`, which ends with `window.location.href = ...` — a hard page redirect that unloads everything before the print can execute. - Solution: override `closeSession()` fully (mirroring core logic) and invoke `_printClosingReceipt()` **before** `pos.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.