refactor: organiza imports e simplifica funções em diversos arquivos
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
entities::{
|
entities::{
|
||||||
nota::Nota,
|
nota::Nota,
|
||||||
resultado_analise::{IntervaloSerie, ResultadoAnalise, ResultadoPreAnalise},
|
resultado_analise::{ResultadoAnalise, ResultadoPreAnalise},
|
||||||
},
|
},
|
||||||
services::{
|
services::{
|
||||||
detector_duplicidade::duplicidades_por_serie,
|
detector_duplicidade::duplicidades_por_serie,
|
||||||
detector_sequencia::{calcular_intervalo, detectar_faltantes, LIMITE_FALTANTES},
|
detector_sequencia::{LIMITE_FALTANTES, calcular_intervalo, detectar_faltantes},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@@ -55,9 +55,7 @@ pub fn pre_analisar(notas: &[Nota]) -> ResultadoPreAnalise {
|
|||||||
|
|
||||||
/// Verifica se alguma série excede o limite de faltantes.
|
/// Verifica se alguma série excede o limite de faltantes.
|
||||||
/// Retorna lista de séries que precisam de confirmação.
|
/// Retorna lista de séries que precisam de confirmação.
|
||||||
pub fn series_com_intervalo_excessivo(
|
pub fn series_com_intervalo_excessivo(pre: &ResultadoPreAnalise) -> Vec<(String, u64)> {
|
||||||
pre: &ResultadoPreAnalise,
|
|
||||||
) -> Vec<(String, u64)> {
|
|
||||||
pre.intervalos_por_serie
|
pre.intervalos_por_serie
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, iv)| iv.excede_limite(LIMITE_FALTANTES))
|
.filter(|(_, iv)| iv.excede_limite(LIMITE_FALTANTES))
|
||||||
@@ -89,19 +87,18 @@ pub fn expandir_analise(pre: ResultadoPreAnalise, notas: &[Nota]) -> ResultadoAn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executa análise completa sem verificar limites (use case simplificado).
|
|
||||||
/// Útil quando o caller já confirmou ou sabe que não há intervalos excessivos.
|
|
||||||
pub fn executar_analise(notas: &[Nota]) -> ResultadoAnalise {
|
|
||||||
let pre = pre_analisar(notas);
|
|
||||||
expandir_analise(pre, notas)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::domain::entities::nota::Nota;
|
use crate::domain::entities::nota::Nota;
|
||||||
use rust_decimal_macros::dec;
|
use rust_decimal_macros::dec;
|
||||||
|
|
||||||
|
/// Executa análise completa sem verificar limites (atalho para testes).
|
||||||
|
fn executar_analise(notas: &[Nota]) -> ResultadoAnalise {
|
||||||
|
let pre = pre_analisar(notas);
|
||||||
|
expandir_analise(pre, notas)
|
||||||
|
}
|
||||||
|
|
||||||
fn nota(numero: u64, serie: &str, valor: Option<rust_decimal::Decimal>) -> Nota {
|
fn nota(numero: u64, serie: &str, valor: Option<rust_decimal::Decimal>) -> Nota {
|
||||||
Nota::new(numero, serie.to_string(), valor, None)
|
Nota::new(numero, serie.to_string(), valor, None)
|
||||||
}
|
}
|
||||||
@@ -133,10 +130,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_analise_detecta_intervalo_excessivo() {
|
fn pre_analise_detecta_intervalo_excessivo() {
|
||||||
let notas = vec![
|
let notas = vec![nota(1, "001", None), nota(20_000, "001", None)];
|
||||||
nota(1, "001", None),
|
|
||||||
nota(20_000, "001", None),
|
|
||||||
];
|
|
||||||
let pre = pre_analisar(¬as);
|
let pre = pre_analisar(¬as);
|
||||||
let excessivos = series_com_intervalo_excessivo(&pre);
|
let excessivos = series_com_intervalo_excessivo(&pre);
|
||||||
assert_eq!(excessivos.len(), 1);
|
assert_eq!(excessivos.len(), 1);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::domain::entities::layout::{Layout, LayoutCsv, LayoutXlsx};
|
use crate::domain::entities::layout::{LayoutCsv, LayoutXlsx};
|
||||||
use crate::domain::entities::nota::Nota;
|
use crate::domain::entities::nota::Nota;
|
||||||
use crate::domain::entities::serie::validar_serie;
|
use crate::domain::entities::serie::validar_serie;
|
||||||
use crate::domain::errors::{ErroArquivo, ResumoAvisos};
|
use crate::domain::errors::{ErroArquivo, ResumoAvisos};
|
||||||
@@ -24,7 +24,10 @@ pub fn listar_abas_xlsx(caminho: &Path) -> Result<InfoXlsx, ErroArquivo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Importa um arquivo CSV e retorna as notas válidas + avisos consolidados.
|
/// Importa um arquivo CSV e retorna as notas válidas + avisos consolidados.
|
||||||
pub fn importar_csv(caminho: &Path, config: &LayoutCsv) -> Result<ResultadoImportacao, ErroArquivo> {
|
pub fn importar_csv(
|
||||||
|
caminho: &Path,
|
||||||
|
config: &LayoutCsv,
|
||||||
|
) -> Result<ResultadoImportacao, ErroArquivo> {
|
||||||
let resultado = csv_reader::ler_csv(
|
let resultado = csv_reader::ler_csv(
|
||||||
caminho,
|
caminho,
|
||||||
config.delimitador as u8,
|
config.delimitador as u8,
|
||||||
@@ -46,17 +49,21 @@ pub fn importar_csv(caminho: &Path, config: &LayoutCsv) -> Result<ResultadoImpor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Importa um arquivo XLSX e retorna as notas válidas + avisos consolidados.
|
/// Importa um arquivo XLSX e retorna as notas válidas + avisos consolidados.
|
||||||
pub fn importar_xlsx(caminho: &Path, config: &LayoutXlsx) -> Result<ResultadoImportacao, ErroArquivo> {
|
pub fn importar_xlsx(
|
||||||
|
caminho: &Path,
|
||||||
|
config: &LayoutXlsx,
|
||||||
|
) -> Result<ResultadoImportacao, ErroArquivo> {
|
||||||
// Determinar linha de início a partir de qualquer campo mapeado
|
// Determinar linha de início a partir de qualquer campo mapeado
|
||||||
let linha_inicio = {
|
let linha_inicio = {
|
||||||
let coord_num = xlsx_reader::parsear_letra_linha(&config.pos_numero)
|
let coord_num = xlsx_reader::parsear_letra_linha(&config.pos_numero).ok_or_else(|| {
|
||||||
.ok_or_else(|| ErroArquivo::ErroLeitura(format!(
|
ErroArquivo::ErroLeitura(format!(
|
||||||
"Posição de Numero inválida: '{}'", config.pos_numero
|
"Posição de Numero inválida: '{}'",
|
||||||
)))?;
|
config.pos_numero
|
||||||
let coord_ser = xlsx_reader::parsear_letra_linha(&config.pos_serie)
|
))
|
||||||
.ok_or_else(|| ErroArquivo::ErroLeitura(format!(
|
})?;
|
||||||
"Posição de Serie inválida: '{}'", config.pos_serie
|
let coord_ser = xlsx_reader::parsear_letra_linha(&config.pos_serie).ok_or_else(|| {
|
||||||
)))?;
|
ErroArquivo::ErroLeitura(format!("Posição de Serie inválida: '{}'", config.pos_serie))
|
||||||
|
})?;
|
||||||
coord_num.linha.min(coord_ser.linha)
|
coord_num.linha.min(coord_ser.linha)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,7 +120,9 @@ fn mapear_linhas_para_notas(
|
|||||||
Some(s) if !s.trim().is_empty() => s.trim().to_string(),
|
Some(s) if !s.trim().is_empty() => s.trim().to_string(),
|
||||||
_ => {
|
_ => {
|
||||||
avisos.numeros_invalidos += 1;
|
avisos.numeros_invalidos += 1;
|
||||||
avisos.detalhes.push(format!("Linha {}: campo Numero ausente", linha_num));
|
avisos
|
||||||
|
.detalhes
|
||||||
|
.push(format!("Linha {}: campo Numero ausente", linha_num));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -123,7 +132,9 @@ fn mapear_linhas_para_notas(
|
|||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
avisos.numeros_invalidos += 1;
|
avisos.numeros_invalidos += 1;
|
||||||
avisos.detalhes.push(format!("Linha {}: {}", linha_num, msg));
|
avisos
|
||||||
|
.detalhes
|
||||||
|
.push(format!("Linha {}: {}", linha_num, msg));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -133,7 +144,9 @@ fn mapear_linhas_para_notas(
|
|||||||
Some(s) => s.trim().to_string(),
|
Some(s) => s.trim().to_string(),
|
||||||
None => {
|
None => {
|
||||||
avisos.series_invalidas += 1;
|
avisos.series_invalidas += 1;
|
||||||
avisos.detalhes.push(format!("Linha {}: campo Serie ausente", linha_num));
|
avisos
|
||||||
|
.detalhes
|
||||||
|
.push(format!("Linha {}: campo Serie ausente", linha_num));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::domain::{
|
|||||||
entities::layout::{Layout, LayoutJson},
|
entities::layout::{Layout, LayoutJson},
|
||||||
errors::ErroLayout,
|
errors::ErroLayout,
|
||||||
};
|
};
|
||||||
use crate::infrastructure::sqlite::{layout_repository, migrations};
|
use crate::infrastructure::sqlite::layout_repository;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
/// Salva um layout no banco de dados.
|
/// Salva um layout no banco de dados.
|
||||||
@@ -14,12 +14,10 @@ pub fn salvar_layout(conn: &Connection, layout: &Layout) -> Result<i64, String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = layout.id() {
|
if let Some(id) = layout.id() {
|
||||||
layout_repository::atualizar(conn, layout)
|
layout_repository::atualizar(conn, layout).map_err(|e| e.to_string())?;
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
} else {
|
} else {
|
||||||
layout_repository::salvar(conn, layout)
|
layout_repository::salvar(conn, layout).map_err(|e| e.to_string())
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,11 +26,6 @@ pub fn listar_layouts(conn: &Connection) -> Result<Vec<Layout>, String> {
|
|||||||
layout_repository::listar(conn).map_err(|e| e.to_string())
|
layout_repository::listar(conn).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Carrega um layout pelo id.
|
|
||||||
pub fn carregar_layout(conn: &Connection, id: i64) -> Result<Option<Layout>, String> {
|
|
||||||
layout_repository::buscar_por_id(conn, id).map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Exclui um layout pelo id.
|
/// Exclui um layout pelo id.
|
||||||
pub fn excluir_layout(conn: &Connection, id: i64) -> Result<(), String> {
|
pub fn excluir_layout(conn: &Connection, id: i64) -> Result<(), String> {
|
||||||
layout_repository::excluir(conn, id).map_err(|e| e.to_string())
|
layout_repository::excluir(conn, id).map_err(|e| e.to_string())
|
||||||
@@ -61,8 +54,8 @@ pub fn importar_layout_json(
|
|||||||
novo_nome: Option<&str>,
|
novo_nome: Option<&str>,
|
||||||
) -> Result<i64, ErroLayout> {
|
) -> Result<i64, ErroLayout> {
|
||||||
// Deserializar
|
// Deserializar
|
||||||
let json_repr: LayoutJson = serde_json::from_str(json)
|
let json_repr: LayoutJson =
|
||||||
.map_err(|e| ErroLayout::JsonMalformado(e.to_string()))?;
|
serde_json::from_str(json).map_err(|e| ErroLayout::JsonMalformado(e.to_string()))?;
|
||||||
|
|
||||||
let mut layout = Layout::try_from(json_repr)?;
|
let mut layout = Layout::try_from(json_repr)?;
|
||||||
|
|
||||||
@@ -105,6 +98,5 @@ pub fn importar_layout_json(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inserir novo
|
// Inserir novo
|
||||||
layout_repository::salvar(conn, &layout)
|
layout_repository::salvar(conn, &layout).map_err(|e| ErroLayout::JsonMalformado(e.to_string()))
|
||||||
.map_err(|e| ErroLayout::JsonMalformado(e.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error, Clone)]
|
|
||||||
pub enum ErroNumero {
|
|
||||||
#[error("Número zero não é válido para uma nota fiscal")]
|
|
||||||
Zero,
|
|
||||||
#[error("Valor não numérico: '{0}'")]
|
|
||||||
NaoNumerico(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error, Clone)]
|
#[derive(Debug, Error, Clone)]
|
||||||
pub enum ErroSerie {
|
pub enum ErroSerie {
|
||||||
#[error("Série inválida: '{0}' (deve conter de 1 a 3 dígitos numéricos)")]
|
#[error("Série inválida: '{0}' (deve conter de 1 a 3 dígitos numéricos)")]
|
||||||
@@ -40,52 +32,10 @@ pub enum ErroArquivo {
|
|||||||
TamanhoExcedido(u64),
|
TamanhoExcedido(u64),
|
||||||
#[error("Arquivo corrompido ou ilegível: {0}")]
|
#[error("Arquivo corrompido ou ilegível: {0}")]
|
||||||
Corrompido(String),
|
Corrompido(String),
|
||||||
#[error("Formato não suportado: {0}")]
|
|
||||||
FormatoNaoSuportado(String),
|
|
||||||
#[error("Erro de leitura: {0}")]
|
#[error("Erro de leitura: {0}")]
|
||||||
ErroLeitura(String),
|
ErroLeitura(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum ErroAnalise {
|
|
||||||
#[error("Intervalo de faltantes muito grande para a série '{serie}': {intervalo} registros")]
|
|
||||||
IntervaloMuitoGrande { serie: String, intervalo: u64 },
|
|
||||||
#[error("Campo obrigatório não mapeado: {0}")]
|
|
||||||
CampoObrigatorioNaoMapeado(String),
|
|
||||||
#[error("Índice de coluna inválido: campo '{campo}', índice {indice}")]
|
|
||||||
IndiceInvalido { campo: String, indice: usize },
|
|
||||||
#[error("Dois campos mapeados para o mesmo índice: {0}")]
|
|
||||||
IndicesDuplicados(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Aviso coletado durante a importação, para ser exibido de forma consolidada ao usuário.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AvisoImportacao {
|
|
||||||
pub categoria: CategoriaAviso,
|
|
||||||
pub mensagem: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum CategoriaAviso {
|
|
||||||
LinhaMalformada,
|
|
||||||
NumeroInvalido,
|
|
||||||
SerieInvalida,
|
|
||||||
ValorInvalido,
|
|
||||||
RegistroDescartado,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for CategoriaAviso {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
CategoriaAviso::LinhaMalformada => write!(f, "Linhas malformadas"),
|
|
||||||
CategoriaAviso::NumeroInvalido => write!(f, "Valores de Numero inválidos"),
|
|
||||||
CategoriaAviso::SerieInvalida => write!(f, "Registros com Série inválida"),
|
|
||||||
CategoriaAviso::ValorInvalido => write!(f, "Valores monetários inválidos"),
|
|
||||||
CategoriaAviso::RegistroDescartado => write!(f, "Registros descartados"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resumo consolidado de avisos para exibição em um único modal.
|
/// Resumo consolidado de avisos para exibição em um único modal.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ResumoAvisos {
|
pub struct ResumoAvisos {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::domain::services::parser_monetario::formatar_valor_br;
|
|||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use genpdf::{
|
use genpdf::{
|
||||||
elements::{Break, Paragraph},
|
elements::{Break, Paragraph},
|
||||||
fonts, style, Document, Element, SimplePageDecorator,
|
fonts, style, Document, SimplePageDecorator,
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
const LIMITE_50MB: u64 = 50 * 1024 * 1024;
|
|
||||||
|
|
||||||
/// Determina o caminho do banco de dados conforme o sistema operacional.
|
/// Determina o caminho do banco de dados conforme o sistema operacional.
|
||||||
pub fn caminho_banco() -> PathBuf {
|
pub fn caminho_banco() -> PathBuf {
|
||||||
let config_dir = dirs::config_dir().unwrap_or_else(|| PathBuf::from("."));
|
let config_dir = dirs::config_dir().unwrap_or_else(|| PathBuf::from("."));
|
||||||
@@ -33,7 +31,7 @@ pub fn abrir_banco_no_caminho(caminho: &Path) -> Result<(Connection, bool), Stri
|
|||||||
// Testar se o banco funciona com uma query simples
|
// Testar se o banco funciona com uma query simples
|
||||||
match conn.execute_batch("SELECT 1;") {
|
match conn.execute_batch("SELECT 1;") {
|
||||||
Ok(_) => return Ok((conn, false)),
|
Ok(_) => return Ok((conn, false)),
|
||||||
Err(e) => {
|
Err(_e) => {
|
||||||
// Banco corrompido
|
// Banco corrompido
|
||||||
drop(conn);
|
drop(conn);
|
||||||
let bak = caminho.with_extension("db.bak");
|
let bak = caminho.with_extension("db.bak");
|
||||||
|
|||||||
@@ -145,55 +145,6 @@ pub fn listar(conn: &Connection) -> Result<Vec<Layout>> {
|
|||||||
layouts
|
layouts
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Busca um layout pelo id.
|
|
||||||
pub fn buscar_por_id(conn: &Connection, id: i64) -> Result<Option<Layout>> {
|
|
||||||
let mut stmt = conn.prepare(
|
|
||||||
"SELECT id, nome, tipo,
|
|
||||||
delimitador, encoding, linha_cabecalho,
|
|
||||||
indice_numero, indice_serie, indice_valor, indice_data,
|
|
||||||
aba, pos_numero, pos_serie, pos_valor, pos_data
|
|
||||||
FROM layouts WHERE id = ?1",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut results = stmt.query_map([id], |row| {
|
|
||||||
let id: i64 = row.get(0)?;
|
|
||||||
let nome: String = row.get(1)?;
|
|
||||||
let tipo: String = row.get(2)?;
|
|
||||||
|
|
||||||
if tipo == "csv" {
|
|
||||||
let delim_str: String = row.get(3)?;
|
|
||||||
let delimitador = delim_str.chars().next().unwrap_or(';');
|
|
||||||
Ok(Layout::Csv {
|
|
||||||
id: Some(id),
|
|
||||||
nome,
|
|
||||||
config: LayoutCsv {
|
|
||||||
delimitador,
|
|
||||||
encoding: row.get(4)?,
|
|
||||||
linha_cabecalho: row.get::<_, i64>(5)? as usize,
|
|
||||||
indice_numero: row.get::<_, i64>(6)? as usize,
|
|
||||||
indice_serie: row.get::<_, i64>(7)? as usize,
|
|
||||||
indice_valor: row.get::<_, Option<i64>>(8)?.map(|v| v as usize),
|
|
||||||
indice_data: row.get::<_, Option<i64>>(9)?.map(|v| v as usize),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(Layout::Xlsx {
|
|
||||||
id: Some(id),
|
|
||||||
nome,
|
|
||||||
config: LayoutXlsx {
|
|
||||||
aba: row.get(10)?,
|
|
||||||
pos_numero: row.get(11)?,
|
|
||||||
pos_serie: row.get(12)?,
|
|
||||||
pos_valor: row.get(13)?,
|
|
||||||
pos_data: row.get(14)?,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
results.next().transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove um layout pelo id.
|
/// Remove um layout pelo id.
|
||||||
pub fn excluir(conn: &Connection, id: i64) -> Result<()> {
|
pub fn excluir(conn: &Connection, id: i64) -> Result<()> {
|
||||||
conn.execute("DELETE FROM layouts WHERE id = ?1", [id])?;
|
conn.execute("DELETE FROM layouts WHERE id = ?1", [id])?;
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ pub fn aplicar_migrations(conn: &Connection) -> Result<()> {
|
|||||||
)
|
)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
if versao_atual < 1 {
|
if versao_atual < VERSAO_SCHEMA_ATUAL {
|
||||||
migration_v1(conn)?;
|
migration_v1(conn)?;
|
||||||
if versao_atual == 0 {
|
if versao_atual == 0 {
|
||||||
conn.execute("INSERT INTO schema_version (versao) VALUES (?1);", [1])?;
|
conn.execute("INSERT INTO schema_version (versao) VALUES (?1);", [VERSAO_SCHEMA_ATUAL])?;
|
||||||
} else {
|
} else {
|
||||||
conn.execute("UPDATE schema_version SET versao = ?1;", [1])?;
|
conn.execute("UPDATE schema_version SET versao = ?1;", [VERSAO_SCHEMA_ATUAL])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ pub fn ler_xlsx(
|
|||||||
.worksheet_range(nome_aba)
|
.worksheet_range(nome_aba)
|
||||||
.map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
.map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||||
|
|
||||||
let mut avisos = ResumoAvisos::default();
|
let avisos = ResumoAvisos::default();
|
||||||
let mut linhas: Vec<Vec<String>> = Vec::new();
|
let mut linhas: Vec<Vec<String>> = Vec::new();
|
||||||
|
|
||||||
let linha_inicio_base0 = (linha_inicio.saturating_sub(1)) as usize;
|
let linha_inicio_base0 = (linha_inicio.saturating_sub(1)) as usize;
|
||||||
|
|||||||
+47
-45
@@ -1,10 +1,6 @@
|
|||||||
use crate::application::usecases::{
|
use crate::application::usecases::{
|
||||||
executar_analise::{expandir_analise, pre_analisar, series_com_intervalo_excessivo},
|
executar_analise::{expandir_analise, pre_analisar, series_com_intervalo_excessivo},
|
||||||
importar_arquivo::{importar_csv, importar_xlsx, listar_abas_xlsx, ResultadoImportacao},
|
layouts::{excluir_layout, listar_layouts, salvar_layout},
|
||||||
layouts::{
|
|
||||||
carregar_layout, excluir_layout, exportar_layout_json, importar_layout_json,
|
|
||||||
listar_layouts, salvar_layout,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
entities::{
|
entities::{
|
||||||
@@ -14,10 +10,7 @@ use crate::domain::{
|
|||||||
},
|
},
|
||||||
errors::ResumoAvisos,
|
errors::ResumoAvisos,
|
||||||
};
|
};
|
||||||
use crate::infrastructure::{
|
use crate::infrastructure::sqlite::{connection::abrir_banco, migrations::aplicar_migrations};
|
||||||
pdf_generator::GenpdfGenerator,
|
|
||||||
sqlite::{connection::abrir_banco, migrations::aplicar_migrations},
|
|
||||||
};
|
|
||||||
use egui::Context;
|
use egui::Context;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -34,10 +27,7 @@ pub enum EstadoApp {
|
|||||||
/// Resultado pronto para exibição.
|
/// Resultado pronto para exibição.
|
||||||
ExibindoResultado(ResultadoAnalise),
|
ExibindoResultado(ResultadoAnalise),
|
||||||
/// Aguardando confirmação do usuário para expandir faltantes.
|
/// Aguardando confirmação do usuário para expandir faltantes.
|
||||||
ConfirmandoIntervalo {
|
ConfirmandoIntervalo { pre: ResultadoPreAnalise },
|
||||||
pre: ResultadoPreAnalise,
|
|
||||||
series_excessivas: Vec<(String, u64)>,
|
|
||||||
},
|
|
||||||
/// Gerenciamento de layouts.
|
/// Gerenciamento de layouts.
|
||||||
GerenciandoLayouts,
|
GerenciandoLayouts,
|
||||||
/// Análise em execução em background (thread separada).
|
/// Análise em execução em background (thread separada).
|
||||||
@@ -72,6 +62,7 @@ pub enum TipoModal {
|
|||||||
pub enum AcaoModal {
|
pub enum AcaoModal {
|
||||||
ConfirmarExpansaoFaltantes,
|
ConfirmarExpansaoFaltantes,
|
||||||
ConfirmarExclusaoLayout(i64),
|
ConfirmarExclusaoLayout(i64),
|
||||||
|
#[allow(dead_code)]
|
||||||
SobrescreverLayout,
|
SobrescreverLayout,
|
||||||
ConfirmarNovaAnalise,
|
ConfirmarNovaAnalise,
|
||||||
/// Salvar a configuração atual como novo layout, usando modal.input_texto como nome.
|
/// Salvar a configuração atual como novo layout, usando modal.input_texto como nome.
|
||||||
@@ -396,12 +387,20 @@ impl App {
|
|||||||
/// Processa o resultado recebido do canal de análise em background.
|
/// Processa o resultado recebido do canal de análise em background.
|
||||||
pub fn processar_resultado_pendente(&mut self, resultado: ResultadoPendente) {
|
pub fn processar_resultado_pendente(&mut self, resultado: ResultadoPendente) {
|
||||||
match resultado {
|
match resultado {
|
||||||
ResultadoPendente::Concluido { resultado, avisos, notas } => {
|
ResultadoPendente::Concluido {
|
||||||
|
resultado,
|
||||||
|
avisos,
|
||||||
|
notas,
|
||||||
|
} => {
|
||||||
if let Some(n) = notas {
|
if let Some(n) = notas {
|
||||||
self.notas_importadas = n;
|
self.notas_importadas = n;
|
||||||
}
|
}
|
||||||
if let Some(av) = &avisos {
|
if let Some(av) = &avisos {
|
||||||
self.avisos_importacao = if av.tem_avisos() { Some(av.clone()) } else { None };
|
self.avisos_importacao = if av.tem_avisos() {
|
||||||
|
Some(av.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
}
|
}
|
||||||
self.estado = EstadoApp::ExibindoResultado(resultado);
|
self.estado = EstadoApp::ExibindoResultado(resultado);
|
||||||
if let Some(av) = &self.avisos_importacao.clone() {
|
if let Some(av) = &self.avisos_importacao.clone() {
|
||||||
@@ -418,18 +417,22 @@ impl App {
|
|||||||
notas,
|
notas,
|
||||||
} => {
|
} => {
|
||||||
self.notas_importadas = notas;
|
self.notas_importadas = notas;
|
||||||
self.avisos_importacao = if avisos.tem_avisos() { Some(avisos) } else { None };
|
self.avisos_importacao = if avisos.tem_avisos() {
|
||||||
|
Some(avisos)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let msg = series_excessivas
|
let msg = series_excessivas
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(serie, count)| {
|
.map(|(serie, count)| {
|
||||||
format!("Série {}: intervalo de {} faltantes detectado", serie, count)
|
format!(
|
||||||
|
"Série {}: intervalo de {} faltantes detectado",
|
||||||
|
serie, count
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
self.estado = EstadoApp::ConfirmandoIntervalo {
|
self.estado = EstadoApp::ConfirmandoIntervalo { pre };
|
||||||
pre,
|
|
||||||
series_excessivas,
|
|
||||||
};
|
|
||||||
self.exibir_confirmacao(
|
self.exibir_confirmacao(
|
||||||
"Intervalo muito grande",
|
"Intervalo muito grande",
|
||||||
format!(
|
format!(
|
||||||
@@ -464,16 +467,16 @@ impl App {
|
|||||||
let msg = excessivos
|
let msg = excessivos
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(serie, count)| {
|
.map(|(serie, count)| {
|
||||||
format!("Série {}: intervalo de {} faltantes detectado", serie, count)
|
format!(
|
||||||
|
"Série {}: intervalo de {} faltantes detectado",
|
||||||
|
serie, count
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
let acao = AcaoModal::ConfirmarExpansaoFaltantes;
|
let acao = AcaoModal::ConfirmarExpansaoFaltantes;
|
||||||
self.estado = EstadoApp::ConfirmandoIntervalo {
|
self.estado = EstadoApp::ConfirmandoIntervalo { pre };
|
||||||
pre,
|
|
||||||
series_excessivas: excessivos,
|
|
||||||
};
|
|
||||||
self.exibir_confirmacao(
|
self.exibir_confirmacao(
|
||||||
"Intervalo muito grande",
|
"Intervalo muito grande",
|
||||||
format!(
|
format!(
|
||||||
@@ -502,7 +505,8 @@ impl App {
|
|||||||
egui::TopBottomPanel::top("breadcrumb").show(ctx, |ui| {
|
egui::TopBottomPanel::top("breadcrumb").show(ctx, |ui| {
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
for (i, label) in ["① Arquivo", "② Colunas", "③ Resultado"].iter().enumerate() {
|
for (i, label) in ["① Arquivo", "② Colunas", "③ Resultado"].iter().enumerate()
|
||||||
|
{
|
||||||
let n = i + 1;
|
let n = i + 1;
|
||||||
let texto = egui::RichText::new(*label);
|
let texto = egui::RichText::new(*label);
|
||||||
if n == passo_ativo {
|
if n == passo_ativo {
|
||||||
@@ -552,25 +556,23 @@ impl eframe::App for App {
|
|||||||
let tela_id = self.tela_atual();
|
let tela_id = self.tela_atual();
|
||||||
|
|
||||||
// Renderizar a tela atual
|
// Renderizar a tela atual
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| match tela_id {
|
||||||
match tela_id {
|
0 => crate::ui::screens::import::renderizar(ui, ctx, self),
|
||||||
0 => crate::ui::screens::import::renderizar(ui, ctx, self),
|
1 => crate::ui::screens::import::renderizar_selecao_aba(ui, ctx, self),
|
||||||
1 => crate::ui::screens::import::renderizar_selecao_aba(ui, ctx, self),
|
2 => crate::ui::screens::configuracao_colunas::renderizar(ui, ctx, self),
|
||||||
2 => crate::ui::screens::configuracao_colunas::renderizar(ui, ctx, self),
|
3 => crate::ui::screens::resultado::renderizar(ui, ctx, self),
|
||||||
3 => crate::ui::screens::resultado::renderizar(ui, ctx, self),
|
4 => crate::ui::screens::configuracao_colunas::renderizar(ui, ctx, self),
|
||||||
4 => crate::ui::screens::configuracao_colunas::renderizar(ui, ctx, self),
|
5 => crate::ui::screens::layouts::renderizar(ui, ctx, self),
|
||||||
5 => crate::ui::screens::layouts::renderizar(ui, ctx, self),
|
6 => {
|
||||||
6 => {
|
ui.centered_and_justified(|ui| {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.label(
|
||||||
ui.label(
|
egui::RichText::new("⏳ Analisando... aguarde.")
|
||||||
egui::RichText::new("⏳ Analisando... aguarde.")
|
.size(22.0)
|
||||||
.size(22.0)
|
.strong(),
|
||||||
.strong(),
|
);
|
||||||
);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,16 @@ pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if valido && !app.notas_importadas.is_empty() {
|
||||||
|
if ui
|
||||||
|
.button("🔄 Reanalisar")
|
||||||
|
.on_hover_text("Reanalisa as notas já importadas sem reimportar o arquivo")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
app.executar_analise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ui.button("💾 Salvar como layout...").clicked() {
|
if ui.button("💾 Salvar como layout...").clicked() {
|
||||||
app.exibir_modal_salvar_layout();
|
app.exibir_modal_salvar_layout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::application::usecases::importar_arquivo::{
|
use crate::application::usecases::importar_arquivo::listar_abas_xlsx;
|
||||||
importar_csv, importar_xlsx, listar_abas_xlsx,
|
|
||||||
};
|
|
||||||
use crate::domain::entities::layout::TipoArquivo;
|
use crate::domain::entities::layout::TipoArquivo;
|
||||||
use crate::ui::app::{App, EstadoApp};
|
use crate::ui::app::{App, EstadoApp};
|
||||||
use egui::{Context, Ui};
|
use egui::{Context, Ui};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::application::usecases::layouts::{
|
use crate::application::usecases::layouts::{
|
||||||
exportar_layout_json, importar_layout_json, salvar_layout,
|
exportar_layout_json, importar_layout_json, salvar_layout,
|
||||||
};
|
};
|
||||||
use crate::domain::entities::layout::{Layout, LayoutCsv, LayoutXlsx, TipoArquivo};
|
use crate::domain::entities::layout::{Layout, TipoArquivo};
|
||||||
use crate::domain::errors::ErroLayout;
|
use crate::domain::errors::ErroLayout;
|
||||||
use crate::ui::app::{AcaoModal, App, EstadoApp};
|
use crate::ui::app::{AcaoModal, App, EstadoApp};
|
||||||
use egui::{Context, Ui};
|
use egui::{Context, Ui};
|
||||||
@@ -83,7 +83,7 @@ pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) {
|
|||||||
|
|
||||||
fn renderizar_secao_layouts(
|
fn renderizar_secao_layouts(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
ctx: &Context,
|
_ctx: &Context,
|
||||||
app: &mut App,
|
app: &mut App,
|
||||||
titulo: &str,
|
titulo: &str,
|
||||||
layouts: &[Layout],
|
layouts: &[Layout],
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use egui::{Context, Ui};
|
|||||||
const OPCOES_PAGINA: &[usize] = &[50, 100, 200, 1000];
|
const OPCOES_PAGINA: &[usize] = &[50, 100, 200, 1000];
|
||||||
|
|
||||||
/// Renderiza a tela de resultados.
|
/// Renderiza a tela de resultados.
|
||||||
pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) {
|
pub fn renderizar(ui: &mut Ui, _ctx: &Context, app: &mut App) {
|
||||||
// Extrair resultado do estado (sem mover)
|
// Extrair resultado do estado (sem mover)
|
||||||
let resultado = match &app.estado {
|
let resultado = match &app.estado {
|
||||||
EstadoApp::ExibindoResultado(r) => r.clone(),
|
EstadoApp::ExibindoResultado(r) => r.clone(),
|
||||||
|
|||||||
Reference in New Issue
Block a user