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:
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user