diff --git a/pos/blueprints/config_bp.py b/pos/blueprints/config_bp.py index 843afe5..8cb7bd3 100644 --- a/pos/blueprints/config_bp.py +++ b/pos/blueprints/config_bp.py @@ -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(): diff --git a/pos/static/css/pos.css b/pos/static/css/pos.css index dd2a7e7..f1ea3e8 100644 --- a/pos/static/css/pos.css +++ b/pos/static/css/pos.css @@ -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; } diff --git a/pos/static/js/pos.js b/pos/static/js/pos.js index 63d3f9d..2ecd5b0 100644 --- a/pos/static/js/pos.js +++ b/pos/static/js/pos.js @@ -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(` + + + + + +${src.innerHTML} +`); + 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 { diff --git a/pos/templates/pos.html b/pos/templates/pos.html index 6a4c519..8b185de 100644 --- a/pos/templates/pos.html +++ b/pos/templates/pos.html @@ -16,7 +16,7 @@ - + @@ -733,7 +733,7 @@ - +