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
Generated
+4472 -1
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -7,6 +7,21 @@ edition = "2021"
name = "dmed"
path = "src/lib.rs"
[[bin]]
name = "gui"
path = "src/bin/gui.rs"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
calamine = "0.26"
encoding_rs = "0.8"
eframe = "0.29"
egui = "0.29"
rfd = "0.15"
[build-dependencies]
winres = "0.1"
[dev-dependencies]
chrono = "0.4"
-341
View File
@@ -1,341 +0,0 @@
Dmed|2026|2025|N|||
RESPO|21681939835|Anelisa Pirola Chalegre|17|35316200|205||anelisa@edegarpirolacontabilidade.com.br|
DECPJ|03364887000195|CLINICA DE CIRURGIA PLASTICA DR VIDAL LTDA|1|||01877281867|N|||
PSS|
RPPSS|00276808860|MARCO ANTONIO MACHADO|2200000|
RPPSS|00633999032|ELISANDRA BARBOSA TORRES|600000|
RPPSS|01133252826|JULIA AGUIAR MARTINEZ JACOB|130000|
RPPSS|01201476887|ANNIBAL ANTONIO BIANCHINI|50000|
RPPSS|01313055816|JUSSARA TAVARES PEREIRA|90000|
RPPSS|01595808620|GABRIEL SOUZA VASCONCELOS|30000|
RPPSS|01611859409|Maria Raquel dos Santos Silva|150000|
RPPSS|01869161831|SOZIGENES EDUARDO BARALDI|110000|
RPPSS|01876897848|VALTER ESTEVES ASCENCIO|100000|
RPPSS|01887885811|APARECIDA VERA BALDAO FACHINI|50000|
RPPSS|01888554835|MARIA HELENA DE PAIVA VIEIRA|1416000|
RPPSS|02013748892|ANA MARIA SCARDELATO SAURIN|4532000|
RPPSS|02039876839|MARIUSA DE FATIMA ALVES VERMELHO|120000|
RPPSS|02229875108|RODRINY FERREIRA PORFIRIO|50000|
RPPSS|02347481843|MARLENE MOYSES|2000000|
RPPSS|02656473802|EUNICE APARECIDA L MORCELLE|3632000|
RPPSS|02926769806|MARIA JOSE BORDUQUE|4390000|
RPPSS|03078759547|LUCIANA SILVA CARDOSO|1300000|
RPPSS|03109032899|MARIA CRISTINA FROELICH FELIPPE|40000|
RPPSS|03222414807|CRISTINA DE FTIMA LADEIA|360000|
RPPSS|03298225517|Joana Edisia Barbosa deArajo|20000|
RPPSS|03429318815|ALIPIO MARIA|200000|
RPPSS|04408590851|JUSSARA MANCHINI|1140000|
RPPSS|04583077858|MARCOS ARLINDO PEDRAZZOLI|400000|
RPPSS|04718485819|HELOISA HELENA DE OLIVEIRA|4974000|
RPPSS|04974084810|MARIA SILVA SALES BATISTA|1553000|
RPPSS|05124141816|ELIAN ANTONIO FIORIN|2270000|
RPPSS|05406352865|RITA ELIANA SILVRIO GARCIA|50000|
RPPSS|05511018813|LEILA SILVIA BRAGA|3666000|
RPPSS|06058898803|SONIA ISABEL TOFONELLI SANFLORIAN|130000|
RPPSS|06102333861|CLEUSA APARECIDA BARQUILLA DA CRUZ|3200000|
RPPSS|06230090805|Rubens Piva Junior|50000|
RPPSS|06232611896|REGINA ELIZABETH CLEMENTE DE MORAES|1453000|
RPPSS|06381245816|Antnio Cesar Cappellanes|80000|
RPPSS|06441062631|ALINE FERNANDES|2350000|
RPPSS|06512306830|MARCIO ARAUJO|3000000|
RPPSS|06647641858|Luzia dos Santos Scandelai|1163000|
RPPSS|07044365825|ELISABETE RIBEIRO|550000|
RPPSS|07049345822|JEANE KELLI RAMOS ALVARENGA CAMPANHOLO|820000|
RPPSS|07063335863|ELIANE PELIZZON|400000|
RPPSS|07063375814||1600000|
RPPSS|07458218800|ANA CLAUDIA CAMARGO BENATTI|30000|
RPPSS|07549485879|LUCIA HELENA KATER NOBALBOS|40000|
RPPSS|08070565888|JOSELINA DA COSTA SIMON|1400000|
RPPSS|08294891851|DALVA APARECIDA LODI JULIANO|40000|
RPPSS|08458045885|SILVIA HELENA GONSALVES DE OLIVEIRA|210000|
RPPSS|08477994846|ANA LUCIA DORIGAN PEDRAZZOLI|5141000|
RPPSS|08511120807|LILIANI APARECIDA CASEMIRO|4000000|
RPPSS|08662369880|ANGELA BERNADETTE SARANZA AYUSSO|120000|
RPPSS|09073721202|ANDRE LUIS PERRI VALENTIM FILHO|90000|
RPPSS|09249519826|VANETE APARECIDA NUNES VIEIRA|310000|
RPPSS|09405406817|LUIZ PAULO SILVA|50000|
RPPSS|09815468898|LUCIENE THOMAZ DA SILVA|50000|
RPPSS|09819839874|Mara Elisa Dias Martin|1760000|
RPPSS|09830857816|EDSON ANDRELLA|110000|
RPPSS|09831539818|MIRIAM LEILA ISA BOTURA|50000|
RPPSS|09835947830|RICARDO PINFILDI|40000|
RPPSS|10031204821|SILVIA HELENA ZACARIAS BETANHO|4000000|
RPPSS|10171610890|MARCIA CRISTINA FALSONI SIMAO|260000|
RPPSS|10178140821|SONIA APARECIDA GALASSI CHIARI 213611|3660000|
RPPSS|10295110805|MARIA DE FATIMA SOARES GAIARIM|1303000|
RPPSS|10324217897|IRANI TEODORO GODOI|3200000|
RPPSS|10942717805|SILMARA ALEXANDRA TARSITANO RENZETTI|170000|
RPPSS|10946768889|ROSEMEIRE FERRASSA|540000|
RPPSS|11287629865|SIDNEI GIAMPANI DI BACCHI|3723000|
RPPSS|11395324808|FABIANA FONSECA PALETA|30000|
RPPSS|11408372819|MARIVONE BOTOS DE CARVALHO|3682000|
RPPSS|12150927845|GLAUCIA REGINA DOTTO ESTEVES PAES|270000|
RPPSS|12158433809|ADEVAIR EDSON RASCAZZI|1500000|
RPPSS|12158599880|ROBERTA PAZIANI ESCOBOSA 004280|50000|
RPPSS|12160043826|LUCIANA CARVALHO T BIASI|150000|
RPPSS|12177777844|ALESSANDRA RENATA FERREIRA|3300000|
RPPSS|12767645881|DEBORA CRISTINA LOPES RIBEIRO|3200000|
RPPSS|12830717899|Adilson dos Santos Marques|50000|
RPPSS|13222453802|ELIANE CASSIA FALCO|2866000|
RPPSS|13451628805|Maria Aparecida Valheri Lobato|40000|
RPPSS|13593785889|GISSELE DE CASTRO SILVA LEAL|000|
RPPSS|13779233800|DENISE BUSNARDO|50000|
RPPSS|14101127808|MARCIA REGINA NOBUKUNI FRANCISCO|3150000|
RPPSS|14112719809|CLEIDE DE LOURDES REGHIN HAINST|2700000|
RPPSS|14874805809|Angela Maria Vicentini Maria|260000|
RPPSS|14948084808|Renata Cristina braz crispim Marquini|40000|
RPPSS|15066683801|SILVIA DE LOURDES CASTRO DORIGAN|450000|
RPPSS|15076010850|Cristiane Roberta da Silva Santos|40000|
RPPSS|15180363845|LUCILENE DE BRITO NUNES|5500000|
RPPSS|15327351890|KETI APARECIDA SOARES BIASSI|100000|
RPPSS|15845895820|EDUARDO GEORGETTE FERNANDES|1000000|
RPPSS|15925751884|DANIELA BOBADILHA CASSEVERINI|3600000|
RPPSS|15926701805|SANDRA TEREZINHA ARTUSO AZEVEDO|130000|
RPPSS|15934524895|DANIELA CARVALHO SILVA LOTO|110000|
RPPSS|15977778899|CRISTIANA PIRASSOLI|3240000|
RPPSS|16397326837|REGINA APARECIDA MUSSATO VERZA|3920000|
RPPSS|16504025894|MILENA VOLPI FIGUEIREDO|150000|
RPPSS|16899212854|ADRIANA CRISTINA DE CARVALHO|1500000|
RPPSS|17212484806|MARIA JOSE FIDELIS BRITO|2547000|
RPPSS|18139917826|CLEONICE IZILDINHA SPERANDIO RUGERI|40000|
RPPSS|18451315844|SIMONE MANFRIN CALMON RIBEIRO|1663000|
RPPSS|18454014845|PATRICIA DE SOUZA LIMA FERNANDES|1550000|
RPPSS|18638480881|ROSIMAR CRISTINA POLI POLASTRI|4400000|
RPPSS|18694699842|ELAINE CAVALCANTE DA COSTA|3300000|
RPPSS|19091615869|ROSA MARIA GULGIERMETTI SANDRIN|40000|
RPPSS|19091825839|SILMARA TEREZINHA FINANCI|40000|
RPPSS|19644203852|DANIELA APARECIDA COSTA RODEIRO|80000|
RPPSS|20263257827|MARCELO CESAR RANGEL 000001|1250000|
RPPSS|21330186893|MARCO AURELIO VOLPI FIGUEIREDO|3300000|
RPPSS|21426111843|MARIA AMELIA SERRA NEGRA ROTELLA|2430000|
RPPSS|21441691871|ANA PAULA PIMENTEL|2230000|
RPPSS|21474314864|MARINA APARECIDA SOLER|3100000|
RPPSS|21478807873|TATIANA CURY CAVALHEIRO|40000|
RPPSS|21562118846|GIANCA SILVA MONTELEONE|40000|
RPPSS|21676005854|VANESA CRISTINA BUSNARDO GARILO|50000|
RPPSS|21898064881|GIOVANNA NOVAES CESTARI|50000|
RPPSS|21957555807|Danieli Cristina Andrade|40000|
RPPSS|22012218806|KAREN SILVIA BELTRAME|1750000|
RPPSS|22034414888|Flvia Costa Brando Piva|50000|
RPPSS|22042714895|LILIANE AMARAL DE ALMEIDA|3300000|
RPPSS|22084536898|MARCELA ALEXANDRINO GENTIL|40000|
RPPSS|22135997854|FABIO EVANDRO RIVA|4000000|
RPPSS|22209848857|CRISTIANE PONSON BARONI LOPES|3150000|
RPPSS|22216654817|VIVIANE ROGANTE|3300000|
RPPSS|22217846850|FERNANDA ELISA AGUIRRE RESSUDE COR|2200000|
RPPSS|22249145806|GISELE BITTO OLIVEIRA|2557000|
RPPSS|22314447808|CAROLINE APARECIDA WARAYAARIKI|3240000|
RPPSS|22319070893|QUELI ELAINE MATIAS BONFIM|1350000|
RPPSS|22481920880|SANDRA REGINA FEMENIA BUZON|50000|
RPPSS|22490928856|VANESSA EIKO IMAMURA|1295000|
RPPSS|22555844848|ALICIA DARMA HERRERA RAMIREZ|50000|
RPPSS|22561482898|SAMIRA GARCIA DE OLIVEIRA DA SILVEIRA|5100000|
RPPSS|22679192818|TATIANA RUIZ BRANCALIAO|40000|
RPPSS|22758980851|ALINE SPINDOLA PENARIOL|3920000|
RPPSS|22811518835|Patrcia de Oliveira Santos|3250000|
RPPSS|22894375816|ANDREIA BONIFACIO DE SALLES|1400000|
RPPSS|22941788824|Beatriz Gerlak|40000|
RPPSS|23151028802|ADRIANA DA SILVA BASTOS|40000|
RPPSS|24579987861|MARIA DE LOURDES SIGULI CHAVES|130000|
RPPSS|24712740809|REGINALDO ROSSI|40000|
RPPSS|25163063855|LEANDRA PATRICIA MACHADO|1214000|
RPPSS|25233784858|KATIA CILENE ANATIR CAROLINO|2550000|
RPPSS|25236536804|MARIA DE LOURDES TOFFANELLI BETIOLI|50000|
RPPSS|25561794817|ISA PAULA POLETTI ROMANIN|40000|
RPPSS|25954649812|FABIANA CAMACHO GONCALVES RODRIGUES|700000|
RPPSS|25983884883|SIMONE FLORENTINO PEREZ|3300000|
RPPSS|26229377845|SANDRA MARA FELICIANO|2550000|
RPPSS|26714125844|VILMA MARIA VIEIRA|450000|
RPPSS|27579848880|ISAURA DE SOUZA MELO RIBEIRO|1600000|
RPPSS|27828260857|RODRIGO ARCURIO|1300000|
RPPSS|27852015824|MICHELLE CRISTIANE DOS SANTOS|250000|
RPPSS|27923902801|ARIANA CANDIDA GONCALVES RODRIGUES|40000|
RPPSS|28159991886|PAULA RODRIGUES FARIA|3120000|
RPPSS|28452896859|TAISA SOARES DA SILVA SPADA|540000|
RPPSS|28498870801|RITA DE LIMA CAPPELLANES|4813000|
RPPSS|28523071890|ELISA VALOSO|2350000|
RPPSS|28561437847|FABIANA PRIOLI DA SILVA|2800000|
RPPSS|28591223896|EDUARDO COGO ABIB|779000|
RPPSS|28695113884|JOSE CARLOS SILVEIRA JUNIOR|50000|
RPPSS|28793303866|SABRINA SPADA|3117000|
RPPSS|29137400819|EDMILSON APARECIDO BARTOLI|110000|
RPPSS|29397820168|ROMIR ALVES LEAL|300000|
RPPSS|29491344838|GRASIELA VENTURIN|40000|
RPPSS|29703693890|FERNANDA VALERIO|40000|
RPPSS|29714190886|MCHELLE LAZARO DA SILVA VIEIRA|3600000|
RPPSS|29805238873|Dbora Sumie Iwata|50000|
RPPSS|29823332878|MARION CATANZANO RAMOS|3630000|
RPPSS|30078128870|Karina Cury Orsi|3200000|
RPPSS|30150489889|Priscila Yumi Yabiku|50000|
RPPSS|30406578877|JESSICA ROBERTA MARCOS|2122000|
RPPSS|30475471814|SAMUEL CAPPELLANES|2013000|
RPPSS|30604065884|ANA CLAUDIA RODRIGUES DA SILVA|40000|
RPPSS|30655210830|PRISCILA STOCHI MIRANDA|40000|
RPPSS|30998038806|LUCELAINE ALVES DA SILVA|50000|
RPPSS|31000585859|RENATA SOUZA LAZARO DONATANGELO|216000|
RPPSS|31024810860|JULIANA ANGELICO BERTONCELLO 060846|1950000|
RPPSS|31442001828|JOSE EDUARDO PEREIRA DE CARVALHO|100000|
RPPSS|31470174863|Bianor Jesus Morante Junior|150000|
RPPSS|31669983811|ANA PAULA MANDUCCHI|330000|
RPPSS|31693766833|MICHELLA BOSSOLANI BUCK PEREIRA|2440000|
RPPSS|31699028818|MARIO ANTONIO ETTIOPI|40000|
RPPSS|31720841837|MAISA REGINA DA SILVA MAGRI|50000|
RPPSS|31981052852|JESSICA EROLES CASSILLAS|2800000|
RPPSS|31981055878|JULIANE EROLES CASSILLAS|2500000|
RPPSS|32088384847|Neuza de Ftima Barbosa Carrero|150000|
RPPSS|32101913879|JEAN DE CARVALHO BARALDI 000001|40000|
RPPSS|32172666840|MONIQUE ESPINOSA CORDEIRO|2250000|
RPPSS|32338926838|MARCELA CAMARGO COSTA|1760000|
RPPSS|32349755827|FERNANDA FRANZINI|4200000|
RPPSS|32456647882|MARIA VITORIA BARBIZAN 261394|3200000|
RPPSS|32508446831|FABRICIA JULIANA ROMERA|40000|
RPPSS|32604361892|VALDINEIA MORAES DE LIMA|200000|
RPPSS|32795801884|Nelvina Ribeiro Da Silva|50000|
RPPSS|32799255841|FERNANDA BETIOLI|240000|
RPPSS|32801168807|SIMEI GARCIA OLIVEIRA DA COSTA|2000000|
RPPSS|32829002873|MARIANA SLAZARI DAVOGLIO|3100000|
RPPSS|32842574893|ALEXANDRE CAZETTO PARDIM|50000|
RPPSS|32946947840|DEBORA ELISA MINOTTI MATOS|150000|
RPPSS|33217764803|VANESSA ROSA DA COSTA|3500000|
RPPSS|33286403806|ADILSON SELIS DA SILVA|50000|
RPPSS|33404269888|CAMILA AGUILAR ALEXANDRE SILVEIRA|50000|
RPPSS|33410993878|ANA JULIA DE SOUZA SOARES|2150000|
RPPSS|33514485895|JULIA M FIORI|50000|
RPPSS|33610388803|MARIA EDUARDA CANDIDO BATISTA|2500000|
RPPSS|33644927863|ERIKA LUCIANA BEVOLO|3000000|
RPPSS|33895275832|MILIANE MARTINS|3000000|
RPPSS|33915203882|GABRIELA SALVADOR|40000|
RPPSS|34215931835|Cristiane de Ftima da SilvaVale|50000|
RPPSS|34306392805|Juliana Aparecida Martimiano Leonardo|50000|
RPPSS|34318009807|TATIANA JOICE MORAES|50000|
RPPSS|34370997879|FRANCIELE CAROLINE AMANCIO|2850000|
RPPSS|34402174856|SOLANGE APARECIDA BETARELLO RIOS|3550000|
RPPSS|34440527825|THAIZ FERNANDA SCARAFICI BARBOSA|800000|
RPPSS|34460401886|TAIS CAROLINA MARTINEZ DOS SANTOS|1400000|
RPPSS|34810602893|MARCELA BIZARI CARVALHO|100000|
RPPSS|34875193807|JAQUELINE GONZAGA DA SILVA|2620000|
RPPSS|35057288881|THAIS PAMELLA MADALENA|3050000|
RPPSS|35113833827|LAURA CAROLINA MANCHINI PAFARO GONCALVES|2500000|
RPPSS|35149029840|PAULINE RUTH DE LIMA|30000|
RPPSS|35255377864|ALISE FERNANDA SOARES|40000|
RPPSS|35347883898|VERENA ROGANTE GONZAGA|000|
RPPSS|35765497810|AMANDA FAVERO BORTOLIM|40000|
RPPSS|35789361897|Fernanda Alexandre do Amaral|250000|
RPPSS|36109384801|SIMONE MARTA DOS SANTOS GARCIA|50000|
RPPSS|36120680837|PAMELA ZAGATTO|1140000|
RPPSS|36362634802|ADRIANA CRISTINA DO AMARAL|100000|
RPPSS|36712891809|AURELIO AUGUSTINHO RUETE FILHO|250000|
RPPSS|36796002830|RENATA DE FAZZIO STUCHI 063643|3300000|
RPPSS|36835710802|MARILIA MARCHINI SILVA|3050000|
RPPSS|36862376870|SAMARA DAIANA ROLLA|40000|
RPPSS|36891611857|JULIANA BONORA PAULINO|515000|
RPPSS|36902313881|MARIANA GISSE PINTO|1400000|
RPPSS|36943033832|BEATRIZ APARECIDA MORETTO|3000000|
RPPSS|37277458869|CAMILA ROSA RODRIGUES|2350000|
RPPSS|37540150874|LETICIA CLEMENTE CASA GRANDE GONZALEZ|60000|
RPPSS|37592194899|TAINE BARBIZAN|50000|
RPPSS|37769876850|JULIELI DEL NERO LEPRE|3000000|
RPPSS|37878324832|BEATRIZ FERRAREZZI SACILOTTO|40000|
RPPSS|37955075889|RAYLTON KLEBER PEDRETI|50000|
RPPSS|37985087800|Ana Carolina Gomez Ferreira|15000|
RPPSS|38124272816|RAFAELA CARRETEL BELLUCCI|1600000|
RPPSS|38201772806|JACQUELINE BUENO FLOIS PEREIRA|3600000|
RPPSS|38355468805|CAMILA PITON|3100000|
RPPSS|38560706801|ANA LUCIA SEVERINO|50000|
RPPSS|38787632802|MARIA CLARA BARBIZAN|2470000|
RPPSS|39049951899|MANOELA DURAN MONTELEONE|40000|
RPPSS|39215344888|ANGELICA CRISTINA DA SILVA RAMOS|40000|
RPPSS|39349838800|ALINE LIMOLI SIOTTI|2500000|
RPPSS|39360976822|ANTONIO APPENDINO ROMO|2210000|
RPPSS|39408974859|GABRIEL ZAMPERLINI|40000|
RPPSS|39440765836|MARCOS TULIO SASSO CEZARE 234337|50000|
RPPSS|39592945810|LETICIA MARIA FORATO|2800000|
RPPSS|39592948836|DANIELLE MAGUETAS FRASSON|1400000|
RPPSS|39782518840|SERGIA APARECIDA PAVANI|240000|
RPPSS|39941673888|MONIZE CRISTINA LOFRANO DA SILVA|50000|
RPPSS|40239491831|Carine Elaine Teixeira|40000|
RPPSS|40505960834|MARIA APARECIDA LUIZ FORTUNA 211960|115000|
RPPSS|40515101826|JESSICA FERNANDA CLAUDINO TEIXEIRA|3300000|
RPPSS|40558832857|Eveling Thais Won Ancken|350000|
RPPSS|40571844812|Giulia Serain Alberto|1763000|
RPPSS|40715174843|Raiele Arajo Bernardes|3440000|
RPPSS|40777528843|GABRIELA FAZIO GOMES|2500000|
RPPSS|40812426827|SARA JANE DO AMARAL|2810000|
RPPSS|40820650838|William Chagas Bodin|3250000|
RPPSS|40992827833|BRUNA ANGELA MARIA|4500000|
RPPSS|41186690801|LORAINE APARECIDA RODRIGUES COLOMBO|1400000|
RPPSS|41479945803|GUSTAVO KUMAGAI DE OLIVEIRA|1150000|
RPPSS|41540111890|AMANDA THOMAZ SALVATIERRA|3200000|
RPPSS|41565055888|Marcus Vinicius Pereira Dispatto|50000|
RPPSS|41575901803|DANIELY SALES|40000|
RPPSS|41577518845|NATALIA MIHOTO VACCARI|2060000|
RPPSS|41593131836|INAYARA DE CASSIA RISSI|2150000|
RPPSS|41609504852|WELLINGTON CESAR DA SILVA JUNIOR|1450000|
RPPSS|41946226858|Nathalia Aparecida Dias de Freitas|2415000|
RPPSS|42005326895|ANA CLARA ROBLES SANTAMARIA|1452000|
RPPSS|42031475819|LUISA SARANZA AYUSSO|120000|
RPPSS|42066193879|ISABELA FONSECA RODRIGUES|500000|
RPPSS|42097588840|ANA LETICIA PHANTASIA|50000|
RPPSS|42279802830|LAIS FERNANDA DE SOUZA|800000|
RPPSS|42724292804|ANA JULIA GIANINI|1600000|
RPPSS|42742995897|BIANCA CRISTINA GOMES CAPELETTI|2100000|
RPPSS|42790707847|LETICIA GABRIELA DEVINCOLA|50000|
RPPSS|43086496843|LEONORA BRANDAO PIVA|50000|
RPPSS|43108982893|Henrique Carnelossi De Melo|1463000|
RPPSS|43323069889|DANIELI APARECIDA DE OLIVEIRA IMAMURA|350000|
RPPSS|43496467878|MANUELLA BAISSO MAIORANO|1181000|
RPPSS|43632340838|ANA LAURA CASTRO GERLACH|800000|
RPPSS|43870182830|VITOR FRARE CASATTI|150000|
RPPSS|43887191889|LARA DOLENCE CROSARIOL|150000|
RPPSS|44066671802|ISADORA GOMES RAMOS|1600000|
RPPSS|44129911805|EMILLY GIOVANA DA SILVA|2250000|
RPPSS|44254735820|Maria Clara Cabral Brando|50000|
RPPSS|44480048804|Natalia Marchini|80000|
RPPSS|44774472808|CRISLEY CORREA DE FARIAS|40000|
RPPSS|44929601800|LEONARDO APARECIDO MARQUES|1400000|
RPPSS|45022157837|JULIA COSTA DE OLIVEIRA|1000000|
RPPSS|45329731801|Maiara Carolina Rangel Amncio|200000|
RPPSS|45440593802|MAISA THAINA DE MELO MOLIM|3000000|
RPPSS|45453794860|STHEFANY GABRIELI BARALDO DE CALLIS|1963000|
RPPSS|45551088847|ISADORA DE SOUZA MOREIRA|2290000|
RPPSS|45724543885|MILENA MARQUES CERVANTES|3250000|
RPPSS|45887176857|Giovana Santana Gonalves|921000|
RPPSS|46158913855|TAILA JULIANA DA SILVA DOS SANTOS|3500000|
RPPSS|46373592871|Giovana Pereira da Silva|450000|
RPPSS|46727537860|GIOVANA LAZARA BARBOSA|1860000|
RPPSS|46992173898|Bruna Roberta Camargo|50000|
RPPSS|47067248809|ISABELLY TURIN LAZARINI|1000000|
RPPSS|47661768801|RAQUEL CORDOBA BOTELHO|40000|
RPPSS|47806007822|NATHALIA SIVIERO DA SILVA|3600000|
RPPSS|48003353882|Nathalia Gomes de Souza|50000|
RPPSS|48048165856|ANA JULIA DE OLIVEIRA CAMPOS|50000|
RPPSS|48085664860|MARIA EDUARDA SOLFA RIBEIRO BERNARDO|1600000|
RPPSS|48145567807|CAMILA KANESIRO CHENERI|30000|
RPPSS|48661261830|MANUELA TORRES ONDON|1750000|
RPPSS|48982412808|Isabeli Samara Rodrigues Amancio|2100000|
RPPSS|49285008810|VITORIA BERTI SANTOS|1500000|
RPPSS|49330436803|Julia Ferreira|1400000|
RPPSS|50399917896|BEATRIZ ATHAYDES SILVA|120000|
RPPSS|51126823880|CLARA UBEDA GARCIA|200000|
RPPSS|51293533823|JAMILE LUCIO DO SANTOS|1400000|
RPPSS|51314439898|LARISSA PIGONI BOSCHEZZE|1280000|
RPPSS|53781862852|BEATRIZ MICHELAN|40000|
RPPSS|54112613849|GLADIS APARECIDA ANDALO DOS SANTOS|300000|
RPPSS|54735681817|LUCAS BERTELLINI PIROLA|2281000|
RPPSS|55316836855|LETICIA BEATRIZ COSTA|1100000|
RPPSS|55979308857|MURILO PIMENTA GARBIM|40000|
RPPSS|57038141899|ANA LUIZA ZUIM LAMBERTE|40000|
RPPSS|57064880881|LARA IZILDA NOGUEIRA|1850000|
RPPSS|59061106826|VALENTINA BASTOS COSTA|150000|
RPPSS|62106449887|JOSE ANSELMO LUCIANO|500000|
RPPSS|64791076320|LEIDIANE REGIA DE SOUSA|3000000|
RPPSS|73702692800|SONIA TAYAR BENEDITO JORGE|1400000|
RPPSS|73734853834|LAETE ROSA|40000|
RPPSS|73923443900|Soleni Borsatto Massoco|200000|
RPPSS|78460298868|IVONETE VOLPI NEZINHO|150000|
RPPSS|78558395853|PEDRO ANTONIO ARTUZO|250000|
RPPSS|78719690800|LEDA MARIA FREITAS CHIODINI|300000|
RPPSS|84687436153|SIMONE DE JESUS BATISTA|3850000|
RPPSS|88861660878|NEUSA MIGUEL|2200000|
RPPSS|93940084891|CARMEM SILVIA FIORIN FERNANDEZ|1750000|
FIMDmed|
+210
View File
@@ -0,0 +1,210 @@
# DMED - Comparador TXT vs XLS
Biblioteca Rust para processar e comparar arquivos DMED (TXT) com relatórios Prosoft (XLS).
## 📋 Funcionalidades
### 1️⃣ Processador TXT (DMED)
- Lê arquivos TXT do formato DMED
- Extrai registros RPPSS (CPF, Nome, Valor)
- Pula as 4 primeiras linhas automaticamente
- Converte valores (últimos 2 dígitos são decimais)
### 2️⃣ Processador XLS (Prosoft)
- Lê arquivos XLS e XLSX (detecção automática)
- Extrai CPF (coluna E) e Valor (coluna H)
- Remove formatação de CPF (pontos e hífens)
- **Agrupa por CPF** e **soma valores** de múltiplas notas
- Filtra apenas CPF (11 dígitos), descarta CNPJ
### 3️⃣ Comparador
- Compara valores entre TXT e XLS
- Identifica divergências
- Gera relatório detalhado
- Estatísticas completas
## 🚀 Como usar
### Instalação
Adicione ao seu `Cargo.toml`:
```toml
[dependencies]
dmed = { path = "../dmed" }
```
### Como biblioteca
```rust
use dmed::{processar_arquivo, xls::processar_xls};
fn main() -> std::io::Result<()> {
// Processa TXT
let pessoas_txt = processar_arquivo("LfsIssDmed068325.txt")?;
// Processa XLS
let pessoas_xls = processar_xls("RELATORIO_PROSOFT_MOD_51.xls")?;
// Comparar, buscar, etc.
Ok(())
}
```
### Executar exemplos
#### 1. Comparador básico (só TXT)
```bash
cargo run --example comparador
```
#### 2. Comparador completo (TXT + XLS)
```bash
# Coloque o arquivo RELATORIO_PROSOFT_MOD_51.xls na pasta do projeto
cargo run --example comparador_completo
```
#### 3. Main (demonstração)
```bash
cargo run
```
## 📁 Estrutura do projeto
```
dmed/
├── src/
│ ├── lib.rs # API principal
│ ├── xls.rs # Processador XLS
│ └── main.rs # Exemplo de uso
├── examples/
│ ├── comparador.rs # Exemplo com TXT
│ └── comparador_completo.rs # Exemplo TXT vs XLS
├── Cargo.toml
└── README.md
```
## 🔧 API Pública
### Structs
```rust
pub struct Pessoa {
pub cpf: String, // CPF (11 dígitos, sem formatação)
pub nome: String, // Nome completo
pub valor: f64, // Valor em reais
}
```
### Funções
#### Processamento TXT
```rust
// Processa arquivo TXT
pub fn processar_arquivo(caminho: &str) -> io::Result<Vec<Pessoa>>
// Processa string (útil para testes)
pub fn processar_conteudo(conteudo: &str) -> io::Result<Vec<Pessoa>>
```
#### Processamento XLS
```rust
// Processa arquivo XLS/XLSX (módulo xls)
pub fn processar_xls(caminho: &str) -> io::Result<Vec<Pessoa>>
```
**Nota:** Aceita tanto `.xls` quanto `.xlsx` - a detecção é automática!
#### Utilidades
```rust
// Converte para JSON
pub fn para_json(pessoas: &[Pessoa]) -> Result<String, serde_json::Error>
// Busca por CPF
pub fn buscar_por_cpf<'a>(pessoas: &'a [Pessoa], cpf: &str) -> Option<&'a Pessoa>
// Filtra por valor mínimo
pub fn filtrar_por_valor_minimo(pessoas: &[Pessoa], valor_minimo: f64) -> Vec<&Pessoa>
```
## 📊 Formato dos arquivos
### TXT (DMED)
```
Dmed|2026|2025|N||| <- Linha 1 (pular)
RESPO|...|... <- Linha 2 (pular)
DECPJ|...|... <- Linha 3 (pular)
PSS| <- Linha 4 (pular)
RPPSS|00276808860|MARCO ANTONIO MACHADO|2200000| <- Processar
RPPSS|00633999032|ELISANDRA BARBOSA TORRES|600000| <- Processar
...
```
**Campos:**
- Campo 1: `RPPSS` (filtro)
- Campo 2: CPF (11 dígitos)
- Campo 3: Nome
- Campo 4: Valor (últimos 2 dígitos = decimais)
### XLS (Prosoft)
| A | B | C | D | E (CPF) | F | G | H (Valor) |
|---|---|---|---|---------|---|---|-----------|
| ... | ... | ... | ... | 123.456.789-01 | ... | ... | 1500.50 |
| ... | ... | ... | ... | 123.456.789-01 | ... | ... | 500.00 |
- Linhas 1-3: Identificação (pular)
- Linha 4: Cabeçalho (pular)
- Coluna E: CPF/CNPJ (manter apenas CPF)
- Coluna H: Valores (somar por CPF)
## 🧪 Testes
```bash
cargo test
```
## 📝 Exemplo de saída
```
═══════════════════════════════════════════════
COMPARADOR DMED (TXT) vs PROSOFT (XLS)
═══════════════════════════════════════════════
📄 Processando arquivo TXT (DMED)...
✓ 336 registros encontrados
📊 Processando arquivo XLS (Prosoft)...
✓ 245 registros únicos encontrados
═══════════════════════════════════════════════
ANÁLISE
═══════════════════════════════════════════════
📊 Estatísticas:
Total de CPFs únicos: 400
Presentes em ambos: 181
Apenas no TXT: 155
Apenas no XLS: 64
📈 Divergências de valores:
Total: 12 registros com diferenças
═══════════════════════════════════════════════
TOP 10 DIVERGÊNCIAS
═══════════════════════════════════════════════
1. CPF: 00276808860
Nome: MARCO ANTONIO MACHADO
TXT (DMED): R$ 22000.00
XLS (Prosoft): R$ 22500.00
Diferença: R$ 500.00 (+2.3%)
...
```
## 📄 Licença
MIT
## 🤝 Contribuindo
Pull requests são bem-vindos!
Binary file not shown.
+13
View File
@@ -0,0 +1,13 @@
#[cfg(windows)]
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
let mut res = winres::WindowsResource::new();
res.set_icon("icon.ico"); // Caminho do seu arquivo .ico
res.compile().unwrap();
}
}
#[cfg(not(windows))]
fn main() {
// Não faz nada em outros sistemas
}
BIN
View File
Binary file not shown.
+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");
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+465
View File
@@ -0,0 +1,465 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use eframe::egui;
use dmed::{processar_arquivo, xls::processar_xls, Pessoa};
use std::collections::HashMap;
fn main() -> eframe::Result {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([950.0, 850.0])
.with_title("DMED - Comparador TXT vs XLS"),
..Default::default()
};
eframe::run_native(
"DMED Comparador",
options,
Box::new(|_cc| Ok(Box::new(ComparadorApp::default()))),
)
}
#[derive(Default)]
struct ComparadorApp {
arquivo_txt: Option<String>,
arquivo_xls: Option<String>,
resultado: String,
divergencias: Vec<Divergencia>,
cpfs_so_txt: Vec<CpfExclusivo>,
cpfs_so_xls: Vec<CpfExclusivo>,
processando: bool,
}
#[derive(Clone)]
struct CpfExclusivo {
cpf: String,
nome: String,
valor: f64,
}
#[derive(Clone)]
struct Divergencia {
cpf: String,
nome: String,
valor_txt: f64,
valor_xls: f64,
diferenca: f64,
percentual: f64,
}
impl eframe::App for ComparadorApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("📊 Comparador DMED (TXT) vs Prosoft (XLS)");
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
// Seleção de arquivos
ui.horizontal(|ui| {
ui.label("📄 Arquivo TXT (DMED):");
if ui.button("📁 Selecionar").clicked() {
if let Some(path) = rfd::FileDialog::new()
.add_filter("Arquivos TXT", &["txt"])
.pick_file()
{
self.arquivo_txt = Some(path.display().to_string());
self.resultado.clear();
self.divergencias.clear();
}
}
if let Some(ref path) = self.arquivo_txt {
ui.label(format!("{}", path));
}
});
ui.add_space(5.0);
ui.horizontal(|ui| {
ui.label("📊 Arquivo XLS (Prosoft):");
if ui.button("📁 Selecionar").clicked() {
if let Some(path) = rfd::FileDialog::new()
.add_filter("Arquivos Excel", &["xls", "xlsx"])
.pick_file()
{
self.arquivo_xls = Some(path.display().to_string());
self.resultado.clear();
self.divergencias.clear();
}
}
if let Some(ref path) = self.arquivo_xls {
ui.label(format!("{}", path));
}
});
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
// Botão de comparar
ui.horizontal(|ui| {
let pode_comparar = self.arquivo_txt.is_some() &&
self.arquivo_xls.is_some() &&
!self.processando;
if ui.add_enabled(pode_comparar, egui::Button::new("🔍 Comparar Arquivos"))
.clicked()
{
self.comparar();
}
if self.processando {
ui.spinner();
ui.label("Processando...");
}
});
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
// Resultados
if !self.resultado.is_empty() {
ui.label(&self.resultado);
ui.add_space(10.0);
}
// Tabela de divergências
if !self.divergencias.is_empty() {
ui.heading(format!("📋 Divergências de valores: {}", self.divergencias.len()));
ui.add_space(5.0);
egui::ScrollArea::vertical()
.max_height(200.0)
.show(ui, |ui| {
egui::Grid::new("divergencias_grid")
.striped(true)
.min_col_width(100.0)
.show(ui, |ui| {
// Cabeçalho
ui.strong("CPF");
ui.strong("Nome");
ui.strong("TXT (R$)");
ui.strong("XLS (R$)");
ui.strong("Diferença (R$)");
ui.strong("Diferença (%)");
ui.end_row();
// Dados
for div in &self.divergencias {
ui.label(&div.cpf);
ui.label(&div.nome);
ui.label(format!("{:.2}", div.valor_txt));
ui.label(format!("{:.2}", div.valor_xls));
let cor = if div.diferenca > 0.0 {
egui::Color32::RED
} else {
egui::Color32::GREEN
};
ui.colored_label(cor, format!("{:.2}", div.diferenca));
ui.colored_label(cor, format!("{:+.1}%", div.percentual));
ui.end_row();
}
});
});
ui.add_space(10.0);
}
// CPFs apenas no TXT
if !self.cpfs_so_txt.is_empty() {
ui.separator();
ui.add_space(5.0);
ui.heading(format!("⚠️ CPFs apenas no TXT: {}", self.cpfs_so_txt.len()));
ui.label("Estes CPFs estão no DMED mas não aparecem no Prosoft");
ui.add_space(5.0);
egui::ScrollArea::vertical()
.max_height(150.0)
.show(ui, |ui| {
egui::Grid::new("cpfs_txt_grid")
.striped(true)
.min_col_width(100.0)
.show(ui, |ui| {
ui.strong("CPF");
ui.strong("Nome");
ui.strong("Valor (R$)");
ui.end_row();
for cpf in &self.cpfs_so_txt {
ui.label(&cpf.cpf);
ui.label(&cpf.nome);
ui.label(format!("{:.2}", cpf.valor));
ui.end_row();
}
});
});
ui.add_space(10.0);
}
// CPFs apenas no XLS
if !self.cpfs_so_xls.is_empty() {
ui.separator();
ui.add_space(5.0);
ui.heading(format!("⚠️ CPFs apenas no XLS: {}", self.cpfs_so_xls.len()));
ui.label("Estes CPFs estão no Prosoft mas não aparecem no DMED");
ui.add_space(5.0);
egui::ScrollArea::vertical()
.max_height(150.0)
.show(ui, |ui| {
egui::Grid::new("cpfs_xls_grid")
.striped(true)
.min_col_width(100.0)
.show(ui, |ui| {
ui.strong("CPF");
ui.strong("Valor (R$)");
ui.end_row();
for cpf in &self.cpfs_so_xls {
ui.label(&cpf.cpf);
ui.label(format!("{:.2}", cpf.valor));
ui.end_row();
}
});
});
ui.add_space(10.0);
}
// Botão para exportar (mostra se tiver qualquer resultado)
if !self.divergencias.is_empty() || !self.cpfs_so_txt.is_empty() || !self.cpfs_so_xls.is_empty() {
ui.separator();
ui.add_space(5.0);
// Botão para exportar
if ui.button("💾 Exportar Relatório Completo").clicked() {
if let Some(path) = rfd::FileDialog::new()
.set_file_name("relatorio_completo.txt")
.save_file()
{
if let Err(e) = self.exportar_relatorio(&path.display().to_string()) {
self.resultado = format!("❌ Erro ao exportar: {}", e);
} else {
self.resultado = format!("✅ Relatório exportado para: {}", path.display());
}
}
}
}
});
}
}
impl ComparadorApp {
fn comparar(&mut self) {
self.processando = true;
self.resultado.clear();
self.divergencias.clear();
self.cpfs_so_txt.clear();
self.cpfs_so_xls.clear();
let txt_path = self.arquivo_txt.as_ref().unwrap().clone();
let xls_path = self.arquivo_xls.as_ref().unwrap().clone();
// Processa arquivos
let pessoas_txt = match processar_arquivo(&txt_path) {
Ok(p) => p,
Err(e) => {
self.resultado = format!("❌ Erro ao processar TXT: {}", e);
self.processando = false;
return;
}
};
let pessoas_xls = match processar_xls(&xls_path) {
Ok(p) => p,
Err(e) => {
self.resultado = format!("❌ Erro ao processar XLS: {}", e);
self.processando = false;
return;
}
};
// Cria mapas
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();
// Estatísticas
let cpfs_ambos = mapa_txt.keys()
.filter(|cpf| mapa_xls.contains_key(**cpf))
.count();
let cpfs_so_txt = mapa_txt.keys()
.filter(|cpf| !mapa_xls.contains_key(**cpf))
.count();
let cpfs_so_xls = mapa_xls.keys()
.filter(|cpf| !mapa_txt.contains_key(**cpf))
.count();
// Encontra divergências
let margem_tolerancia = 0.01;
for (cpf, p_txt) in &mapa_txt {
if let Some(p_xls) = mapa_xls.get(*cpf) {
let diferenca = p_txt.valor - p_xls.valor;
if diferenca.abs() > margem_tolerancia {
let percentual = if p_xls.valor != 0.0 {
(diferenca / p_xls.valor) * 100.0
} else {
0.0
};
self.divergencias.push(Divergencia {
cpf: cpf.to_string(),
nome: p_txt.nome.clone(),
valor_txt: p_txt.valor,
valor_xls: p_xls.valor,
diferenca,
percentual,
});
}
} else {
// CPF só está no TXT
self.cpfs_so_txt.push(CpfExclusivo {
cpf: cpf.to_string(),
nome: p_txt.nome.clone(),
valor: p_txt.valor,
});
}
}
// CPFs que estão só no XLS
for (cpf, p_xls) in &mapa_xls {
if !mapa_txt.contains_key(*cpf) {
self.cpfs_so_xls.push(CpfExclusivo {
cpf: cpf.to_string(),
nome: String::new(), // XLS não tem nome
valor: p_xls.valor,
});
}
}
// Ordena por diferença absoluta
self.divergencias.sort_by(|a, b| {
b.diferenca.abs().partial_cmp(&a.diferenca.abs()).unwrap()
});
// Ordena CPFs exclusivos por CPF
self.cpfs_so_txt.sort_by(|a, b| a.cpf.cmp(&b.cpf));
self.cpfs_so_xls.sort_by(|a, b| a.cpf.cmp(&b.cpf));
// Resultado
self.resultado = format!(
"📊 Estatísticas:\n\
• Total TXT: {} registros\n\
• Total XLS: {} registros únicos\n\
• CPFs em ambos: {}\n\
• Apenas no TXT: {}\n\
• Apenas no XLS: {}\n\
• Divergências: {} CPFs com diferenças",
pessoas_txt.len(),
pessoas_xls.len(),
cpfs_ambos,
cpfs_so_txt,
cpfs_so_xls,
self.divergencias.len()
);
self.processando = false;
}
fn exportar_relatorio(&self, caminho: &str) -> std::io::Result<()> {
use std::io::Write;
let mut arquivo = std::fs::File::create(caminho)?;
writeln!(arquivo, "╔═══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(arquivo, "║ RELATÓRIO DE COMPARAÇÃO - DMED vs PROSOFT ║")?;
writeln!(arquivo, "╚═══════════════════════════════════════════════════════════════════════════════╝")?;
writeln!(arquivo)?;
// Resumo
writeln!(arquivo, "📊 RESUMO:")?;
writeln!(arquivo, " • Total de divergências de valores: {}", self.divergencias.len())?;
writeln!(arquivo, " • CPFs apenas no TXT: {}", self.cpfs_so_txt.len())?;
writeln!(arquivo, " • CPFs apenas no XLS: {}", self.cpfs_so_xls.len())?;
writeln!(arquivo)?;
writeln!(arquivo, "{}", "=".repeat(120))?;
writeln!(arquivo)?;
// Divergências de valores
if !self.divergencias.is_empty() {
writeln!(arquivo, "🔍 DIVERGÊNCIAS DE VALORES ({} registros)", self.divergencias.len())?;
writeln!(arquivo, "{}", "-".repeat(120))?;
writeln!(arquivo, "{:<15} {:<40} {:>15} {:>15} {:>15} {:>10}",
"CPF", "Nome", "TXT (R$)", "XLS (R$)", "Diferença (R$)", "Dif (%)")?;
writeln!(arquivo, "{}", "-".repeat(120))?;
for div in &self.divergencias {
writeln!(arquivo, "{:<15} {:<40} {:>15.2} {:>15.2} {:>15.2} {:>10.1}%",
div.cpf,
div.nome,
div.valor_txt,
div.valor_xls,
div.diferenca,
div.percentual)?;
}
writeln!(arquivo)?;
writeln!(arquivo, "{}", "=".repeat(120))?;
writeln!(arquivo)?;
}
// CPFs apenas no TXT
if !self.cpfs_so_txt.is_empty() {
writeln!(arquivo, "⚠️ CPFs APENAS NO TXT ({} registros)", self.cpfs_so_txt.len())?;
writeln!(arquivo, " Estes CPFs estão no arquivo DMED mas não aparecem no Prosoft")?;
writeln!(arquivo, "{}", "-".repeat(120))?;
writeln!(arquivo, "{:<15} {:<50} {:>15}", "CPF", "Nome", "Valor (R$)")?;
writeln!(arquivo, "{}", "-".repeat(120))?;
for cpf in &self.cpfs_so_txt {
writeln!(arquivo, "{:<15} {:<50} {:>15.2}",
cpf.cpf,
cpf.nome,
cpf.valor)?;
}
writeln!(arquivo)?;
writeln!(arquivo, "{}", "=".repeat(120))?;
writeln!(arquivo)?;
}
// CPFs apenas no XLS
if !self.cpfs_so_xls.is_empty() {
writeln!(arquivo, "⚠️ CPFs APENAS NO XLS ({} registros)", self.cpfs_so_xls.len())?;
writeln!(arquivo, " Estes CPFs estão no Prosoft mas não aparecem no DMED")?;
writeln!(arquivo, "{}", "-".repeat(120))?;
writeln!(arquivo, "{:<15} {:>15}", "CPF", "Valor (R$)")?;
writeln!(arquivo, "{}", "-".repeat(120))?;
for cpf in &self.cpfs_so_xls {
writeln!(arquivo, "{:<15} {:>15.2}",
cpf.cpf,
cpf.valor)?;
}
writeln!(arquivo)?;
writeln!(arquivo, "{}", "=".repeat(120))?;
writeln!(arquivo)?;
}
writeln!(arquivo)?;
writeln!(arquivo, "Fim do relatório")?;
Ok(())
}
}
+20 -9
View File
@@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::io;
pub mod xls;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Pessoa {
pub cpf: String,
@@ -35,9 +37,18 @@ impl Pessoa {
}
/// Processa um arquivo DMED e retorna um vetor de pessoas
/// Tenta ler em UTF-8, se falhar tenta Windows-1252 (comum em arquivos brasileiros)
pub fn processar_arquivo(caminho: &str) -> io::Result<Vec<Pessoa>> {
let conteudo = fs::read_to_string(caminho)?;
// Primeiro tenta UTF-8
match fs::read_to_string(caminho) {
Ok(conteudo) => processar_conteudo(&conteudo),
Err(_) => {
// Se falhar, tenta ler como bytes e converter de Windows-1252
let bytes = fs::read(caminho)?;
let (conteudo, _, _) = encoding_rs::WINDOWS_1252.decode(&bytes);
processar_conteudo(&conteudo)
}
}
}
/// Processa o conteúdo de um arquivo DMED (útil para testes ou strings)
@@ -77,11 +88,11 @@ mod tests {
#[test]
fn test_pessoa_from_line() {
let linha = "RPPSS|00276808860|MARCO ANTONIO MACHADO|2200000|";
let linha = "RPPSS|00000000000|DR ALGUEM|2200000|";
let pessoa = Pessoa::from_line(linha).unwrap();
assert_eq!(pessoa.cpf, "00276808860");
assert_eq!(pessoa.nome, "MARCO ANTONIO MACHADO");
assert_eq!(pessoa.cpf, "00000000000");
assert_eq!(pessoa.nome, "DR ALGUEM");
assert_eq!(pessoa.valor, 22000.0);
}
@@ -94,16 +105,16 @@ mod tests {
#[test]
fn test_processar_conteudo() {
let conteudo = r#"Dmed|2026|2025|N|||
RESPO|21681939835|Anelisa Pirola Chalegre|17|35316200|205||anelisa@edegarpirolacontabilidade.com.br|
DECPJ|03364887000195|CLINICA DE CIRURGIA PLASTICA DR VIDAL LTDA|1|||01877281867|N|||
RESPO|00000000000|Funcionario do ano|17|00000000|205||fun@cionario.com.br|
DECPJ|00000000000000|CLINICA DE CIRURGIA PLASTICA|1|||00000000000|N|||
PSS|
RPPSS|00276808860|MARCO ANTONIO MACHADO|2200000|
RPPSS|00633999032|ELISANDRA BARBOSA TORRES|600000|
RPPSS|00000000000|DR ALGUEM|2200000|
RPPSS|00000000000|SOCIO DO ALGUEM|600000|
FIMDmed|"#;
let pessoas = processar_conteudo(conteudo).unwrap();
assert_eq!(pessoas.len(), 2);
assert_eq!(pessoas[0].cpf, "00276808860");
assert_eq!(pessoas[0].cpf, "00000000000");
assert_eq!(pessoas[1].valor, 6000.0);
}
}
+127
View File
@@ -0,0 +1,127 @@
use calamine::{Reader, Xlsx, Xls, open_workbook, Data};
use std::collections::HashMap;
use std::io;
use crate::Pessoa;
/// Processa um arquivo XLS/XLSX do Prosoft e retorna pessoas agrupadas por CPF
/// Tenta primeiro XLSX (mesmo com extensão .xls), depois XLS se falhar
pub fn processar_xls(caminho: &str) -> io::Result<Vec<Pessoa>> {
// Tenta abrir como XLSX primeiro (muitos sistemas exportam XLSX com extensão .xls)
let planilha = match open_workbook::<Xlsx<_>, _>(caminho) {
Ok(mut workbook) => {
workbook.worksheet_range_at(0)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Planilha não encontrada"))?
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
}
Err(_) => {
// Se falhar, tenta abrir como XLS
let mut workbook: Xls<_> = open_workbook(caminho)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData,
format!("Erro ao abrir arquivo: {}. Certifique-se que é um arquivo Excel válido (.xls ou .xlsx).", e)))?;
workbook.worksheet_range_at(0)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Planilha não encontrada"))?
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
}
};
// HashMap para agrupar valores por CPF
let mut valores_por_cpf: HashMap<String, (String, f64)> = HashMap::new();
// Processa linhas (pula as 3 primeiras + 1 de cabeçalho = começa na linha 4, índice 3)
let linhas: Vec<_> = planilha.rows().collect();
for (idx, linha) in linhas.iter().enumerate() {
// Pula as 4 primeiras linhas (0, 1, 2 = identificação, 3 = cabeçalho)
if idx < 4 {
continue;
}
// Coluna E (índice 4) = CPF/CNPJ
// Coluna H (índice 7) = Valor
if linha.len() > 7 {
let cpf_cnpj = extrair_texto(&linha[4]);
let valor = extrair_valor(&linha[7]);
// Limpa CPF/CNPJ removendo . - /
let documento_limpo = limpar_documento(&cpf_cnpj);
// Verifica se é CPF (11 caracteres após limpeza)
if documento_limpo.len() == 11 {
// Acumula valores para o mesmo CPF
valores_por_cpf
.entry(documento_limpo.clone())
.and_modify(|(_, v)| *v += valor)
.or_insert((cpf_cnpj.clone(), valor));
}
// CNPJ tem 14 caracteres - descartamos
}
}
// Converte HashMap em Vec<Pessoa>
let mut pessoas: Vec<Pessoa> = valores_por_cpf
.into_iter()
.map(|(cpf, (_original, valor))| Pessoa {
cpf,
nome: String::new(), // XLS não tem nome, deixa vazio
valor,
})
.collect();
// Ordena por CPF para facilitar comparação
pessoas.sort_by(|a, b| a.cpf.cmp(&b.cpf));
Ok(pessoas)
}
/// Extrai texto de uma célula do Excel
fn extrair_texto(celula: &Data) -> String {
match celula {
Data::String(s) => s.clone(),
Data::Float(f) => format!("{:.0}", f),
Data::Int(i) => i.to_string(),
_ => String::new(),
}
}
/// Extrai valor numérico de uma célula do Excel
fn extrair_valor(celula: &Data) -> f64 {
match celula {
Data::Float(f) => *f,
Data::Int(i) => *i as f64,
Data::String(s) => {
// Tenta converter string para número (remove separadores)
s.replace(".", "")
.replace(",", ".")
.parse::<f64>()
.unwrap_or(0.0)
}
_ => 0.0,
}
}
/// Remove caracteres especiais do CPF/CNPJ
fn limpar_documento(doc: &str) -> String {
doc.chars()
.filter(|c| c.is_ascii_digit())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_limpar_documento() {
assert_eq!(limpar_documento("123.456.789-01"), "12345678901");
assert_eq!(limpar_documento("12.345.678/0001-90"), "12345678000190");
assert_eq!(limpar_documento("00276808860"), "00276808860");
}
#[test]
fn test_extrair_valor() {
assert_eq!(extrair_valor(&Data::Float(1500.50)), 1500.50);
assert_eq!(extrair_valor(&Data::Int(1000)), 1000.0);
assert_eq!(extrair_valor(&Data::String("1.500,50".to_string())), 1500.50);
}
}