140 lines
4.7 KiB
Rust
140 lines
4.7 KiB
Rust
use crate::domain::{
|
|
entities::{
|
|
nota::Nota,
|
|
resultado_analise::{ResultadoAnalise, ResultadoPreAnalise},
|
|
},
|
|
services::{
|
|
detector_duplicidade::duplicidades_por_serie,
|
|
detector_sequencia::{LIMITE_FALTANTES, calcular_intervalo, detectar_faltantes},
|
|
},
|
|
};
|
|
use rust_decimal::Decimal;
|
|
use std::collections::HashMap;
|
|
|
|
/// Executa a pré-análise: calcula intervalos e duplicatas, sem expandir faltantes.
|
|
/// O caller deve verificar se algum intervalo excede `LIMITE_FALTANTES` e,
|
|
/// se sim, exibir confirmação ao usuário antes de chamar `expandir_analise`.
|
|
pub fn pre_analisar(notas: &[Nota]) -> ResultadoPreAnalise {
|
|
// Agrupar por série
|
|
let mut por_serie: HashMap<String, Vec<&Nota>> = HashMap::new();
|
|
for nota in notas {
|
|
por_serie.entry(nota.serie.clone()).or_default().push(nota);
|
|
}
|
|
|
|
let mut intervalos_por_serie = HashMap::new();
|
|
let mut soma_total = Decimal::ZERO;
|
|
let mut soma_por_serie: HashMap<String, Decimal> = HashMap::new();
|
|
let mut total_por_serie: HashMap<String, usize> = HashMap::new();
|
|
|
|
for (serie, notas_serie) in &por_serie {
|
|
// Somar valores
|
|
for nota in notas_serie.iter() {
|
|
if let Some(v) = nota.valor {
|
|
soma_total += v;
|
|
*soma_por_serie.entry(serie.clone()).or_insert(Decimal::ZERO) += v;
|
|
}
|
|
}
|
|
*total_por_serie.entry(serie.clone()).or_insert(0) += notas_serie.len();
|
|
|
|
// Calcular intervalo de faltantes
|
|
if let Some(intervalo) = calcular_intervalo(notas_serie) {
|
|
intervalos_por_serie.insert(serie.clone(), intervalo);
|
|
}
|
|
}
|
|
|
|
let duplicadas_por_serie = duplicidades_por_serie(notas);
|
|
|
|
ResultadoPreAnalise {
|
|
intervalos_por_serie,
|
|
duplicadas_por_serie,
|
|
soma_total,
|
|
soma_por_serie,
|
|
total_por_serie,
|
|
}
|
|
}
|
|
|
|
/// Verifica se alguma série excede o limite de faltantes.
|
|
/// Retorna lista de séries que precisam de confirmação.
|
|
pub fn series_com_intervalo_excessivo(pre: &ResultadoPreAnalise) -> Vec<(String, u64)> {
|
|
pre.intervalos_por_serie
|
|
.iter()
|
|
.filter(|(_, iv)| iv.excede_limite(LIMITE_FALTANTES))
|
|
.map(|(serie, iv)| (serie.clone(), iv.contagem_faltantes))
|
|
.collect()
|
|
}
|
|
|
|
/// Expande a pré-análise para o resultado completo, materializando a lista de faltantes.
|
|
/// Deve ser chamado após confirmação do usuário (ou quando nenhum intervalo excede o limite).
|
|
pub fn expandir_analise(pre: ResultadoPreAnalise, notas: &[Nota]) -> ResultadoAnalise {
|
|
let mut por_serie: HashMap<String, Vec<&Nota>> = HashMap::new();
|
|
for nota in notas {
|
|
por_serie.entry(nota.serie.clone()).or_default().push(nota);
|
|
}
|
|
|
|
let mut faltantes_por_serie = HashMap::new();
|
|
|
|
for (serie, notas_serie) in &por_serie {
|
|
let faltantes = detectar_faltantes(notas_serie);
|
|
faltantes_por_serie.insert(serie.clone(), faltantes);
|
|
}
|
|
|
|
ResultadoAnalise {
|
|
faltantes_por_serie,
|
|
duplicadas_por_serie: pre.duplicadas_por_serie,
|
|
soma_total: pre.soma_total,
|
|
soma_por_serie: pre.soma_por_serie,
|
|
total_por_serie: pre.total_por_serie,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::domain::entities::nota::Nota;
|
|
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 {
|
|
Nota::new(numero, serie.to_string(), valor, None)
|
|
}
|
|
|
|
#[test]
|
|
fn analise_simples() {
|
|
let notas = vec![
|
|
nota(1, "001", Some(dec!(100.00))),
|
|
nota(2, "001", Some(dec!(200.00))),
|
|
nota(4, "001", Some(dec!(50.00))),
|
|
];
|
|
let resultado = executar_analise(¬as);
|
|
assert_eq!(resultado.faltantes_por_serie["001"], vec![3u64]);
|
|
assert_eq!(resultado.soma_total, dec!(350.00));
|
|
}
|
|
|
|
#[test]
|
|
fn analise_multiplas_series() {
|
|
let notas = vec![
|
|
nota(1, "001", None),
|
|
nota(3, "001", None),
|
|
nota(1, "002", None),
|
|
nota(2, "002", None),
|
|
];
|
|
let resultado = executar_analise(¬as);
|
|
assert_eq!(resultado.faltantes_por_serie["001"], vec![2u64]);
|
|
assert!(resultado.faltantes_por_serie["002"].is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn pre_analise_detecta_intervalo_excessivo() {
|
|
let notas = vec![nota(1, "001", None), nota(20_000, "001", None)];
|
|
let pre = pre_analisar(¬as);
|
|
let excessivos = series_com_intervalo_excessivo(&pre);
|
|
assert_eq!(excessivos.len(), 1);
|
|
assert_eq!(excessivos[0].0, "001");
|
|
}
|
|
}
|