Compare commits

...

5 Commits

Author SHA1 Message Date
root 275d92abe6 docs: adicionar PROJECT_HISTORY.md com todo o trajeto do projeto
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-05 22:16:39 +00:00
root 237ceab042 fix: ao fechar preview, reabrir modal confirmacao se ainda estava aberto
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-05 22:03:23 +00:00
root 73c20272e5 fix: ao fechar preview do relatório, reabrir modal de confirmaçã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-07-05 21:25:35 +00:00
root 56a2ed5d31 fix: buildPrintHTML usa sys_total correto
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-05 20:57:49 +00:00
root acb90f093d fix: diferenca calculada sempre a partir da array de cancelamentos (nao do escalar)
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-04 23:34:56 +00:00
3 changed files with 204 additions and 72 deletions
+164
View File
@@ -0,0 +1,164 @@
# Histórico do Projeto — Fechamento de Caixa O Frangão
## Contexto
**Dono:** Filipe.Tavares — O Frangão (lojas União e Aliança)
**Problema original:** Planilhas manuais + webhooks n8n frágeis para registrar fechamento de caixa
**Solução:** App desktop Tauri 2 + SQLite offline + Supabase cloud + impressão térmica 80mm
---
## Repositório
```
https://git.ofrangao.com.br/filipe/fechamento-caixa
```
---
## Stack Técnica
| Camada | Tecnologia |
|--------|-----------|
| Frontend | HTML5 + CSS + JavaScript vanilla (single-file `src/index.html`) |
| Runtime | Tauri 2 (Rust + WebView2) |
| Dados local | SQLite via `rusqlite` |
| Dados cloud | Supabase — PostgreSQL + REST API |
| Impressão | HTML → `window.print()` com CSS @media print (80mm) |
| Build | Cross-compile Linux → Windows (`x86_64-pc-windows-gnu`) |
---
## Releases
| Tag | Data | Descrição |
|-----|------|-----------|
| `v2.0.4-alianca` | 2026-07-05 | Fix preview modal re-open + sys_total recalc |
| `v2.0.3-uniao` | 2026-07-05 | Fix diferença cálculo from array |
| `v2.0.2-uniao` | 2026-07-05 | Fix buildPrintHTML sys_total |
| `v2.0.1-uniao` | 2026-07-05 | Builds separadas + docs |
| `v2.0.0-uniao/alianca/ambos` | 2026-07-04 | Lançamento — app completo com Tauri 2 |
---
## Bugs Corrigidos (desde v2.0.0)
### Preview + Modal (v2.0.4)
- **237ceab** — `confirm-print` não fechava mais o modal antes de abrir o preview; `openPrintPreview` reabria o modal ao fechar se ele ainda estava aberto
- **73c2027** — `closeConfirmModal()` era chamada antes de `openPrintPreview()` — preview cobria o modal, mas ao fechar voltava pro form
- **56a2ed5** — `buildPrintHTML` usava `data.diferenca` (precomputado, stale) em vez de recalcular `sys_total - saldo_esperado - cancelamentos` inline
### Cálculo da Diferença (v2.0.3)
- **acb90f0** — `diferenca` sempre recalculada da array de cancelamentos (não do escalar `total_cancelamentos`)
- **6eb08b6** — mesmo fix em `buildReportHTML`, `buildPrintHTML`, `buildConfirmHTML`
- **5a01264** — ignora `data.diferenca` pré-computado, recalcula sempre de `sys_total`
### Rust ESCPOS (v2.0.3)
- **733519e** — Rust `to_escpos`: diferença = `sys_total - saldo_esperado - total_cancelamentos` (não mais `fechamento`)
### CSS / Layout (v2.0.2)
- **6440b26** — `buildReportHTML`: diferença = `sysTotal - saldoEsp - cancelamentos`
- **420e104** — `buildConfirmHTML` e `buildPrintHTML` usavam `sysTotal`/`total_cancelamentos` em vez de `sys_total`/`cancelamentos`
- **b04e5ac** — `.main min-height:0` (footer invisível em resoluções pequenas)
- **65c444a** — seletor loja escondia errado (caracteres asiáticos no DOM)
- **2bfbf29** — values das options de loja sem acento (`Uniao`/`Alianca`) — alinhado com `LOJA_FIXA` do Rust
### Impressão (v2.0.1)
- **b1585af / 155782f / 16972f4 / d3d5c4f** — fonte 18px→22px bold nos relatórios e preview
- **5f1d9a4 / cc45ed9** — botão "Dia Anterior" agora exige senha antes de abrir
### Builds (v2.0.0)
- **659d977** — `DOBRADO_LOJA` injetado via env no build — 3 binários: `fc-uniao.exe`, `fc-alianca.exe`, `fc-ambos.exe`
---
## Bugs Pendentes (conhecidos)
| # | Bug | Prioridade | Status |
|---|-----|-----------|--------|
| #6 | `salvarEdicaoModal` reconstrói `id_fechamento` dos inputs em vez de usar `full.id_fechamento` — PATCH pode errar se operador mudar loja/data/operador | Média | Pendente |
---
## Arquitetura de Dados
### SQLite local (offline-first)
```
%LOCALAPPDATA%/FechamentoCaixa/fechamento.db
```
### Supabase cloud
```
https://supabase.ofrangao.com.br
Tabela: fechamentos_web
```
### Fluxo de sync
```
App abre → carrega rascunho do SQLite → operador trabalha offline
operador clica "Salvar/Sincronizar"
1. Salva em SQLite local
2. Envia para Supabase (se online)
operador clica "Imprimir"
window.print() → receipt 80mm
```
---
## Comandos Tauri (frontend chama via `invoke`)
| Comando | Descrição |
|---------|-----------|
| `salvar_fechamento` | Salva estado no SQLite local |
| `carregar_rascunho` | Carrega rascunho por loja+data |
| `sb_salvar_fechamento` | POST para Supabase |
| `sb_listar_recentes` | Lista fechamentos recentes |
| `sb_buscar_por_id` | Busca por ID numérico |
| `sb_buscar_por_uuid` | Busca por UUID |
| `detectar_impressora` | Detecta EPSON TM-T20 |
| `imprimir_recibo` | Imprime via ESCPOS ou fallback window.print() |
---
## Builds — 3 versões
| Binário | Loja | Seletor |
|---------|------|---------|
| `fc-uniao.exe` | Fixa: UNIÃO | Escondido |
| `fc-alianca.exe` | Fixa: ALIANÇA | Escondido |
| `fc-ambos.exe` | Livre | Dropdown visível |
### Como buildar
```bash
cd src-tauri/
# UNIÃO
DOBRADO_LOJA=Uniao cargo build --release --target x86_64-pc-windows-gnu
# ALIANÇA
DOBRADO_LOJA=Alianca cargo build --release --target x86_64-pc-windows-gnu
# AMBOS
cargo build --release --target x86_64-pc-windows-gnu
```
---
## Lições Aprendidas
1. **Cálculos sempre inline** — nunca usar valores pré-computados (`data.diferenca`, `data.total_cancelamentos`) que podem estar stale. Sempre recalcular da fonte.
2. **Fechar modal ANTES de abrir overlay** — se um modal vai ser coberto por outro, fechar primeiro e reabrir depois (salvar estado).
3. **Cross-compile Linux→Windows** — funciona perfeitamente com `x86_64-pc-windows-gnu` target.
4. **Gitea API**`Authorization: token {TOKEN}` (minúsculo `token`) funciona; `Token {TOKEN}` não. Upload de assets funciona; DELETE de assets retorna 404 (bug do Gitea?).
---
## Autores
- **Filipe.Tavares** — dono das lojas / demandante
- **Hermes Agent** — desenvolvimento + debugging
+6 -5
View File
@@ -322,13 +322,12 @@ impl ReciboData {
out.extend(escpos::divider()); out.extend(escpos::divider());
// ── DIFERENÇA ── // ── DIFERENÇA ──
// Diferença = TOTAL SISTEMA - saldo_esperado - cancelamentos // Usa o valor calculado pelo frontend (já correto)
out.extend(escpos::line_bold("DIFERENCA")); out.extend(escpos::line_bold("DIFERENCA"));
let diff = self.sys_total - self.saldo_esperado - self.cancelamentos; let diff_str = if self.diferenca >= 0.0 {
let diff_str = if diff >= 0.0 { format!("+{}", self.fmt_money(self.diferenca))
format!("+{}", self.fmt_money(diff))
} else { } else {
self.fmt_money(diff) self.fmt_money(self.diferenca)
}; };
out.extend(escpos::title(&diff_str)); out.extend(escpos::title(&diff_str));
@@ -528,8 +527,10 @@ pub fn caminho_impressora() -> Option<String> {
#[tauri::command] #[tauri::command]
pub fn imprimir_recibo(data: String) -> Result<usize, String> { pub fn imprimir_recibo(data: String) -> Result<usize, String> {
eprintln!("[DEBUG RECIBO JSON] {}", &data[..data.len().min(500)]);
let r: ReciboData = let r: ReciboData =
serde_json::from_str(&data).map_err(|e| format!("Erro ao analisar dados: {}", e))?; serde_json::from_str(&data).map_err(|e| format!("Erro ao analisar dados: {}", e))?;
eprintln!("[DEBUG DIFERENCA] self.diferenca={}", r.diferenca);
let bytes = r.to_escpos(); let bytes = r.to_escpos();
log::info!("Imprimindo recibo: {} bytes ESC/POS", bytes.len()); log::info!("Imprimindo recibo: {} bytes ESC/POS", bytes.len());
PRINTER.imprimir_bytes(&bytes) PRINTER.imprimir_bytes(&bytes)
+34 -67
View File
@@ -63,8 +63,8 @@
.meta-select { cursor: pointer; } .meta-select { cursor: pointer; }
/* ── MAIN 2 COLUMNS — single scroll, no internal scrollbars ──────────── */ /* ── MAIN 2 COLUMNS — single scroll, no internal scrollbars ──────────── */
.main { flex: 1; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden; min-height: 0; } .main { flex: 1; display: grid; grid-template-columns: 1fr 1fr; overflow: visible; }
.col { padding: 12px 16px; overflow: hidden; } .col { padding: 12px 16px; overflow: visible; }
.col-left { border-right: 1px solid var(--border); } .col-left { border-right: 1px solid var(--border); }
.col-right { background: var(--surface); display: flex; flex-direction: column; } .col-right { background: var(--surface); display: flex; flex-direction: column; }
.col-right .section { flex-shrink: 0; } .col-right .section { flex-shrink: 0; }
@@ -323,8 +323,8 @@
<div class="meta-field"> <div class="meta-field">
<span class="meta-label">Loja</span> <span class="meta-label">Loja</span>
<select class="meta-select" id="f-loja"> <select class="meta-select" id="f-loja">
<option value="Uniao">União</option> <option value="União">União</option>
<option value="Alianca">Aliança</option> <option value="Aliança">Aliança</option>
</select> </select>
</div> </div>
</div> </div>
@@ -1323,14 +1323,12 @@ function buildConfirmHTML(data) {
// Relatório completo — mesmo layout do PDF // Relatório completo — mesmo layout do PDF
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ','); const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ','); const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ',');
// Cancelamentos: SEMPRE recalcular da array — nunca usar o campo escalar // Cancelamentos: SEMPRE recalcular da array (o escalar pode estar desatualizado)
const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : []; const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : [];
const cancelamentosCalc = cancelamentos_arr.reduce((s, r) => s + (r.valor || 0), 0); const totalCancelCalc = cancelamentos_arr.reduce((s,r) => s+(r.valor||0), 0);
// Diferença = Total Sistema - Saldo Esperado - Cancelamentos const sysTotalCalc = (data.sys_total || data.fechamento || 0);
// ⚠️ buildReciboData retorna snake_case (sys_total, total_cancelamentos)
const sysTotalCalc = (data.sys_total || data.sysTotal || 0);
const saldoEsperadoCalc = (data.saldo_esperado || 0); const saldoEsperadoCalc = (data.saldo_esperado || 0);
const diferenca = sysTotalCalc - saldoEsperadoCalc - cancelamentosCalc; const diferenca = sysTotalCalc - saldoEsperadoCalc - totalCancelCalc;
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca); const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca);
const diffOk = diferenca === 0; const diffOk = diferenca === 0;
@@ -1339,7 +1337,6 @@ function buildConfirmHTML(data) {
const pixcnpj_arr = Array.isArray(data.pixcnpj_arr) ? data.pixcnpj_arr : []; const pixcnpj_arr = Array.isArray(data.pixcnpj_arr) ? data.pixcnpj_arr : [];
const vales_arr = Array.isArray(data.vales_arr) ? data.vales_arr : []; const vales_arr = Array.isArray(data.vales_arr) ? data.vales_arr : [];
const receber_arr = Array.isArray(data.receber_arr) ? data.receber_arr : []; const receber_arr = Array.isArray(data.receber_arr) ? data.receber_arr : [];
const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : [];
const cartCred_arr = Array.isArray(data.cartoes_credito) ? data.cartoes_credito : []; const cartCred_arr = Array.isArray(data.cartoes_credito) ? data.cartoes_credito : [];
const cartDeb_arr = Array.isArray(data.cartoes_debito) ? data.cartoes_debito : []; const cartDeb_arr = Array.isArray(data.cartoes_debito) ? data.cartoes_debito : [];
@@ -1494,30 +1491,28 @@ ${data.observacoes ? `<div class="obs">${data.observacoes}</div>` : ''}
function buildPrintHTML(data) { function buildPrintHTML(data) {
const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ','); const fmt = v => 'R$ ' + (v || 0).toFixed(2).replace('.', ',');
const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ','); const fmtNeg = v => (v < 0 ? '-' : '') + 'R$ ' + Math.abs(v || 0).toFixed(2).replace('.', ',');
// Cancelamentos: SEMPRE recalcular da array — nunca usar o campo escalar
const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : [];
const cancelamentosCalc = cancelamentos_arr.reduce((s, r) => s + (r.valor || 0), 0);
// Diferença = Total Sistema - Saldo Esperado - Cancelamentos // Diferença = Total Sistema - Saldo Esperado - Cancelamentos
// ⚠️ buildReciboData retorna snake_case (sys_total, total_cancelamentos) const sysTotalCalc = (data.sys_total || 0);
const sysTotalCalc = (data.sys_total || data.sysTotal || 0);
const saldoEsperadoCalc = (data.saldo_esperado || 0); const saldoEsperadoCalc = (data.saldo_esperado || 0);
const diferenca = sysTotalCalc - saldoEsperadoCalc - cancelamentosCalc; // Cancelamentos: SEMPRE recalcular da array (o scalar pode estar desatualizado)
const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : [];
const totalCancelCalc = cancelamentos_arr.reduce((s,r) => s+(r.valor||0), 0);
const diferenca = sysTotalCalc - saldoEsperadoCalc - totalCancelCalc;
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca); const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca);
const diffColor = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d'); const diffColor = diferenca === 0 ? '#2e7d32' : (diferenca > 0 ? '#e65100' : '#c0272d');
const cartCred_arr = Array.isArray(data.cartoes_credito) ? data.cartoes_credito : []; // Arrays — support both array-of-objects and flat totals
const cartDeb_arr = Array.isArray(data.cartoes_debito) ? data.cartoes_debito : [];
const cartAli_arr = Array.isArray(data.cartoes_alimentacao) ? data.cartoes_alimentacao : [];
const cartPix_arr = Array.isArray(data.cartoes_pix) ? data.cartoes_pix : [];
const sangrias_arr = Array.isArray(data.sangrias_arr) ? data.sangrias_arr : []; const sangrias_arr = Array.isArray(data.sangrias_arr) ? data.sangrias_arr : [];
const despesas_arr = Array.isArray(data.despesas_arr) ? data.despesas_arr : []; const despesas_arr = Array.isArray(data.despesas_arr) ? data.despesas_arr : [];
const pixcnpj_arr = Array.isArray(data.pixcnpj_arr) ? data.pixcnpj_arr : []; const pixcnpj_arr = Array.isArray(data.pixcnpj_arr) ? data.pixcnpj_arr : [];
const vales_arr = Array.isArray(data.vales_arr) ? data.vales_arr : []; const vales_arr = Array.isArray(data.vales_arr) ? data.vales_arr : [];
const receber_arr = Array.isArray(data.receber_arr) ? data.receber_arr : []; const receber_arr = Array.isArray(data.receber_arr) ? data.receber_arr : [];
const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : [];
// Cartoes arrays from estado // Cartoes arrays from estado
// (cartCred_arr etc. already declared above) const cartCred_arr = Array.isArray(data.cartoes_credito) ? data.cartoes_credito : [];
const cartDeb_arr = Array.isArray(data.cartoes_debito) ? data.cartoes_debito : [];
const cartAli_arr = Array.isArray(data.cartoes_alimentacao) ? data.cartoes_alimentacao : [];
const cartPix_arr = Array.isArray(data.cartoes_pix) ? data.cartoes_pix : [];
// Flat totals // Flat totals
const totalSangrias = sangrias_arr.reduce((s,r) => s+(r.retirada||0)+(r.valor||0), 0); const totalSangrias = sangrias_arr.reduce((s,r) => s+(r.retirada||0)+(r.valor||0), 0);
@@ -1731,16 +1726,13 @@ function buildReportHTML(data) {
const vales = data.vales || 0; const vales = data.vales || 0;
const receber = data.areceber || 0; const receber = data.areceber || 0;
const pixcnpj = data.pixcnpj || 0; const pixcnpj = data.pixcnpj || 0;
// Cancelamentos: SEMPRE recalcular da array — nunca usar o campo escalar (pode estar corrompido de bug anterior) // Cancelamentos: SEMPRE recalcular da array (o escalar pode estar desatualizado)
const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : []; const cancelamentos_arr = Array.isArray(data.cancelamentos_arr) ? data.cancelamentos_arr : [];
const cancelamentosCalc = cancelamentos_arr.reduce((s, r) => s + (r.valor || 0), 0); const totalCancelCalc = cancelamentos_arr.reduce((s,r) => s+(r.valor||0), 0);
const fechamento = data.fechamento || 0; const fechamento = data.fechamento || 0;
const sysTotal = data.sys_total || 0; const sysTotal = data.sys_total || 0;
const saldoEsp = data.saldo_esperado || 0; const saldoEsp = data.saldo_esperado || 0;
// Diferença = Total Sistema - Saldo Esperado - Cancelamentos const diferenca = sysTotal - saldoEsp - totalCancelCalc;
const sysTotalCalc = (data.sys_total || data.sysTotal || 0);
const saldoEspCalc = (data.saldo_esperado || 0);
const diferenca = sysTotalCalc - saldoEspCalc - cancelamentosCalc;
const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca); const diffStr = diferenca >= 0 ? `+${fmt(diferenca)}` : fmtNeg(diferenca);
const diffColor = diferenca === 0 ? '#2e7d32' : '#c0272d'; const diffColor = diferenca === 0 ? '#2e7d32' : '#c0272d';
@@ -1774,7 +1766,7 @@ function buildReportHTML(data) {
<div class="row"><span>💳 Crédito:</span><span class="val">${fmt(cartCred)}</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>💳 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>💳 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>❌ Cancelamentos:</span><span class="val">${fmt(totalCancelCalc)}</span></div>
<hr> <hr>
<div class="row"><span>📋 Fechamento (contado):</span><span class="val">${fmt(fechamento)}</span></div> <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> <div class="row"><span>🖥️ Total Sistema:</span><span class="val">${fmt(sysTotal)}</span></div>
@@ -1843,6 +1835,11 @@ function openPrintPreview(data, mode) {
`; `;
btnFechar.onclick = () => { btnFechar.onclick = () => {
document.body.removeChild(overlay); document.body.removeChild(overlay);
// Garante que o modal de confirmação volte a aparecer (caso ainda tenha a classe 'open')
const modalConfirm = document.getElementById('modal-confirm');
if (modalConfirm && !modalConfirm.classList.contains('open')) {
modalConfirm.classList.add('open');
}
}; };
const btnImprimir = document.createElement('button'); const btnImprimir = document.createElement('button');
@@ -1993,11 +1990,9 @@ async function tauriCarregarRascunho() {
// ─── Confirm Modal ──────────────────────────────────────────────────────── // ─── Confirm Modal ────────────────────────────────────────────────────────
function openConfirmModal() { function openConfirmModal() {
console.log('[DEBUG] openConfirmModal chamado'); console.log('[DEBUG] openConfirmModal chamado');
// Usa estado (que o boot fixou para builds fixas) em vez de ler DOM const operador = val('f-operador').trim();
// — o DOM pode estar vazio se o select está disabled/hidden const turno = val('f-turno').trim();
const operador = estado.operador.trim(); const loja = val('f-loja').trim();
const turno = estado.turno.trim();
const loja = estado.loja.trim();
console.log('[DEBUG] openConfirmModal campos:', {operador, turno, loja}); console.log('[DEBUG] openConfirmModal campos:', {operador, turno, loja});
if (!operador || !turno || !loja) { if (!operador || !turno || !loja) {
console.log('[DEBUG] openConfirmModal: campos faltando, alert'); console.log('[DEBUG] openConfirmModal: campos faltando, alert');
@@ -2055,6 +2050,8 @@ document.getElementById('confirm-cancel').addEventListener('click', closeConfirm
document.getElementById('modal-confirm').addEventListener('click', e => { if (e.target.id === 'modal-confirm') closeConfirmModal(); }); document.getElementById('modal-confirm').addEventListener('click', e => { if (e.target.id === 'modal-confirm') closeConfirmModal(); });
// Print from confirm modal — abre preview idêntico ao modal de confirmação // Print from confirm modal — abre preview idêntico ao modal de confirmação
// Não fecha o modal aqui — o preview é um overlay por cima; quando fechar o preview,
// o modal já estará visível por baixo (continua no DOM, só que coberto)
document.getElementById('confirm-print').addEventListener('click', async () => { document.getElementById('confirm-print').addEventListener('click', async () => {
// Usa buildReciboData para não duplicar lógica e ter todos os campos certos // Usa buildReciboData para não duplicar lógica e ter todos os campos certos
let data; let data;
@@ -2065,7 +2062,7 @@ document.getElementById('confirm-print').addEventListener('click', async () => {
alert('Erro ao montar dados do recibo.'); alert('Erro ao montar dados do recibo.');
return; return;
} }
// NÃO fecha o modal — o preview de impressão abre POR CIMA (depois de imprimir e fechar, continua no confirm) // NÃO fecha o modal aqui — só abre o preview por cima
openPrintPreview(data, 'report'); openPrintPreview(data, 'report');
}); });
@@ -3680,40 +3677,10 @@ async function salvarEdicaoSupabase() {
} }
// ─── Boot ───────────────────────────────────────────────────────────────── // ─── Boot ─────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', () => {
// Detecta se é build com loja fixa (União ou Aliança)
try {
const fixa = await window.__TAURI__.core.invoke('get_loja_fixa');
if (fixa) {
// Build fixa: marca global, esconde e bloqueia o seletor de loja
window.__LOJA_FIXA__ = fixa;
estado.loja = fixa;
const sel = document.getElementById('f-loja');
if (sel) {
sel.value = fixa;
sel.disabled = true;
sel.style.display = 'none';
}
// Esconde o label "Loja" (procura pelo texto, não pela posição)
document.querySelectorAll('.meta-label').forEach(l => {
if (l.textContent.trim() === 'Loja') l.style.display = 'none';
});
}
} catch(e) {
console.warn('get_loja_fixa não disponível:', e);
}
loadConfig(); loadConfig();
buildDatalists(); buildDatalists();
if (!loadState()) init(); if (!loadState()) init();
// ⚠️ Segurança extra: se build fixa, garante que o valor do select está correto
// e que estado.loja nunca fica vazio (independentemente do que init() fez)
if (window.__LOJA_FIXA__) {
const sel = document.getElementById('f-loja');
if (sel) sel.value = window.__LOJA_FIXA__;
estado.loja = window.__LOJA_FIXA__;
}
}); });
</script> </script>
</body> </body>