d623d11e23
1. PIX CNPJ: linha separada no Saldo de Caixa + incluído na soma sys-total 2. Casas decimais: fmtNum sempre 2 casas (padrão dinheiro) 3. btn-anterior: agora busca do Supabase via sb_carregar_rascunho e repovoa todas as tabelas 4. Impressão: reimplementado usb_windows (WriteFile no Windows); adicionado modo teste (grava .bin) 5. Supabase: feedback de erro com console.error; storage salvar_agora persiste TODAS as tabelas (sangrias, pixcnpj, vales, cancelamentos)
612 lines
23 KiB
Rust
612 lines
23 KiB
Rust
//! Plugin de armazenamento local via SQLite
|
|
//! Fonte primária de dados — funciona offline, sync com Supabase quando online.
|
|
|
|
use rusqlite::{Connection, params};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::PathBuf;
|
|
use std::sync::Mutex;
|
|
use tauri::State;
|
|
use thiserror::Error;
|
|
use uuid::Uuid;
|
|
use chrono::Utc;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum StorageError {
|
|
#[error("SQLite error: {0}")]
|
|
Sqlite(#[from] rusqlite::Error),
|
|
#[error("Lock error")]
|
|
Lock,
|
|
#[error("IO error: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
#[error("JSON error: {0}")]
|
|
Json(#[from] serde_json::Error),
|
|
}
|
|
|
|
impl serde::Serialize for StorageError {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
// ── Tipos ──────────────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
pub struct FechamentoEstado {
|
|
pub id: Option<i64>,
|
|
pub uuid: String,
|
|
pub loja: String,
|
|
pub data: String,
|
|
pub operador: String,
|
|
pub turno: String,
|
|
pub saldo_troco: f64,
|
|
pub saldo_esperado: f64,
|
|
pub fechamento: f64,
|
|
pub tabelas: TableData,
|
|
pub cartoes: CartaoData,
|
|
pub sync_status: SyncStatus,
|
|
pub criado_em: String,
|
|
pub atualizado_em: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
pub struct TableData {
|
|
pub sangrias: Vec<TableRow>,
|
|
pub despesas: Vec<TableRow>,
|
|
pub pixcnpj: Vec<TableRow>,
|
|
pub vales: Vec<TableRow>,
|
|
pub areceber: Vec<TableRow>,
|
|
pub cancelamentos: Vec<TableRow>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
pub struct TableRow {
|
|
pub retirada: Option<f64>,
|
|
pub valor: Option<f64>,
|
|
pub descricao: Option<String>,
|
|
pub motivo: Option<String>,
|
|
pub cliente: Option<String>,
|
|
#[serde(flatten)]
|
|
pub extra: std::collections::HashMap<String, serde_json::Value>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
pub struct CartaoData {
|
|
pub credito: Vec<TableRow>,
|
|
pub debito: Vec<TableRow>,
|
|
pub alimentacao: Vec<TableRow>,
|
|
pub voucher: Vec<TableRow>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Default)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum SyncStatus {
|
|
#[default]
|
|
Local, // só local, não sincronizado
|
|
Syncing, // sync em progresso
|
|
Synced, // sincronizado com servidor
|
|
Error, // erro no sync
|
|
}
|
|
|
|
impl std::fmt::Display for SyncStatus {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
SyncStatus::Local => write!(f, "local"),
|
|
SyncStatus::Syncing => write!(f, "syncing"),
|
|
SyncStatus::Synced => write!(f, "synced"),
|
|
SyncStatus::Error => write!(f, "error"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&str> for SyncStatus {
|
|
fn from(s: &str) -> Self {
|
|
match s {
|
|
"local" => SyncStatus::Local,
|
|
"syncing" => SyncStatus::Syncing,
|
|
"synced" => SyncStatus::Synced,
|
|
"error" => SyncStatus::Error,
|
|
_ => SyncStatus::Local,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SalvarResult {
|
|
pub id: i64,
|
|
pub uuid: String,
|
|
pub sync_status: String,
|
|
}
|
|
|
|
// ── DB Manager ──────────────────────────────────────────────────────────────
|
|
|
|
pub struct DbManager {
|
|
conn: Mutex<Connection>,
|
|
}
|
|
|
|
impl DbManager {
|
|
pub fn new(app_dir: PathBuf) -> Result<Self, StorageError> {
|
|
std::fs::create_dir_all(&app_dir)?;
|
|
let db_path = app_dir.join("fechamento.db");
|
|
let conn = Connection::open(&db_path)?;
|
|
|
|
conn.execute_batch(
|
|
"PRAGMA journal_mode=WAL;
|
|
PRAGMA synchronous=NORMAL;
|
|
PRAGMA foreign_keys=ON;
|
|
PRAGMA busy_timeout=5000;
|
|
CREATE TABLE IF NOT EXISTS fechamentos (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uuid TEXT NOT NULL UNIQUE,
|
|
loja TEXT NOT NULL,
|
|
data TEXT NOT NULL,
|
|
operador TEXT NOT NULL DEFAULT '',
|
|
turno TEXT NOT NULL DEFAULT '',
|
|
saldo_troco REAL NOT NULL DEFAULT 0,
|
|
saldo_esperado REAL NOT NULL DEFAULT 0,
|
|
fechamento REAL NOT NULL DEFAULT 0,
|
|
tabelas_json TEXT NOT NULL DEFAULT '{}',
|
|
cartoes_json TEXT NOT NULL DEFAULT '{}',
|
|
sync_status TEXT NOT NULL DEFAULT 'local',
|
|
criado_em TEXT NOT NULL DEFAULT (datetime('now')),
|
|
atualizado_em TEXT NOT NULL DEFAULT (datetime('now')),
|
|
UNIQUE(loja, data, operador, turno)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_fechamentos_lookup
|
|
ON fechamentos(loja, data, operador, turno);
|
|
CREATE INDEX IF NOT EXISTS idx_fechamentos_data
|
|
ON fechamentos(data);"
|
|
)?;
|
|
|
|
log::info!("SQLite ready: {:?}", db_path);
|
|
Ok(Self { conn: Mutex::new(conn) })
|
|
}
|
|
|
|
/// Salva ou atualiza um estado de fechamento. Retorna (id, uuid).
|
|
pub fn salvar(&self, estado: &FechamentoEstado) -> Result<SalvarResult, StorageError> {
|
|
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
|
|
let uuid = if estado.uuid.is_empty() {
|
|
Uuid::new_v4().to_string()
|
|
} else {
|
|
estado.uuid.clone()
|
|
};
|
|
let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
|
|
let tabelas_json = serde_json::to_string(&estado.tabelas)?;
|
|
let cartoes_json = serde_json::to_string(&estado.cartoes)?;
|
|
let status_str = estado.sync_status.to_string();
|
|
|
|
// Upsert
|
|
conn.execute(
|
|
r#"INSERT INTO fechamentos
|
|
(uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
|
|
fechamento, tabelas_json, cartoes_json, sync_status, atualizado_em)
|
|
VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)
|
|
ON CONFLICT(loja, data, operador, turno) DO UPDATE SET
|
|
uuid=excluded.uuid,
|
|
saldo_troco=excluded.saldo_troco,
|
|
saldo_esperado=excluded.saldo_esperado,
|
|
fechamento=excluded.fechamento,
|
|
tabelas_json=excluded.tabelas_json,
|
|
cartoes_json=excluded.cartoes_json,
|
|
sync_status=excluded.sync_status,
|
|
atualizado_em=excluded.atualizado_em
|
|
WHERE atualizado_em <= excluded.atualizado_em"#,
|
|
params![
|
|
uuid,
|
|
estado.loja,
|
|
estado.data,
|
|
estado.operador,
|
|
estado.turno,
|
|
estado.saldo_troco,
|
|
estado.saldo_esperado,
|
|
estado.fechamento,
|
|
tabelas_json,
|
|
cartoes_json,
|
|
status_str,
|
|
now,
|
|
],
|
|
)?;
|
|
|
|
let id = conn.query_row(
|
|
"SELECT id FROM fechamentos WHERE uuid = ?1",
|
|
params![uuid],
|
|
|r| r.get(0),
|
|
)?;
|
|
|
|
Ok(SalvarResult { id, uuid, sync_status: status_str })
|
|
}
|
|
|
|
/// Busca o rascunho mais recente para loja+data (independente de operador/turno).
|
|
pub fn carregar_rascunho(&self, loja: &str, data: &str) -> Result<Option<FechamentoEstado>, StorageError> {
|
|
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
|
|
let mut stmt = conn.prepare(
|
|
"SELECT id, uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
|
|
fechamento, tabelas_json, cartoes_json, sync_status, criado_em, atualizado_em
|
|
FROM fechamentos
|
|
WHERE loja = ?1 AND data = ?2
|
|
ORDER BY atualizado_em DESC
|
|
LIMIT 1"
|
|
)?;
|
|
|
|
let result = stmt.query_row(params![loja, data], |r| {
|
|
let tabelas_json: String = r.get(9)?;
|
|
let cartoes_json: String = r.get(10)?;
|
|
let sync_str: String = r.get(11)?;
|
|
Ok(FechamentoEstado {
|
|
id: Some(r.get(0)?),
|
|
uuid: r.get(1)?,
|
|
loja: r.get(2)?,
|
|
data: r.get(3)?,
|
|
operador: r.get(4)?,
|
|
turno: r.get(5)?,
|
|
saldo_troco: r.get(6)?,
|
|
saldo_esperado: r.get(7)?,
|
|
fechamento: r.get(8)?,
|
|
tabelas: serde_json::from_str(&tabelas_json).unwrap_or_default(),
|
|
cartoes: serde_json::from_str(&cartoes_json).unwrap_or_default(),
|
|
sync_status: SyncStatus::from(sync_str.as_str()),
|
|
criado_em: r.get(12)?,
|
|
atualizado_em: r.get(13)?,
|
|
})
|
|
});
|
|
|
|
match result {
|
|
Ok(e) => Ok(Some(e)),
|
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
|
Err(e) => Err(StorageError::Sqlite(e)),
|
|
}
|
|
}
|
|
|
|
/// Lista todos os fechamentos de uma loja (para o histórico).
|
|
pub fn listar(&self, loja: &str, limite: i64) -> Result<Vec<FechamentoEstado>, StorageError> {
|
|
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
|
|
let mut stmt = conn.prepare(
|
|
"SELECT id, uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
|
|
fechamento, tabelas_json, cartoes_json, sync_status, criado_em, atualizado_em
|
|
FROM fechamentos
|
|
WHERE loja = ?1
|
|
ORDER BY data DESC, id DESC
|
|
LIMIT ?2"
|
|
)?;
|
|
|
|
let rows = stmt.query_map(params![loja, limite], |r| {
|
|
let tabelas_json: String = r.get(9)?;
|
|
let cartoes_json: String = r.get(10)?;
|
|
let sync_str: String = r.get(11)?;
|
|
Ok(FechamentoEstado {
|
|
id: Some(r.get(0)?),
|
|
uuid: r.get(1)?,
|
|
loja: r.get(2)?,
|
|
data: r.get(3)?,
|
|
operador: r.get(4)?,
|
|
turno: r.get(5)?,
|
|
saldo_troco: r.get(6)?,
|
|
saldo_esperado: r.get(7)?,
|
|
fechamento: r.get(8)?,
|
|
tabelas: serde_json::from_str(&tabelas_json).unwrap_or_default(),
|
|
cartoes: serde_json::from_str(&cartoes_json).unwrap_or_default(),
|
|
sync_status: SyncStatus::from(sync_str.as_str()),
|
|
criado_em: r.get(12)?,
|
|
atualizado_em: r.get(13)?,
|
|
})
|
|
})?;
|
|
|
|
rows.collect::<Result<Vec<_>, _>>().map_err(StorageError::Sqlite)
|
|
}
|
|
|
|
/// Busca um fechamento específico por ID.
|
|
pub fn buscar_por_id(&self, id: i64) -> Result<Option<FechamentoEstado>, StorageError> {
|
|
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
|
|
let mut stmt = conn.prepare(
|
|
"SELECT id, uuid, loja, data, operador, turno, saldo_troco, saldo_esperado,
|
|
fechamento, tabelas_json, cartoes_json, sync_status, criado_em, atualizado_em
|
|
FROM fechamentos WHERE id = ?1"
|
|
)?;
|
|
|
|
let result = stmt.query_row(params![id], |r| {
|
|
let tabelas_json: String = r.get(9)?;
|
|
let cartoes_json: String = r.get(10)?;
|
|
let sync_str: String = r.get(11)?;
|
|
Ok(FechamentoEstado {
|
|
id: Some(r.get(0)?),
|
|
uuid: r.get(1)?,
|
|
loja: r.get(2)?,
|
|
data: r.get(3)?,
|
|
operador: r.get(4)?,
|
|
turno: r.get(5)?,
|
|
saldo_troco: r.get(6)?,
|
|
saldo_esperado: r.get(7)?,
|
|
fechamento: r.get(8)?,
|
|
tabelas: serde_json::from_str(&tabelas_json).unwrap_or_default(),
|
|
cartoes: serde_json::from_str(&cartoes_json).unwrap_or_default(),
|
|
sync_status: SyncStatus::from(sync_str.as_str()),
|
|
criado_em: r.get(12)?,
|
|
atualizado_em: r.get(13)?,
|
|
})
|
|
});
|
|
|
|
match result {
|
|
Ok(e) => Ok(Some(e)),
|
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
|
Err(e) => Err(StorageError::Sqlite(e)),
|
|
}
|
|
}
|
|
|
|
/// Atualiza sync_status de um registro.
|
|
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();
|
|
conn.execute(
|
|
"UPDATE fechamentos SET sync_status = ?1, atualizado_em = ?2 WHERE uuid = ?3",
|
|
params![status.to_string(), now, uuid],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove um registro (para debugging).
|
|
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])?;
|
|
Ok(n > 0)
|
|
}
|
|
|
|
/// Retorna quantos registros estão pendentes de sync.
|
|
pub fn pendentes_sync(&self) -> Result<i64, StorageError> {
|
|
let conn = self.conn.lock().map_err(|_| StorageError::Lock)?;
|
|
let count: i64 = conn.query_row(
|
|
"SELECT COUNT(*) FROM fechamentos WHERE sync_status = 'local' OR sync_status = 'error'",
|
|
[],
|
|
|r| r.get(0),
|
|
)?;
|
|
Ok(count)
|
|
}
|
|
}
|
|
|
|
// ── Tauri Commands ─────────────────────────────────────────────────────────
|
|
|
|
// ── Tauri Commands ─────────────────────────────────────────────────────────
|
|
|
|
/// Salva fechamento com estrutura flexível ( campos simples do frontend JS )
|
|
#[tauri::command]
|
|
pub fn salvar_fechamento(
|
|
db: State<'_, DbManager>,
|
|
estado: serde_json::Value,
|
|
) -> Result<SalvarResult, StorageError> {
|
|
// Extrai campos simples do JSON flexível
|
|
let uuid = estado.get("uuid").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
|
let loja = estado.get("loja").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
|
let data = estado.get("data").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
|
let operador = estado.get("operador").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
|
let turno = estado.get("turno").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
|
let saldo_troco = estado.get("saldo_troco").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
|
let fechamento = estado.get("fechamento").and_then(|v| v.as_f64()).unwrap_or(0.0);
|
|
let saldo_esperado = saldo_troco + fechamento;
|
|
// Constrói TableData a partir de todas as tabelas do frontend
|
|
let despesas: Vec<serde_json::Value> = estado
|
|
.get("despesas")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| arr.to_vec())
|
|
.unwrap_or_default();
|
|
let sangrias: Vec<serde_json::Value> = estado
|
|
.get("sangrias")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| arr.to_vec())
|
|
.unwrap_or_default();
|
|
let pixcnpj: Vec<serde_json::Value> = estado
|
|
.get("pixcnpj")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| arr.to_vec())
|
|
.unwrap_or_default();
|
|
let vales: Vec<serde_json::Value> = estado
|
|
.get("vales")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| arr.to_vec())
|
|
.unwrap_or_default();
|
|
let receber: Vec<serde_json::Value> = estado
|
|
.get("receber")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| arr.to_vec())
|
|
.unwrap_or_default();
|
|
let cancelamentos: Vec<serde_json::Value> = estado
|
|
.get("cancelamentos")
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| arr.to_vec())
|
|
.unwrap_or_default();
|
|
let cartoes_json = estado.get("cartoes");
|
|
|
|
let to_table_row = |v: &serde_json::Value, _tipo: &str| -> TableRow {
|
|
TableRow {
|
|
descricao: v.get("desc").or_else(|| v.get("nome")).and_then(|s| s.as_str()).map(String::from),
|
|
retirada: v.get("retirada").and_then(|n| n.as_f64()),
|
|
valor: v.get("valor").and_then(|n| n.as_f64()),
|
|
motivo: v.get("motivo").and_then(|s| s.as_str()).map(String::from),
|
|
cliente: v.get("nome").and_then(|s| s.as_str()).map(String::from),
|
|
// hora, gerente, obs, numero vão para extra via flatten
|
|
extra: std::collections::HashMap::new(),
|
|
..Default::default()
|
|
}
|
|
};
|
|
|
|
let tabelas = TableData {
|
|
despesas: despesas.iter().map(|v| to_table_row(v, "despesas")).collect(),
|
|
sangrias: sangrias.iter().map(|v| to_table_row(v, "sangrias")).collect(),
|
|
pixcnpj: pixcnpj.iter().map(|v| to_table_row(v, "pixcnpj")).collect(),
|
|
vales: vales.iter().map(|v| to_table_row(v, "vales")).collect(),
|
|
areceber: receber.iter().map(|v| to_table_row(v, "receber")).collect(),
|
|
cancelamentos: cancelamentos.iter().map(|v| to_table_row(v, "cancelamentos")).collect(),
|
|
};
|
|
|
|
let cartoes = CartaoData {
|
|
credito: cartoes_json
|
|
.and_then(|c| c.get("credito"))
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| {
|
|
arr.iter()
|
|
.map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
|
|
.collect()
|
|
})
|
|
.unwrap_or_default(),
|
|
debito: cartoes_json
|
|
.and_then(|c| c.get("debito"))
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| {
|
|
arr.iter()
|
|
.map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
|
|
.collect()
|
|
})
|
|
.unwrap_or_default(),
|
|
alimentacao: cartoes_json
|
|
.and_then(|c| c.get("alimentacao"))
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| {
|
|
arr.iter()
|
|
.map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
|
|
.collect()
|
|
})
|
|
.unwrap_or_default(),
|
|
voucher: cartoes_json
|
|
.and_then(|c| c.get("pix"))
|
|
.and_then(|v| v.as_array())
|
|
.map(|arr| {
|
|
arr.iter()
|
|
.map(|v| TableRow { valor: v.as_f64(), ..Default::default() })
|
|
.collect()
|
|
})
|
|
.unwrap_or_default(),
|
|
};
|
|
|
|
let estado_rust = FechamentoEstado {
|
|
id: estado.get("id_local").and_then(|v| v.as_i64()),
|
|
uuid,
|
|
loja,
|
|
data,
|
|
operador,
|
|
turno,
|
|
saldo_troco,
|
|
fechamento,
|
|
saldo_esperado,
|
|
tabelas,
|
|
cartoes,
|
|
sync_status: SyncStatus::Local,
|
|
..Default::default()
|
|
};
|
|
|
|
db.salvar(&estado_rust)
|
|
}
|
|
|
|
/// Carrega rascunho e devolve estrutura simples para o frontend
|
|
#[tauri::command]
|
|
pub fn carregar_rascunho(
|
|
db: State<'_, DbManager>,
|
|
loja: String,
|
|
data: String,
|
|
) -> Result<Option<serde_json::Value>, StorageError> {
|
|
match db.carregar_rascunho(&loja, &data)? {
|
|
Some(e) => {
|
|
// 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 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();
|
|
let receber_rows: Vec<_> = e.tabelas.areceber.iter().filter_map(|r| {
|
|
r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))
|
|
}).collect();
|
|
|
|
Ok(Some(serde_json::json!({
|
|
"id_local": e.id,
|
|
"uuid": e.uuid,
|
|
"loja": e.loja,
|
|
"data": e.data,
|
|
"operador": e.operador,
|
|
"turno": e.turno,
|
|
"saldo_troco": e.saldo_troco,
|
|
"fechamento": e.fechamento,
|
|
"saldo_esperado": e.saldo_esperado,
|
|
"diferenca": e.fechamento - e.saldo_esperado,
|
|
"despesas": despesa_rows,
|
|
"total_sangrias": total_sangrias,
|
|
"total_despesas": total_despesas,
|
|
"vales": 0.0,
|
|
"cartoes": {
|
|
"credito": e.cartoes.credito.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"debito": e.cartoes.debito.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"alimentacao": e.cartoes.alimentacao.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"voucher": e.cartoes.voucher.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"cancelamentos": 0.0,
|
|
},
|
|
"receber": receber_rows,
|
|
"observacoes": "",
|
|
"sync_status": "local",
|
|
})))
|
|
}
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn listar_fechamentos(
|
|
db: State<'_, DbManager>,
|
|
loja: String,
|
|
limite: Option<i64>,
|
|
) -> Result<Vec<serde_json::Value>, StorageError> {
|
|
let rows = db.listar(&loja, limite.unwrap_or(50))?;
|
|
Ok(rows.into_iter().map(|e| serde_json::json!({
|
|
"id": e.id,
|
|
"uuid": e.uuid,
|
|
"data": e.data,
|
|
"operador": e.operador,
|
|
"turno": e.turno,
|
|
"saldo_esperado": e.saldo_esperado,
|
|
"sync_status": format!("{:?}", e.sync_status).to_lowercase(),
|
|
})).collect())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn buscar_fechamento_por_id(
|
|
db: State<'_, DbManager>,
|
|
id: i64,
|
|
) -> Result<Option<serde_json::Value>, StorageError> {
|
|
match db.buscar_por_id(id)? {
|
|
Some(e) => Ok(Some(serde_json::json!({
|
|
"id_local": e.id,
|
|
"uuid": e.uuid,
|
|
"loja": e.loja,
|
|
"data": e.data,
|
|
"operador": e.operador,
|
|
"turno": e.turno,
|
|
"saldo_troco": e.saldo_troco,
|
|
"fechamento": e.fechamento,
|
|
"saldo_esperado": e.saldo_esperado,
|
|
"diferenca": e.fechamento - e.saldo_esperado,
|
|
"despesas": e.tabelas.despesas.iter().filter_map(|r| r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))).collect::<Vec<_>>(),
|
|
"total_sangrias": e.tabelas.sangrias.iter().filter_map(|r| r.valor).sum::<f64>(),
|
|
"total_despesas": e.tabelas.despesas.iter().filter_map(|r| r.valor).sum::<f64>(),
|
|
"vales": 0.0,
|
|
"cartoes": {
|
|
"credito": e.cartoes.credito.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"debito": e.cartoes.debito.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"alimentacao": e.cartoes.alimentacao.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"voucher": e.cartoes.voucher.iter().find_map(|r| r.valor).unwrap_or(0.0),
|
|
"cancelamentos": 0.0,
|
|
},
|
|
"receber": e.tabelas.areceber.iter().filter_map(|r| r.descricao.as_ref().map(|d| serde_json::json!({"desc": d, "valor": r.valor}))).collect::<Vec<_>>(),
|
|
"observacoes": "",
|
|
"sync_status": format!("{:?}", e.sync_status).to_lowercase(),
|
|
}))),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn pendentes_sync(db: State<'_, DbManager>) -> Result<i64, StorageError> {
|
|
db.pendentes_sync()
|
|
}
|
|
|
|
pub fn init(app_dir: PathBuf) -> Result<DbManager, StorageError> {
|
|
DbManager::new(app_dir)
|
|
}
|