Compare commits

...

26 Commits

Author SHA1 Message Date
root bd867e8419 v1.3.21: PDF/envio - CONTADORES no final do buildConfirmHTML
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 14:41:43 +00:00
root da861ef964 v1.3.20: Diferença tela = SaldoCaixa(Sistema) - SaldoEsperado - Cancelamentos; CONTADORES no final do Recibo 80mm
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 14:32:25 +00:00
root 8e642f2e0c v1.3.18: SaldoCaixa(Sistema) agora inclui TROCO. Diferença = SaldoCaixa(Sistema) - SaldoEsperado - Cancelamentos
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 14:02:41 +00:00
root 7473db6b1f Revert "v1.3.16: Diferença=SaldoCaixa(Sistema)-SaldoEsperado-Cancelamentos; nomes padronizados"
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
This reverts commit 13489d58cf.
2026-06-26 13:55:23 +00:00
root 13489d58cf v1.3.16: Diferença=SaldoCaixa(Sistema)-SaldoEsperado-Cancelamentos; nomes padronizados
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:52:34 +00:00
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
root b47d7766ad v1.3.10: relatório completo idêntico ao PDF — todas seções, tabela cartões 4 colunas, saldo sistema, fechamento contado, diff sys_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-06-26 03:24:27 +00:00
root 78fa778197 v1.3.9: dif ESC/POS usa self.diferenca (sys_total - saldo_esperado)
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:11:16 +00:00
root 5e4340adca v1.3.7: só nomenclatura — Fechamento de Caixa / Saldo de Caixa
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 02:57:34 +00:00
root c01133f39c v1.3.6: nomenclatura trocada; relatórios com todos os lançamentos detalhados por seção
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 02:52:08 +00:00
root a6b295a6d0 v1.3.5: to_escpos completo — totais por seção, diferenca via self.diferenca; confirm antes de dia anterior; sem senha
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 02:41:37 +00:00
root f850831e98 fix: buildPrintHTML usa data.diferenca (sys_total - saldo_esperado); btn-anterior null-check robusto
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 02:23:57 +00:00
root e70f31e00f debug: btn-anterior logs; cartoes.norm guard
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 01:44:15 +00:00
root 79e1dbd645 fix: capabilities Tauri 2 dialog; imprimir_recibo {data}; cartoes.map guard; debug logs
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-25 17:44:22 +00:00
root cce8f3df60 fix: newEstado is not defined — reset estado inline; clear form fields on limpar
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-25 17:30:07 +00:00
root 318d44e934 fix: modal confirm usa sysTotal pra diferenca, nao so sys-troco
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-25 16:38:26 +00:00
root bfbcd89be9 fix: diferenca usa sys_total; novo layout 80mm sem ENTRADAS; confirm 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-25 14:32:05 +00:00
root 1848c1aa35 fix: diferenca no recibo - TOTAL agora usa total_operado, diferenca = fechamento - saldo_esperado
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
- Adicionado campo total_operado (credito+debito+alimentacao+voucher+pixcnpj)
- WHITE section: Fechamento mostra total_operado (era fechamento contado)
- BLACK section: diferenca usa fechamento - saldo_esperado (nao mais self.diferenca)
- Confirm modal: diferenca usava sysTroco - saldoEsp (errado), corrigido
- buildPrintHTML: diferenca usa total_operado
2026-06-25 13:22:03 +00:00
root 9176b764fd feat: alinha recibo ESC/POS com formato do screenshot
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
ReciboData agora recebe arrays (sangrias_arr, despesas_arr, etc.)
to_escpos() gera: SANGRIAS → ENTRADAS → DESPESAS → PIX CNPJ → VALES → A RECEBER → CARTÕES → CANCELAMENTOS → TROCO → OBSERVAÇÕES → TOTAL SALDO CAIXA → SALDO ESPERADO → DIFERENÇA

Preview impressao: modal com iframe 360x600px + botao Fechar + botao Imprimir — usuario consegue voltar ao app
2026-06-24 18:31:03 +00:00
root 5cab38a27c chore: clean Rust warnings and remove unused deps
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
- Remove serde_yaml and tokio from Cargo.toml (unused)
- Add #[allow(dead_code)] to utility code kept for future use
  (CUT_FULL, ALIGN_RIGHT, line(), PrinterError, SupabaseError,
   atualizar_sync, remover, Lock variant)
- Zero compiler warnings on release build
2026-06-24 14:51:56 +00:00
root 5b1f3a2691 chore: adiciona README, .gitignore, remove binarios do repo
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
- Adiciona .gitignore com *.exe, *.tar.gz, *.dll, *.log
- Remove binários commitados do tracking (58MB节省)
- Adiciona README.md com instruções de setup, estrutura, comandos Tauri
- Substitui location.reload() na impressão por iframe (preserva estado)
- Substitui location.reload() no btn-limpar por reset de estado in-memory
- Nota: security — service_role_key ainda no cliente desktop
2026-06-24 14:30:26 +00:00
20 changed files with 1380 additions and 335 deletions
+13
View File
@@ -7,3 +7,16 @@ src-tauri/gen/
*.msi
*.dmg
.DS_Store
# Binarios compilados
*.exe
*.tar.gz
*.zip
fechamento-caixa.exe
fechamento-caixa-latest.tar.gz
WebView2Loader.dll
src-tauri/target/x86_64-pc-windows-gnu/release/*.exe
src-tauri/target/x86_64-pc-windows-gnu/release/*.dll
# Logs
*.log
+140
View File
@@ -0,0 +1,140 @@
# Fechamento de Caixa — O Frangão
App desktop nativo para fechamento de caixa com impressão em impressora térmica 80mm.
## Stack
- **Backend:** Tauri 2 + Rust
- **Frontend:** HTML + CSS + JavaScript (vanilla, ~1700 linhas)
- **Storage local:** SQLite (rusqlite) — funciona offline
- **Sync:** Supabase REST API
- **Impressão:** ESC/POS via USB (TM-T20 e compatíveis, Windows)
## Estrutura do Projeto
```
fechamento-caixa/
├── src/
│ └── index.html # Frontend completo (HTML + CSS + JS inline)
├── src-tauri/
│ ├── Cargo.toml # Dependências Rust
│ ├── tauri.conf.json # Config do app Tauri
│ └── src/
│ ├── main.rs # Entry point — registra comandos Tauri
│ └── plugins/
│ ├── supabase.rs # Integração Supabase (REST API)
│ ├── printer.rs # Geração ESC/POS + envio USB
│ ├── storage.rs # SQLite local
│ ├── app.rs # Config da app (loja, operador default)
│ └── escpos.rs # Helpers ESC/POS (INIT, CUT, divider, etc.)
└── README.md
```
## Pré-requisitos
- Rust 1.70+ (`rustup install stable`)
- Node.js 18+ (para build do frontend Tauri)
- Windows 10/11 (impressão USB só funciona no Windows)
## Setup
### 1. Clonar o repositório
```bash
git clone https://git.ofrangao.com.br/filipe/fechamento-caixa.git
cd fechamento-caixa
```
### 2. Configurar chaves do Supabase
Crie o arquivo `config.json` em `%LOCALAPPDATA%\FechamentoCaixa\config.json` (Windows):
```json
{
"SUPABASE_ANON_KEY": "sua_chave_anon_aqui",
"SUPABASE_SERVICE_KEY": "sua_chave_service_role_aqui"
}
```
O app procura esse arquivo na inicialização. Sem ele, tenta ler das variáveis de ambiente `SUPABASE_ANON_KEY` e `SUPABASE_SERVICE_KEY`.
### 3. Build
```bash
cd src-tauri
cargo build --release --target x86_64-pc-windows-gnu
```
O executável fica em:
```
src-tauri/target/x86_64-pc-windows-gnu/release/fechamento-caixa.exe
```
Para gerar o pacote de distribuição:
```bash
cp src-tauri/target/x86_64-pc-windows-gnu/release/fechamento-caixa.exe .
cp src-tauri/target/x86_64-pc-windows-gnu/release/WebView2Loader.dll .
tar -czf fechamento-caixa-vX.X.X.tar.gz fechamento-caixa.exe WebView2Loader.dll
```
## Códigos de comando Tauri
O frontend chama esses comandos via `window.__TAURI__.core.invoke()`:
| Comando | Arquivo | Descrição |
|---------|---------|-----------|
| `salvar_fechamento` | storage.rs | Salva no SQLite local |
| `carregar_rascunho` | storage.rs | Busca rascunho por loja+data |
| `listar_fechamentos` | storage.rs | Lista histórico do SQLite |
| `buscar_fechamento_por_id` | storage.rs | Busca por ID no SQLite |
| `sb_salvar_fechamento` | supabase.rs | Envia para Supabase |
| `sb_carregar_rascunho` | supabase.rs | Busca rascunho no Supabase |
| `sb_listar_recentes` | supabase.rs | Lista fechamentos recentes |
| `sb_buscar_por_id` | supabase.rs | Busca por ID no Supabase |
| `sb_salvar_listas` | supabase.rs | Salva operadores/gerentes |
| `sb_carregar_listas` | supabase.rs | Carrega listas do servidor |
| `sb_online` | supabase.rs | Verifica conectividade |
| `imprimir_recibo` | printer.rs | Envia bytes ESC/POS pra USB |
| `detectar_impressora` | printer.rs | Detecta porta USB |
| `configurar_impressora` | printer.rs | Define porta manual |
| `teste_impressora` | printer.rs | Imprime página de teste |
| `get_loja` / `set_loja` | app.rs | Loja atual |
| `get_config` / `set_config` | app.rs | Config completo |
## Fluxo de dados
```
[Usuário preenche formulário]
▼ (auto-save a cada 1.5s)
SQLite local (fechamento.db)
├── [Botão Enviar] ──► Supabase (fechamentos_web)
└── [Botão Recibo] ──► printer.rs ──► to_escpos() ──► USB
├── Sucesso: imprime
└── Falha: openPrintPreview() ──► iframe ──► window.print()
```
## Campos do formulário
| Campo | Tabela Supabase | Notas |
|-------|----------------|-------|
| uuid, loja, data, operador, turno | fechamentos_web | PK composto |
| saldo_troco, saldo_esperado, fechamento, diferenca | fechamentos_web | diferenca = fechamento - saldo_esperado |
| despesas[], sangrias[], vales[], receber[], pixcnpj[], cancelamentos[] | dados (JSON) | Arrays de {desc/nome, valor, ...} |
| cartoes{credito[],debito[],alimentacao[],pix[]} | dados (JSON) | Arrays de {valor} |
| operadores[], gerentes[], despesas_custom[], clientes[] | listas_personalizadas | Listas por loja |
## Problemas conhecidos
- Impressão USB só funciona no Windows
- `service_role_key` no cliente desktop é risco de segurança — usar apenas anon_key + RLS
- Sem testes automatizados
## Version History
- **v1.2.7** — 2026-06-24: 4 bugs críticos concertados (impressão, enviar, dia anterior, localStorage)
- **v1.2.6** — 2026-06-24: WebView2Loader.dll incluso; init() restaura operador+turno
- **v1.2.5** — 2026-06-24: diferenca = fechamento - esperado (não mais saldo_troco - esperado)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-35
View File
@@ -900,14 +900,12 @@ dependencies = [
"rusqlite",
"serde",
"serde_json",
"serde_yaml",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-shell",
"thiserror 2.0.18",
"tokio",
"uuid",
]
@@ -3281,19 +3279,6 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.14.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "serialize-to-javascript"
version = "0.1.2"
@@ -4070,25 +4055,11 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.118",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
@@ -4399,12 +4370,6 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
-2
View File
@@ -15,8 +15,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
rusqlite = { version = "0.32", features = ["bundled"] }
reqwest = { version = "0.12", features = ["json", "blocking"] }
tokio = { version = "1", features = ["full"] }
serde_yaml = "0.9"
uuid = { version = "1", features = ["v4"] }
chrono = { version = "0.4", features = ["serde"] }
log = "0.4"
+23
View File
@@ -0,0 +1,23 @@
{
"identifier": "default",
"description": "Default capabilities for Fechamento de Caixa",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"dialog:allow-message",
"dialog:allow-confirm",
"dialog:allow-ask",
"dialog:allow-save",
"dialog:allow-open",
"fs:default",
"fs:allow-app-read",
"fs:allow-app-write",
"fs:allow-appdata-read",
"fs:allow-appdata-write",
"fs:allow-appconfig-read",
"fs:allow-appconfig-write",
"fs:allow-applocaldata-read",
"fs:allow-applocaldata-write"
]
}
+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,
+3
View File
@@ -8,6 +8,7 @@ pub const LF: &[u8] = b"\x0A";
/// Cortar papel (parcial)
pub const CUT: &[u8] = b"\x1D\x56\x01";
/// Cortar papel (total)
#[allow(dead_code)]
pub const CUT_FULL: &[u8] = b"\x1D\x56\x00";
/// Alimentar 3 linhas antes de cortar
pub const FEED_CUT: &[u8] = b"\x1B\x64\x03";
@@ -17,6 +18,7 @@ pub const BOLD_OFF: &[u8] = b"\x1B\x45\x00";
/// Alinhamento
pub const ALIGN_LEFT: &[u8] = b"\x1B\x61\x00";
pub const ALIGN_CENTER: &[u8] = b"\x1B\x61\x01";
#[allow(dead_code)]
pub const ALIGN_RIGHT: &[u8] = b"\x1B\x61\x02";
/// Fonte normal / dupla
pub const FONT_NORMAL: &[u8] = b"\x1B\x21\x00";
@@ -38,6 +40,7 @@ pub fn title(text: &str) -> Vec<u8> {
}
/// Monta linha normal (alinhada à esquerda)
#[allow(dead_code)]
pub fn line(text: &str) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(ALIGN_LEFT);
+264 -27
View File
@@ -5,6 +5,7 @@ use std::sync::Mutex;
use thiserror::Error;
#[derive(Error, Debug)]
#[allow(dead_code)]
pub enum PrinterError {
#[error("Impressora não encontrada. Verifique se está conectada via USB e ligada.")]
NotFound,
@@ -30,6 +31,50 @@ use crate::plugins::escpos;
// ── Struct de dados do recibo ────────────────────────────────────────────────
/// Linha de uma tabela no recibo (despesas, sangrias, vales, etc.)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ReciboLinha {
#[serde(default)]
pub desc: Option<String>,
#[serde(rename = "hora", default)]
pub hora: Option<String>,
#[serde(rename = "nome", default)]
pub nome: Option<String>,
#[serde(default)]
pub retirada: Option<f64>,
#[serde(default)]
pub valor: Option<f64>,
#[serde(default)]
pub obs: Option<String>,
#[serde(default)]
pub gerente: Option<String>,
#[serde(default)]
pub numero: Option<String>,
#[serde(default)]
pub motivo: Option<String>,
}
impl ReciboLinha {
fn fmt_v(&self) -> String {
let v = self.retirada.or(self.valor).unwrap_or(0.0);
format!("R$ {:.2}", v).replace('.', ",")
}
fn label(&self) -> String {
self.desc
.clone()
.or(self.nome.clone())
.or(self.hora.clone().map(|h| format!("{}h", h)))
.unwrap_or_default()
}
fn obs(&self) -> String {
self.gerente
.clone()
.or(self.obs.clone())
.or(self.motivo.clone())
.unwrap_or_default()
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ReciboData {
pub loja: String,
@@ -37,19 +82,55 @@ pub struct ReciboData {
pub operador: String,
pub turno: String,
pub saldo_troco: f64,
/// Valor contado no fechamento (dinheiro + cartões + receber)
pub fechamento: f64,
/// Saldo esperado = total operações sangrias despesas
pub saldo_esperado: f64,
/// Diferença = fechamento saldo_esperado
pub diferenca: f64,
pub sangrias: f64,
pub despesas: f64,
pub vales: f64,
pub areceber: f64,
/// Total do sistema (troco + crédito + débito + alimentação + voucher + pixcnpj + vales + receber)
#[serde(default)]
pub sys_total: f64,
/// Soma de todas operações creditadas (crédito + débito + alimentação + voucher + pixcnpj) — usado no BLACK section como TOTAL
#[serde(default)]
pub total_operado: f64,
// Totais de cartões
pub credito: f64,
pub debito: f64,
pub alimentacao: f64,
pub voucher: f64,
// Totais diversos (mantidos para compatibilidade com JSON antigo)
#[serde(default)]
pub sangrias: f64,
#[serde(default)]
pub despesas: f64,
#[serde(default)]
pub vales: f64,
#[serde(default)]
pub areceber: f64,
#[serde(default)]
pub pixcnpj: f64,
#[serde(default)]
pub cancelamentos: f64,
// Arrays para linhas detalhadas (recebidos do frontend)
#[serde(default)]
pub sangrias_arr: Vec<ReciboLinha>,
#[serde(default)]
pub despesas_arr: Vec<ReciboLinha>,
#[serde(default)]
pub pixcnpj_arr: Vec<ReciboLinha>,
#[serde(default)]
pub vales_arr: Vec<ReciboLinha>,
#[serde(default)]
pub receber_arr: Vec<ReciboLinha>,
#[serde(default)]
pub cancelamentos_arr: Vec<ReciboLinha>,
#[serde(default)]
pub observacoes: Option<String>,
#[serde(default)]
pub clientes: i32,
#[serde(default)]
pub frango: i32,
}
impl ReciboData {
@@ -58,6 +139,7 @@ impl ReciboData {
}
/// Gera todos os bytes ESC/POS do recibo 80mm
/// Layout: Header → SANGRIAS → DESPESAS → PIX CNPJ → VALES → A RECEBER → CANCELAMENTOS → CARTÕES → TROCO/Saldo Caixa → SALDO ESPERADO → DIFERENÇA
pub fn to_escpos(&self) -> Vec<u8> {
let mut out = Vec::new();
@@ -77,40 +159,194 @@ impl ReciboData {
out.extend(escpos::kv("Turno:", &self.turno));
out.extend(escpos::divider());
// Entradas
out.extend(escpos::line_bold("ENTRADAS"));
out.extend(escpos::kv("Saldo Troco:", &self.fmt_money(self.saldo_troco)));
out.extend(escpos::kv("Fechamento:", &self.fmt_money(self.fechamento)));
out.extend(escpos::kv("Saldo Esperado:", &self.fmt_money(self.saldo_esperado)));
out.extend(escpos::divider());
// ── SANGRIAS ──
if !self.sangrias_arr.is_empty() {
out.extend(escpos::line_bold("SANGRIAS"));
for linha in &self.sangrias_arr {
if linha.retirada.unwrap_or(0.0) > 0.0 || linha.valor.unwrap_or(0.0) > 0.0 {
let label = linha.label();
let obs = linha.obs();
out.extend(escpos::kv(&label, &linha.fmt_v()));
if !obs.is_empty() {
out.extend(escpos::line_center(&format!(" {}", obs)));
}
}
}
// Total sangrias
let total_sangrias: f64 = self.sangrias_arr.iter()
.map(|l| l.retirada.unwrap_or(0.0) + l.valor.unwrap_or(0.0))
.sum();
if total_sangrias > 0.0 {
out.extend(escpos::kv("Total:", &self.fmt_money(total_sangrias)));
}
out.extend(escpos::divider());
}
// 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());
// ── DESPESAS ──
if !self.despesas_arr.is_empty() {
out.extend(escpos::line_bold("DESPESAS"));
for linha in &self.despesas_arr {
if linha.valor.unwrap_or(0.0) > 0.0 {
let label = linha.label();
let obs = linha.obs();
out.extend(escpos::kv(&label, &linha.fmt_v()));
if !obs.is_empty() {
out.extend(escpos::line_center(&format!(" {}", obs)));
}
}
}
// Total despesas
let total_despesas: f64 = self.despesas_arr.iter()
.map(|l| l.valor.unwrap_or(0.0))
.sum();
out.extend(escpos::kv("Total:", &self.fmt_money(total_despesas)));
out.extend(escpos::divider());
}
// Cartões
out.extend(escpos::line_bold("CARTÕES"));
// ── PIX CNPJ ──
if !self.pixcnpj_arr.is_empty() {
out.extend(escpos::line_bold("PIX CNPJ"));
for linha in &self.pixcnpj_arr {
if linha.valor.unwrap_or(0.0) > 0.0 {
let label = linha.nome.clone().unwrap_or_else(|| "PIX CNPJ".to_string());
out.extend(escpos::kv(&label, &linha.fmt_v()));
}
}
// Total PIX CNPJ
let total_pixcnpj: f64 = self.pixcnpj_arr.iter()
.map(|l| l.valor.unwrap_or(0.0))
.sum();
if total_pixcnpj > 0.0 {
out.extend(escpos::kv("Total:", &self.fmt_money(total_pixcnpj)));
}
out.extend(escpos::divider());
}
// ── VALES ──
if !self.vales_arr.is_empty() {
out.extend(escpos::line_bold("VALES"));
for linha in &self.vales_arr {
if linha.valor.unwrap_or(0.0) > 0.0 {
let label = linha.label();
let obs = linha.obs();
out.extend(escpos::kv(&label, &linha.fmt_v()));
if !obs.is_empty() {
out.extend(escpos::line_center(&format!(" {}", obs)));
}
}
}
// Total vales
let total_vales: f64 = self.vales_arr.iter()
.map(|l| l.valor.unwrap_or(0.0))
.sum();
if total_vales > 0.0 {
out.extend(escpos::kv("Total:", &self.fmt_money(total_vales)));
}
out.extend(escpos::divider());
}
// ── A RECEBER ──
if !self.receber_arr.is_empty() {
out.extend(escpos::line_bold("A RECEBER"));
for linha in &self.receber_arr {
if linha.valor.unwrap_or(0.0) > 0.0 {
let label = linha.label();
out.extend(escpos::kv(&label, &linha.fmt_v()));
}
}
// Total a receber
let total_receber: f64 = self.receber_arr.iter()
.map(|l| l.valor.unwrap_or(0.0))
.sum();
if total_receber > 0.0 {
out.extend(escpos::kv("Total:", &self.fmt_money(total_receber)));
}
out.extend(escpos::divider());
}
// ── CANCELAMENTOS ──
if !self.cancelamentos_arr.is_empty() {
out.extend(escpos::line_bold("CANCELAMENTOS"));
for linha in &self.cancelamentos_arr {
if linha.numero.is_some() || linha.valor.unwrap_or(0.0) > 0.0 {
let num = linha.numero.clone().unwrap_or_default();
out.extend(escpos::kv(&num, &linha.fmt_v()));
if let Some(ref m) = linha.motivo {
if !m.is_empty() {
out.extend(escpos::line_center(&format!(" {}", m)));
}
}
}
}
// Total cancelamentos
let total_cancel: f64 = self.cancelamentos_arr.iter()
.map(|l| l.valor.unwrap_or(0.0))
.sum();
if total_cancel > 0.0 {
out.extend(escpos::kv("Total:", &self.fmt_money(total_cancel)));
}
out.extend(escpos::divider());
}
// ── CARTÕES ──
out.extend(escpos::line_bold("CARTOES"));
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("PIX Cartao:", &self.fmt_money(self.voucher)));
out.extend(escpos::kv("Cancelamentos:", &self.fmt_money(self.cancelamentos)));
out.extend(escpos::divider());
// Diferença
let diff_str = if self.diferenca >= 0.0 {
format!("+{}", self.fmt_money(self.diferenca))
// ── TROCO / SALDO DE CAIXA ──
out.extend(escpos::line_bold("SALDO DE CAIXA"));
out.extend(escpos::kv("Troco (+):", &self.fmt_money(self.saldo_troco)));
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("PIX Cartao (+):", &self.fmt_money(self.voucher)));
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());
// ── TOTAL ──
if self.sys_total > 0.0 {
out.extend(escpos::line_bold("TOTAL"));
out.extend(escpos::kv("Total:", &self.fmt_money(self.sys_total)));
out.extend(escpos::divider());
}
// ── SALDO ESPERADO ──
out.extend(escpos::line_bold("SALDO ESPERADO"));
out.extend(escpos::kv("Esperado:", &self.fmt_money(self.saldo_esperado)));
out.extend(escpos::divider());
// ── DIFERENÇA ──
// Usa self.diferenca que o frontend calcula como sys_total - saldo_esperado
// (não recalcula de fechamento, que é o valor contado e não deve ser usado na diferença)
out.extend(escpos::line_bold("DIFERENCA"));
let diff = self.diferenca;
let diff_str = if diff >= 0.0 {
format!("+{}", self.fmt_money(diff))
} else {
self.fmt_money(self.diferenca)
self.fmt_money(diff)
};
out.extend(escpos::line_bold("DIFERENCA:"));
out.extend(escpos::title(&diff_str));
out.extend(escpos::blank_lines(2));
// ── 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());
// ── OBSERVAÇÕES ──
if let Some(ref obs) = self.observacoes {
if !obs.trim().is_empty() {
out.extend(escpos::line_bold("OBSERVACOES"));
out.extend(escpos::line_center(obs.trim()));
out.extend(escpos::divider());
}
}
// Rodapé
out.extend(escpos::divider());
@@ -122,6 +358,7 @@ impl ReciboData {
// Corta papel
out.extend(escpos::FEED_CUT);
out.extend(escpos::CUT);
out
}
+3 -1
View File
@@ -335,6 +335,7 @@ impl DbManager {
}
/// Atualiza sync_status de um registro.
#[allow(dead_code)]
pub fn atualizar_sync(&self, uuid: &str, status: SyncStatus) -> Result<(), StorageError> {
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
@@ -346,6 +347,7 @@ impl DbManager {
}
/// Remove um registro (para debugging).
#[allow(dead_code)]
pub fn remover(&self, uuid: &str) -> Result<bool, StorageError> {
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
let n = conn.execute("DELETE FROM fechamentos WHERE uuid = ?1", params![uuid])?;
@@ -508,7 +510,7 @@ pub fn carregar_rascunho(
// Simplifica para formato que o frontend espera
let total_sangrias: f64 = e.tabelas.sangrias.iter().filter_map(|r| r.valor).sum();
let total_despesas: f64 = e.tabelas.despesas.iter().filter_map(|r| r.valor).sum();
let total_receber: f64 = e.tabelas.areceber.iter().filter_map(|r| r.valor).sum();
let _total_receber: f64 = e.tabelas.areceber.iter().filter_map(|r| r.valor).sum();
let despesa_rows: Vec<_> = e.tabelas.despesas.iter().filter_map(|r| {
r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))
}).collect();
+71 -1
View File
@@ -8,6 +8,7 @@ use tauri::State;
use thiserror::Error;
#[derive(Error, Debug)]
#[allow(dead_code)]
pub enum SupabaseError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
@@ -152,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",
@@ -321,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>,
+11 -4
View File
@@ -11,7 +11,7 @@
"app": {
"windows": [
{
"title": "Fechamento de Caixa O Frangão",
"title": "Fechamento de Caixa \u2014 O Frang\u00e3o",
"width": 1100,
"height": 780,
"minWidth": 900,
@@ -22,13 +22,18 @@
}
],
"security": {
"csp": null
"csp": null,
"capabilities": [
"default"
]
},
"withGlobalTauri": true
},
"bundle": {
"active": true,
"targets": ["nsis"],
"targets": [
"nsis"
],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@@ -37,7 +42,9 @@
"icons/icon.ico"
],
"windows": {
"webviewInstallMode": { "type": "embedBootstrapper" }
"webviewInstallMode": {
"type": "embedBootstrapper"
}
},
"resources": {
"../src/*": "./"
+850 -265
View File
File diff suppressed because it is too large Load Diff