feat: implement main application structure with UI and layout management
- Added main application logic in `src/ui/app.rs` to handle state and modal dialogs. - Created module structure for UI components in `src/ui/mod.rs`. - Implemented column configuration screen in `src/ui/screens/configuracao_colunas.rs`. - Developed file import screen in `src/ui/screens/import.rs` for CSV and XLSX files. - Added layout management screen in `src/ui/screens/layouts.rs` for saving and importing layouts. - Created result display screen in `src/ui/screens/resultado.rs` to show analysis results. - Introduced modular organization for screens in `src/ui/screens/mod.rs`.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
use crate::domain::entities::nota::Nota;
|
||||
use crate::domain::entities::resultado_analise::IntervaloSerie;
|
||||
|
||||
/// Limite de faltantes por série antes de solicitar confirmação do usuário (RF04).
|
||||
pub const LIMITE_FALTANTES: u64 = 10_000;
|
||||
|
||||
/// Calcula o intervalo de faltantes de uma lista de notas de uma mesma série,
|
||||
/// sem materializar a lista completa.
|
||||
///
|
||||
/// Retorna `None` se não há notas.
|
||||
pub fn calcular_intervalo(notas: &[&Nota]) -> Option<IntervaloSerie> {
|
||||
if notas.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut numeros: Vec<u64> = notas.iter().map(|n| n.numero).collect();
|
||||
numeros.sort_unstable();
|
||||
numeros.dedup(); // Ignorar duplicatas no cálculo de sequência
|
||||
|
||||
let minimo = *numeros.first().unwrap();
|
||||
let maximo = *numeros.last().unwrap();
|
||||
|
||||
// Contar faltantes de forma incremental
|
||||
let mut faltantes: u64 = 0;
|
||||
for w in numeros.windows(2) {
|
||||
let a = w[0];
|
||||
let b = w[1];
|
||||
if b > a + 1 {
|
||||
faltantes += b - a - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Some(IntervaloSerie {
|
||||
minimo,
|
||||
maximo,
|
||||
contagem_faltantes: faltantes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Materializa a lista completa de números faltantes para uma série.
|
||||
/// Deve ser chamado apenas após confirmação do usuário quando o intervalo
|
||||
/// excede `LIMITE_FALTANTES`.
|
||||
///
|
||||
/// A lista é retornada em ordem crescente.
|
||||
pub fn detectar_faltantes(notas: &[&Nota]) -> Vec<u64> {
|
||||
if notas.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut numeros: Vec<u64> = notas.iter().map(|n| n.numero).collect();
|
||||
numeros.sort_unstable();
|
||||
numeros.dedup();
|
||||
|
||||
if numeros.len() <= 1 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut faltantes = Vec::new();
|
||||
for w in numeros.windows(2) {
|
||||
let a = w[0];
|
||||
let b = w[1];
|
||||
for faltante in (a + 1)..b {
|
||||
faltantes.push(faltante);
|
||||
}
|
||||
}
|
||||
|
||||
faltantes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::domain::entities::nota::Nota;
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
fn nota(numero: u64) -> Nota {
|
||||
Nota::new(numero, "001".to_string(), None, None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sem_faltantes() {
|
||||
let notas = vec![nota(1), nota(2), nota(3)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn com_faltante_simples() {
|
||||
let notas = vec![nota(1), nota(2), nota(3), nota(5)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
assert_eq!(detectar_faltantes(&refs), vec![4u64]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn com_multiplos_faltantes() {
|
||||
let notas = vec![nota(1), nota(5)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
assert_eq!(detectar_faltantes(&refs), vec![2u64, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serie_com_um_registro() {
|
||||
let notas = vec![nota(7)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vazio() {
|
||||
let refs: Vec<&Nota> = vec![];
|
||||
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intervalo_correto() {
|
||||
let notas = vec![nota(1), nota(2), nota(5)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
let intervalo = calcular_intervalo(&refs).unwrap();
|
||||
assert_eq!(intervalo.minimo, 1);
|
||||
assert_eq!(intervalo.maximo, 5);
|
||||
assert_eq!(intervalo.contagem_faltantes, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intervalo_excede_limite() {
|
||||
let notas = vec![nota(1), nota(20_000)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
let intervalo = calcular_intervalo(&refs).unwrap();
|
||||
assert!(intervalo.excede_limite(LIMITE_FALTANTES));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicatas_ignoradas_no_calculo() {
|
||||
// Duplicados não devem gerar faltantes falsos
|
||||
let notas = vec![nota(1), nota(1), nota(2), nota(3)];
|
||||
let refs: Vec<&Nota> = notas.iter().collect();
|
||||
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user