fix(pos): print tickets via iframe with measured height and configurable paper width
This commit is contained in:
@@ -1044,6 +1044,7 @@ RECEIPT_CONFIG_KEYS = [
|
||||
'receipt_show_iva_breakdown',
|
||||
'receipt_show_payment_details',
|
||||
'receipt_show_employee',
|
||||
'receipt_paper_width',
|
||||
]
|
||||
|
||||
|
||||
@@ -1081,6 +1082,7 @@ def get_receipt_config():
|
||||
'show_iva_breakdown': _bool('receipt_show_iva_breakdown', True),
|
||||
'show_payment_details': _bool('receipt_show_payment_details', True),
|
||||
'show_employee': _bool('receipt_show_employee', False),
|
||||
'paper_width': rows.get('receipt_paper_width', '80') or '80',
|
||||
})
|
||||
|
||||
|
||||
@@ -1108,6 +1110,7 @@ def update_receipt_config():
|
||||
'receipt_show_iva_breakdown': 'true' if data.get('show_iva_breakdown') else 'false',
|
||||
'receipt_show_payment_details': 'true' if data.get('show_payment_details') else 'false',
|
||||
'receipt_show_employee': 'true' if data.get('show_employee') else 'false',
|
||||
'receipt_paper_width': str(data.get('paper_width', '80') or '80'),
|
||||
}
|
||||
|
||||
for key, value in settings.items():
|
||||
|
||||
@@ -909,6 +909,7 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ticket-80 { width: 80mm; max-width: 80mm; }
|
||||
.ticket-58 { width: 48mm; max-width: 48mm; }
|
||||
.ticket .store-name { font-size: 14px; font-weight: bold; text-align: center; margin-bottom: 2px; }
|
||||
.ticket .store-tagline { font-size: 9px; text-align: center; color: #555; margin-bottom: 4px; }
|
||||
.ticket .store-info { font-size: 9px; text-align: center; color: #333; margin-bottom: 6px; line-height: 1.3; }
|
||||
@@ -933,11 +934,16 @@
|
||||
.ticket-line { page-break-inside: avoid; }
|
||||
|
||||
@media print {
|
||||
@page { margin: 0; size: 80mm auto; }
|
||||
body * { display: none !important; }
|
||||
.ticket-print-area, .ticket-print-area * { display: block !important; }
|
||||
.ticket-print-area { position: fixed; top: 0; left: 0; width: 80mm; }
|
||||
.ticket, .ticket-80 { width: 80mm !important; max-width: 80mm !important; border: none !important; box-shadow: none !important; padding: 3mm !important; font-size: 9pt !important; }
|
||||
@page { margin: 0; size: 72mm auto; }
|
||||
html, body { height: auto !important; overflow: visible !important; }
|
||||
body > * { display: none !important; }
|
||||
.ticket-print-area,
|
||||
.ticket-print-area * { display: block !important; }
|
||||
.ticket-print-area {
|
||||
position: static !important; width: 72mm !important; margin: 0 !important; padding: 0 !important;
|
||||
}
|
||||
.ticket, .ticket-80 { width: 72mm !important; max-width: 72mm !important; border: none !important; box-shadow: none !important; padding: 3mm !important; font-size: 9pt !important; }
|
||||
.ticket-58 { width: 48mm !important; max-width: 48mm !important; }
|
||||
.ticket * { word-break: break-word !important; }
|
||||
.ticket .item-line-wide { display: grid !important; grid-template-columns: auto 1fr auto auto !important; gap: 2mm !important; font-size: 8pt !important; }
|
||||
.ticket .ticket-row, .ticket .folio-line, .ticket .total-line { display: flex !important; }
|
||||
|
||||
@@ -1597,11 +1597,64 @@ const POS = (() => {
|
||||
}
|
||||
|
||||
function printTicket() {
|
||||
// Make print area visible for @media print
|
||||
const area = document.getElementById('ticketPrintArea');
|
||||
if (area) area.style.display = 'block';
|
||||
window.print();
|
||||
setTimeout(() => { if (area) area.style.display = 'none'; }, 500);
|
||||
const src = document.getElementById('ticketContent');
|
||||
if (!src) return;
|
||||
|
||||
// Paper width: 58 mm -> 48 mm printable; 80 mm -> 72 mm printable.
|
||||
const paperWidth = parseInt(rc('paper_width', '80'), 10) || 80;
|
||||
const printWidth = paperWidth === 58 ? 48 : 72;
|
||||
|
||||
let iframe = document.getElementById('ticketPrintFrame');
|
||||
if (!iframe) {
|
||||
iframe = document.createElement('iframe');
|
||||
iframe.id = 'ticketPrintFrame';
|
||||
iframe.style.position = 'fixed';
|
||||
iframe.style.left = '-9999px';
|
||||
iframe.style.top = '0';
|
||||
iframe.style.width = printWidth + 'mm';
|
||||
iframe.style.border = '0';
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
|
||||
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
doc.open();
|
||||
doc.write(`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
@page { margin: 0; size: ${printWidth}mm auto; }
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; height: auto; overflow: visible; background: #fff; color: #000; font-family: 'Courier New', Consolas, monospace; font-size: 9pt; }
|
||||
body { width: ${printWidth}mm; }
|
||||
.ticket { width: ${printWidth}mm; padding: 3mm; border: none !important; box-shadow: none !important; }
|
||||
.item-line-wide { display: grid !important; grid-template-columns: auto 1fr auto auto !important; gap: 2mm; font-size: 8pt; margin-bottom: 1mm; }
|
||||
.item-line-wide .name { white-space: normal !important; word-break: break-word; overflow: visible; }
|
||||
.ticket-row, .folio-line, .total-line { display: flex !important; justify-content: space-between; }
|
||||
.total-line.grand { font-size: 11pt; font-weight: bold; border-top: 0.5mm solid #000; padding-top: 1mm; margin-top: 1mm; }
|
||||
.store-name { font-size: 12pt; font-weight: bold; text-align: center; margin-bottom: 2px; }
|
||||
.store-tagline, .store-info { font-size: 8pt; text-align: center; color: #333; margin-bottom: 4px; line-height: 1.3; }
|
||||
.divider { border: none; border-top: 1px dashed #999; margin: 4px 0; }
|
||||
.divider-double { border: none; border-top: 2px solid #333; margin: 4px 0; }
|
||||
.footer-section { text-align: center; margin-top: 6px; font-size: 8pt; color: #555; }
|
||||
img { max-width: 100%; height: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${src.innerHTML}</body>
|
||||
</html>`);
|
||||
doc.close();
|
||||
|
||||
// Measure real content height and set exact page size (avoids phantom 3276 mm).
|
||||
const bodyEl = doc.body;
|
||||
const heightPx = Math.max(bodyEl.scrollHeight, bodyEl.offsetHeight);
|
||||
const heightMm = Math.ceil((heightPx * 25.4 / 96) + 5);
|
||||
|
||||
const exactPage = doc.createElement('style');
|
||||
exactPage.textContent = `@page { margin: 0; size: ${printWidth}mm ${heightMm}mm; }`;
|
||||
doc.head.appendChild(exactPage);
|
||||
|
||||
iframe.contentWindow.focus();
|
||||
setTimeout(() => { iframe.contentWindow.print(); }, 150);
|
||||
}
|
||||
|
||||
// ─── Thermal Printing ─────────────────
|
||||
@@ -1623,7 +1676,8 @@ const POS = (() => {
|
||||
return;
|
||||
}
|
||||
if (!lastSaleId) { showToast('No hay venta para imprimir'); return; }
|
||||
const ok = await NexusPrinter.printSale(lastSaleId);
|
||||
const paperWidth = parseInt(rc('paper_width', '80'), 10) || 80;
|
||||
const ok = await NexusPrinter.printSale(lastSaleId, paperWidth);
|
||||
if (ok) {
|
||||
showToast('Ticket enviado a impresora termica');
|
||||
} else {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
<script src="/pos/static/js/native-bridge.js" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/pos.css?v=34"></head>
|
||||
<link rel="stylesheet" href="/pos/static/css/pos.css?v=35"></head>
|
||||
|
||||
<body class="pos-shell" id="appBody">
|
||||
|
||||
@@ -733,7 +733,7 @@
|
||||
<script src="/pos/static/js/splash-loader.js?v=33" defer></script>
|
||||
<script src="/pos/static/js/push.js" defer></script>
|
||||
<script src="/pos/static/js/printer.js" defer></script>
|
||||
<script src="/pos/static/js/pos.js?v=41" defer></script>
|
||||
<script src="/pos/static/js/pos.js?v=42" defer></script>
|
||||
|
||||
<script>
|
||||
// Cancel sale button wiring
|
||||
|
||||
Reference in New Issue
Block a user