fix: diferenca usa sys_total; novo layout 80mm sem ENTRADAS; confirm modal
Build Fechamento de Caixa / build (macos-14, app) (push) Has been cancelled
Build Fechamento de Caixa / build (ubuntu-22.04, deb) (push) Has been cancelled
Build Fechamento de Caixa / build (windows-2022, msi) (push) Has been cancelled

This commit is contained in:
root
2026-06-25 14:32:05 +00:00
parent 1848c1aa35
commit bfbcd89be9
2 changed files with 125 additions and 66 deletions
+48 -42
View File
@@ -88,7 +88,11 @@ pub struct ReciboData {
pub saldo_esperado: f64, pub saldo_esperado: f64,
/// Diferença = fechamento saldo_esperado /// Diferença = fechamento saldo_esperado
pub diferenca: f64, pub diferenca: f64,
/// Soma de todas operações creditadas (crédito + débito + alimentação + voucher + pixcnpj) /// Total do sistema (troco + crédito + débito + alimentação + voucher + pixcnpj + vales + receber)
#[serde(default)]
pub sys_total: f64,
/// Soma de todas operações creditadas (crédito + débito + alimentação + voucher + pixcnpj) — usado no BLACK section como TOTAL
#[serde(default)]
pub total_operado: f64, pub total_operado: f64,
// Totais de cartões // Totais de cartões
pub credito: f64, pub credito: f64,
@@ -131,7 +135,7 @@ impl ReciboData {
} }
/// Gera todos os bytes ESC/POS do recibo 80mm /// Gera todos os bytes ESC/POS do recibo 80mm
/// Layout: SANGRIAS → ENTRADAS → DESPESAS → PIX CNPJ → VALES → A RECEBER → CARTÕES → CANCELAMENTOS → TROCO → OBSERVAÇÕES → TOTAL SALDO CAIXA → SALDO ESPERADO → DIFERENÇA /// Layout: Header → SANGRIAS → DESPESAS → PIX CNPJ → VALES → A RECEBER → CANCELAMENTOS → CARTÕES → TROCO/Saldo Caixa → SALDO ESPERADO → DIFERENÇA
pub fn to_escpos(&self) -> Vec<u8> { pub fn to_escpos(&self) -> Vec<u8> {
let mut out = Vec::new(); let mut out = Vec::new();
@@ -165,13 +169,6 @@ impl ReciboData {
} }
out.extend(escpos::divider()); out.extend(escpos::divider());
// ── ENTRADAS ──
out.extend(escpos::line_bold("ENTRADAS"));
out.extend(escpos::kv("Saldo Troco:", &self.fmt_money(self.saldo_troco)));
out.extend(escpos::kv("Fechamento:", &self.fmt_money(self.fechamento)));
out.extend(escpos::kv("Saldo Esperado:", &self.fmt_money(self.saldo_esperado)));
out.extend(escpos::divider());
// ── DESPESAS ── // ── DESPESAS ──
if !self.despesas_arr.is_empty() { if !self.despesas_arr.is_empty() {
out.extend(escpos::line_bold("DESPESAS")); out.extend(escpos::line_bold("DESPESAS"));
@@ -228,16 +225,7 @@ impl ReciboData {
out.extend(escpos::divider()); out.extend(escpos::divider());
} }
// ── CARTÕES ── // ── CANCELAMENTOS ──
out.extend(escpos::line_bold("CARTOES"));
out.extend(escpos::kv("Credito:", &self.fmt_money(self.credito)));
out.extend(escpos::kv("Debito:", &self.fmt_money(self.debito)));
out.extend(escpos::kv("Alimentacao:", &self.fmt_money(self.alimentacao)));
out.extend(escpos::kv("PIX Cartao:", &self.fmt_money(self.voucher)));
out.extend(escpos::kv("Cancelamentos:", &self.fmt_money(self.cancelamentos)));
out.extend(escpos::divider());
// ── CANCELAMENTOS (detalhe) ──
if !self.cancelamentos_arr.is_empty() { if !self.cancelamentos_arr.is_empty() {
out.extend(escpos::line_bold("CANCELAMENTOS")); out.extend(escpos::line_bold("CANCELAMENTOS"));
for linha in &self.cancelamentos_arr { for linha in &self.cancelamentos_arr {
@@ -254,11 +242,49 @@ impl ReciboData {
out.extend(escpos::divider()); out.extend(escpos::divider());
} }
// ── TROCO ── // ── CARTÕES ──
out.extend(escpos::line_bold("TROCO")); out.extend(escpos::line_bold("CARTOES"));
out.extend(escpos::kv("Troco:", &self.fmt_money(self.saldo_troco))); out.extend(escpos::kv("Credito:", &self.fmt_money(self.credito)));
out.extend(escpos::kv("Debito:", &self.fmt_money(self.debito)));
out.extend(escpos::kv("Alimentacao:", &self.fmt_money(self.alimentacao)));
out.extend(escpos::kv("PIX Cartao:", &self.fmt_money(self.voucher)));
out.extend(escpos::kv("Cancelamentos:", &self.fmt_money(self.cancelamentos)));
out.extend(escpos::divider()); out.extend(escpos::divider());
// ── TROCO / SALDO DE CAIXA ──
out.extend(escpos::line_bold("SALDO DE CAIXA"));
out.extend(escpos::kv("Troco (+):", &self.fmt_money(self.saldo_troco)));
out.extend(escpos::kv("Credito (+):", &self.fmt_money(self.credito)));
out.extend(escpos::kv("Debito (+):", &self.fmt_money(self.debito)));
out.extend(escpos::kv("Alimentacao (+):", &self.fmt_money(self.alimentacao)));
out.extend(escpos::kv("PIX Cartao (+):", &self.fmt_money(self.voucher)));
out.extend(escpos::kv("Vales (+):", &self.fmt_money(self.vales)));
out.extend(escpos::kv("A Receber (+):", &self.fmt_money(self.areceber)));
out.extend(escpos::kv("PIX CNPJ (+):", &self.fmt_money(self.pixcnpj)));
out.extend(escpos::divider());
// ── TOTAL ──
if self.sys_total > 0.0 {
out.extend(escpos::line_bold("TOTAL"));
out.extend(escpos::kv("Total:", &self.fmt_money(self.sys_total)));
out.extend(escpos::divider());
}
// ── SALDO ESPERADO ──
out.extend(escpos::line_bold("SALDO ESPERADO"));
out.extend(escpos::kv("Esperado:", &self.fmt_money(self.saldo_esperado)));
out.extend(escpos::divider());
// ── DIFERENÇA ──
out.extend(escpos::line_bold("DIFERENCA"));
let diff = self.sys_total - self.saldo_esperado;
let diff_str = if diff >= 0.0 {
format!("+{}", self.fmt_money(diff))
} else {
self.fmt_money(diff)
};
out.extend(escpos::title(&diff_str));
// ── OBSERVAÇÕES ── // ── OBSERVAÇÕES ──
if let Some(ref obs) = self.observacoes { if let Some(ref obs) = self.observacoes {
if !obs.trim().is_empty() { if !obs.trim().is_empty() {
@@ -268,26 +294,6 @@ impl ReciboData {
} }
} }
// ── TOTAL SALDO CAIXA ──
out.extend(escpos::line_bold("TOTAL SALDO DE CAIXA"));
out.extend(escpos::kv("Total:", &self.fmt_money(self.fechamento)));
out.extend(escpos::divider());
// ── SALDO ESPERADO ──
out.extend(escpos::line_bold("SALDO ESPERADO"));
out.extend(escpos::kv("Esperado:", &self.fmt_money(self.saldo_esperado)));
out.extend(escpos::divider());
// ── DIFERENÇA ──
out.extend(escpos::line_bold("DIFERENCA"));
let diff = self.fechamento - self.saldo_esperado;
let diff_str = if diff >= 0.0 {
format!("+{}", self.fmt_money(diff))
} else {
self.fmt_money(diff)
};
out.extend(escpos::title(&diff_str));
// Rodapé // Rodapé
out.extend(escpos::divider()); out.extend(escpos::divider());
out.extend(escpos::line_center( out.extend(escpos::line_center(
+77 -24
View File
@@ -1229,10 +1229,68 @@ async function tauriEnviar() {
} }
} }
function buildConfirmHTML(data) {
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const sangrias = (data.sangrias_arr || data.sangrias || []).reduce((s,r) => s+(parseNum(r.retirada||r.valor||0)||0), 0);
const despesas = (data.despesas_arr || data.despesas || []).reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const vales = (data.vales_arr || data.vales || []).reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const receber = (data.receber_arr || data.receber || []).reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const pixcnpj = (data.pixcnpj_arr || data.pixcnpj || []).reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const cancelamentos = (data.cancelamentos_arr || data.cancelamentos || []).reduce((s,c) => s+(parseNum(c.valor||0)||0), 0);
const cartCred = data.credito || 0;
const cartDeb = data.debito || 0;
const cartAli = data.alimentacao || 0;
const cartPix = data.voucher || data.pix || 0;
const fechamento = data.fechamento || 0;
const saldoEsp = data.saldo_esperado || 0;
const diferenca = fechamento - saldoEsp;
const diffClass = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
return `<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<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 #b3d9f7; margin: 6px 0; }
.row { display: flex; justify-content: space-between; padding: 2px 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; } }
</style>
</head><body>
<div class="header">
<h1>${data.loja}</h1>
<h2>Fechamento de Caixa</h2>
</div>
<div style="margin-bottom:6px"><strong>📅 Data:</strong> ${data.data} &nbsp; <strong>👤 Operador:</strong> ${data.operador} &nbsp; <strong>⏰ Turno:</strong> ${data.turno}</div>
<hr>
<div>💸 Sangrias: <strong>${fmt(sangrias)}</strong></div>
<div>📝 Despesas: <strong>${fmt(despesas)}</strong></div>
<div>💰 Vales: <strong>${fmt(vales)}</strong></div>
<div>👥 À Receber: <strong>${fmt(receber)}</strong></div>
<div>🔵 PIX CNPJ: <strong>${fmt(pixcnpj)}</strong></div>
<div>💳 Crédito: <strong>${fmt(cartCred)}</strong></div>
<div>💳 Débito: <strong>${fmt(cartDeb)}</strong></div>
<div>💳 Alimentação: <strong>${fmt(cartAli)}</strong></div>
<div>❌ Cancelamentos: <strong>${fmt(cancelamentos)}</strong></div>
<div>📋 Fechamento: <strong>${fmt(fechamento)}</strong></div>
<hr>
<div>🎯 Saldo Esperado: <strong>${fmt(saldoEsp)}</strong></div>
<div style="margin-top:4px">📐 Diferença: <strong style="color:${diffClass}">${fmt(diferenca)}</strong></div>
<div style="margin-top:4px;font-size:10px;color:#888">ID: ${data.loja}_${data.data}_${data.operador}</div>
<div class="footer">Gerado em ${new Date().toLocaleString('pt-BR')}</div>
</body></html>`;
}
// ─── Print Preview Window ────────────────────────────────────────────────── // ─── Print Preview Window ──────────────────────────────────────────────────
function buildPrintHTML(data) { function buildPrintHTML(data) {
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ','); const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const diferenca = (data.fechamento || 0) - (data.total_operado || 0); const diferenca = (data.fechamento || 0) - (data.saldo_esperado || 0);
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmt(diferenca); const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmt(diferenca);
const diffColor = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d'); const diffColor = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
@@ -1307,7 +1365,7 @@ function buildPrintHTML(data) {
<div class="section-title">ENTRADAS</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>Saldo Troco:</span><span>${fmt(data.saldo_troco)}</span></div>
<div class="row"><span>Fechamento:</span><span>${fmt(data.total_operado || 0)}</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> <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>` : ''} ${rows ? `<div class="section-title">DETALHES</div><table><tbody>${rows}</tbody></table>` : ''}
@@ -1328,7 +1386,8 @@ ${data.observacoes ? `<div class="obs">Obs: ${data.observacoes}</div>` : ''}
</body></html>`; </body></html>`;
} }
function openPrintPreview(data) { function openPrintPreview(data, mode) {
const html = mode === 'confirm' ? buildConfirmHTML(data) : buildPrintHTML(data);
// Remove preview anterior se existir // Remove preview anterior se existir
const existing = document.getElementById('print-preview-overlay'); const existing = document.getElementById('print-preview-overlay');
if (existing) document.body.removeChild(existing); if (existing) document.body.removeChild(existing);
@@ -1362,7 +1421,6 @@ function openPrintPreview(data) {
</style> </style>
`; `;
const html = buildPrintHTML(data);
const fullHtml = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Recibo</title>${previewStyle}</head><body>${html}</body></html>`; const fullHtml = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Recibo</title>${previewStyle}</head><body>${html}</body></html>`;
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
@@ -1423,9 +1481,8 @@ async function tauriRecibo() {
const cancelTotal = estado.cancelamentos.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0); const cancelTotal = estado.cancelamentos.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const fechamentoCounted = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber')); const fechamentoCounted = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber'));
const saldoEsp = parseNum(val('f-saldo-esperado')); const saldoEsp = parseNum(val('f-saldo-esperado'));
const sysTroco = parseNum(val('sys-troco')); // SYS total = troco + crédito + débito + alimentação + vales + receber + pixCartão + pixCnpj
// total_operado = soma de todas operações creditadas (não é o fechamento contado) const sysTotal = parseNum(val('sys-troco')) + cartCred + cartDeb + cartAli + valesTotal + receberTotal + cartPix + pixCnpjTotal;
const totalOperado = cartCred + cartDeb + cartAli + cartPix + pixCnpjTotal;
const data = { const data = {
uuid: estado.uuid, uuid: estado.uuid,
id_local: estado.id_local, id_local: estado.id_local,
@@ -1433,11 +1490,11 @@ async function tauriRecibo() {
data: estado.data, data: estado.data,
operador: estado.operador, operador: estado.operador,
turno: estado.turno, turno: estado.turno,
saldo_troco: sysTroco, saldo_troco: parseNum(val('sys-troco')),
saldo_esperado: saldoEsp, saldo_esperado: saldoEsp,
fechamento: fechamentoCounted, fechamento: fechamentoCounted,
diferenca: fechamentoCounted - saldoEsp, diferenca: sysTotal - saldoEsp,
total_operado: totalOperado, sys_total: sysTotal,
// Flat totals for ReciboData struct (Rust) — must be numbers // Flat totals for ReciboData struct (Rust) — must be numbers
credito: cartCred, credito: cartCred,
debito: cartDeb, debito: cartDeb,
@@ -1502,7 +1559,7 @@ function openConfirmModal() {
const pixcnpj = estado.pixcnpj.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0); const pixcnpj = estado.pixcnpj.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const fechamento = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber')); const fechamento = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber'));
const saldoEsp = parseNum(val('f-saldo-esperado')); const saldoEsp = parseNum(val('f-saldo-esperado'));
const diferenca = fechamento - saldoEsp; const diferenca = (parseNum(val('sys-troco')) || 0) - saldoEsp;
const diffClass = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d'); const diffClass = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
document.getElementById('confirm-summary').innerHTML = document.getElementById('confirm-summary').innerHTML =
`<div style="margin-bottom:6px"><strong>📅 Data:</strong> ${estado.data} &nbsp; <strong>👤 Operador:</strong> ${operador} &nbsp; <strong>⏰ Turno:</strong> ${turno} &nbsp; <strong>🏪 Loja:</strong> ${loja}</div> `<div style="margin-bottom:6px"><strong>📅 Data:</strong> ${estado.data} &nbsp; <strong>👤 Operador:</strong> ${operador} &nbsp; <strong>⏰ Turno:</strong> ${turno} &nbsp; <strong>🏪 Loja:</strong> ${loja}</div>
@@ -1532,36 +1589,32 @@ document.getElementById('confirm-close').addEventListener('click', closeConfirmM
document.getElementById('confirm-cancel').addEventListener('click', closeConfirmModal); document.getElementById('confirm-cancel').addEventListener('click', closeConfirmModal);
document.getElementById('modal-confirm').addEventListener('click', e => { if (e.target.id === 'modal-confirm') closeConfirmModal(); }); document.getElementById('modal-confirm').addEventListener('click', e => { if (e.target.id === 'modal-confirm') closeConfirmModal(); });
// Print from confirm modal — abre preview no navegador // Print from confirm modal — abre preview idêntico ao modal de confirmação
document.getElementById('confirm-print').addEventListener('click', async () => { document.getElementById('confirm-print').addEventListener('click', async () => {
closeConfirmModal(); closeConfirmModal();
const cartCred = estado.cartoes.credito.reduce((s,r) => s+(r.valor||0), 0); const cartCred = estado.cartoes.credito.reduce((s,r) => s+(r.valor||0), 0);
const cartDeb = estado.cartoes.debito.reduce((s,r) => s+(r.valor||0), 0); const cartDeb = estado.cartoes.debito.reduce((s,r) => s+(r.valor||0), 0);
const cartAli = estado.cartoes.alimentacao.reduce((s,r) => s+(r.valor||0), 0); const cartAli = estado.cartoes.alimentacao.reduce((s,r) => s+(r.valor||0), 0);
const cartPix = estado.cartoes.pix.reduce((s,r) => s+(r.valor||0), 0); const cartPix = estado.cartoes.pix.reduce((s,r) => s+(r.valor||0), 0);
const cancelTotal = estado.cancelamentos.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const fechamentoCounted = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber')); const fechamentoCounted = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber'));
const saldoEsp = parseNum(val('f-saldo-esperado')); const saldoEsp = parseNum(val('f-saldo-esperado'));
const totalOperado = cartCred + cartDeb + cartAli + cartPix + (estado.pixcnpj.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0));
const data = { const data = {
loja: estado.loja, data: estado.data, operador: estado.operador, turno: estado.turno, loja: estado.loja, data: estado.data, operador: estado.operador, turno: estado.turno,
saldo_troco: parseNum(val('sys-troco')) || 0, saldo_troco: parseNum(val('sys-troco')) || 0,
saldo_esperado: saldoEsp, saldo_esperado: saldoEsp,
fechamento: fechamentoCounted, fechamento: fechamentoCounted,
diferenca: fechamentoCounted - saldoEsp, diferenca: fechamentoCounted - saldoEsp,
total_operado: totalOperado, despesas_arr: estado.despesas.map(r => ({ desc: r.desc||'', obs: r.obs||'', valor: r.valor||0 })),
despesas: estado.despesas.map(r => ({ desc: r.desc||'', obs: r.obs||'', valor: r.valor||0 })), sangrias_arr: estado.sangrias.map(r => ({ hora: r.hora||'', retirada: parseNum(r.retirada)||0, gerente: r.gerente||'' })),
sangrias: estado.sangrias.map(r => ({ hora: r.hora||'', retirada: parseNum(r.retirada)||0, gerente: r.gerente||'' })), pixcnpj_arr: estado.pixcnpj.map(r => ({ nome: r.nome||'', valor: r.valor||0 })),
pixcnpj: estado.pixcnpj.map(r => ({ nome: r.nome||'', valor: r.valor||0 })), vales_arr: estado.vales.map(r => ({ nome: r.nome||'', obs: r.obs||'', valor: r.valor||0 })),
vales: estado.vales.map(r => ({ nome: r.nome||'', obs: r.obs||'', valor: r.valor||0 })), receber_arr: estado.receber.map(r => ({ nome: r.nome||'', valor: r.valor||0 })),
receber: estado.receber.map(r => ({ nome: r.nome||'', valor: r.valor||0 })), cancelamentos_arr: estado.cancelamentos.map(r => ({ numero: r.numero||'', valor: r.valor||0, motivo: r.motivo||'' })),
cancelamentos: estado.cancelamentos.map(r => ({ numero: r.numero||'', valor: r.valor||0, motivo: r.motivo||'' })),
credito: cartCred, debito: cartDeb, alimentacao: cartAli, credito: cartCred, debito: cartDeb, alimentacao: cartAli,
pix: cartPix, // flat float for buildPrintHTML voucher: cartPix,
cartoes: { credito: estado.cartoes.credito, debito: estado.cartoes.debito, alimentacao: estado.cartoes.alimentacao, pix: estado.cartoes.pix },
observacoes: estado.observacoes, observacoes: estado.observacoes,
}; };
openPrintPreview(data); openPrintPreview(data, 'confirm');
}); });
// Confirm send // Confirm send