Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00fa17fa52 | |||
| 5e6f8b09a9 | |||
| 9ac4eb7042 | |||
| 2b8fa98ed7 | |||
| 77e927fef0 | |||
| 3f6b2ae6e4 | |||
| 35dcd27dda |
@@ -36,10 +36,10 @@ fn load_config() -> (String, String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback: tenta variáveis de ambiente
|
// Fallback 3: chaves embedadas no binário (funciona em qualquer PC sem config)
|
||||||
(
|
(
|
||||||
std::env::var("SUPABASE_ANON_KEY").unwrap_or_default(),
|
"sb_publishable_NCUGwstslOvVt5JtGxKB9A_p-BERG57".to_string(),
|
||||||
std::env::var("SUPABASE_SERVICE_KEY").unwrap_or_default(),
|
"sb_secret_T6eHIKJDGaH53HMTG6UUKa_6ou6KfEW".to_string(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,10 +322,9 @@ impl ReciboData {
|
|||||||
out.extend(escpos::divider());
|
out.extend(escpos::divider());
|
||||||
|
|
||||||
// ── DIFERENÇA ──
|
// ── DIFERENÇA ──
|
||||||
// Usa self.diferenca que o frontend calcula como sys_total - saldo_esperado
|
// Diferença = total_fechamento - saldo_esperado - cancelamentos
|
||||||
// (não recalcula de fechamento, que é o valor contado e não deve ser usado na diferença)
|
|
||||||
out.extend(escpos::line_bold("DIFERENCA"));
|
out.extend(escpos::line_bold("DIFERENCA"));
|
||||||
let diff = self.diferenca;
|
let diff = self.fechamento - self.saldo_esperado - self.cancelamentos;
|
||||||
let diff_str = if diff >= 0.0 {
|
let diff_str = if diff >= 0.0 {
|
||||||
format!("+{}", self.fmt_money(diff))
|
format!("+{}", self.fmt_money(diff))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,29 @@ use std::time::Duration;
|
|||||||
use tauri::State;
|
use tauri::State;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Remove acentos de uma string (ex: "União" → "Uniao")
|
||||||
|
fn sem_acento(s: &str) -> String {
|
||||||
|
s.replace("ã", "a")
|
||||||
|
.replace("á", "a")
|
||||||
|
.replace("à", "a")
|
||||||
|
.replace("â", "a")
|
||||||
|
.replace("é", "e")
|
||||||
|
.replace("è", "e")
|
||||||
|
.replace("ê", "e")
|
||||||
|
.replace("í", "i")
|
||||||
|
.replace("ì", "i")
|
||||||
|
.replace("î", "i")
|
||||||
|
.replace("õ", "o")
|
||||||
|
.replace("ó", "o")
|
||||||
|
.replace("ò", "o")
|
||||||
|
.replace("ô", "o")
|
||||||
|
.replace("ú", "u")
|
||||||
|
.replace("ù", "u")
|
||||||
|
.replace("û", "u")
|
||||||
|
.replace("ç", "c")
|
||||||
|
.replace("ñ", "n")
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum SupabaseError {
|
pub enum SupabaseError {
|
||||||
@@ -69,58 +92,75 @@ impl SupabaseClient {
|
|||||||
|
|
||||||
/// Salva/atualiza um fechamento na tabela `fechamentos_web`.
|
/// Salva/atualiza um fechamento na tabela `fechamentos_web`.
|
||||||
pub fn salvar_fechamento(&self, estado: &serde_json::Value) -> Result<Value, SupabaseError> {
|
pub fn salvar_fechamento(&self, estado: &serde_json::Value) -> Result<Value, SupabaseError> {
|
||||||
let uuid = estado
|
// Normaliza loja e turno para minúsculo (CHECK constraints do Postgres)
|
||||||
.get("uuid")
|
let loja = estado
|
||||||
|
.get("loja")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("");
|
.map(|s| sem_acento(&s.to_lowercase()).replace(" ", "_"))
|
||||||
|
.unwrap_or_else(|| "uniao".to_string());
|
||||||
|
let data = estado.get("data").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let operador = estado.get("operador").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let id_fechamento = format!("{}_{}_{}", loja, data, operador)
|
||||||
|
.to_lowercase()
|
||||||
|
.replace(" ", "_");
|
||||||
|
let turno = estado
|
||||||
|
.get("turno")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(|s| sem_acento(&s.to_lowercase()))
|
||||||
|
.unwrap_or_else(|| "manha".to_string());
|
||||||
|
|
||||||
let payload = serde_json::json!({
|
let payload = serde_json::json!({
|
||||||
"uuid": uuid,
|
"id_fechamento": id_fechamento,
|
||||||
"loja": estado["loja"],
|
"loja": loja,
|
||||||
"data": estado["data"],
|
"data": data,
|
||||||
"operador": estado["operador"],
|
"operador": operador,
|
||||||
"turno": estado["turno"],
|
"turno": turno,
|
||||||
"saldo_troco": estado["saldo_troco"],
|
"saldo_troco": estado.get("saldo_troco").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
"saldo_esperado": estado["saldo_esperado"],
|
"saldo_credito": estado.get("credito").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
"fechamento": estado["fechamento"],
|
"saldo_debito": estado.get("debito").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"saldo_alimentacao": estado.get("alimentacao").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"saldo_vales": estado.get("vales").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"saldo_areceber": estado.get("areceber").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"saldo_pixcnpj": estado.get("pixcnpj").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"fechamento_dinheiro": estado.get("fd").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"fechamento_cartoes": estado.get("fc").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"fechamento_areceber": estado.get("fr").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_sangrias": estado.get("total_sangrias").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_despesas": estado.get("total_despesas").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_cancelamentos": estado.get("total_cancelamentos").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_vales": estado.get("total_vales").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_pixcnpj": estado.get("total_pixcnpj").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_areceber": estado.get("total_areceber").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_cartao_credito": estado.get("total_cartao_credito").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_cartao_debito": estado.get("total_cartao_debito").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_cartao_alimentacao": estado.get("total_cartao_alimentacao").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"total_cartao_pix": estado.get("total_cartao_pix").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
"saldo_esperado": estado.get("saldo_esperado").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
"dados": estado,
|
"dados": estado,
|
||||||
"observacoes": "rascunho",
|
"observacoes": estado.get("observacoes").and_then(|v| v.as_str()).unwrap_or(""),
|
||||||
|
"clientes": estado.get("clientes").and_then(|v| v.as_i64()).unwrap_or(0),
|
||||||
|
"frango": estado.get("frango").and_then(|v| v.as_i64()).unwrap_or(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
let _url = format!(
|
// Primeiro tenta INSERT (upsert via Prefer)
|
||||||
"{}/rest/v1/fechamentos_web?uuid=eq.{}&select=id",
|
|
||||||
self.base_url, uuid
|
|
||||||
);
|
|
||||||
|
|
||||||
// Upsert via POST com Prefer: resolution=merge-duplicates
|
|
||||||
let resp = self
|
let resp = self
|
||||||
.http
|
.http
|
||||||
.post(&format!("{}/rest/v1/rpc/upsert_fechamento", self.base_url))
|
.post(&format!("{}/rest/v1/fechamentos_web", self.base_url))
|
||||||
.headers(self.headers(true))
|
.headers(self.headers(true))
|
||||||
|
.header("Prefer", "resolution=merge-duplicates")
|
||||||
.json(&payload)
|
.json(&payload)
|
||||||
.send()?;
|
.send()?;
|
||||||
|
|
||||||
if resp.status().is_success() {
|
let status = resp.status();
|
||||||
Ok(serde_json::json!({ "ok": true, "uuid": uuid }))
|
if status.is_success() || status.as_u16() == 409 {
|
||||||
|
Ok(serde_json::json!({ "ok": true, "id_fechamento": id_fechamento }))
|
||||||
} else {
|
} else {
|
||||||
let body = resp.text().unwrap_or_default();
|
let body = resp.text().unwrap_or_default();
|
||||||
// Fallback: tenta POST direto na tabela
|
Err(SupabaseError::Api(format!(
|
||||||
let resp2 = self
|
"HTTP {}: {}",
|
||||||
.http
|
status.as_u16(),
|
||||||
.post(&format!("{}/rest/v1/fechamentos_web", self.base_url))
|
body
|
||||||
.headers(self.headers(true))
|
)))
|
||||||
.header("Prefer", "resolution=merge-duplicates")
|
|
||||||
.json(&payload)
|
|
||||||
.send()?;
|
|
||||||
if resp2.status().is_success() {
|
|
||||||
Ok(serde_json::json!({ "ok": true, "uuid": uuid }))
|
|
||||||
} else {
|
|
||||||
Err(SupabaseError::Api(format!(
|
|
||||||
"HTTP {}: {}",
|
|
||||||
resp2.status().as_u16(),
|
|
||||||
body
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
-3
@@ -873,9 +873,9 @@ function updateTotals() {
|
|||||||
const fr = receberTotal;
|
const fr = receberTotal;
|
||||||
setText('fech-total', fmt(fd + fc + fr));
|
setText('fech-total', fmt(fd + fc + fr));
|
||||||
|
|
||||||
// Diferença = Saldo de Caixa (Sistema) - saldo_esperado - cancelamentos
|
// Diferença = total_fechamento - saldo_esperado - cancelamentos
|
||||||
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 diff = sysTotal - parseNum(val('f-saldo-esperado')) - cancelTotal;
|
const diff = (fd + fc + fr) - parseNum(val('f-saldo-esperado')) - cancelTotal;
|
||||||
const difEl = document.getElementById('diferenca');
|
const difEl = document.getElementById('diferenca');
|
||||||
const badgeEl = document.getElementById('dif-badge');
|
const badgeEl = document.getElementById('dif-badge');
|
||||||
if (difEl) {
|
if (difEl) {
|
||||||
@@ -1059,6 +1059,18 @@ const LS_KEY = 'fc_v7';
|
|||||||
const CFG_KEY = 'fc_cfg';
|
const CFG_KEY = 'fc_cfg';
|
||||||
|
|
||||||
function buildEstadoForBackend() {
|
function buildEstadoForBackend() {
|
||||||
|
// Calcula totais das tabelas (mesma lógica do updateTotals)
|
||||||
|
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 pixcnpjTotal = estado.pixcnpj.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 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);
|
||||||
|
|
||||||
// Monta o objeto exactly as o backend espera (storage.rs → salvar_fechamento)
|
// Monta o objeto exactly as o backend espera (storage.rs → salvar_fechamento)
|
||||||
return {
|
return {
|
||||||
uuid: estado.uuid,
|
uuid: estado.uuid,
|
||||||
@@ -1067,9 +1079,31 @@ function buildEstadoForBackend() {
|
|||||||
data: estado.data,
|
data: estado.data,
|
||||||
operador: estado.operador,
|
operador: estado.operador,
|
||||||
turno: estado.turno,
|
turno: estado.turno,
|
||||||
saldo_troco: parseNum(val('sys-troco')), // troco do sistema
|
saldo_troco: parseNum(val('sys-troco')),
|
||||||
saldo_esperado: parseNum(val('f-saldo-esperado')),
|
saldo_esperado: parseNum(val('f-saldo-esperado')),
|
||||||
fechamento: parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber')),
|
fechamento: parseNum(val('f-fech-dinheiro')) + parseNum(val('f-fech-cartoes')) + parseNum(val('f-fech-receber')),
|
||||||
|
// Saldos de cartões (do Sys / Saldo de Caixa)
|
||||||
|
credito: parseNum(val('sys-credito')) || cartCred,
|
||||||
|
debito: parseNum(val('sys-debito')) || cartDeb,
|
||||||
|
alimentacao: parseNum(val('sys-alimentacao')) || cartAli,
|
||||||
|
pixcnpj: parseNum(val('sys-pixcnpj')) || pixcnpjTotal,
|
||||||
|
vales: parseNum(val('sys-vales')) || valesTotal,
|
||||||
|
receber: parseNum(val('sys-receber')) || receberTotal,
|
||||||
|
// Totais para colunas SQL
|
||||||
|
total_sangrias: sangriasTotal,
|
||||||
|
total_despesas: despesasTotal,
|
||||||
|
total_pixcnpj: pixcnpjTotal,
|
||||||
|
total_vales: valesTotal,
|
||||||
|
total_areceber: receberTotal,
|
||||||
|
total_cancelamentos: cancelTotal,
|
||||||
|
total_cartao_credito: cartCred,
|
||||||
|
total_cartao_debito: cartDeb,
|
||||||
|
total_cartao_alimentacao: cartAli,
|
||||||
|
total_cartao_pix: cartPix,
|
||||||
|
// Fechamento contado
|
||||||
|
fd: parseNum(val('f-fech-dinheiro')),
|
||||||
|
fc: parseNum(val('f-fech-cartoes')),
|
||||||
|
fr: parseNum(val('f-fech-receber')),
|
||||||
despesas: 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: estado.sangrias.map(r => ({ hora: r.hora || '', retirada: r.retirada || 0, gerente: r.gerente || '' })),
|
sangrias: estado.sangrias.map(r => ({ hora: r.hora || '', retirada: r.retirada || 0, gerente: r.gerente || '' })),
|
||||||
pixcnpj: estado.pixcnpj.map(r => ({ nome: r.nome || '', valor: r.valor || 0 })),
|
pixcnpj: estado.pixcnpj.map(r => ({ nome: r.nome || '', valor: r.valor || 0 })),
|
||||||
|
|||||||
Reference in New Issue
Block a user