feat: add DMED comparador library with TXT and XLS processing

- Implemented TXT processor for DMED files, extracting RPPSS records.
- Added XLS processor for Prosoft reports, supporting both XLS and XLSX formats.
- Created a comparison tool to identify discrepancies between TXT and XLS data.
- Developed a GUI using eframe for user-friendly interaction.
- Included detailed README documentation for setup and usage instructions.
- Added error handling for file processing and improved data extraction methods.
- Implemented tests for data processing functions and utilities.
- Introduced a build script for Windows resource management.
- Added example scripts for basic and complete comparisons.
- Included a report generation feature for discrepancies found during comparisons.
This commit is contained in:
FelipeCN
2026-01-30 10:42:23 -03:00
parent fab1c26445
commit 3a888a058f
14 changed files with 5570 additions and 353 deletions
+200
View File
@@ -0,0 +1,200 @@
// Comparador completo: TXT (DMED) vs XLS (Prosoft)
use dmed::{processar_arquivo, xls::processar_xls, Pessoa};
use std::collections::{HashMap, HashSet};
#[derive(Debug)]
struct Divergencia {
cpf: String,
nome: String,
valor_txt: f64,
valor_xls: f64,
diferenca: f64,
percentual: f64,
}
fn main() -> std::io::Result<()> {
println!("═══════════════════════════════════════════════");
println!(" COMPARADOR DMED (TXT) vs PROSOFT (XLS)");
println!("═══════════════════════════════════════════════\n");
// Processa arquivo TXT (DMED)
println!("📄 Processando arquivo TXT (DMED)...");
let arquivo_txt = "LfsIssDmed008425.txt";
let pessoas_txt = processar_arquivo(arquivo_txt)?;
println!("{} registros encontrados\n", pessoas_txt.len());
// Processa arquivo XLS (Prosoft)
println!("📊 Processando arquivo XLS (Prosoft)...");
let arquivo_xls = "RELATORIO_PROSOFT_MOD_51.xls";
let pessoas_xls = match processar_xls(arquivo_xls) {
Ok(p) => {
println!("{} registros únicos encontrados\n", p.len());
p
},
Err(e) => {
println!(" ✗ Erro ao processar XLS: {}", e);
println!(" ️ Coloque o arquivo RELATORIO_PROSOFT_MOD_51.xls na pasta do projeto\n");
return Ok(());
}
};
// Cria mapas para comparação
let mapa_txt: HashMap<&str, &Pessoa> = pessoas_txt
.iter()
.map(|p| (p.cpf.as_str(), p))
.collect();
let mapa_xls: HashMap<&str, &Pessoa> = pessoas_xls
.iter()
.map(|p| (p.cpf.as_str(), p))
.collect();
// Conjunto de todos os CPFs
let todos_cpfs: HashSet<&str> = mapa_txt.keys()
.chain(mapa_xls.keys())
.copied()
.collect();
println!("═══════════════════════════════════════════════");
println!(" ANÁLISE");
println!("═══════════════════════════════════════════════\n");
// Estatísticas
let cpfs_ambos = todos_cpfs.iter()
.filter(|cpf| mapa_txt.contains_key(**cpf) && mapa_xls.contains_key(**cpf))
.count();
let cpfs_so_txt = todos_cpfs.iter()
.filter(|cpf| mapa_txt.contains_key(**cpf) && !mapa_xls.contains_key(**cpf))
.count();
let cpfs_so_xls = todos_cpfs.iter()
.filter(|cpf| !mapa_txt.contains_key(**cpf) && mapa_xls.contains_key(**cpf))
.count();
println!("📊 Estatísticas:");
println!(" Total de CPFs únicos: {}", todos_cpfs.len());
println!(" Presentes em ambos: {}", cpfs_ambos);
println!(" Apenas no TXT: {}", cpfs_so_txt);
println!(" Apenas no XLS: {}", cpfs_so_xls);
// Encontra divergências
let mut divergencias = Vec::new();
let margem_tolerancia = 0.01; // R$ 0,01
for cpf in todos_cpfs.iter() {
if let (Some(p_txt), Some(p_xls)) = (mapa_txt.get(*cpf), mapa_xls.get(*cpf)) {
let diferenca = (p_txt.valor - p_xls.valor).abs();
if diferenca > margem_tolerancia {
let percentual = if p_xls.valor != 0.0 {
((p_txt.valor - p_xls.valor) / p_xls.valor) * 100.0
} else {
0.0
};
divergencias.push(Divergencia {
cpf: cpf.to_string(),
nome: p_txt.nome.clone(),
valor_txt: p_txt.valor,
valor_xls: p_xls.valor,
diferenca,
percentual,
});
}
}
}
// Ordena divergências por diferença (maior primeiro)
divergencias.sort_by(|a, b| b.diferenca.abs().partial_cmp(&a.diferenca.abs()).unwrap());
println!("\n📈 Divergências de valores:");
println!(" Total: {} registros com diferenças\n", divergencias.len());
if !divergencias.is_empty() {
println!("═══════════════════════════════════════════════");
println!(" TOP 10 DIVERGÊNCIAS");
println!("═══════════════════════════════════════════════\n");
for (i, div) in divergencias.iter().take(10).enumerate() {
println!("{}. CPF: {}", i + 1, div.cpf);
if !div.nome.is_empty() {
println!(" Nome: {}", div.nome);
}
println!(" TXT (DMED): R$ {:>12.2}", div.valor_txt);
println!(" XLS (Prosoft): R$ {:>12.2}", div.valor_xls);
println!(" Diferença: R$ {:>12.2} ({:+.1}%)", div.diferenca, div.percentual);
println!();
}
// Salva relatório completo
salvar_relatorio(&divergencias, &mapa_txt, &mapa_xls, cpfs_so_txt, cpfs_so_xls)?;
} else {
println!(" ✓ Todos os valores conferem!\n");
}
// CPFs que estão apenas em um dos arquivos
if cpfs_so_txt > 0 {
println!("⚠️ CPFs apenas no TXT (primeiros 5):");
for cpf in todos_cpfs.iter().filter(|c| mapa_txt.contains_key(**c) && !mapa_xls.contains_key(**c)).take(5) {
if let Some(p) = mapa_txt.get(*cpf) {
println!(" {} - {} - R$ {:.2}", cpf, p.nome, p.valor);
}
}
println!();
}
if cpfs_so_xls > 0 {
println!("⚠️ CPFs apenas no XLS (primeiros 5):");
for cpf in todos_cpfs.iter().filter(|c| !mapa_txt.contains_key(**c) && mapa_xls.contains_key(**c)).take(5) {
if let Some(p) = mapa_xls.get(*cpf) {
println!(" {} - R$ {:.2}", cpf, p.valor);
}
}
println!();
}
Ok(())
}
fn salvar_relatorio(
divergencias: &[Divergencia],
mapa_txt: &HashMap<&str, &Pessoa>,
mapa_xls: &HashMap<&str, &Pessoa>,
cpfs_so_txt: usize,
cpfs_so_xls: usize,
) -> std::io::Result<()> {
use std::fs::File;
use std::io::Write;
let mut arquivo = File::create("relatorio_divergencias.txt")?;
writeln!(arquivo, "RELATÓRIO DE DIVERGÊNCIAS - DMED vs PROSOFT")?;
writeln!(arquivo, "Data: {}", chrono::Local::now().format("%d/%m/%Y %H:%M:%S"))?;
writeln!(arquivo, "=")?;
writeln!(arquivo)?;
writeln!(arquivo, "RESUMO:")?;
writeln!(arquivo, "Total de divergências: {}", divergencias.len())?;
writeln!(arquivo, "CPFs apenas no TXT: {}", cpfs_so_txt)?;
writeln!(arquivo, "CPFs apenas no XLS: {}", cpfs_so_xls)?;
writeln!(arquivo)?;
writeln!(arquivo, "DETALHAMENTO DAS DIVERGÊNCIAS:")?;
writeln!(arquivo, "{:-<100}", "")?;
for (i, div) in divergencias.iter().enumerate() {
writeln!(arquivo, "\n{}. CPF: {}", i + 1, div.cpf)?;
if !div.nome.is_empty() {
writeln!(arquivo, " Nome: {}", div.nome)?;
}
writeln!(arquivo, " TXT (DMED): R$ {:>12.2}", div.valor_txt)?;
writeln!(arquivo, " XLS (Prosoft): R$ {:>12.2}", div.valor_xls)?;
writeln!(arquivo, " Diferença: R$ {:>12.2} ({:+.1}%)", div.diferenca, div.percentual)?;
}
println!("✓ Relatório completo salvo em relatorio_divergencias.txt");
Ok(())
}