pos_closing_receipt/README.md

6.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.
  • 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_receiptInstall.

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