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
+1 -1
View File
@@ -7,7 +7,7 @@ fn main() -> std::io::Result<()> {
// Processa dois arquivos diferentes
println!("=== COMPARADOR DE ARQUIVOS DMED ===\n");
let arquivo1 = "LfsIssDmed068325.txt";
let arquivo1 = "LfsIssDmed008425.txt";
let pessoas1 = processar_arquivo(arquivo1)?;
println!("Arquivo 1: {} registros", pessoas1.len());
+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(())
}
+46
View File
@@ -0,0 +1,46 @@
// Script de teste rápido - execute com: cargo run --example teste_xls
use dmed::xls::processar_xls;
fn main() {
println!("=== TESTE DO PROCESSADOR XLS/XLSX ===\n");
let arquivo = "RELATORIO_PROSOFT_MOD_51.xls";
println!("📂 Tentando abrir: {}", arquivo);
println!("️ Suporta .xls e .xlsx (detecção automática)\n");
match processar_xls(arquivo) {
Ok(pessoas) => {
println!("✓ Arquivo processado com sucesso!");
println!("Total de registros únicos: {}\n", pessoas.len());
println!("=== Primeiros 10 registros ===");
for (i, pessoa) in pessoas.iter().take(10).enumerate() {
println!("{}. CPF: {} - R$ {:.2}",
i + 1,
pessoa.cpf,
pessoa.valor
);
}
// Estatísticas
let total_valor: f64 = pessoas.iter().map(|p| p.valor).sum();
let maior_valor = pessoas.iter()
.max_by(|a, b| a.valor.partial_cmp(&b.valor).unwrap())
.unwrap();
println!("\n=== Estatísticas ===");
println!("Total de valores: R$ {:.2}", total_valor);
println!("Maior valor: R$ {:.2} (CPF: {})", maior_valor.valor, maior_valor.cpf);
}
Err(e) => {
println!("✗ Erro ao processar arquivo: {}", e);
println!("\n💡 Dica:");
println!(" 1. Certifique-se que o arquivo existe na pasta do projeto");
println!(" 2. Nome esperado: RELATORIO_PROSOFT_MOD_51.xls");
println!(" 3. Formato: Excel .xls ou .xlsx (ambos são suportados)");
println!(" 4. Mesmo que a extensão seja .xls, pode ser .xlsx internamente");
}
}
}