Add ticket paper width selector and improve thermal cut/print layout
This commit is contained in:
@@ -100,7 +100,8 @@ def generate_ticket(sale_data, business_info, width=80):
|
||||
buf += 'Gracias por su compra!\n'.encode('cp437', errors='replace')
|
||||
buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
|
||||
buf += b'\n\n\n'
|
||||
buf += PARTIAL_CUT
|
||||
buf += FEED + b'\x05' # Feed 5 lines before cutting
|
||||
buf += CUT # Full cut
|
||||
|
||||
return bytes(buf)
|
||||
|
||||
@@ -187,7 +188,8 @@ def generate_quotation_ticket(quote_data, business_info, width=80):
|
||||
buf += 'Precios sujetos a disponibilidad\n'.encode('cp437', errors='replace')
|
||||
buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
|
||||
buf += b'\n\n\n'
|
||||
buf += PARTIAL_CUT
|
||||
buf += FEED + b'\x05'
|
||||
buf += CUT
|
||||
|
||||
return bytes(buf)
|
||||
|
||||
@@ -338,6 +340,7 @@ def generate_service_order_ticket(so_data, business_info, width=80):
|
||||
buf += "No es comprobante fiscal\n".encode("cp437", errors="replace")
|
||||
buf += "Nexus Autoparts POS\n".encode("cp437", errors="replace")
|
||||
buf += b"\n\n\n"
|
||||
buf += PARTIAL_CUT
|
||||
buf += FEED + b'\x05'
|
||||
buf += CUT
|
||||
|
||||
return bytes(buf)
|
||||
|
||||
@@ -503,6 +503,7 @@ const Config = (() => {
|
||||
setVal('receipt-rfc', d.rfc);
|
||||
setVal('receipt-address', d.address);
|
||||
setVal('receipt-phone', d.phone);
|
||||
setVal('receipt-paper-width', d.paper_width || '80');
|
||||
setVal('receipt-thanks', d.thanks_message);
|
||||
setVal('receipt-footer', d.footer);
|
||||
setChecked('receipt-show-logo', d.show_logo);
|
||||
@@ -588,6 +589,7 @@ const Config = (() => {
|
||||
rfc: getVal('receipt-rfc'),
|
||||
address: getVal('receipt-address'),
|
||||
phone: getVal('receipt-phone'),
|
||||
paper_width: getVal('receipt-paper-width') || '80',
|
||||
thanks_message: getVal('receipt-thanks'),
|
||||
footer: getVal('receipt-footer'),
|
||||
show_logo: getChecked('receipt-show-logo'),
|
||||
|
||||
@@ -1664,7 +1664,11 @@ const POS = (() => {
|
||||
|
||||
// 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;
|
||||
const is58 = paperWidth === 58;
|
||||
const printWidth = is58 ? 48 : 72;
|
||||
const fontSize = is58 ? '7.5pt' : '9pt';
|
||||
const smallFont = is58 ? '6.5pt' : '8pt';
|
||||
const nameMax = is58 ? '18mm' : '34mm';
|
||||
|
||||
let iframe = document.getElementById('ticketPrintFrame');
|
||||
if (!iframe) {
|
||||
@@ -1687,18 +1691,21 @@ const POS = (() => {
|
||||
<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; }
|
||||
html, body { margin: 0; padding: 0; height: auto; overflow: visible; background: #fff; color: #000; font-family: 'Courier New', Consolas, monospace; font-size: ${fontSize}; line-height: 1.25; }
|
||||
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; }
|
||||
.ticket { width: ${printWidth}mm; padding: 2mm; border: none !important; box-shadow: none !important; }
|
||||
.item-line-wide { display: grid !important; grid-template-columns: 7mm 1fr auto auto !important; gap: 1.5mm; font-size: ${smallFont}; margin-bottom: 1mm; align-items: start; }
|
||||
.item-line-wide .qty { text-align: center; }
|
||||
.item-line-wide .name { white-space: normal !important; word-break: break-word; overflow-wrap: anywhere; max-width: ${nameMax}; }
|
||||
.item-line-wide .price, .item-line-wide .subtotal { text-align: right; white-space: nowrap; }
|
||||
.ticket-row, .folio-line, .total-line { display: flex !important; justify-content: space-between; gap: 2mm; }
|
||||
.total-line.grand { font-size: ${is58 ? '9pt' : '11pt'}; font-weight: bold; border-top: 0.5mm solid #000; padding-top: 1mm; margin-top: 1mm; }
|
||||
.store-name { font-size: ${is58 ? '10pt' : '12pt'}; font-weight: bold; text-align: center; margin-bottom: 2px; }
|
||||
.store-tagline, .store-info { font-size: ${smallFont}; text-align: center; color: #333; margin-bottom: 4px; line-height: 1.3; }
|
||||
.divider { border: none; border-top: 1px dashed #999; margin: 3px 0; }
|
||||
.divider-double { border: none; border-top: 2px solid #333; margin: 3px 0; }
|
||||
.footer-section { text-align: center; margin-top: 6px; font-size: ${smallFont}; color: #555; }
|
||||
.payment-section, .total-section { width: 100%; }
|
||||
img { max-width: 100%; height: auto; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// The fetch handler normalizes static asset URLs (strips ?v= query strings)
|
||||
// so templates can use cache-busting query params freely.
|
||||
|
||||
const VERSION = 46;
|
||||
const VERSION = 47;
|
||||
const CACHE_NAME = 'nexus-pos-v' + VERSION;
|
||||
|
||||
const APP_SHELL = [
|
||||
|
||||
@@ -342,6 +342,13 @@
|
||||
<label class="form-label">Teléfono en ticket</label>
|
||||
<input class="form-input" id="receipt-phone" type="tel" placeholder="Ej: 664-123-4567" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Ancho del papel</label>
|
||||
<select class="form-select" id="receipt-paper-width">
|
||||
<option value="58">58 mm (térmica pequeña)</option>
|
||||
<option value="80" selected>80 mm (térmica estándar)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group form-group--full">
|
||||
<label class="form-label">Mensaje de agradecimiento</label>
|
||||
<input class="form-input" id="receipt-thanks" type="text" placeholder="Gracias por su compra!" />
|
||||
|
||||
Reference in New Issue
Block a user