174 lines
6.3 KiB
Markdown
174 lines
6.3 KiB
Markdown
# 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 <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:
|
|
|
|
```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 `<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`:
|
|
|
|
```xml
|
|
<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.
|