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 { api } from '../api';
|
||||||
import { renderNavbar, toast, formatBRL, getUser, statusBadge, parseDisplayDate } from '../utils';
|
import { renderNavbar, toast, formatBRL, getUser, statusBadge, parseDisplayDate } from '../utils';
|
||||||
|
import { printConta } from '../receipt';
|
||||||
|
|
||||||
let localComandas: any[] = [];
|
let localComandas: any[] = [];
|
||||||
|
|
||||||
@@ -766,9 +767,15 @@ function bindOrderEvents() {
|
|||||||
const acoesContainer = document.getElementById('detalhes-acoes-pagamento')!;
|
const acoesContainer = document.getElementById('detalhes-acoes-pagamento')!;
|
||||||
acoesContainer.innerHTML = '';
|
acoesContainer.innerHTML = '';
|
||||||
const canPay = user && ['ADMIN', 'CAIXA'].includes(user.role);
|
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) {
|
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">
|
<button type="button" class="btn btn-success" style="flex:1;" id="btn-pagamento-parcial">
|
||||||
💰 Pagamento Parcial
|
💰 Pagamento Parcial
|
||||||
</button>
|
</button>
|
||||||
@@ -796,9 +803,15 @@ function bindOrderEvents() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (canPay && c.status === 'PAGA') {
|
} 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.getElementById('modal-detalhes-comanda') as HTMLElement).style.display = 'flex';
|
||||||
document.querySelector('.detalhes-edit-section')?.classList.remove('expanded');
|
document.querySelector('.detalhes-edit-section')?.classList.remove('expanded');
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user