diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index deac9ce..059807d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -97,6 +97,7 @@ fn main() { plugins::supabase::sb_listar_recentes, plugins::supabase::sb_buscar_por_id, plugins::supabase::sb_buscar_por_uuid, + plugins::supabase::sb_buscar_por_id_fechamento, plugins::supabase::sb_atualizar_fechamento, plugins::supabase::sb_salvar_listas, plugins::supabase::sb_carregar_listas, diff --git a/src-tauri/src/plugins/supabase.rs b/src-tauri/src/plugins/supabase.rs index e0fea7e..a2ce730 100644 --- a/src-tauri/src/plugins/supabase.rs +++ b/src-tauri/src/plugins/supabase.rs @@ -193,7 +193,7 @@ impl SupabaseClient { } } - /// Busca um fechamento pelo UUID. + /// Busca um fechamento por UUID (campo "id" do registro). pub fn buscar_por_uuid(&self, uuid: &str) -> Result, SupabaseError> { let url = format!( "{}/rest/v1/fechamentos_web?uuid=eq.{}&select=*", @@ -220,6 +220,33 @@ impl SupabaseClient { } } + /// Busca um fechamento pelo id_fechamento (ex: uniao_2026-06-24_silanea). + pub fn buscar_por_id_fechamento(&self, id_fechamento: &str) -> Result, SupabaseError> { + let url = format!( + "{}/rest/v1/fechamentos_web?id_fechamento=eq.{}&select=*", + self.base_url, urlencoding::encode(id_fechamento) + ); + + let resp = self + .http + .get(&url) + .headers(self.headers(false)) + .send()?; + + if resp.status().is_success() { + let arr: Vec = 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 { let url = format!( @@ -248,7 +275,7 @@ impl SupabaseClient { } pub fn listar_recentes(&self, loja: &str, limite: i64) -> Result, 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", + "{}/rest/v1/fechamentos_web?loja=eq.{}&order=data.desc,atualizado_em.desc&limit={}&select=id,uuid,id_fechamento,data,operador,turno,saldo_troco,saldo_esperado,fechamento_dinheiro,fechamento_cartoes,fechamento_areceber,diferenca,status_diferenca,observacoes,atualizado_em", self.base_url, urlencoding::encode(loja), limite, @@ -409,9 +436,9 @@ pub fn sb_listar_recentes( #[tauri::command] pub fn sb_buscar_por_id( sb: State<'_, SupabaseClient>, - id: i64, + id: String, ) -> Result, SupabaseError> { - sb.buscar_por_id(id) + sb.buscar_por_uuid(&id) } #[tauri::command] @@ -422,6 +449,15 @@ pub fn sb_buscar_por_uuid( sb.buscar_por_uuid(&uuid) } +/// Busca um fechamento pelo id_fechamento (ex: uniao_2026-06-24_silanea). +#[tauri::command] +pub fn sb_buscar_por_id_fechamento( + sb: State<'_, SupabaseClient>, + id_fechamento: String, +) -> Result, SupabaseError> { + sb.buscar_por_id_fechamento(&id_fechamento) +} + #[tauri::command] pub fn sb_atualizar_fechamento( sb: State<'_, SupabaseClient>, diff --git a/src/index.html b/src/index.html index 7aa4295..fce0a74 100644 --- a/src/index.html +++ b/src/index.html @@ -2064,20 +2064,17 @@ document.getElementById('btn-limpar').addEventListener('click', () => { showToast('Rascunho limpo.', 'info'); }); -// ─── Dia Anterior — seleção de registro do Supabase ──────────────────────── +// ─── Dia Anterior — seleção + consulta + edição de registros do Supabase ─── const btnAnterior = document.getElementById('btn-anterior'); if (btnAnterior) { btnAnterior.addEventListener('click', async () => { - console.log('[DEBUG] btn-anterior clicado'); const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core); - // Pega lista do Supabase let recentList = []; if (hasTauri) { try { 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); } @@ -2088,19 +2085,17 @@ if (btnAnterior) { return; } - // Mostra modal de seleção com datas - buildAnteriorModal(recentList); + buildConsultaModal(recentList); }); } -// Monta e abre o modal de seleção de dia anterior -function buildAnteriorModal(recentList) { - // Remove modal anterior - const old = document.getElementById('modal-anterior'); +// Monta o modal de consulta — lista de fechamentos do Supabase +function buildConsultaModal(recentList) { + const old = document.getElementById('modal-consulta'); if (old) document.body.removeChild(old); const overlay = document.createElement('div'); - overlay.id = 'modal-anterior'; + overlay.id = 'modal-consulta'; overlay.style.cssText = ` position:fixed;top:0;left:0;right:0;bottom:0; background:rgba(0,0,0,0.6);z-index:99998; @@ -2112,35 +2107,49 @@ function buildAnteriorModal(recentList) { const [y,m,day] = d.split('-'); return `${day}/${m}/${y}`; }; + const fmtMoney = v => v != null ? `R$ ${parseFloat(v).toFixed(2).replace('.', ',')}` : 'R$ 0,00'; const syncBadge = s => { const colors = { synced: '#2e7d32', local: '#e65100', syncing: '#f57f17', error: '#c0272d' }; const labels = { synced: '✓ Sync', local: '⚠ Local', syncing: '⏳ Sync', error: '✗ Erro' }; return `${labels[s]||s||'?'}`; }; + const statusBadge = s => { + if (s === 'ok') return '✓ OK'; + if (s === 'sobrando') return '⚠ SOBRANDO'; + if (s === 'faltando') return '⚠ FALTANDO'; + return s || '?'; + }; - const rows = recentList.map((item, i) => ` - + const rows = recentList.map((item, i) => { + const total = (parseFloat(item.fechamento_dinheiro)||0) + (parseFloat(item.fechamento_cartoes)||0) + (parseFloat(item.fechamento_areceber)||0); + return ` ${fmtDate(item.data)} ${item.operador||'?'} ${item.turno||'?'} + ${fmtMoney(total)} + ${fmtMoney(item.saldo_esperado)} + ${statusBadge(item.status_diferenca)} ${syncBadge(item.sync_status)} - - `).join(''); + `; + }).join(''); overlay.innerHTML = ` -
+
-

📅 Selecionar Dia Anterior

- +

📅 Consultar Fechamentos Anteriores

+
-

Clique em um registro para carregar. Dados do Supabase.

+

Clique em um registro para ver o relatório e, se necessário, editar campos com senha admin.

- + + + + ${rows} @@ -2152,102 +2161,275 @@ function buildAnteriorModal(recentList) { window._recentList = recentList; } -function closeAnteriorModal() { - const m = document.getElementById('modal-anterior'); +function closeConsultaModal() { + const m = document.getElementById('modal-consulta'); if (m) document.body.removeChild(m); window._recentList = null; + window._currentConsulta = null; } -// Usuário clicou numa linha — pede senha se configurada -window.selectDiaAnterior = async function(idx) { +// Abre o relatório de um registro selecionado +window.openConsultaRegistro = async function(idx) { const item = window._recentList[idx]; if (!item) return; - closeAnteriorModal(); + closeConsultaModal(); + + const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core); + if (!hasTauri) { alert('Tauri não disponível.'); return; } + + let full = null; + try { + if (item.uuid) { + try { full = await window.__TAURI__.core.invoke('sb_buscar_por_uuid', { uuid: item.uuid }); } catch(e) {} + } + if (!full && item.id_fechamento) { + try { full = await window.__TAURI__.core.invoke('sb_buscar_por_id_fechamento', { id_fechamento: item.id_fechamento }); } catch(e) {} + } + if (!full && item.id) { + try { full = await window.__TAURI__.core.invoke('sb_buscar_por_id', { id: String(item.id) }); } catch(e) {} + } + } catch(e) { + console.warn('Erro buscando registro:', e); + } + + if (!full) { alert('Registro não encontrado.'); return; } + + window._currentConsulta = full; + buildRelatorioModal(full); +}; + +// Monta o modal de relatório (readonly) com opção de editar +function buildRelatorioModal(full) { + const old = document.getElementById('modal-relatorio'); + if (old) document.body.removeChild(old); + + const overlay = document.createElement('div'); + overlay.id = 'modal-relatorio'; + 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 fmt = v => v != null ? parseFloat(v).toFixed(2).replace('.', ',') : '0,00'; + const fmtMoney = v => `R$ ${fmt(v)}`; + + const dados = full.dados || {}; + + // Campos do sistema + const troco = parseFloat(full.saldo_troco || 0); + const credito = parseFloat(full.saldo_credito || 0); + const debito = parseFloat(full.saldo_debito || 0); + const alimentacao = parseFloat(full.saldo_alimentacao || 0); + const vales = parseFloat(full.saldo_vales || 0); + const pixCartao = parseFloat(full.total_cartao_pix || 0); + const pixCnpj = parseFloat(full.total_pixcnpj || 0); + const sysTotal = troco + credito + debito + alimentacao + vales + (parseFloat(full.saldo_areceber)||0) + pixCartao + pixCnpj; + + // Campos contados + const fd = parseFloat(full.fechamento_dinheiro || 0); + const fc = parseFloat(full.fechamento_cartoes || 0); + const fr = parseFloat(full.fechamento_areceber || 0); + const fechamentoContado = fd + fc + fr; + + // Despesas e sangrias + const totalDespesas = parseFloat(full.total_despesas || 0); + const totalSangrias = parseFloat(full.total_sangrias || 0); + const totalCancelamentos = parseFloat(full.total_cancelamentos || 0); + + // Esperado e diferença + const saldoEsperado = parseFloat(full.saldo_esperado || 0); + const diferenca = parseFloat(full.diferenca || 0); + const statusDif = full.status_diferenca || '?'; + + const diffClass = statusDif === 'ok' ? '#2e7d32' : (statusDif === 'sobrando' ? '#e65100' : '#c0272d'); + const diffLabel = statusDif === 'ok' ? '✓ OK' : (statusDif === 'sobrando' ? '⚠ SOBRANDO' : (statusDif === 'faltando' ? '⚠ FALTANDO' : statusDif)); + + const operador = full.operador || '?'; + const turno = full.turno || '?'; + const data = full.data || '?'; + const loja = full.loja || '?'; + const obs = full.observacoes || ''; + const clientes = full.clientes || 0; + const frango = full.frango || 0; + const atualizado = full.atualizado_em ? new Date(full.atualizado_em).toLocaleString('pt-BR') : '?'; + + const totalGeral = fd + fc + fr; + + overlay.innerHTML = ` +
+
+

🧾 Relatório — ${loja.toUpperCase()}

+ +
+ +
+
📅 Data: ${data}   Turno: ${turno}   Operador: ${operador}
+
Atualizado: ${atualizado}
+
+ +
+
FECHAMENTO DE CAIXA
+
+
Troco (+)${fmtMoney(troco)}
+
Credito (+)${fmtMoney(credito)}
+
Debito (+)${fmtMoney(debito)}
+
Alimentacao (+)${fmtMoney(alimentacao)}
+
Vales (+)${fmtMoney(vales)}
+
A Receber (+)${fmtMoney(parseFloat(full.saldo_areceber||0))}
+
PIX Cartao (+)${fmtMoney(pixCartao)}
+
PIX CNPJ (+)${fmtMoney(pixCnpj)}
+
TOTAL SISTEMA${fmtMoney(sysTotal)}
+
+
+ +
+
SALDO DE CAIXA (CONTADO)
+
+
Dinheiro${fmtMoney(fd)}
+
Cartoes${fmtMoney(fc)}
+
A Receber${fmtMoney(fr)}
+
TOTAL CONTADO${fmtMoney(totalGeral)}
+
+
+ +
+
RESULTADO
+
+
Saldo Esperado${fmtMoney(saldoEsperado)}
+
Cancelamentos- ${fmtMoney(totalCancelamentos)}
+
+ Diferenca + ${fmtMoney(Math.abs(diferenca))} ${diferenca >= 0 ? '(+)' : '(-)'} +
+
${diffLabel}
+
+
+ +
+
DEMAIS TOTAIS
+
+
Total Sangrias${fmtMoney(totalSangrias)}
+
Total Despesas${fmtMoney(totalDespesas)}
+
Clientes${clientes}
+
Frango Assado${frango}
+
+
+ + ${obs ? `
Obs: ${obs}
` : ''} + + + +
+ + + +
+
+ `; + + document.body.appendChild(overlay); + window._editMode = false; +} + +// Fecha o modal de relatório +function closeRelatorioModal() { + const m = document.getElementById('modal-relatorio'); + if (m) document.body.removeChild(m); + window._currentConsulta = null; + window._editMode = false; +} + +// Toggle modo de edição — pede senha admin +window.toggleEditConsulta = async function() { + if (window._editMode) { + // Cancela edição + window._editMode = false; + document.getElementById('edit-section').style.display = 'none'; + document.getElementById('btn-salvar-consulta').style.display = 'none'; + document.getElementById('btn-edit-consulta').textContent = '✏️ Editar (senha)'; + return; + } const pw = config.admin_password; + let ok = true; if (pw) { const input = prompt('🔐 Digite a senha admin para editar este registro:'); - if (input !== pw) { - alert('Senha incorreta. Acesso negado.'); - return; - } + ok = (input === pw); + if (!ok) { alert('Senha incorreta.'); return; } } + window._editMode = true; + document.getElementById('edit-section').style.display = 'block'; + document.getElementById('btn-salvar-consulta').style.display = 'block'; + document.getElementById('btn-edit-consulta').textContent = '✕ Cancelar'; +}; + +// Salva edições de volta ao Supabase +window.salvarEdicaoConsulta = async function() { + const full = window._currentConsulta; + if (!full) return; + + const newEsperado = parseNum(document.getElementById('edit-esperado').value); + const newObs = document.getElementById('edit-obs').value; + + const updates = { + saldo_esperado: newEsperado, + observacoes: newObs + }; + const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core); if (!hasTauri) { alert('Tauri não disponível.'); return; } try { - // 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) {} - } - // 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 }); + let uuid = full.uuid; + if (!uuid && full.id_fechamento) { + // Buscar uuid pelo id_fechamento primeiro + const found = await window.__TAURI__.core.invoke('sb_buscar_por_id_fechamento', { id_fechamento: full.id_fechamento }); + if (found && found.uuid) uuid = found.uuid; } - if (!full) { alert('Registro não encontrado.'); return; } + if (!uuid) { alert('UUID do registro não encontrado. Não é possível atualizar.'); 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); - } - - // 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(); - - 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).`); + await window.__TAURI__.core.invoke('sb_atualizar_fechamento', { uuid, updates }); + alert('✅ Atualizado com sucesso!'); + closeRelatorioModal(); } catch(e) { - console.error('Erro ao carregar registro:', e); - alert('Erro ao carregar registro: ' + (e?.message || e)); + console.error('Erro ao salvar:', e); + alert('Erro ao salvar: ' + (e?.message || e)); } }; +// Imprime o relatório (abre janela de impressão do navegador) +window.printRelatorio = function() { + const inner = document.getElementById('relatorio-inner'); + if (!inner) return; + const Win = window.open('', '', 'width=600,height=700'); + Win.document.write(`Relatorio Fechamento${inner.innerHTML}`); + Win.document.close(); + Win.focus(); + setTimeout(() => { Win.print(); Win.close(); }, 500); +}; + // Intercepta o envio — se sync_status=editing, faz PATCH const _originalBtnEnviarHandler = () => { const operador = val('f-operador').trim();
DATA OPERADOR TURNOSTATUSFECHAMENTOESPERADOSTATUSSYNC