Compare commits

...

4 Commits

Author SHA1 Message Date
root 42aadcca2c fix: dados JSONB no PATCH, total_cancelamentos, corrige duplicados total_*
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-07-01 14:50:35 +00:00
root a8319d96a6 fix: parseNum strip 'R$', fmtMR/fmtBR mostram R$ 0,00 para zero
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-07-01 14:38:32 +00:00
root bf878545b3 fix: denylist移除arrays/objetos — 解决PATCH成功但字段不保存
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-07-01 14:17:38 +00:00
root 22cda312cf fix: remove whitelist do PATCH — Supabase valida colunas; adiciona edited_at/edited_by no payload
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-07-01 13:40:37 +00:00
2 changed files with 39 additions and 33 deletions
+16 -20
View File
@@ -250,32 +250,28 @@ impl SupabaseClient {
/// Atualiza campos específicos de um fechamento pelo id_fechamento. /// Atualiza campos específicos de um fechamento pelo id_fechamento.
pub fn atualizar_fechamento(&self, id_fechamento: &str, updates: &serde_json::Value) -> Result<Value, SupabaseError> { pub fn atualizar_fechamento(&self, id_fechamento: &str, updates: &serde_json::Value) -> Result<Value, SupabaseError> {
// Whitelist: só colunas que existem na tabela fechamentos_web. // Campos que o frontend envia mas NÃO existem como colunas na tabela.
// Campos de lista (vales, despesas, sangrias, etc.) vivem no JSONB `dados` // Arrays e objetos são guardados no JSONB `dados` — não em colunas escalar.
// e são tratados separadamente via salvar_listas / carregar_listas. static BLOCKED: &[&str] = &[
static ALLOWED: &[&str] = &[ "id", "uuid", "criado_em", "atualizado_em", "enviado_por",
"data", "loja", "turno", "operador", "ip_origem", "user_agent", "id_fechamento",
"saldo_troco", "saldo_credito", "saldo_debito", "saldo_alimentacao", // arrays e objetos que existem dentro do JSONB `dados`
"saldo_vales", "saldo_areceber", "vales", "despesas", "sangrias", "cancelamentos",
"total_cartao_credito", "total_cartao_debito", "total_cartao_alimentacao", "pixcnpj", "receber",
"total_cartao_pix", "total_pixcnpj", "cartoes_credito", "cartoes_debito",
"total_sangrias", "total_despesas", "total_vales", "cartoes_alimentacao", "cartoes_pix",
"total_areceber", "total_cancelamentos",
"fechamento_dinheiro", "fechamento_cartoes", "fechamento_areceber",
"clientes", "frango", "vendas", "saldo_esperado",
"observacoes", "edited_at", "edited_by",
]; ];
let filtered: serde_json::Map<String, serde_json::Value> = updates let map = updates.as_object().cloned().unwrap_or_default();
.as_object()
.cloned() // Remove campos bloqueados
.unwrap_or_default() let filtered: serde_json::Map<String, serde_json::Value> = map
.into_iter() .into_iter()
.filter(|(k, _)| ALLOWED.contains(&k.as_str())) .filter(|(k, _)| !BLOCKED.contains(&k.as_str()))
.collect(); .collect();
if filtered.is_empty() { if filtered.is_empty() {
return Err(SupabaseError::Api("Nenhum campo válido para atualizar".into())); return Err(SupabaseError::Api("Nenhum campo para atualizar".into()));
} }
let url = format!( let url = format!(
+23 -13
View File
@@ -613,7 +613,7 @@ const fmt = v => 'R$ ' + (parseFloat(v) || 0).toLocaleString('pt-BR', { minimumF
const parseNum = v => { const parseNum = v => {
// Already a number? Return directly (don't treat '.' as thousand separator) // Already a number? Return directly (don't treat '.' as thousand separator)
if (typeof v === 'number') return isNaN(v) ? 0 : v; if (typeof v === 'number') return isNaN(v) ? 0 : v;
const s = String(v || '0').trim(); const s = String(v || '0').trim().replace(/^R\$\s*/, ''); // strip currency prefix first
if (!s) return 0; if (!s) return 0;
// Brazilian format: 1.234,56 → 1234.56 // Brazilian format: 1.234,56 → 1234.56
// Strategy: if string has comma, treat as BRL (decimal comma) // Strategy: if string has comma, treat as BRL (decimal comma)
@@ -2707,7 +2707,8 @@ function openEditModal(full) {
// helper para fmt dinheiro // helper para fmt dinheiro
const fmtMR = v => { const fmtMR = v => {
const n = parseFloat(v||0); const n = parseFloat(v||0);
if (isNaN(n) || n === 0) return ''; if (isNaN(n)) return '';
if (n === 0) return 'R$ 0,00';
return 'R$ ' + n.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); return 'R$ ' + n.toLocaleString('pt-BR', { minimumFractionDigits: 2 });
}; };
@@ -2936,7 +2937,8 @@ function openEditModal(full) {
// Formata campo de dinheiro brasileiro // Formata campo de dinheiro brasileiro
function fmtBR(inp) { function fmtBR(inp) {
const v = parseNum(inp.value); const v = parseNum(inp.value);
if (isNaN(v) || v === 0) return ''; if (isNaN(v)) return '';
if (v === 0) return 'R$ 0,00';
return 'R$ ' + v.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); return 'R$ ' + v.toLocaleString('pt-BR', { minimumFractionDigits: 2 });
} }
@@ -3032,23 +3034,29 @@ window.salvarEdicaoModal = async function() {
saldo_debito: cartDeb_tot, saldo_debito: cartDeb_tot,
saldo_alimentacao: cartAli_tot, saldo_alimentacao: cartAli_tot,
saldo_vales: vales_tot, saldo_vales: vales_tot,
total_pixcnpj: pixcnpj_tot,
saldo_areceber: recv_tot, saldo_areceber: recv_tot,
// Totais // Totais
total_vales: vales_tot, total_vales: vales_tot,
total_despesas: desp_tot, total_despesas: desp_tot,
total_sangrias: sang_tot, total_sangrias: sang_tot,
total_areceber: recv_tot, total_pixcnpj: pixcnpj_tot,
total_areceber: recv_tot,
total_cancelamentos: canc_tot, total_cancelamentos: canc_tot,
// Lançamentos detalhados // Lançamentos detalhados (JSONB — gravados no campo `dados`)
vales: vales, dados: JSON.stringify({
despesas: despesas, cartoes,
sangrias: sangrias, vales,
cancelamentos: cancelamentos, despesas,
pixcnpj: pixcnpj, sangrias,
receber: receber, cancelamentos,
pixcnpj,
receber,
clientes: parseInt(inp('edit-clientes') || '0'),
frango: parseInt(inp('edit-frango') || '0'),
vendas: p(inp('edit-vendas')),
}),
// Contadores // Contadores
clientes: parseInt(inp('edit-clientes') || '0'), clientes: parseInt(inp('edit-clientes') || '0'),
@@ -3113,7 +3121,9 @@ window.salvarEdicaoConsulta = async function() {
frango: parseInt(inp('edit-frango') || '0'), frango: parseInt(inp('edit-frango') || '0'),
vendas: p(inp('edit-vendas')), vendas: p(inp('edit-vendas')),
saldo_esperado: p(inp('edit-esperado')), saldo_esperado: p(inp('edit-esperado')),
observacoes: inp('edit-obs') observacoes: inp('edit-obs'),
edited_at: new Date().toISOString(),
edited_by: full.operador || '',
}; };
const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core); const hasTauri = !!(window.__TAURI__ && window.__TAURI__.core);