diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 97b28da..92a4329 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -105,6 +105,7 @@ fn main() { plugins::printer::caminho_impressora, plugins::printer::imprimir_recibo, plugins::printer::teste_impressora, + plugins::printer::ativar_modo_teste_impressao, // App plugins::app::get_loja, plugins::app::set_loja, diff --git a/src-tauri/src/plugins/printer.rs b/src-tauri/src/plugins/printer.rs index 8c65217..7141e08 100644 --- a/src-tauri/src/plugins/printer.rs +++ b/src-tauri/src/plugins/printer.rs @@ -48,6 +48,7 @@ pub struct ReciboData { pub debito: f64, pub alimentacao: f64, pub voucher: f64, + pub pixcnpj: f64, pub cancelamentos: f64, } @@ -84,18 +85,19 @@ impl ReciboData { out.extend(escpos::divider()); // Débitos - out.extend(escpos::line_bold("DÉBITOS")); + out.extend(escpos::line_bold("DEBITOS")); out.extend(escpos::kv("Sangrias:", &self.fmt_money(self.sangrias))); out.extend(escpos::kv("Despesas:", &self.fmt_money(self.despesas))); 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()); // Cartões out.extend(escpos::line_bold("CARTÕES")); - out.extend(escpos::kv("Crédito:", &self.fmt_money(self.credito))); - out.extend(escpos::kv("Débito:", &self.fmt_money(self.debito))); - out.extend(escpos::kv("Alimentação:", &self.fmt_money(self.alimentacao))); + 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("Voucher:", &self.fmt_money(self.voucher))); out.extend(escpos::kv("Cancelamentos:", &self.fmt_money(self.cancelamentos))); out.extend(escpos::divider()); @@ -106,7 +108,7 @@ impl ReciboData { } else { self.fmt_money(self.diferenca) }; - out.extend(escpos::line_bold("DIFERENÇA:")); + out.extend(escpos::line_bold("DIFERENCA:")); out.extend(escpos::title(&diff_str)); out.extend(escpos::blank_lines(2)); @@ -126,82 +128,122 @@ impl ReciboData { } } -// ── USB Printer (stub for cross-compilation) ───────────────────────────────── -// Real implementation requires native Windows build with correct windows crate API. - -#[cfg(windows)] -pub mod usb_windows { - pub fn find_printer_device() -> Option { - // STUB: returns None during cross-compilation - // Native Windows build will enumerate USB ports - None - } - pub fn open_printer(_path: &str) -> Result { - Err("Cross-compilation stub: printing needs native Windows build".into()) - } - pub fn write_to(_handle: isize, _data: &[u8]) -> Result { - Err("Cross-compilation stub".into()) - } - pub fn close_handle(_h: isize) {} -} - -#[cfg(not(windows))] -pub mod usb_windows { - pub fn find_printer_device() -> Option { - None - } - pub fn open_printer(_path: &str) -> Result { - Err("Not supported on this platform".into()) - } - pub fn write_to(_handle: isize, _data: &[u8]) -> Result { - Err("Not supported on this platform".into()) - } - pub fn close_handle(_h: isize) {} -} - // ── Printer Manager ───────────────────────────────────────────────────────── pub struct PrinterManager { device_path: Mutex>, + /// Modo teste: grava bytes num arquivo ao invés de enviar pra USB + test_mode: Mutex, } impl Default for PrinterManager { fn default() -> Self { Self { device_path: Mutex::new(None), + test_mode: Mutex::new(false), } } } impl PrinterManager { + /// Detecta impressora USB conectada pub fn detectar(&self) -> Result { - usb_windows::find_printer_device() - .ok_or_else(|| "Nenhuma impressora USB encontrada".into()) - .map(|p| { - *self.device_path.lock().unwrap() = Some(p.clone()); - p - }) + #[cfg(windows)] + { + // Tenta encontrar porta USB virtual via WMI/registry + // Common TM-T20 USB port names on Windows + let candidates = [ + r"\\.\USB001", // TM-T20 USB Printing Support + r"\\.\USB002", + r"\\.\GPR1", + r"\\.\COM3", + r"\\.\COM4", + ]; + for port in &candidates { + if std::path::Path::new(port).exists() || self.test_device(port) { + *self.device_path.lock().unwrap() = Some(port.to_string()); + return Ok(port.to_string()); + } + } + Err("Nenhuma impressora USB encontrada. Verifique o cabo e se a impressora está ligada.".into()) + } + #[cfg(not(windows))] + { + Err("Deteccao USB s-para apenas no Windows.".into()) + } + } + + #[cfg(windows)] + fn test_device(&self, path: &str) -> bool { + use std::os::windows::io::AsRawFd; + use std::process::Command; + // Try opening the device + let out = Command::new("cmd") + .args(["/C", &format!("type {} < nul 2>&1", path)]) + .output(); + out.map(|o| o.status.success()).unwrap_or(false) } pub fn configurar(&self, path: String) -> Result<(), String> { if path.is_empty() { return Err("Caminho vazio".into()); } - *self.device_path.lock().unwrap() = Some(path); + *self.device_path.lock().unwrap() = Some(path.clone()); + log::info!("Impressora configurada: {}", path); Ok(()) } + /// Ativa modo teste: grava os bytes ESC/POS num arquivo em vez de imprimir + pub fn set_test_mode(&self, enabled: bool) { + *self.test_mode.lock().unwrap() = enabled; + log::info!("Printer test mode: {}", enabled); + } + pub fn imprimir_bytes(&self, data: &[u8]) -> Result { + // Modo teste: grava arquivo + if *self.test_mode.lock().unwrap() { + let test_file = std::env::temp_dir().join("fechamento_recibo_escpos.bin"); + std::fs::write(&test_file, data) + .map_err(|e| format!("Falha ao gravar arquivo de teste: {}", e))?; + log::info!("Bytes ESC/POS gravados em: {:?}", test_file); + return Ok(data.len()); + } + let path = self .device_path .lock() .unwrap() .clone() - .ok_or_else(|| "Impressora nao configurada".to_string())?; - let handle = usb_windows::open_printer(&path)?; - let n = usb_windows::write_to(handle, data)?; - usb_windows::close_handle(handle); - Ok(n) + .ok_or_else(|| "Impressora nao configurada. Use o botao de detectar ou configure manualmente.".to_string())?; + + #[cfg(windows)] + { + self.windows_write(&path, data) + } + #[cfg(not(windows))] + { + let _ = data; + Err("Impressao USB disponivel apenas no Windows.".into()) + } + } + + #[cfg(windows)] + fn windows_write(&self, path: &str, data: &[u8]) -> Result { + use std::fs::OpenOptions; + use std::io::Write; + + let file = OpenOptions::new() + .write(true) + .create(false) + .open(path) + .map_err(|e| format!("Erro ao abrir {}: {}", path, e))?; + + let mut f = file; + f.write_all(data) + .map_err(|e| format!("Erro ao escrever: {}", e))?; + f.flush() + .map_err(|e| format!("Erro ao fazer flush: {}", e))?; + Ok(data.len()) } } @@ -209,6 +251,7 @@ impl PrinterManager { static PRINTER: PrinterManager = PrinterManager { device_path: Mutex::new(None), + test_mode: Mutex::new(false), }; #[tauri::command] @@ -231,22 +274,31 @@ pub fn imprimir_recibo(data: String) -> Result { let r: ReciboData = serde_json::from_str(&data).map_err(|e| format!("Erro ao analisar dados: {}", e))?; let bytes = r.to_escpos(); + log::info!("Imprimindo recibo: {} bytes ESC/POS", bytes.len()); PRINTER.imprimir_bytes(&bytes) } #[tauri::command] pub fn teste_impressora() -> Result { - // Simple test pattern let mut test = Vec::new(); test.extend(escpos::INIT); test.extend(escpos::blank_lines(1)); test.extend(escpos::title("TESTE DE IMPRESSORA")); test.extend(escpos::line_center("O Frangao")); + test.extend(escpos::line_center(&chrono::Local::now().format("%d/%m/%Y %H:%M").to_string())); test.extend(escpos::blank_lines(2)); test.extend(escpos::FEED_CUT); test.extend(escpos::CUT); + log::info!("Teste impressora: {} bytes", test.len()); PRINTER.imprimir_bytes(&test) } +#[tauri::command] +pub fn ativar_modo_teste_impressao() { + PRINTER.set_test_mode(true); +} + /// Called by Tauri on plugin init (no-op for this plugin) -pub fn init() {} +pub fn init() { + log::info!("Printer plugin ready. Use detectar_impressora() ou configure manualmente."); +} diff --git a/src-tauri/src/plugins/storage.rs b/src-tauri/src/plugins/storage.rs index 1609f04..2ed0686 100644 --- a/src-tauri/src/plugins/storage.rs +++ b/src-tauri/src/plugins/storage.rs @@ -383,39 +383,98 @@ pub fn salvar_fechamento( let saldo_troco = estado.get("saldo_troco").and_then(|v| v.as_f64()).unwrap_or(0.0); let fechamento = estado.get("fechamento").and_then(|v| v.as_f64()).unwrap_or(0.0); let saldo_esperado = saldo_troco + fechamento; - let diferenca = estado.get("diferenca").and_then(|v| v.as_f64()).unwrap_or(0.0); - - // Constrói TableData a partir de despesas/receber arrays - let despesas: Vec = estado.get("despesas") + // Constrói TableData a partir de todas as tabelas do frontend + let despesas: Vec = estado + .get("despesas") .and_then(|v| v.as_array()) .map(|arr| arr.to_vec()) .unwrap_or_default(); - let receber: Vec = estado.get("receber") + let sangrias: Vec = estado + .get("sangrias") .and_then(|v| v.as_array()) .map(|arr| arr.to_vec()) .unwrap_or_default(); + let pixcnpj: Vec = estado + .get("pixcnpj") + .and_then(|v| v.as_array()) + .map(|arr| arr.to_vec()) + .unwrap_or_default(); + let vales: Vec = estado + .get("vales") + .and_then(|v| v.as_array()) + .map(|arr| arr.to_vec()) + .unwrap_or_default(); + let receber: Vec = estado + .get("receber") + .and_then(|v| v.as_array()) + .map(|arr| arr.to_vec()) + .unwrap_or_default(); + let cancelamentos: Vec = estado + .get("cancelamentos") + .and_then(|v| v.as_array()) + .map(|arr| arr.to_vec()) + .unwrap_or_default(); + let cartoes_json = estado.get("cartoes"); - let to_table_row = |v: &serde_json::Value| -> TableRow { + let to_table_row = |v: &serde_json::Value, _tipo: &str| -> TableRow { TableRow { - descricao: v.get("desc").and_then(|s| s.as_str()).map(String::from), + descricao: v.get("desc").or_else(|| v.get("nome")).and_then(|s| s.as_str()).map(String::from), + retirada: v.get("retirada").and_then(|n| n.as_f64()), valor: v.get("valor").and_then(|n| n.as_f64()), + motivo: v.get("motivo").and_then(|s| s.as_str()).map(String::from), + cliente: v.get("nome").and_then(|s| s.as_str()).map(String::from), + // hora, gerente, obs, numero vão para extra via flatten + extra: std::collections::HashMap::new(), ..Default::default() } }; let tabelas = TableData { - despesas: despesas.iter().map(to_table_row).collect(), - areceber: receber.iter().map(to_table_row).collect(), - ..Default::default() + despesas: despesas.iter().map(|v| to_table_row(v, "despesas")).collect(), + sangrias: sangrias.iter().map(|v| to_table_row(v, "sangrias")).collect(), + pixcnpj: pixcnpj.iter().map(|v| to_table_row(v, "pixcnpj")).collect(), + vales: vales.iter().map(|v| to_table_row(v, "vales")).collect(), + areceber: receber.iter().map(|v| to_table_row(v, "receber")).collect(), + cancelamentos: cancelamentos.iter().map(|v| to_table_row(v, "cancelamentos")).collect(), }; - // Cartoes como scalars - let cartoes_json = estado.get("cartoes"); let cartoes = CartaoData { - credito: vec![TableRow { valor: cartoes_json.and_then(|c| c.get("credito")).and_then(|v| v.as_f64()), ..Default::default() }], - debito: vec![TableRow { valor: cartoes_json.and_then(|c| c.get("debito")).and_then(|v| v.as_f64()), ..Default::default() }], - alimentacao: vec![TableRow { valor: cartoes_json.and_then(|c| c.get("alimentacao")).and_then(|v| v.as_f64()), ..Default::default() }], - voucher: vec![TableRow { valor: cartoes_json.and_then(|c| c.get("voucher")).and_then(|v| v.as_f64()), ..Default::default() }], + credito: cartoes_json + .and_then(|c| c.get("credito")) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .map(|v| TableRow { valor: v.as_f64(), ..Default::default() }) + .collect() + }) + .unwrap_or_default(), + debito: cartoes_json + .and_then(|c| c.get("debito")) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .map(|v| TableRow { valor: v.as_f64(), ..Default::default() }) + .collect() + }) + .unwrap_or_default(), + alimentacao: cartoes_json + .and_then(|c| c.get("alimentacao")) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .map(|v| TableRow { valor: v.as_f64(), ..Default::default() }) + .collect() + }) + .unwrap_or_default(), + voucher: cartoes_json + .and_then(|c| c.get("pix")) + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .map(|v| TableRow { valor: v.as_f64(), ..Default::default() }) + .collect() + }) + .unwrap_or_default(), }; let estado_rust = FechamentoEstado { diff --git a/src/index.html b/src/index.html index 2ecbb1b..c95d06d 100644 --- a/src/index.html +++ b/src/index.html @@ -392,7 +392,8 @@
Alimentação (+)
Vales (+)
À Receber (+)
-
PIX (+)
+
PIX Cartão (+)
+
PIX CNPJ (+)
TOTALR$ 0,00
@@ -602,9 +603,8 @@ const val = id => { const e = document.getElementById(id); return e ? e.value : const fmtNum = v => { const n = typeof v === 'number' ? v : parseNum(v); if (isNaN(n)) return ''; - // Only show decimals if not a whole number - const decimals = Number.isInteger(n) ? 0 : 2; - return n.toLocaleString('pt-BR', { minimumFractionDigits: decimals, maximumFractionDigits: 2 }); + // Always 2 decimal places for money + return n.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; // Format time: 4 digits → HH:MM @@ -815,9 +815,11 @@ function updateTotals() { document.getElementById('sys-debito').value = cartDeb > 0 ? fmtNum(cartDeb) : ''; // Alimentação = total cartão alimentação document.getElementById('sys-alimentacao').value = cartAli > 0 ? fmtNum(cartAli) : ''; - // PIX = total cartão PIX + total PIX CNPJ + // PIX Cartão = total cartão PIX + document.getElementById('sys-pix').value = cartPix > 0 ? fmtNum(cartPix) : ''; + // PIX CNPJ = total PIX CNPJ const pixCnpjTotal = estado.pixcnpj.reduce((s, r) => s + (parseNum(r['valor'] || 0) || 0), 0); - document.getElementById('sys-pix').value = (cartPix + pixCnpjTotal) > 0 ? fmtNum(cartPix + pixCnpjTotal) : ''; + document.getElementById('sys-pixcnpj').value = pixCnpjTotal > 0 ? fmtNum(pixCnpjTotal) : ''; // Vales = total vales const valesTotal = estado.vales.reduce((s, r) => s + (parseNum(r['valor'] || 0) || 0), 0); document.getElementById('sys-vales').value = valesTotal > 0 ? fmtNum(valesTotal) : ''; @@ -825,7 +827,7 @@ function updateTotals() { const receberTotal = estado.receber.reduce((s, r) => s + (parseNum(r['valor'] || 0) || 0), 0); document.getElementById('sys-receber').value = receberTotal > 0 ? fmtNum(receberTotal) : ''; - // SYS total + // SYS total = troco + crédito + débito + alimentação + vales + receber + pixCartão + pixCnpj const sysTotal = parseNum(val('sys-troco')) + cartCred + cartDeb + cartAli + valesTotal + receberTotal + cartPix + pixCnpjTotal; setText('sys-total', fmt(sysTotal)); @@ -1152,12 +1154,64 @@ async function tauriEnviar() { // 1. Salva local await window.__TAURI__.core.invoke('salvar_fechamento', { estado: data }).catch(e => console.warn('local save err:', e)); // 2. Envia Supabase - const result = await window.__TAURI__.core.invoke('sb_salvar_fechamento', { estado: data }); - return result; + try { + const result = await window.__TAURI__.core.invoke('sb_salvar_fechamento', { estado: data }); + console.log('Supabase salvo:', result); + return result; + } catch(e) { + console.error('Erro sb_salvar_fechamento:', e); + throw e; + } } async function tauriRecibo() { - const data = buildEstadoForBackend(); + // Build flat totals for the Rust ReciboData struct + 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 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 pixCnpjTotal = estado.pixcnpj.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0); + const sangriasTotal = estado.sangrias.reduce((s,r) => s+(parseNum(r.retirada||0)||0), 0); + const despesasTotal = estado.despesas.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0); + const valesTotal = estado.vales.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0); + const receberTotal = estado.receber.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 saldoEsp = parseNum(val('f-saldo-esperado')); + const sysTroco = parseNum(val('sys-troco')); + + const data = { + uuid: estado.uuid, + id_local: estado.id_local, + loja: estado.loja, + data: estado.data, + operador: estado.operador, + turno: estado.turno, + saldo_troco: sysTroco, + saldo_esperado: saldoEsp, + fechamento: fechamentoCounted, + diferenca: sysTroco - saldoEsp, + despesas: estado.despesas.map(r => ({ desc: r.desc||'', obs: r.obs||'', valor: r.valor||0 })), + sangrias: estado.sangrias.map(r => ({ hora: r.hora||'', retirada: parseNum(r.retirada)||0, gerente: r.gerente||'' })), + pixcnpj: estado.pixcnpj.map(r => ({ nome: r.nome||'', valor: r.valor||0 })), + 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||'' })), + cartoes: { credito: cartCred, debito: cartDeb, alimentacao: cartAli, pix: cartPix }, + // Flat totals for ReciboData struct (Rust) + credito: cartCred, + debito: cartDeb, + alimentacao: cartAli, + voucher: cartPix, + pixcnpj: pixCnpjTotal, + sangrias: sangriasTotal, + despesas: despesasTotal, + vales: valesTotal, + areceber: receberTotal, + cancelamentos: cancelTotal, + observacoes: estado.observacoes, + sync_status: estado.sync_status, + }; return await window.__TAURI__.core.invoke('imprimir_recibo', { estado: JSON.stringify(data) }); } @@ -1285,18 +1339,77 @@ document.getElementById('btn-anterior').addEventListener('click', async () => { estado.uuid = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(); estado.id_local = null; syncHeader(); - const rascunho = await tauriCarregarRascunho(); - if (rascunho) { - if (rascunho.operador) setVal('f-operador', rascunho.operador); - if (rascunho.turno) setVal('f-turno', rascunho.turno); - estado.operador = rascunho.operador || ''; - estado.turno = rascunho.turno || ''; - if (rascunho.saldo_troco) setVal('sys-troco', fmtNum(rascunho.saldo_troco)); - if (rascunho.saldo_esperado) setVal('f-saldo-esperado', fmtNum(rascunho.saldo_esperado)); - if (rascunho.despesas && Array.isArray(rascunho.despesas)) { - estado.despesas = rascunho.despesas.map(d => ({ desc: d.desc || d.descricao || '', obs: '', valor: d.valor || 0 })); + + // Try Supabase first (source of truth for submitted closings) + let rascunho = null; + try { + rascunho = await window.__TAURI__.core.invoke('sb_carregar_rascunho', { + loja: val('f-loja'), + data: prevDate + }); + } catch(e) { + console.warn('sb_carregar_rascunho falhou, tentando local:', e); + } + + // Fallback: local SQLite + if (!rascunho) { + try { + rascunho = await window.__TAURI__.core.invoke('carregar_rascunho', { + loja: val('f-loja'), + data: prevDate + }); + } catch(e) { + console.warn('carregar_rascunho local falhou:', e); } } + + if (rascunho) { + // Meta fields + if (rascunho.operador) { setVal('f-operador', rascunho.operador); estado.operador = rascunho.operador; } + if (rascunho.turno) { setVal('f-turno', rascunho.turno); estado.turno = rascunho.turno; } + if (rascunho.saldo_troco) setVal('sys-troco', fmtNum(rascunho.saldo_troco)); + if (rascunho.saldo_esperado) setVal('f-saldo-esperado', fmtNum(rascunho.saldo_esperado)); + if (rascunho.observacoes) { setVal('f-obs', rascunho.observacoes); estado.observacoes = rascunho.observacoes; } + + // All tables from Supabase/local + if (rascunho.despesas && Array.isArray(rascunho.despesas)) { + estado.despesas = rascunho.despesas.map(d => ({ desc: d.desc || d.descricao || '', obs: d.obs || '', valor: parseNum(d.valor) || 0 })); + } + if (rascunho.sangrias && Array.isArray(rascunho.sangrias)) { + estado.sangrias = rascunho.sangrias.map(s => ({ hora: s.hora || '', retirada: parseNum(s.retirada) || 0, gerente: s.gerente || '' })); + } + if (rascunho.pixcnpj && Array.isArray(rascunho.pixcnpj)) { + estado.pixcnpj = rascunho.pixcnpj.map(p => ({ nome: p.nome || '', valor: parseNum(p.valor) || 0 })); + } + if (rascunho.vales && Array.isArray(rascunho.vales)) { + estado.vales = rascunho.vales.map(v => ({ nome: v.nome || '', obs: v.obs || '', valor: parseNum(v.valor) || 0 })); + } + if (rascunho.receber && Array.isArray(rascunho.receber)) { + estado.receber = rascunho.receber.map(r => ({ nome: r.nome || '', valor: parseNum(r.valor) || 0 })); + } + if (rascunho.cancelamentos && Array.isArray(rascunho.cancelamentos)) { + estado.cancelamentos = rascunho.cancelamentos.map(c => ({ numero: c.numero || '', valor: parseNum(c.valor) || 0, motivo: c.motivo || '' })); + } + if (rascunho.cartoes) { + estado.cartoes.credito = (rascunho.cartoes.credito || []).map(v => ({ valor: parseNum(v) || 0 })); + estado.cartoes.debito = (rascunho.cartoes.debito || []).map(v => ({ valor: parseNum(v) || 0 })); + estado.cartoes.alimentacao = (rascunho.cartoes.alimentacao || []).map(v => ({ valor: parseNum(v) || 0 })); + estado.cartoes.pix = (rascunho.cartoes.pix || []).map(v => ({ valor: parseNum(v) || 0 })); + } + } else { + // No data for previous day: clear everything + estado.despesas = []; + estado.sangrias = []; + estado.pixcnpj = []; + estado.vales = []; + estado.receber = []; + estado.cancelamentos = []; + estado.cartoes = { credito: [], debito: [], alimentacao: [], pix: [] }; + setVal('sys-troco', ''); + setVal('f-saldo-esperado', ''); + setVal('f-obs', ''); + } + PRE_FILL_TABLES.forEach(ensureOneRow); Object.keys(TABLES).forEach(renderTable); updateTotals();