Compare commits

...

6 Commits

Author SHA1 Message Date
root 5ad8c1f395 v1.3.15: diferença=fechamento-saldo_esperado-cancelamentos; ESCPOS 80mm+HTML com clientes+frango
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
2026-06-26 13:33:28 +00:00
root 103cd31e50 v1.3.14b: campos Clientes e Frango Assado no formulário, reports e 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
2026-06-26 13:21:02 +00:00
root 06783de4e2 v1.3.14: dia anterior com modal+senha, editing mode PATCH, sys_total sem troco, fórmulas corrigidas, reportHTML
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
2026-06-26 12:57:39 +00:00
root 11abac8daf v1.3.13: fórmulas corrigidas — diferença=(fechamento-(saldo_esp-cancel)), sys_total sem troco, reportHTML separado
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
2026-06-26 12:45:46 +00:00
root fea957dfb9 v1.3.12: dia anterior robusto — hasTauri check em todas as chamadas
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
2026-06-26 03:44:53 +00:00
root 130b2b6da7 v1.3.11: 3 bugs corrigidos — preview sempre abre, tabela cartoes com traços, variaveis ausentes
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
2026-06-26 03:40:48 +00:00
4 changed files with 481 additions and 205 deletions
+2
View File
@@ -96,6 +96,8 @@ fn main() {
plugins::supabase::sb_carregar_rascunho,
plugins::supabase::sb_listar_recentes,
plugins::supabase::sb_buscar_por_id,
plugins::supabase::sb_buscar_por_uuid,
plugins::supabase::sb_atualizar_fechamento,
plugins::supabase::sb_salvar_listas,
plugins::supabase::sb_carregar_listas,
plugins::supabase::sb_online,
+10
View File
@@ -127,6 +127,10 @@ pub struct ReciboData {
pub cancelamentos_arr: Vec<ReciboLinha>,
#[serde(default)]
pub observacoes: Option<String>,
#[serde(default)]
pub clientes: i32,
#[serde(default)]
pub frango: i32,
}
impl ReciboData {
@@ -284,6 +288,12 @@ impl ReciboData {
out.extend(escpos::divider());
}
// ── CONTADORES ──
out.extend(escpos::line_bold("CONTADORES"));
out.extend(escpos::kv("Clientes:", &self.clientes.to_string()));
out.extend(escpos::kv("Frango Assado:", &self.frango.to_string()));
out.extend(escpos::divider());
// ── CARTÕES ──
out.extend(escpos::line_bold("CARTOES"));
out.extend(escpos::kv("Credito:", &self.fmt_money(self.credito)));
+70 -1
View File
@@ -153,7 +153,59 @@ impl SupabaseClient {
}
}
/// Lista fechamentos recentes de uma loja.
/// Busca um fechamento pelo UUID.
pub fn buscar_por_uuid(&self, uuid: &str) -> Result<Option<Value>, SupabaseError> {
let url = format!(
"{}/rest/v1/fechamentos_web?uuid=eq.{}&select=*",
self.base_url, uuid
);
let resp = self
.http
.get(&url)
.headers(self.headers(false))
.send()?;
if resp.status().is_success() {
let arr: Vec<Value> = resp.json()?;
Ok(arr.into_iter().next())
} else {
let status = resp.status();
let body = resp.text().unwrap_or_default();
Err(SupabaseError::Api(format!(
"HTTP {}: {}",
status.as_u16(),
body
)))
}
}
/// Atualiza campos específicos de um fechamento pelo UUID (PATCH).
pub fn atualizar_fechamento(&self, uuid: &str, updates: &serde_json::Value) -> Result<Value, SupabaseError> {
let url = format!(
"{}/rest/v1/fechamentos_web?uuid=eq.{}",
self.base_url, uuid
);
let resp = self
.http
.patch(&url)
.headers(self.headers(true))
.json(updates)
.send()?;
if resp.status().is_success() {
Ok(serde_json::json!({ "ok": true, "uuid": uuid }))
} else {
let status = resp.status();
let body = resp.text().unwrap_or_default();
Err(SupabaseError::Api(format!(
"HTTP {}: {}",
status.as_u16(),
body
)))
}
}
pub fn listar_recentes(&self, loja: &str, limite: i64) -> Result<Vec<Value>, SupabaseError> {
let url = format!(
"{}/rest/v1/fechamentos_web?loja=eq.{}&order=data.desc,atualizado_em.desc&limit={}&select=id,uuid,data,operador,turno,saldo_troco,saldo_esperado,fechamento,sync_status,observacoes,atualizado_em",
@@ -322,6 +374,23 @@ pub fn sb_buscar_por_id(
sb.buscar_por_id(id)
}
#[tauri::command]
pub fn sb_buscar_por_uuid(
sb: State<'_, SupabaseClient>,
uuid: String,
) -> Result<Option<serde_json::Value>, SupabaseError> {
sb.buscar_por_uuid(&uuid)
}
#[tauri::command]
pub fn sb_atualizar_fechamento(
sb: State<'_, SupabaseClient>,
uuid: String,
updates: serde_json::Value,
) -> Result<serde_json::Value, SupabaseError> {
sb.atualizar_fechamento(&uuid, &updates)
}
#[tauri::command]
pub fn sb_salvar_listas(
sb: State<'_, SupabaseClient>,
+389 -194
View File
@@ -427,6 +427,21 @@
</div>
</div>
<!-- CONTADORES -->
<div class="section">
<div class="section-header">👥 Clientes & Frango Assado</div>
<div style="display:flex;gap:12px">
<div style="flex:1">
<label style="font-size:12px;color:#666">Clientes</label>
<input type="number" id="f-clientes" min="0" placeholder="0" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:var(--radius);font-size:15px" />
</div>
<div style="flex:1">
<label style="font-size:12px;color:#666">Frango Assado</label>
<input type="number" id="f-frango" min="0" placeholder="0" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:var(--radius);font-size:15px" />
</div>
</div>
</div>
<!-- OBS -->
<div class="section" style="flex:1;display:flex;flex-direction:column">
<div class="section-header">📝 Observações</div>
@@ -514,6 +529,12 @@
<div id="cfg-vales-list"></div>
<button class="btn-add-config" id="cfg-add-vales">+ Adicionar nome</button>
</div>
<div class="cfg-section">
<div class="cfg-section-title">🔐 Senha Admin (editar dias anteriores)</div>
<div style="display:flex;gap:6px;margin-top:4px">
<input type="password" id="cfg-admin-password" style="flex:1;padding:6px;border:1px solid #ddd;border-radius:4px;font-size:13px" placeholder="Deixe em branco se não quiser senha">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-primary" id="cfg-salvar">Salvar Configuração</button>
@@ -561,6 +582,7 @@ let config = {
despesas: ['Energia', 'Água', 'Manutenção', 'Limpeza', 'Insumos'],
clientes: [], // preenchimento livre — não é datalist fixo
vales_nomes: [], // nomes usados em vales ( histórico para autofill)
admin_password: '', // senha para editar dias anteriores no Supabase
};
// ─── App State ────────────────────────────────────────────────────────────
@@ -571,6 +593,7 @@ let estado = {
despesas: [], sangrias: [], pixcnpj: [], vales: [], receber: [], cancelamentos: [],
cartoes: { credito: [], debito: [], alimentacao: [], pix: [] },
fechamento_contado: { dinheiro: 0, cartoes: 0, receber: 0 },
clientes: 0, frango: 0, // contadores
observacoes: '', id_local: null, sync_status: 'local'
};
@@ -832,8 +855,8 @@ 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 = 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;
// SYS total = crédito + débito + alimentação + vales + receber + pixCartão + pixCnpj (NÃO inclui troco)
const sysTotal = cartCred + cartDeb + cartAli + valesTotal + receberTotal + cartPix + pixCnpjTotal;
setText('sys-total', fmt(sysTotal));
// ── Fechamento Contado — auto-populate (readonly, filled from tables) ──
@@ -850,9 +873,10 @@ function updateTotals() {
const fr = receberTotal;
setText('fech-total', fmt(fd + fc + fr));
// Diferença = Saldo Caixa Saldo Esperado
const saldoCaixa = sysTotal;
const diff = saldoCaixa - parseNum(val('f-saldo-esperado'));
// Diferença = fechamento - saldo_esperado - cancelamentos
const fechamentoTotal = fd + fc + fr;
const cancelTotal = estado.cancelamentos.reduce((s,r) => s+(parseNum(r.valor||0)||0), 0);
const diff = fechamentoTotal - parseNum(val('f-saldo-esperado')) - cancelTotal;
const difEl = document.getElementById('diferenca');
const badgeEl = document.getElementById('dif-badge');
if (difEl) {
@@ -1060,6 +1084,8 @@ function buildEstadoForBackend() {
pix: estado.cartoes.pix.map(r => r.valor || 0),
},
observacoes: estado.observacoes,
clientes: estado.clientes,
frango: estado.frango,
sync_status: estado.sync_status,
};
}
@@ -1076,6 +1102,8 @@ function saveState() {
cartoes: parseNum(val('f-fech-cartoes')),
receber: parseNum(val('f-fech-receber')),
};
estado.clientes = parseInt(document.getElementById('f-clientes').value) || 0;
estado.frango = parseInt(document.getElementById('f-frango').value) || 0;
estado.updatedAt = new Date().toISOString();
try { localStorage.setItem(LS_KEY, JSON.stringify(estado)); } catch(e) {}
}
@@ -1095,6 +1123,8 @@ function loadState() {
estado.saldo_esperado = saved.saldo_esperado || 0;
estado.fechamento = saved.fechamento || 0;
estado.observacoes = saved.observacoes || '';
estado.clientes = saved.clientes || 0;
estado.frango = saved.frango || 0;
estado.sync_status = saved.sync_status || 'local';
// Restore tables (always as arrays of objects)
estado.despesas = Array.isArray(saved.despesas) ? saved.despesas : [];
@@ -1147,6 +1177,7 @@ function loadConfig() {
} catch(e) {}
}
function saveConfig() {
config.admin_password = document.getElementById('cfg-admin-password').value.trim();
try { localStorage.setItem(CFG_KEY, JSON.stringify(config)); } catch(e) {}
buildDatalists();
}
@@ -1159,6 +1190,7 @@ function openConfig() {
renderCfgList('despesas', 'cfg-despesas-list');
renderCfgList('clientes', 'cfg-clientes-list');
renderCfgList('vales_nomes', 'cfg-vales-list');
document.getElementById('cfg-admin-password').value = config.admin_password || '';
}
function closeConfig() { document.getElementById('modal-config').classList.remove('open'); }
function renderCfgList(key, containerId) {
@@ -1233,8 +1265,9 @@ function buildConfirmHTML(data) {
// Relatório completo — mesmo layout do PDF
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ',');
// Diferença = fechamento - saldo_esperado - cancelamentos
const diferenca = data.diferenca !== undefined ? data.diferenca
: ((data.sys_total || 0) - (data.saldo_esperado || 0));
: ((data.fechamento || 0) - (data.saldo_esperado || 0) - (data.cancelamentos || 0));
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca);
const diffOk = diferenca === 0;
@@ -1279,10 +1312,10 @@ function buildConfirmHTML(data) {
const al = cartAli_arr[i] || 0;
const px = cartPix_arr[i] || 0;
cartTableRows += `<tr>
<td>${cr > 0 ? fmt(cr) : ''}</td>
<td>${db > 0 ? fmt(db) : ''}</td>
<td>${al > 0 ? fmt(al) : ''}</td>
<td>${px > 0 ? fmt(px) : ''}</td>
<td>${cr > 0 ? fmt(cr) : '-'}</td>
<td>${db > 0 ? fmt(db) : '-'}</td>
<td>${al > 0 ? fmt(al) : '-'}</td>
<td>${px > 0 ? fmt(px) : '-'}</td>
</tr>`;
}
cartTableRows += `<tr class="total-row">
@@ -1384,6 +1417,10 @@ ${cancelamentos_arr.map(c => `<div class="row"><span>${c.numero ? `Nº ${c.numer
<div class="row"><span>À Receber:</span><span>${fmt(frec)}</span></div>
<div class="row total-row"><span>TOTAL:</span><span>${fmt(ftotal)}</span></div>
<div class="section-title">CONTADORES</div>
<div class="row"><span>Clientes:</span><span>${data.clientes || 0}</span></div>
<div class="row"><span>Frango Assado:</span><span>${data.frango || 0}</span></div>
${data.observacoes ? `<div class="obs">${data.observacoes}</div>` : ''}
<div class="footer">Gerado em ${new Date().toLocaleString('pt-BR')}</div>
@@ -1394,8 +1431,9 @@ ${data.observacoes ? `<div class="obs">${data.observacoes}</div>` : ''}
function buildPrintHTML(data) {
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ',');
// Diferença = fechamento - saldo_esperado - cancelamentos
const diferenca = data.diferenca !== undefined ? data.diferenca
: ((data.sys_total || 0) - (data.saldo_esperado || 0));
: ((data.fechamento || 0) - (data.saldo_esperado || 0) - (data.cancelamentos || 0));
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca);
const diffColor = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
@@ -1487,10 +1525,10 @@ function buildPrintHTML(data) {
const al = cartAli_arr[i] || 0;
const px = cartPix_arr[i] || 0;
cartTableRows += `<tr>
<td>${cr > 0 ? fmt(cr) : ''}</td>
<td>${db > 0 ? fmt(db) : ''}</td>
<td>${al > 0 ? fmt(al) : ''}</td>
<td>${px > 0 ? fmt(px) : ''}</td>
<td>${cr > 0 ? fmt(cr) : '-'}</td>
<td>${db > 0 ? fmt(db) : '-'}</td>
<td>${al > 0 ? fmt(al) : '-'}</td>
<td>${px > 0 ? fmt(px) : '-'}</td>
</tr>`;
}
// Totals row
@@ -1606,8 +1644,75 @@ ${data.observacoes ? `<div class="obs">${data.observacoes}</div>` : ''}
return html;
}
// ─── Relatório de Impressão (layout exato do usuário) ──────────────────────
function buildReportHTML(data) {
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ',');
const cartCred = data.credito || 0;
const cartDeb = data.debito || 0;
const cartAli = data.alimentacao || 0;
const cartPix = data.voucher || 0;
const sangrias = data.sangrias || 0;
const despesas = data.despesas || 0;
const vales = data.vales || 0;
const receber = data.areceber || 0;
const pixcnpj = data.pixcnpj || 0;
const cancelamentos = data.cancelamentos || 0;
const fechamento = data.fechamento || 0;
const sysTotal = data.sys_total || 0;
const saldoEsp = data.saldo_esperado || 0;
const diferenca = data.diferenca !== undefined ? data.diferenca
: (fechamento - saldoEsp - cancelamentos);
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca);
const diffColor = diferenca === 0 ? '#2e7d32' : '#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; }
hr { border: none; border-top: 1px dashed #000; margin: 6px 0; }
.row { display: flex; justify-content: space-between; padding: 2px 0; font-size: 12px; }
.row .val { font-weight: bold; }
.footer { text-align: center; font-size: 11px; color: #555; margin-top: 8px; }
@media print { body { margin: 0; } }
</style>
</head><body>
<div class="header">
<div>📅 Data: ${data.data} &nbsp; 👤 Operador: ${data.operador} &nbsp; ⏰ Turno: ${data.turno} &nbsp; 🏪 Loja: ${data.loja}</div>
</div>
<hr>
<div class="row"><span>💸 Sangrias:</span><span class="val">${fmt(sangrias)}</span></div>
<div class="row"><span>📝 Despesas:</span><span class="val">${fmt(despesas)}</span></div>
<div class="row"><span>💰 Vales:</span><span class="val">${fmt(vales)}</span></div>
<div class="row"><span>👥 À Receber:</span><span class="val">${fmt(receber)}</span></div>
<div class="row"><span>🔵 PIX CNPJ:</span><span class="val">${fmt(pixcnpj)}</span></div>
<div class="row"><span>💳 Crédito:</span><span class="val">${fmt(cartCred)}</span></div>
<div class="row"><span>💳 Débito:</span><span class="val">${fmt(cartDeb)}</span></div>
<div class="row"><span>💳 Alimentação:</span><span class="val">${fmt(cartAli)}</span></div>
<div class="row"><span>❌ Cancelamentos:</span><span class="val">${fmt(cancelamentos)}</span></div>
<div class="row"><span>👥 Clientes:</span><span class="val">${data.clientes || 0}</span></div>
<div class="row"><span>🍗 Frango Assado:</span><span class="val">${data.frango || 0}</span></div>
<hr>
<div class="row"><span>📋 Fechamento (contado):</span><span class="val">${fmt(fechamento)}</span></div>
<div class="row"><span>🖥️ Total Sistema:</span><span class="val">${fmt(sysTotal)}</span></div>
<hr>
<div class="row"><span>🎯 Saldo Esperado (contado):</span><span class="val">${fmt(saldoEsp)}</span></div>
<div class="row"><span>📐 Diferença:</span><span class="val" style="color:${diffColor}">${diffStr}</span></div>
<hr>
<div style="text-align:center;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>`;
}
function openPrintPreview(data, mode) {
const html = mode === 'confirm' ? buildConfirmHTML(data) : buildPrintHTML(data);
const html = mode === 'confirm' ? buildConfirmHTML(data)
: mode === 'report' ? buildReportHTML(data)
: buildPrintHTML(data);
// Remove preview anterior se existir
const existing = document.getElementById('print-preview-overlay');
if (existing) document.body.removeChild(existing);
@@ -1687,8 +1792,9 @@ function triggerWindowPrint() {
}
// ─── Recibo 80mm via Tauri (ESC/POS USB) ─────────────────────────────────
async function tauriRecibo() {
// Build flat totals for the Rust ReciboData struct
// ─── Recibo 80mm via Tauri (ESC/POS USB) ─────────────────────────────────
// Build data object shared by both ESC/POS and preview
function buildReciboData() {
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);
@@ -1701,9 +1807,11 @@ async function tauriRecibo() {
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'));
// 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;
const data = {
// SYS total = crédito + débito + alimentação + voucher pix + pixcnpj + vales + receber (NÃO inclui troco)
const sysTotal = cartCred + cartDeb + cartAli + cartPix + pixCnpjTotal + valesTotal + receberTotal;
// Diferença = fechamento - saldo_esperado - cancelamentos
const diferenca = fechamentoCounted - saldoEsp - cancelTotal;
return {
uuid: estado.uuid,
id_local: estado.id_local,
loja: estado.loja,
@@ -1713,8 +1821,11 @@ async function tauriRecibo() {
saldo_troco: parseNum(val('sys-troco')),
saldo_esperado: saldoEsp,
fechamento: fechamentoCounted,
diferenca: sysTotal - saldoEsp,
diferenca: diferenca,
sys_total: sysTotal,
fechamento_dinheiro: parseNum(val('f-fech-dinheiro')),
fechamento_cartoes: parseNum(val('f-fech-cartoes')),
fechamento_receber: parseNum(val('f-fech-receber')),
// Flat totals for ReciboData struct (Rust) — must be numbers
credito: cartCred,
debito: cartDeb,
@@ -1726,25 +1837,33 @@ async function tauriRecibo() {
vales: valesTotal,
areceber: receberTotal,
cancelamentos: cancelTotal,
// Full arrays for backend/storage
// Full arrays for preview
despesas_arr: 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||'' })),
pixcnpj_arr: 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 })),
receber_arr: 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||'' })),
cartoes: { credito: cartCred, debito: cartDeb, alimentacao: cartAli, pix: cartPix },
cartoes_credito: estado.cartoes.credito.map(r => r.valor || 0),
cartoes_debito: estado.cartoes.debito.map(r => r.valor || 0),
cartoes_alimentacao: estado.cartoes.alimentacao.map(r => r.valor || 0),
cartoes_pix: estado.cartoes.pix.map(r => r.valor || 0),
observacoes: estado.observacoes,
clientes: estado.clientes,
frango: estado.frango,
sync_status: estado.sync_status,
};
}
// Tenta ESC/POS via Tauri primeiro; se falhar, abre preview
async function tauriRecibo() {
const data = buildReciboData();
// Tenta ESC/POS via Tauri primeiro; se falhar, so lanca erro (preview tratado pelo caller)
try {
return await window.__TAURI__.core.invoke('imprimir_recibo', { data: JSON.stringify(data) });
} catch(e) {
console.warn('ESC/POS falhou, abrindo preview:', e);
openPrintPreview(data);
throw e;
console.warn('ESC/POS falhou:', e);
throw e; // re-lanca para o caller tratar o preview
}
}
@@ -1783,8 +1902,8 @@ function openConfirmModal() {
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 saldoEsp = parseNum(val('f-saldo-esperado'));
const sysTotal = (parseNum(val('sys-troco')) || 0) + cartCred + cartDeb + cartAli + cartPix + vales + receber + pixcnpj;
const diferenca = sysTotal - saldoEsp;
const sysTotal = cartCred + cartDeb + cartAli + cartPix + vales + receber + pixcnpj;
const diferenca = fechamento - saldoEsp - cancelamentos;
const diffClass = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
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>
@@ -1798,6 +1917,8 @@ function openConfirmModal() {
<div>💳 Débito: <strong>${fmt(cartDeb)}</strong></div>
<div>💳 Alimentação: <strong>${fmt(cartAli)}</strong></div>
<div>❌ Cancelamentos: <strong>${fmt(cancelamentos)}</strong></div>
<div>👥 Clientes: <strong>${estado.clientes || 0}</strong></div>
<div>🍗 Frango Assado: <strong>${estado.frango || 0}</strong></div>
<div>📋 Fechamento (contado): <strong>${fmt(fechamento)}</strong></div>
<div>🖥️ Total Sistema: <strong>${fmt(sysTotal)}</strong></div>
<hr style="border:none;border-top:1px dashed #b3d9f7;margin:6px 0">
@@ -1818,40 +1939,18 @@ document.getElementById('modal-confirm').addEventListener('click', e => { if (e.
// Print from confirm modal — abre preview idêntico ao modal de confirmação
document.getElementById('confirm-print').addEventListener('click', async () => {
// Usa buildReciboData para não duplicar lógica e ter todos os campos certos
let data;
try {
data = buildReciboData();
} catch(e) {
console.error('Erro ao montar dados do recibo:', e);
alert('Erro ao montar dados do recibo.');
return;
}
// Fecha modal APÓS gerar preview (não antes)
closeConfirmModal();
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 fechamentoCounted = parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber'));
const saldoEsp = parseNum(val('f-saldo-esperado'));
const sysTotal = (parseNum(val('sys-troco')) || 0) + cartCred + cartDeb + cartAli + cartPix + vales + receber + pixcnpj;
const diferenca = sysTotal - saldoEsp;
const data = {
loja: estado.loja, data: estado.data, operador: estado.operador, turno: estado.turno,
saldo_troco: parseNum(val('sys-troco')) || 0,
saldo_esperado: saldoEsp,
fechamento_dinheiro: parseNum(val('f-fech-dinheiro')),
fechamento_cartoes: parseNum(val('f-fech-cartoes')),
fechamento_receber: parseNum(val('f-fech-receber')),
fechamento: fechamentoCounted,
diferenca: diferenca,
sys_total: sysTotal,
despesas_arr: 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||'' })),
pixcnpj_arr: 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 })),
receber_arr: 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||'' })),
credito: cartCred, debito: cartDeb, alimentacao: cartAli,
voucher: cartPix,
cartoes_credito: estado.cartoes.credito.map(r => r.valor || 0),
cartoes_debito: estado.cartoes.debito.map(r => r.valor || 0),
cartoes_alimentacao: estado.cartoes.alimentacao.map(r => r.valor || 0),
cartoes_pix: estado.cartoes.pix.map(r => r.valor || 0),
observacoes: estado.observacoes,
};
openPrintPreview(data, 'confirm');
openPrintPreview(data, 'report');
});
// Confirm send
@@ -1882,26 +1981,24 @@ document.getElementById('confirm-send').addEventListener('click', async () => {
});
// ─── Buttons ───────────────────────────────────────────────────────────────
document.getElementById('btn-enviar').addEventListener('click', () => {
console.log('[DEBUG] btn-enviar clicado');
const operador = document.getElementById('f-operador')?.value;
const turno = document.getElementById('f-turno')?.value;
const loja = document.getElementById('f-loja')?.value;
console.log('[DEBUG] operador:', operador, 'turno:', turno, 'loja:', loja);
openConfirmModal();
});
document.getElementById('f-obs').addEventListener('input', e => { estado.observacoes = e.target.value; scheduleSave(); });
document.getElementById('f-clientes').addEventListener('input', e => { estado.clientes = parseInt(e.target.value) || 0; scheduleSave(); });
document.getElementById('f-frango').addEventListener('input', e => { estado.frango = parseInt(e.target.value) || 0; scheduleSave(); });
document.getElementById('btn-recibo').addEventListener('click', async () => {
const btn = document.getElementById('btn-recibo');
btn.disabled = true;
try {
// Tenta ESC/POS primeiro; se falhar, abre preview no navegador
await tauriRecibo();
// Se chegou aqui sem erro, ESC/POS funcionou
} catch(e) {
// ESC/POS falhou — openPrintPreview já foi chamado dentro de tauriRecibo
// Só mostra alerta se nem o preview abriu
console.warn('Recibo:', e);
console.warn('ESC/POS falhou:', e);
}
try {
const data = buildReciboData();
openPrintPreview(data, 'recibo');
} catch(e2) {
console.error('Preview falhou:', e2);
alert('Erro ao gerar preview do recibo.');
} finally {
btn.disabled = false;
}
@@ -1910,7 +2007,6 @@ document.getElementById('btn-recibo').addEventListener('click', async () => {
document.getElementById('btn-limpar').addEventListener('click', () => {
if (!confirm('Limpar rascunho? Esta acção não pode ser revertida.')) return;
localStorage.removeItem(LS_KEY);
// Reseta estado e re-renderiza sem reload
estado = {
uuid: crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(),
loja: estado.loja, data: estado.data, operador: '', turno: '',
@@ -1924,158 +2020,257 @@ document.getElementById('btn-limpar').addEventListener('click', () => {
setVal('f-fech-dinheiro', ''); setVal('f-fech-cartoes', ''); setVal('f-fech-receber', '');
setVal('f-saldo-esperado', ''); setVal('f-obs', '');
setVal('sys-troco', '');
calcular();
updateTotals();
renderAllTables();
syncUI();
showToast('Rascunho limpo.', 'info');
});
// ─── Dia Anterior — seleção de registro do Supabase ────────────────────────
const btnAnterior = document.getElementById('btn-anterior');
if (btnAnterior) {
btnAnterior.addEventListener('click', async () => {
console.log('[DEBUG] btn-anterior clicado');
const loja = val('f-loja') || 'União';
console.log('[DEBUG] btn-anterior loja:', loja);
const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core);
// Salva rascunho atual antes de tudo
await tauriSaveLocal();
// Calcula dia anterior
const prevDate = new Date(estado.data);
prevDate.setDate(prevDate.getDate() - 1);
const prevDateStr = prevDate.toISOString().split('T')[0];
// Confirmação explícita antes de trocar de dia
const confirmMsg = `ATENÇÃO: vai carregar o fechamento de ${prevDateStr}.\n\nO formulário atual será LIMPO caso não tenha enviado.\n\nContinuar?`;
if (!confirm(confirmMsg)) {
console.log('[DEBUG] btn-anterior cancelado pelo usuário');
return;
}
// Carrega lista de fechamentos recentes do Supabase com uuid
// Pega lista do Supabase
let recentList = [];
if (hasTauri) {
try {
recentList = await window.__TAURI__.core.invoke('sb_listar_recentes', { loja, limite: 20 });
const loja = val('f-loja') || 'União';
recentList = await window.__TAURI__.core.invoke('sb_listar_recentes', { loja, limite: 50 });
console.log('[DEBUG] sb_listar_recentes retornou:', recentList?.length, 'itens');
} catch(e) {
console.warn('sb_listar_recentes falhou:', e);
}
// Extrai datas únicas ordenadas + mapeamento data→id
const dateIdMap = {};
const dateList = [];
if (recentList && recentList.length > 0) {
recentList.forEach(item => {
if (item.data && !dateIdMap[item.data]) {
dateIdMap[item.data] = item.id;
dateList.push(item.data);
}
if (!recentList || recentList.length === 0) {
alert('Nenhum fechamento encontrado no servidor para esta loja.');
return;
}
// Mostra modal de seleção com datas
buildAnteriorModal(recentList);
});
dateList.sort().reverse();
}
console.log('[DEBUG] dateList:', dateList);
}
let selectedDate = null;
let selectedId = null;
if (dateList.length > 0) {
const msg = dateList.map((d, i) => `${i + 1}) ${d}`).join('\n');
const escolha = prompt(`Selecione o número do dia anterior:\n${msg}\n\n(Deixe em branco e clique Cancelar para ver só o dia anterior automático)`);
if (escolha !== null && escolha.trim() !== '') {
const idx = parseInt(escolha) - 1;
if (idx >= 0 && idx < dateList.length) {
selectedDate = dateList[idx];
selectedId = dateIdMap[selectedDate];
}
// Monta e abre o modal de seleção de dia anterior
function buildAnteriorModal(recentList) {
// Remove modal anterior
const old = document.getElementById('modal-anterior');
if (old) document.body.removeChild(old);
const overlay = document.createElement('div');
overlay.id = 'modal-anterior';
overlay.style.cssText = `
position:fixed;top:0;left:0;right:0;bottom:0;
background:rgba(0,0,0,0.6);z-index:99998;
display:flex;align-items:center;justify-content:center;
`;
const fmtDate = d => {
if (!d) return '';
const [y,m,day] = d.split('-');
return `${day}/${m}/${y}`;
};
const syncBadge = s => {
const colors = { synced: '#2e7d32', local: '#e65100', syncing: '#f57f17', error: '#c0272d' };
const labels = { synced: '✓ Sync', local: '⚠ Local', syncing: '⏳ Sync', error: '✗ Erro' };
return `<span style="background:${colors[s]||'#888'};color:#fff;padding:1px 5px;border-radius:3px;font-size:10px">${labels[s]||s||'?'}</span>`;
};
const rows = recentList.map((item, i) => `
<tr style="cursor:pointer" onclick="selectDiaAnterior(${i})">
<td style="padding:6px 8px;border-bottom:1px solid #eee;font-size:12px">${fmtDate(item.data)}</td>
<td style="padding:6px 8px;border-bottom:1px solid #eee;font-size:12px">${item.operador||'?'}</td>
<td style="padding:6px 8px;border-bottom:1px solid #eee;font-size:12px">${item.turno||'?'}</td>
<td style="padding:6px 8px;border-bottom:1px solid #eee;font-size:12px">${syncBadge(item.sync_status)}</td>
</tr>
`).join('');
overlay.innerHTML = `
<div style="background:#fff;border-radius:10px;padding:20px;max-width:480px;width:90%;max-height:80vh;overflow-y:auto">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h2 style="margin:0;font-size:16px">📅 Selecionar Dia Anterior</h2>
<button onclick="closeAnteriorModal()" style="background:none;border:none;font-size:18px;cursor:pointer">✕</button>
</div>
<p style="font-size:12px;color:#666;margin:0 0 10px">Clique em um registro para carregar. Dados do Supabase.</p>
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:#f5f5f5">
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#888">DATA</th>
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#888">OPERADOR</th>
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#888">TURNO</th>
<th style="padding:6px 8px;text-align:left;font-size:11px;color:#888">STATUS</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
`;
document.body.appendChild(overlay);
window._recentList = recentList;
}
function closeAnteriorModal() {
const m = document.getElementById('modal-anterior');
if (m) document.body.removeChild(m);
window._recentList = null;
}
// Usuário clicou numa linha — pede senha se configurada
window.selectDiaAnterior = async function(idx) {
const item = window._recentList[idx];
if (!item) return;
closeAnteriorModal();
const pw = config.admin_password;
if (pw) {
const input = prompt('🔐 Digite a senha admin para editar este registro:');
if (input !== pw) {
alert('Senha incorreta. Acesso negado.');
return;
}
}
// Se não escolheu data, usa dia anterior automático (sem id — carrega por data)
if (!selectedDate) {
selectedDate = prevDateStr;
}
console.log('[DEBUG] selectedDate:', selectedDate, 'selectedId:', selectedId);
const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core);
if (!hasTauri) { alert('Tauri não disponível.'); return; }
setVal('f-data', selectedDate);
estado.data = selectedDate;
estado.uuid = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString();
estado.id_local = null;
syncHeader();
// Tenta Supabase primeiro (com id se disponível), senão local
let rascunho = null;
if (selectedId) {
try {
rascunho = await window.__TAURI__.core.invoke('sb_buscar_por_id', { id: selectedId });
console.log('[DEBUG] sb_buscar_por_id retornou:', !!rascunho);
} catch(e) { console.warn('sb_buscar_por_id falhou:', e); }
// Primeiro tenta buscar por id (retorna registro completo)
let full = null;
if (item.id) {
try { full = await window.__TAURI__.core.invoke('sb_buscar_por_id', { id: item.id }); } catch(e) {}
}
if (!rascunho) {
try {
rascunho = await window.__TAURI__.core.invoke('sb_carregar_rascunho', { loja, data: selectedDate });
console.log('[DEBUG] sb_carregar_rascunho retornou:', !!rascunho);
} catch(e) { console.warn('sb_carregar_rascunho falhou:', e); }
// Fallback: buscar por uuid
if (!full && item.uuid) {
try { full = await window.__TAURI__.core.invoke('sb_buscar_por_uuid', { uuid: item.uuid }); } catch(e) {}
}
// Último fallback: carregar por loja+data
if (!full) {
const loja = val('f-loja') || 'União';
full = await window.__TAURI__.core.invoke('sb_carregar_rascunho', { loja, data: item.data });
}
if (!rascunho) {
try {
rascunho = await window.__TAURI__.core.invoke('carregar_rascunho', { loja, data: selectedDate });
console.log('[DEBUG] carregar_rascunho local retornou:', !!rascunho);
} catch(e) { console.warn('carregar_rascunho local falhou:', e); }
if (!full) { alert('Registro não encontrado.'); return; }
// Aplica dados ao estado
if (full.data) { setVal('f-data', full.data); estado.data = full.data; }
if (full.operador) { setVal('f-operador', full.operador); estado.operador = full.operador; }
if (full.turno) { setVal('f-turno', full.turno); estado.turno = full.turno; }
if (full.saldo_troco != null) setVal('sys-troco', fmtNum(full.saldo_troco));
if (full.saldo_esperado != null) setVal('f-saldo-esperado', fmtNum(full.saldo_esperado));
if (full.observacoes) { setVal('f-obs', full.observacoes); estado.observacoes = full.observacoes; }
// Tenta carregar campos aninhados do campo "dados" (jsonb)
const dados = full.dados || full;
if (dados.despesas && Array.isArray(dados.despesas)) {
estado.despesas = dados.despesas.map(d => ({ desc: d.desc||d.descricao||'', obs: d.obs||'', valor: parseNum(d.valor)||0 }));
}
if (dados.sangrias && Array.isArray(dados.sangrias)) {
estado.sangrias = dados.sangrias.map(s => ({ hora: s.hora||'', retirada: parseNum(s.retirada)||0, gerente: s.gerente||'' }));
}
if (dados.pixcnpj && Array.isArray(dados.pixcnpj)) {
estado.pixcnpj = dados.pixcnpj.map(p => ({ nome: p.nome||'', valor: parseNum(p.valor)||0 }));
}
if (dados.vales && Array.isArray(dados.vales)) {
estado.vales = dados.vales.map(v => ({ nome: v.nome||'', obs: v.obs||'', valor: parseNum(v.valor)||0 }));
}
if (dados.receber && Array.isArray(dados.receber)) {
estado.receber = dados.receber.map(r => ({ nome: r.nome||'', valor: parseNum(r.valor)||0 }));
}
if (dados.cancelamentos && Array.isArray(dados.cancelamentos)) {
estado.cancelamentos = dados.cancelamentos.map(c => ({ numero: c.numero||'', valor: parseNum(c.valor)||0, motivo: c.motivo||'' }));
}
if (dados.cartoes) {
const norm = arr => Array.isArray(arr) ? arr.map(v => ({ valor: (typeof v === 'object') ? (parseNum(v.valor)||0) : (parseNum(v)||0) })) : [];
estado.cartoes.credito = norm(dados.cartoes.credito);
estado.cartoes.debito = norm(dados.cartoes.debito);
estado.cartoes.alimentacao = norm(dados.cartoes.alimentacao);
estado.cartoes.pix = norm(dados.cartoes.pix);
}
if (rascunho) {
console.log('[DEBUG] rascunho carregado com sucesso');
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; }
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) {
const norm = (arr) => Array.isArray(arr) ? arr.map(v => ({ valor: (typeof v === 'object' && v !== null) ? (parseNum(v.valor) || 0) : (parseNum(v) || 0) })) : [];
estado.cartoes.credito = norm(rascunho.cartoes.credito);
estado.cartoes.debito = norm(rascunho.cartoes.debito);
estado.cartoes.alimentacao = norm(rascunho.cartoes.alimentacao);
estado.cartoes.pix = norm(rascunho.cartoes.pix);
}
} else {
console.log('[DEBUG] nenhum rascunho encontrado para esta data');
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', '');
}
estado.sync_status = rascunho ? 'loaded' : 'local';
// Marca como modo de edição — ao clicar Enviar vai fazer PATCH, não POST
estado.uuid = full.uuid || item.uuid || estado.uuid;
estado.id_local = full.id || item.id || null;
estado.sync_status = 'editing'; // flag: editar registro existente
PRE_FILL_TABLES.forEach(ensureOneRow);
Object.keys(TABLES).forEach(renderTable);
updateTotals();
syncUI();
scheduleSave();
console.log('[DEBUG] btn-anterior concluído');
});
} else {
console.error('[DEBUG] btn-anterior NÃO encontrado no DOM!');
}
document.getElementById('f-obs').addEventListener('input', e => { estado.observacoes = e.target.value; scheduleSave(); });
alert(`✅ Registro carregado!\n\n📅 ${item.data}\n👤 ${item.operador||'?'}${item.turno||'?'}\n\nAo clicar "Enviar Fechamento", o registro será ATUALIZADO no Supabase (não criado novamente).`);
} catch(e) {
console.error('Erro ao carregar registro:', e);
alert('Erro ao carregar registro: ' + (e?.message || e));
}
};
// Intercepta o envio — se sync_status=editing, faz PATCH
const _originalBtnEnviarHandler = () => {
const operador = val('f-operador').trim();
const turno = val('f-turno').trim();
const loja = val('f-loja').trim();
if (!operador || !turno || !loja) {
alert('⚠️ Preencha Operador, Turno e Loja antes de enviar.');
return;
}
openConfirmModal();
};
// Modifica btn-enviar para detectar modo editing
document.getElementById('btn-enviar').replaceWith(
Object.assign(document.createElement('button'), {
id: 'btn-enviar', className: 'btn', innerHTML: '💾 Enviar Fechamento',
onclick() {
const operador = val('f-operador').trim();
const turno = val('f-turno').trim();
const loja = val('f-loja').trim();
if (!operador || !turno || !loja) {
alert('⚠️ Preencha Operador, Turno e Loja antes de enviar.');
return;
}
if (estado.sync_status === 'editing') {
// Confirmação antes de atualizar
if (!confirm(`⚠️ ATENÇÃO: vai ATUALIZAR o registro existente no Supabase!\n\nUUID: ${estado.uuid}\n\nContinuar?`)) return;
salvarEdicaoSupabase();
} else {
openConfirmModal();
}
}
})
);
async function salvarEdicaoSupabase() {
const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core);
if (!hasTauri) { alert('Tauri não disponível.'); return; }
const btn = document.getElementById('btn-enviar');
if (btn) { btn.disabled = true; btn.textContent = '⏳ Salvando...'; }
try {
const data = buildReciboData();
// Remove campos que não devem ir no PATCH
delete data.uuid;
delete data.id_local;
const result = await window.__TAURI__.core.invoke('sb_atualizar_fechamento', {
uuid: estado.uuid,
updates: JSON.stringify(data)
});
console.log('[DEBUG] sb_atualizar_fechamento result:', result);
estado.sync_status = 'synced';
saveState();
alert('✅ Registro atualizado com sucesso no Supabase!');
} catch(e) {
console.error('Erro ao atualizar:', e);
alert('❌ Erro ao atualizar no Supabase:\n' + (e?.message || e));
} finally {
if (btn) { btn.disabled = false; btn.textContent = '💾 Enviar Fechamento'; }
}
}
// ─── Boot ─────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {