chore: bump version and update POS closing receipt logic
This commit is contained in:
parent
a894acaefb
commit
33a69b76ce
@ -14,7 +14,7 @@ Odoo 19 custom module — automatically prints a **session closing summary** via
|
||||
- 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
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'POS Closing Receipt Printer',
|
||||
'version': '19.0.1.3.0',
|
||||
'version': '19.0.1.3.1',
|
||||
'category': 'Point of Sale',
|
||||
'summary': 'Print payment summary receipt when closing a POS session, with reprint from backend',
|
||||
'description': """
|
||||
|
||||
@ -198,80 +198,163 @@ patch(ClosePosPopup.prototype, {
|
||||
// builds receipt data, and prints with webPrintFallback.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
patch(Navbar.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.printer = useService("printer");
|
||||
},
|
||||
|
||||
async reprintLastClosingReceipt() {
|
||||
const formatCurrency = this.env.utils.formatCurrency;
|
||||
|
||||
// ── 1. Fetch the last closed session for this config ─────────────────
|
||||
let sessionData;
|
||||
try {
|
||||
const sessions = await this.pos.data.call(
|
||||
const result = await this.pos.data.call(
|
||||
"pos.session",
|
||||
"get_last_closed_session_summary",
|
||||
[this.pos.config.id]
|
||||
);
|
||||
if (!sessions) {
|
||||
if (!result) {
|
||||
this.notification.add(_t("No closed session found to reprint."), {
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
sessionData = sessions;
|
||||
sessionData = result;
|
||||
} catch (err) {
|
||||
console.error("[pos_closing_receipt] Could not fetch last session:", err);
|
||||
this.notification.add(_t("Failed to fetch session data."), { type: "danger" });
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 2. Build receipt props from server data ──────────────────────────
|
||||
let cashPayment = null;
|
||||
const nonCashPayments = [];
|
||||
// ── 2. Build formatted payment rows ──────────────────────────────────
|
||||
let cashRow = "";
|
||||
const nonCashRows = [];
|
||||
|
||||
for (const pm of sessionData.payment_methods) {
|
||||
const formatted = formatCurrency(pm.amount);
|
||||
const row = `
|
||||
<tr>
|
||||
<td style="padding:3px 0;">${pm.name}</td>
|
||||
<td style="text-align:right;padding:3px 0;">${formatted}</td>
|
||||
</tr>`;
|
||||
if (pm.is_cash) {
|
||||
cashPayment = {
|
||||
id: pm.id,
|
||||
name: pm.name,
|
||||
amount: pm.amount,
|
||||
formattedAmount: formatCurrency(pm.amount),
|
||||
};
|
||||
cashRow = row;
|
||||
} else {
|
||||
nonCashPayments.push({
|
||||
id: pm.id,
|
||||
name: pm.name,
|
||||
amount: pm.amount,
|
||||
formattedAmount: formatCurrency(pm.amount),
|
||||
});
|
||||
nonCashRows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
const cashAmount = cashPayment ? cashPayment.amount : 0;
|
||||
const nonCashTotal = nonCashPayments.reduce((s, pm) => s + pm.amount, 0);
|
||||
const cashAmount = (sessionData.payment_methods.find((p) => p.is_cash) || {}).amount || 0;
|
||||
const nonCashTotal = sessionData.payment_methods
|
||||
.filter((p) => !p.is_cash)
|
||||
.reduce((s, p) => s + p.amount, 0);
|
||||
const grandTotal = formatCurrency(cashAmount + nonCashTotal);
|
||||
|
||||
const receiptData = {
|
||||
sessionName: sessionData.session_name,
|
||||
cashierName: sessionData.cashier_name,
|
||||
closingTime: sessionData.closing_time + " (REPRINT)",
|
||||
cashPayment,
|
||||
nonCashPayments,
|
||||
grandTotal,
|
||||
};
|
||||
|
||||
// ── 3. Print ─────────────────────────────────────────────────────────
|
||||
try {
|
||||
await this.printer.print(ClosingReceipt, receiptData, {
|
||||
webPrintFallback: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("[pos_closing_receipt] Reprint failed:", err);
|
||||
this.notification.add(_t("Print failed. Check browser print settings."), {
|
||||
type: "warning",
|
||||
});
|
||||
// ── 3. Build standalone HTML receipt and open print window ───────────
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Closing Summary Reprint</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
.receipt {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
margin: 0 auto;
|
||||
padding: 12px;
|
||||
}
|
||||
.center { text-align: center; }
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.header-sub {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.reprint-label {
|
||||
margin-top: 2px;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
.dashed { border-top: 1px dashed #000; margin: 6px 0; }
|
||||
.solid { border-top: 1px solid #000; margin: 6px 0; }
|
||||
table { width: 100%; border-collapse: collapse; margin-bottom: 6px; }
|
||||
td { padding: 3px 0; }
|
||||
.right { text-align: right; }
|
||||
.bold { font-weight: bold; }
|
||||
.total-row td { font-weight: bold; font-size: 14px; padding: 4px 0; }
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-size: 11px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.feed { margin-top: 24px; }
|
||||
@media print {
|
||||
@page { margin: 3mm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="receipt">
|
||||
<div class="center" style="margin-bottom:10px;">
|
||||
<div class="header-title">${sessionData.session_name}</div>
|
||||
<div class="header-sub">SESSION CLOSING SUMMARY</div>
|
||||
<div class="reprint-label">(REPRINT)</div>
|
||||
</div>
|
||||
<div class="dashed"></div>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="bold">Cashier</td>
|
||||
<td class="right">${sessionData.cashier_name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="bold">Date/Time</td>
|
||||
<td class="right" style="font-size:11px;">${sessionData.closing_time}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="dashed"></div>
|
||||
<table>
|
||||
${cashRow}
|
||||
${nonCashRows.join("")}
|
||||
</table>
|
||||
<div class="solid"></div>
|
||||
<table>
|
||||
<tr class="total-row">
|
||||
<td>TOTAL</td>
|
||||
<td class="right">${grandTotal}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="dashed"></div>
|
||||
<div class="footer">*** Session Closed ***</div>
|
||||
<div class="feed"> </div>
|
||||
</div>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
window.print();
|
||||
setTimeout(function() { window.close(); }, 1000);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const printWindow = window.open("", "_blank", "width=400,height=600");
|
||||
if (!printWindow) {
|
||||
this.notification.add(
|
||||
_t("Pop-up blocked. Please allow pop-ups for this site and try again."),
|
||||
{ type: "warning" }
|
||||
);
|
||||
return;
|
||||
}
|
||||
printWindow.document.open();
|
||||
printWindow.document.write(html);
|
||||
printWindow.document.close();
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user