impressão da comanda
This commit is contained in:
128
web/src/receipt.ts
Normal file
128
web/src/receipt.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { formatBRL } from './utils';
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function calcularTotais(c: any) {
|
||||
const subtotal = c.total || 0;
|
||||
const desconto = c.desconto || 0;
|
||||
const acrescimo = c.acrescimo || 0;
|
||||
const taxaServico = c.taxaServico ? (subtotal - desconto + acrescimo) * 0.1 : 0;
|
||||
const totalFinal = Math.max(subtotal - desconto + acrescimo + taxaServico, 0);
|
||||
const valorPago = c.valorPago || 0;
|
||||
const restante = Math.max(totalFinal - valorPago, 0);
|
||||
return { subtotal, desconto, acrescimo, taxaServico, totalFinal, valorPago, restante };
|
||||
}
|
||||
|
||||
export function printConta(c: any) {
|
||||
const itens = c.itens ?? [];
|
||||
const { subtotal, desconto, acrescimo, taxaServico, totalFinal, valorPago, restante } = calcularTotais(c);
|
||||
|
||||
const data = new Date(c.criadoEm).toLocaleString('pt-BR');
|
||||
const agora = new Date().toLocaleString('pt-BR');
|
||||
|
||||
const itemRows = itens.map((i: any) => {
|
||||
const nome = escapeHtml(i.produto?.nome ?? 'Item');
|
||||
const sub = formatBRL(i.precoUnitario * i.quantidade);
|
||||
return `<div class="row"><span>${i.quantidade}x ${nome}</span><span>${sub}</span></div>`;
|
||||
}).join('');
|
||||
|
||||
const obsRows = itens
|
||||
.filter((i: any) => i.observacao)
|
||||
.map((i: any) => `<div class="obs">(${escapeHtml(i.observacao)})</div>`)
|
||||
.join('');
|
||||
|
||||
const totalsRows = `
|
||||
<div class="row"><span>Subtotal</span><span>${formatBRL(subtotal)}</span></div>
|
||||
${desconto > 0 ? `<div class="row"><span>Desconto</span><span>-${formatBRL(desconto)}</span></div>` : ''}
|
||||
${acrescimo > 0 ? `<div class="row"><span>Acrescimo</span><span>+${formatBRL(acrescimo)}</span></div>` : ''}
|
||||
${c.taxaServico ? `<div class="row"><span>Taxa de Servico (10%)</span><span>+${formatBRL(taxaServico)}</span></div>` : ''}
|
||||
`;
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Conta - ${escapeHtml(c.identificador)}</title>
|
||||
<style>
|
||||
@page { size: 58mm auto; margin: 0; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
width: 48mm;
|
||||
margin: 0 auto;
|
||||
padding: 2mm 2mm 4mm;
|
||||
}
|
||||
.center { text-align: center; }
|
||||
.logo { font-weight: 700; font-size: 13px; letter-spacing: 0.5px; }
|
||||
.sub { font-size: 9px; }
|
||||
.hr { border-top: 1px dashed #000; margin: 2mm 0; }
|
||||
.row { display: flex; justify-content: space-between; gap: 2mm; }
|
||||
.row > span:first-child { flex: 1; }
|
||||
.obs { font-size: 9px; margin: 0 0 1mm 4mm; }
|
||||
.items-title { font-weight: 700; margin-bottom: 1mm; }
|
||||
.total-box { border-top: 1px dashed #000; border-bottom: 1px dashed #000; margin: 2mm 0; padding: 1.5mm 0; }
|
||||
.total { font-weight: 700; font-size: 14px; }
|
||||
.stamp { font-weight: 700; font-size: 12px; }
|
||||
.footer { margin-top: 3mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center logo">CAFE EM SATURNO</div>
|
||||
<div class="center sub">Gastrobar - Conta de Consumo</div>
|
||||
<div class="hr"></div>
|
||||
<div class="row"><span>Comanda</span><span>${escapeHtml(c.identificador)}</span></div>
|
||||
${c.nomeCliente ? `<div class="row"><span>Cliente</span><span>${escapeHtml(c.nomeCliente)}</span></div>` : ''}
|
||||
<div class="row"><span>Data</span><span>${data}</span></div>
|
||||
${c.usuario?.nome ? `<div class="row"><span>Operador</span><span>${escapeHtml(c.usuario.nome)}</span></div>` : ''}
|
||||
<div class="hr"></div>
|
||||
|
||||
<div class="items-title">ITENS CONSUMIDOS</div>
|
||||
${itemRows || '<div class="row"><span>Sem itens</span></div>'}
|
||||
${obsRows}
|
||||
|
||||
<div class="hr"></div>
|
||||
${totalsRows}
|
||||
|
||||
<div class="total-box">
|
||||
<div class="row"><span>TOTAL</span><span>${formatBRL(totalFinal)}</span></div>
|
||||
${valorPago > 0 ? `<div class="row"><span>Pago</span><span>-${formatBRL(valorPago)}</span></div>` : ''}
|
||||
</div>
|
||||
|
||||
${restante > 0
|
||||
? `<div class="center total">A PAGAR</div>
|
||||
<div class="center total">${formatBRL(restante)}</div>`
|
||||
: `<div class="center stamp">PAGO</div>`}
|
||||
|
||||
<div class="footer center">Obrigado pela preferencia!</div>
|
||||
<div class="center sub">Impresso em ${agora}</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
let iframe = document.getElementById('print-iframe') as HTMLIFrameElement | null;
|
||||
if (!iframe) {
|
||||
iframe = document.createElement('iframe');
|
||||
iframe.id = 'print-iframe';
|
||||
iframe.style.cssText = 'position:absolute;width:0;height:0;border:0;visibility:hidden;';
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
|
||||
iframe.onload = () => {
|
||||
const win = iframe?.contentWindow;
|
||||
if (win) {
|
||||
win.focus();
|
||||
win.print();
|
||||
}
|
||||
};
|
||||
|
||||
const doc = iframe.contentDocument;
|
||||
if (!doc) return;
|
||||
doc.open();
|
||||
doc.write(html);
|
||||
doc.close();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { api } from '../api';
|
||||
import { renderNavbar, toast, formatBRL, getUser, statusBadge, parseDisplayDate } from '../utils';
|
||||
import { printConta } from '../receipt';
|
||||
|
||||
let localComandas: any[] = [];
|
||||
|
||||
@@ -766,9 +767,15 @@ function bindOrderEvents() {
|
||||
const acoesContainer = document.getElementById('detalhes-acoes-pagamento')!;
|
||||
acoesContainer.innerHTML = '';
|
||||
const canPay = user && ['ADMIN', 'CAIXA'].includes(user.role);
|
||||
|
||||
|
||||
const printBtnHtml = `
|
||||
<button type="button" class="btn btn-secondary" style="flex:1;" id="btn-imprimir-conta">
|
||||
🖨️ Imprimir Conta
|
||||
</button>
|
||||
`;
|
||||
|
||||
if (canPay && c.status !== 'PAGA' && c.status !== 'CANCELADA' && restante > 0) {
|
||||
acoesContainer.innerHTML = `
|
||||
acoesContainer.innerHTML = printBtnHtml + `
|
||||
<button type="button" class="btn btn-success" style="flex:1;" id="btn-pagamento-parcial">
|
||||
💰 Pagamento Parcial
|
||||
</button>
|
||||
@@ -796,9 +803,15 @@ function bindOrderEvents() {
|
||||
}
|
||||
});
|
||||
} else if (canPay && c.status === 'PAGA') {
|
||||
acoesContainer.innerHTML = `<p style="text-align:center;color:var(--success);width:100%;font-weight:600;">✅ Comanda Paga</p>`;
|
||||
acoesContainer.innerHTML = printBtnHtml + `<p style="text-align:center;color:var(--success);width:100%;font-weight:600;">✅ Comanda Paga</p>`;
|
||||
} else {
|
||||
acoesContainer.innerHTML = printBtnHtml;
|
||||
}
|
||||
|
||||
document.getElementById('btn-imprimir-conta')?.addEventListener('click', () => {
|
||||
printConta(c);
|
||||
});
|
||||
|
||||
(document.getElementById('modal-detalhes-comanda') as HTMLElement).style.display = 'flex';
|
||||
document.querySelector('.detalhes-edit-section')?.classList.remove('expanded');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user