Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2253d45d00 |
Binary file not shown.
Binary file not shown.
+118
-84
@@ -1034,7 +1034,7 @@ const LS_KEY = 'fc_v7';
|
||||
const CFG_KEY = 'fc_cfg';
|
||||
|
||||
function buildEstadoForBackend() {
|
||||
// Monta o objeto exactly as o backend espera (storage.rs → salvar_fechamento)
|
||||
// Monta o objeto que vai para o Supabase — inclui TUDO, todos os detalhes
|
||||
return {
|
||||
uuid: estado.uuid,
|
||||
id_local: estado.id_local,
|
||||
@@ -1042,7 +1042,7 @@ function buildEstadoForBackend() {
|
||||
data: estado.data,
|
||||
operador: estado.operador,
|
||||
turno: estado.turno,
|
||||
saldo_troco: parseNum(val('sys-troco')), // troco do sistema
|
||||
saldo_troco: parseNum(val('sys-troco')),
|
||||
saldo_esperado: parseNum(val('f-saldo-esperado')),
|
||||
fechamento: parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber')),
|
||||
diferenca: parseNum(val('f-saldo-esperado')) - (parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber'))),
|
||||
@@ -1052,11 +1052,12 @@ function buildEstadoForBackend() {
|
||||
vales: estado.vales.map(r => ({ nome: r.nome || '', obs: r.obs || '', valor: r.valor || 0 })),
|
||||
receber: estado.receber.map(r => ({ nome: r.nome || '', valor: r.valor || 0 })),
|
||||
cancelamentos: estado.cancelamentos.map(r => ({ numero: r.numero || '', valor: r.valor || 0, motivo: r.motivo || '' })),
|
||||
// Cartões com detalhes completos (não só totais)
|
||||
cartoes: {
|
||||
credito: estado.cartoes.credito.map(r => r.valor || 0),
|
||||
debito: estado.cartoes.debito.map(r => r.valor || 0),
|
||||
alimentacao: estado.cartoes.alimentacao.map(r => r.valor || 0),
|
||||
pix: estado.cartoes.pix.map(r => r.valor || 0),
|
||||
credito: estado.cartoes.credito.map(r => ({ valor: r.valor || 0, obs: r.obs || '' })),
|
||||
debito: estado.cartoes.debito.map(r => ({ valor: r.valor || 0, obs: r.obs || '' })),
|
||||
alimentacao: estado.cartoes.alimentacao.map(r => ({ valor: r.valor || 0, obs: r.obs || '' })),
|
||||
pix: estado.cartoes.pix.map(r => ({ valor: r.valor || 0, obs: r.obs || '' })),
|
||||
},
|
||||
observacoes: estado.observacoes,
|
||||
sync_status: estado.sync_status,
|
||||
@@ -1194,35 +1195,37 @@ async function tauriEnviar() {
|
||||
// ─── Print Preview Window ──────────────────────────────────────────────────
|
||||
function buildPrintHTML(data) {
|
||||
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
|
||||
const diff = (data.saldo_troco - data.saldo_esperado);
|
||||
const diffStr = diff >= 0 ? `+${fmt(diff)}` : fmt(diff);
|
||||
const diffColor = diff === 0 ? '#2e7d32' : (diff > 0 ? '#e65100' : '#c0272d');
|
||||
const vSangrias = (data.sangrias||[]).reduce((s,r) => s+(parseFloat(r.retirada)||0), 0);
|
||||
const vDespesas = (data.despesas||[]).reduce((s,r) => s+(parseFloat(r.valor)||0), 0);
|
||||
const vVales = (data.vales||[]).reduce((s,r) => s+(parseFloat(r.valor)||0), 0);
|
||||
const vReceber = (data.receber||[]).reduce((s,r) => s+(parseFloat(r.valor)||0), 0);
|
||||
const vPixCnpj = (data.pixcnpj||[]).reduce((s,r) => s+(parseFloat(r.valor)||0), 0);
|
||||
const vCredito = data.cartoes?.credito?.reduce?.((s,r) => s+(parseFloat(r)||0), 0) || data.credito || 0;
|
||||
const vDebito = data.cartoes?.debito?.reduce?.((s,r) => s+(parseFloat(r)||0), 0) || data.debito || 0;
|
||||
const vAliment = data.cartoes?.alimentacao?.reduce?.((s,r) => s+(parseFloat(r)||0), 0) || data.alimentacao || 0;
|
||||
const vPixCartao = data.cartoes?.pix?.reduce?.((s,r) => s+(parseFloat(r)||0), 0) || 0;
|
||||
const vCancel = (data.cancelamentos||[]).reduce((s,r) => s+(parseFloat(r.valor)||0), 0);
|
||||
// Diferença = Fechamento Total − Saldo Esperado
|
||||
const vFechamento = (data.fechamento || 0);
|
||||
const diferenca = vFechamento - (data.saldo_esperado || 0);
|
||||
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmt(diferenca);
|
||||
const diffColor = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
|
||||
|
||||
let rows = '';
|
||||
// Sangrias
|
||||
if (data.sangrias && data.sangrias.length > 0) {
|
||||
data.sangrias.forEach(s => { if (s.hora || s.retirada) rows += `<tr><td>${s.hora||''}</td><td>${fmt(s.retirada)}</td><td>${s.gerente||''}</td></tr>`; });
|
||||
}
|
||||
// Despesas
|
||||
if (data.despesas && data.despesas.length > 0) {
|
||||
data.despesas.forEach(d => { if (d.desc || d.valor) rows += `<tr><td>${d.desc||''}</td><td>${fmt(d.valor)}</td><td>${d.obs||''}</td></tr>`; });
|
||||
}
|
||||
// Vales
|
||||
if (data.vales && data.vales.length > 0) {
|
||||
data.vales.forEach(v => { if (v.nome || v.valor) rows += `<tr><td>${v.nome||''}</td><td>${fmt(v.valor)}</td><td>${v.obs||''}</td></tr>`; });
|
||||
}
|
||||
// Receber
|
||||
if (data.receber && data.receber.length > 0) {
|
||||
data.receber.forEach(r => { if (r.nome || r.valor) rows += `<tr><td>${r.nome||''}</td><td>${fmt(r.valor)}</td><td></td></tr>`; });
|
||||
}
|
||||
// PIX CNPJ
|
||||
if (data.pixcnpj && data.pixcnpj.length > 0) {
|
||||
data.pixcnpj.forEach(p => { if (p.nome || p.valor) rows += `<tr><td>PIX: ${p.nome||''}</td><td>${fmt(p.valor)}</td><td></td></tr>`; });
|
||||
}
|
||||
// Cancelamentos
|
||||
if (data.cancelamentos && data.cancelamentos.length > 0) {
|
||||
data.cancelamentos.forEach(c => { if (c.numero || c.valor) rows += `<tr><td>${c.numero||''}</td><td>${fmt(c.valor)}</td><td>${c.motivo||''}</td></tr>`; });
|
||||
}
|
||||
const fmtRow = (label, valor) =>
|
||||
`<div class="row"><span>${label}</span><span>${fmt(valor)}</span></div>`;
|
||||
const fmtDetail = (label, valor, obs) =>
|
||||
`<div class="row sub"><span>${label}</span><span>${fmt(valor)}</span></div>`;
|
||||
|
||||
const mkDet = (arr, fmtFn) => {
|
||||
if (!arr || arr.length === 0) return '';
|
||||
return arr.map(r => {
|
||||
if (!r) return '';
|
||||
const v = fmtFn(r);
|
||||
const nome = r.nome || r.desc || r.hora || r.numero || '';
|
||||
const obs = r.obs || r.motivo || '';
|
||||
return fmtDetail(nome, v, obs);
|
||||
}).join('');
|
||||
};
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html><head>
|
||||
@@ -1230,91 +1233,122 @@ function buildPrintHTML(data) {
|
||||
<title>Relatório de Fechamento</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Courier New', monospace; font-size: 13px; width: 80mm; margin: 0 auto; padding: 8px; }
|
||||
.header { text-align: center; margin-bottom: 8px; }
|
||||
.header h1 { font-size: 18px; }
|
||||
.header h2 { font-size: 14px; font-weight: normal; }
|
||||
hr { border: none; border-top: 1px dashed #000; margin: 6px 0; }
|
||||
.section { margin: 6px 0; }
|
||||
.section-title { font-weight: bold; font-size: 14px; border-top: 1px dashed #000; padding-top: 4px; margin-top: 6px; }
|
||||
.row { display: flex; justify-content: space-between; padding: 2px 0; }
|
||||
.row.space-between { justify-content: space-between; }
|
||||
.row.below { margin-top: 4px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
td { padding: 2px 4px; vertical-align: top; }
|
||||
td:nth-child(2) { text-align: right; }
|
||||
td:nth-child(3) { text-align: right; color: #555; font-size: 11px; }
|
||||
.total-row { font-weight: bold; border-top: 1px dashed #000; }
|
||||
.grand-total { font-size: 16px; font-weight: bold; text-align: center; padding: 6px; border: 2px solid #000; margin: 8px 0; }
|
||||
.obs { font-size: 11px; color: #555; margin-top: 6px; white-space: pre-wrap; }
|
||||
.footer { text-align: center; font-size: 11px; color: #555; margin-top: 8px; }
|
||||
@media print { body { margin: 0; } }
|
||||
body { font-family: 'Courier New', monospace; font-size: 12px; width: 80mm; margin: 0 auto; padding: 6px; }
|
||||
.header { text-align: center; margin-bottom: 6px; }
|
||||
.header h1 { font-size: 16px; }
|
||||
.header h2 { font-size: 12px; font-weight: normal; }
|
||||
hr { border: none; border-top: 1px dashed #000; margin: 4px 0; }
|
||||
.row { display: flex; justify-content: space-between; padding: 1px 0; }
|
||||
.row.sub { padding-left: 8px; font-size: 11px; color: #444; }
|
||||
.row strong { font-weight: bold; }
|
||||
.section-title { font-weight: bold; font-size: 12px; border-top: 1px dashed #000; padding-top: 3px; margin-top: 5px; }
|
||||
.grand-total { font-size: 14px; font-weight: bold; text-align: center; padding: 5px; border: 2px solid #000; margin: 6px 0; }
|
||||
.footer { text-align: center; font-size: 10px; color: #777; margin-top: 6px; }
|
||||
@media print { body { margin: 0; width: 80mm; } }
|
||||
</style>
|
||||
</head><body>
|
||||
<div class="header">
|
||||
<h1>${data.loja}</h1>
|
||||
<h1>${data.loja || ''}</h1>
|
||||
<h2>Fechamento de Caixa</h2>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row"><span>Data:</span><span>${data.data}</span></div>
|
||||
<div class="row"><span>Operador:</span><span>${data.operador}</span></div>
|
||||
<div class="row"><span>Turno:</span><span>${data.turno}</span></div>
|
||||
|
||||
<div class="section-title">ENTRADAS</div>
|
||||
<div class="row"><span>Saldo Troco:</span><span>${fmt(data.saldo_troco)}</span></div>
|
||||
<div class="row"><span>Fechamento:</span><span>${fmt(data.fechamento)}</span></div>
|
||||
<div class="row"><span>Saldo Esperado:</span><span>${fmt(data.saldo_esperado)}</span></div>
|
||||
|
||||
${rows ? `<div class="section-title">DETALHES</div><table><tbody>${rows}</tbody></table>` : ''}
|
||||
|
||||
${data.observacoes ? `<div class="obs">Obs: ${data.observacoes}</div>` : ''}
|
||||
<div class="row"><span>DATA:</span><span>${data.data || ''}</span></div>
|
||||
<div class="row"><span>OPERADOR:</span><span>${data.operador || ''}</span></div>
|
||||
<div class="row"><span>TURNO:</span><span>${data.turno || ''}</span></div>
|
||||
|
||||
<hr>
|
||||
<div class="section-title">CARTÕES</div>
|
||||
<div class="row"><span>Crédito:</span><span>${fmt(data.credito)}</span></div>
|
||||
<div class="row"><span>Débito:</span><span>${fmt(data.debito)}</span></div>
|
||||
<div class="row"><span>Alimentação:</span><span>${fmt(data.alimentacao)}</span></div>
|
||||
<div class="row"><span>PIX Cartão:</span><span>${fmt(data.cartoes?.pix||0)}</span></div>
|
||||
<div class="row"><span>Cancelamentos:</span><span>${fmt(data.cancelamentos)}</span></div>
|
||||
<div class="section-title">SANGRIAS</div>
|
||||
${mkDet(data.sangrias, r => parseFloat(r.retirada)||0)}
|
||||
${fmtRow('TOTAL SANGRIAS', vSangrias)}
|
||||
|
||||
<div class="section-title">DESPESAS</div>
|
||||
${mkDet(data.despesas, r => parseFloat(r.valor)||0)}
|
||||
${fmtRow('TOTAL DESPESAS', vDespesas)}
|
||||
|
||||
<div class="section-title">VALES</div>
|
||||
${mkDet(data.vales, r => parseFloat(r.valor)||0)}
|
||||
${fmtRow('TOTAL VALES', vVales)}
|
||||
|
||||
<div class="section-title">A RECEBER</div>
|
||||
${mkDet(data.receber, r => parseFloat(r.valor)||0)}
|
||||
${fmtRow('TOTAL A RECEBER', vReceber)}
|
||||
|
||||
<div class="section-title">PIX</div>
|
||||
<div class="section-title" style="font-size:11px;font-weight:normal">— Cartão —</div>
|
||||
${fmtRow('PIX CARTAO', vPixCartao)}
|
||||
<div class="section-title" style="font-size:11px;font-weight:normal">— CNPJ —</div>
|
||||
${mkDet(data.pixcnpj, r => parseFloat(r.valor)||0)}
|
||||
${fmtRow('TOTAL PIX CNPJ', vPixCnpj)}
|
||||
|
||||
<div class="section-title">CARTOES</div>
|
||||
${fmtRow('CREDITO', vCredito)}
|
||||
${fmtRow('DEBITO', vDebito)}
|
||||
${fmtRow('VOUCHER', vAliment)}
|
||||
|
||||
<div class="section-title">CANCELAMENTOS</div>
|
||||
${mkDet(data.cancelamentos, r => parseFloat(r.valor)||0)}
|
||||
${fmtRow('TOTAL CANCELAMENTOS', vCancel)}
|
||||
|
||||
<hr>
|
||||
<div class="grand-total">DIFERENÇA: ${diffStr}</div>
|
||||
${fmtRow('SALDO ESPERADO', data.saldo_esperado || 0)}
|
||||
${fmtRow('FECHAMENTO', vFechamento)}
|
||||
<div class="grand-total" style="color:${diffColor}">DIFERENCA: ${diffStr}</div>
|
||||
<div class="footer">Gerado em ${new Date().toLocaleString('pt-BR')}</div>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
function openPrintPreview(data) {
|
||||
// Gera HTML e injeta no body de uma div temporária para window.print()
|
||||
// Gera HTML e injeta no body para window.print()
|
||||
const html = buildPrintHTML(data);
|
||||
const style = document.createElement('style');
|
||||
style.id = '_print_style';
|
||||
style.textContent = `
|
||||
@media print {
|
||||
body { width: 80mm; margin: 0 auto; font-family: 'Courier New', monospace; font-size: 13px; }
|
||||
.no-print { display: none !important; }
|
||||
.no-print, .print-actions { display: none !important; }
|
||||
hr { border-top: 1px dashed #000 !important; }
|
||||
}
|
||||
.print-actions { position: fixed; top: 10px; right: 10px; z-index: 9999; }
|
||||
.print-actions button { padding: 8px 16px; cursor: pointer; background: #2563eb; color: #fff; border: none; border-radius: 4px; font-size: 14px; }
|
||||
.print-actions button { padding: 8px 16px; cursor: pointer; background: #2563eb; color: #fff; border: none; border-radius: 4px; font-size: 14px; margin-left: 8px; }
|
||||
.print-actions button:hover { background: #1d4ed8; }
|
||||
.print-actions .btn-back { background: #6b7280; }
|
||||
.print-actions .btn-back:hover { background: #4b5563; }
|
||||
`;
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'print-actions no-print';
|
||||
actions.innerHTML = '<button onclick="window.print()">🖨️ Imprimir (Ctrl+P)</button>';
|
||||
actions.innerHTML =
|
||||
'<button class="btn-back" id="_btn_back_print">← Voltar ao App</button>' +
|
||||
'<button onclick="window.print()">🖨️ Imprimir (Ctrl+P)</button>';
|
||||
|
||||
// Guarda conteúdo original
|
||||
// Guarda originais
|
||||
const originalBody = document.body.innerHTML;
|
||||
const originalOnAfterPrint = window.onafterprint;
|
||||
|
||||
// Botão voltar restaura sem reload
|
||||
document.addEventListener('click', function onBackClick(e) {
|
||||
if (e.target.id === '_btn_back_print') {
|
||||
document.body.innerHTML = originalBody;
|
||||
const oldStyle = document.getElementById('_print_style');
|
||||
if (oldStyle) oldStyle.remove();
|
||||
document.removeEventListener('click', onBackClick);
|
||||
window.onafterprint = originalOnAfterPrint;
|
||||
// Re-init app state — mínimo viable restore
|
||||
initApp();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.innerHTML = html;
|
||||
document.head.appendChild(style);
|
||||
document.body.insertBefore(actions, document.body.firstChild);
|
||||
|
||||
// Quando fechar print dialog, restaura
|
||||
const onAfterPrint = () => {
|
||||
window.onafterprint = () => {
|
||||
document.body.innerHTML = originalBody;
|
||||
window.removeEventListener('afterprint', onAfterPrint);
|
||||
// Re-carrega scripts essenciais
|
||||
location.reload();
|
||||
const oldStyle = document.getElementById('_print_style');
|
||||
if (oldStyle) oldStyle.remove();
|
||||
window.onafterprint = originalOnAfterPrint;
|
||||
document.removeEventListener('click', onBackClick);
|
||||
initApp();
|
||||
};
|
||||
window.addEventListener('afterprint', onAfterPrint);
|
||||
}
|
||||
|
||||
function triggerWindowPrint() {
|
||||
|
||||
Reference in New Issue
Block a user