feat(pos/workshop): add 80mm thermal ticket printing for service orders
- Add generate_service_order_ticket() in thermal_printer.py with ESC/POS commands for 58mm and 80mm printers. - Add POST /pos/api/service-orders/:id/print endpoint returning raw bytes or JSON for browser rendering. - Extend printer.js with printServiceOrder() using WebUSB/Web Serial. - Add Imprimir orden button in workshop.js detail modal. - Update FASES_IMPLEMENTADAS.md.
This commit is contained in:
@@ -134,6 +134,31 @@ window.NexusPrinter = (function () {
|
||||
return sendRaw(new Uint8Array(buf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a workshop service order ticket.
|
||||
* @param {number} soId
|
||||
* @param {number} [width=80] — 58 or 80 mm
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function printServiceOrder(soId, width) {
|
||||
width = width || 80;
|
||||
const token = localStorage.getItem('pos_token');
|
||||
const resp = await fetch('/pos/api/service-orders/' + soId + '/print', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ printer_type: 'escpos_raw', width: width })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.error('[NexusPrinter] backend error', resp.status);
|
||||
return false;
|
||||
}
|
||||
const buf = await resp.arrayBuffer();
|
||||
return sendRaw(new Uint8Array(buf));
|
||||
}
|
||||
|
||||
// ── Persistence helpers ────────────────────────
|
||||
|
||||
function _save() {
|
||||
@@ -147,6 +172,7 @@ window.NexusPrinter = (function () {
|
||||
disconnect: disconnect,
|
||||
isConnected: isConnected,
|
||||
sendRaw: sendRaw,
|
||||
printSale: printSale
|
||||
printSale: printSale,
|
||||
printServiceOrder: printServiceOrder
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -250,6 +250,7 @@ var Workshop = (function() {
|
||||
var footer = document.getElementById('detailFooter');
|
||||
footer.innerHTML =
|
||||
'<button class="btn btn--ghost" onclick="Workshop.closeDetailModal()">Cerrar</button>' +
|
||||
'<button class="btn btn--secondary" onclick="Workshop.printOrder()">🖨️ Imprimir orden</button>' +
|
||||
(o.status === 'ready' && !o.sale_id ? '<button class="btn btn--primary" onclick="Workshop.convertToSale()">Convertir a venta</button>' : '') +
|
||||
(o.sale_id ? '<a class="btn btn--secondary" href="/pos/invoicing?sale_id=' + o.sale_id + '">Ver venta #' + o.sale_id + '</a>' : '');
|
||||
}
|
||||
@@ -328,6 +329,29 @@ var Workshop = (function() {
|
||||
}).catch(function(e) { alert('Error: ' + e.message); });
|
||||
}
|
||||
|
||||
function printOrder() {
|
||||
if (!currentOrderId) return;
|
||||
if (!window.NexusPrinter || !window.NexusPrinter.isConnected()) {
|
||||
var connect = confirm('No hay impresora conectada. ¿Conectar ahora?');
|
||||
if (connect) {
|
||||
window.NexusPrinter.connect().then(function(r) {
|
||||
if (r.ok) doPrint();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
doPrint();
|
||||
|
||||
function doPrint() {
|
||||
window.NexusPrinter.printServiceOrder(currentOrderId, 80)
|
||||
.then(function(ok) {
|
||||
if (ok) alert('Orden enviada a la impresora');
|
||||
else alert('No se pudo imprimir');
|
||||
})
|
||||
.catch(function(e) { alert('Error: ' + e.message); });
|
||||
}
|
||||
}
|
||||
|
||||
// ─── New order ───
|
||||
|
||||
function openNewOrderModal() {
|
||||
@@ -469,6 +493,7 @@ var Workshop = (function() {
|
||||
addItemPlaceholder: addItemPlaceholder,
|
||||
addLabor: addLabor,
|
||||
convertToSale: convertToSale,
|
||||
printOrder: printOrder,
|
||||
openNewOrderModal: openNewOrderModal,
|
||||
closeNewOrderModal: closeNewOrderModal,
|
||||
submitNewOrder: submitNewOrder,
|
||||
|
||||
Reference in New Issue
Block a user