From 15bb26ac155ab8256be400f9ff59c1c1d6aac207 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Mon, 21 Sep 2026 15:44:57 +0300 Subject: [PATCH] Etapa 2: plan v3, rapoarte de cercetare si etalonul de regresie Cercetare: ambele formate SAGA (VFP xls/xlsx si Firebird .FDB), cu dovezi fisier:linie in docs/raport_sursa_saga_xlsx.md si raport_exporturi_saga_noua.md. Plan v3 dupa review de strategie si arhitectura: contract intern + trei cititoare, mapare in doua fisiere cu proprietari diferiti, lane-uri. Etalon de regresie anonimizat in tests/golden/ (sume si structura neatinse, zero IBAN si zero cod fiscal real). Tabela de corespondenta ramane ignorata. Iesirile de productie ies din git (raman pe disc); .gitignore acopera si copiile de baze de client si iesirile intermediare ale convertorului. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2 --- .gitignore | 17 + balanta_FUNDATIA.csv | 89 ---- balanta_MASTERJOB.csv | 89 ---- docs/handoff_etapa2_convertor.md | 167 +++++++ docs/parteneri_cui_gasite.md | 10 + docs/plan_etapa2_convertor.md | 248 ++++++++++ docs/plan_solduri2roa.md | 12 + docs/raport_exporturi_saga_noua.md | 326 ++++++++++++++ docs/raport_sursa_saga_xlsx.md | 351 +++++++++++++++ docs/review_ceo_etapa2.md | 424 ++++++++++++++++++ docs/review_eng_etapa2.md | 395 ++++++++++++++++ exemple/saga-sqlite-balanta-09-2026.xls | Bin 0 -> 10752 bytes exemple/saga-sqlite-balanta-09-2026.xlsx | Bin 0 -> 5352 bytes exemple/saga-sqlite-furnizori-09-2026.xlsx | Bin 0 -> 3873 bytes ...furnizori-facturi-un-furnizor-09-2026.xlsx | Bin 0 -> 3866 bytes export_balanta_fundatia_12_2025.xlsx | Bin 10367 -> 0 bytes init_FUNDATIA_2025_12.xlsx | Bin 11196 -> 0 bytes init_MASTER_2025_12.xlsx | Bin 11351 -> 0 bytes sql/update_cod_fiscal_FUNDATIA.sql | 13 +- sql/update_cod_fiscal_MASTER.sql | 15 +- tests/anonimizeaza_etalon.py | 120 +++++ tests/golden/golden_FUNDATIA_2025_12.xlsx | Bin 0 -> 10878 bytes tests/golden/golden_MASTER_2025_12.xlsx | Bin 0 -> 11035 bytes 23 files changed, 2085 insertions(+), 191 deletions(-) delete mode 100644 balanta_FUNDATIA.csv delete mode 100644 balanta_MASTERJOB.csv create mode 100644 docs/handoff_etapa2_convertor.md create mode 100644 docs/plan_etapa2_convertor.md create mode 100644 docs/raport_exporturi_saga_noua.md create mode 100644 docs/raport_sursa_saga_xlsx.md create mode 100644 docs/review_ceo_etapa2.md create mode 100644 docs/review_eng_etapa2.md create mode 100644 exemple/saga-sqlite-balanta-09-2026.xls create mode 100644 exemple/saga-sqlite-balanta-09-2026.xlsx create mode 100644 exemple/saga-sqlite-furnizori-09-2026.xlsx create mode 100644 exemple/saga-sqlite-furnizori-facturi-un-furnizor-09-2026.xlsx delete mode 100644 export_balanta_fundatia_12_2025.xlsx delete mode 100644 init_FUNDATIA_2025_12.xlsx delete mode 100644 init_MASTER_2025_12.xlsx create mode 100644 tests/anonimizeaza_etalon.py create mode 100644 tests/golden/golden_FUNDATIA_2025_12.xlsx create mode 100644 tests/golden/golden_MASTER_2025_12.xlsx diff --git a/.gitignore b/.gitignore index 4040daa..5141cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,20 @@ __pycache__/ *.pyc .gstack/ + +# iesiri si date de firme de productie - raman pe disc, nu in git +init_*.xlsx +export_*.xlsx +mapare_*.xlsx +balanta_*.csv +!init_facturi_balanta_note.xlsx + +# etalonul de regresie e anonimizat si se comite; tabela de corespondenta NU +tests/_corespondenta_anonimizare.json + +# copii de baze de client si iesiri intermediare ale convertorului +*.fdb +*.FDB +parteneri_*.csv +facturi_*.csv +corectii_*.xlsx diff --git a/balanta_FUNDATIA.csv b/balanta_FUNDATIA.csv deleted file mode 100644 index 160337a..0000000 --- a/balanta_FUNDATIA.csv +++ /dev/null @@ -1,89 +0,0 @@ -pagina,cont,denumire,prec_d,prec_c,rulaj_d,rulaj_c,total_d,total_c,sold_d,sold_c,este_total -1,101,CAPITAL SOCIAL,0.0,15000.0,0.0,0.0,0.0,15000.0,0.0,15000.0,0 -1,1171,REZULTATUL REPORTAT - PROFITUL NEREP./ PIREDERE NEACOP.,629448.54,0.0,144159.56,0.0,773608.1,0.0,773608.1,0.0,0 -1,1171.01,REZ. REPORTAT PT. EXCEDENT NEREP. SAU DEFICIT NEACOP. - AFSP,629448.54,0.0,144159.56,0.0,773608.1,0.0,773608.1,0.0,0 -1,1171.01.01,REZULTAT REPORTAT - 2013,7245.69,0.0,0.0,0.0,7245.69,0.0,7245.69,0.0,0 -1,1171.01.02,REZULTAT REPORTAT- 2014,0.0,462166.48,0.0,0.0,0.0,462166.48,0.0,462166.48,0 -1,1171.01.03,REZULTAT REPORTAT- 2015,62733.0,0.0,0.0,0.0,62733.0,0.0,62733.0,0.0,0 -1,1171.01.04,REZULTAT REPORTAT 2016,463593.57,0.0,0.0,0.0,463593.57,0.0,463593.57,0.0,0 -1,1171.01.05,REZULTAT REPORTAT 2017,64580.26,0.0,0.0,0.0,64580.26,0.0,64580.26,0.0,0 -1,1171.01.06,REZULTAT REPORTAT 2018,53819.82,0.0,0.0,0.0,53819.82,0.0,53819.82,0.0,0 -1,1171.01.07,REPORTAT PROFIT/PIERDERE 2019,0.0,277665.19,0.0,0.0,0.0,277665.19,0.0,277665.19,0 -1,1171.01.08,REPORTAT PROFIT/PIERDERE 2020,35546.5,0.0,0.0,0.0,35546.5,0.0,35546.5,0.0,0 -1,1171.01.09,REPORTAT PROFIT/PIERDERE 2021,67800.91,0.0,0.0,0.0,67800.91,0.0,67800.91,0.0,0 -1,1171.01.10,REPORTAT PROFIT/PIERDERE 2022,106630.74,0.0,0.0,0.0,106630.74,0.0,106630.74,0.0,0 -1,1171.01.11,REPORTAT PROFIT/PIERDERE 2023,507329.72,0.0,0.0,0.0,507329.72,0.0,507329.72,0.0,0 -1,1171.01.12,REPORTAT PROFIT/PIERDERE 2024,0.0,0.0,144159.56,0.0,144159.56,0.0,144159.56,0.0,0 -1,1211,EXCEDENT SAU DEFICIT PRIVIND AFSP,164431.54,0.0,130661.51,144159.56,295093.05,144159.56,150933.49,0.0,0 -1,1211.01,EXCEDENT SAU DEFICIT PRIVIND AFSP,164431.54,0.0,130661.51,144159.56,295093.05,144159.56,150933.49,0.0,0 -1,,Total sume clasa 1,793880.08,15000.0,274821.07,144159.56,1068701.15,159159.56,924541.59,15000.0,1 -1,2111,TERENURI,100000.0,0.0,0.0,0.0,100000.0,0.0,100000.0,0.0,0 -1,2111.03,TEREN MOGOSOAIA -260MP /NR CADASTRU 61309,100000.0,0.0,0.0,0.0,100000.0,0.0,100000.0,0.0,0 -1,212,CONSTRUCTII,1408257.53,0.0,0.0,0.0,1408257.53,0.0,1408257.53,0.0,0 -1,212.01,IMOBIL TEREN + CLADIRE MOGOSOAIA,241612.68,0.0,0.0,0.0,241612.68,0.0,241612.68,0.0,0 -1,212.02,IMOBIL APARTAMENT GH. BARITIU,244085.95,0.0,0.0,0.0,244085.95,0.0,244085.95,0.0,0 -1,212.03,IMOBIL MIERCANI TEREN + CONSTRUCTIE,331042.5,0.0,0.0,0.0,331042.5,0.0,331042.5,0.0,0 -1,212.04,IMOBIL STR COCARESCU NR 22,392986.4,0.0,0.0,0.0,392986.4,0.0,392986.4,0.0,0 -1,212.05,IMOBIL MOGOSOAIA-NR CADASTRU 61309,198530.0,0.0,0.0,0.0,198530.0,0.0,198530.0,0.0,0 -1,213,"INSTALATII TEHNICE,MIJ. TRANSP., ANIMALE SI PLANTATII",6300.0,0.0,0.0,0.0,6300.0,0.0,6300.0,0.0,0 -1,2133,MIJLOACE DE TRANSPORT C,250985.0,0.0,0.0,0.0,250985.0,0.0,250985.0,0.0,0 -2,214,"MOBILIER,APARATURA BIROTICA,ETC.",30950.0,0.0,0.0,0.0,30950.0,0.0,30950.0,0.0,0 -2,2812,AMORT. CONSTRUCTIILOR,0.0,409032.32,0.0,55016.88,0.0,464049.2,0.0,464049.2,0 -2,2813,"AMORT. INST., MIJ. DE TRANSPORT",0.0,177781.03,0.0,62746.26,0.0,240527.29,0.0,240527.29,0 -2,2814,AMORT. ALTOR IMOBILIZARI CORP.,0.0,16764.62,0.0,3868.74,0.0,20633.36,0.0,20633.36,0 -2,2814.01,"AMORT. AP. SI INST. DE MASURARE, CONTROL SI REGLARE - AFSP",0.0,16764.62,0.0,3868.74,0.0,20633.36,0.0,20633.36,0 -2,,Total sume clasa 2,1796492.53,603577.97,0.0,121631.88,1796492.53,725209.85,1796492.53,725209.85,1 -2,401,FURNIZORI,0.0,305673.95,0.0,9029.63,0.0,314703.58,0.0,314703.58,0 -2,401.00004,GTC AUTO TRADE,0.0,0.3,0.0,0.0,0.0,0.3,0.0,0.3,0 -2,401.00018,HOSTVISION SRL,0.0,143.92,0.0,470.18,0.0,614.1,0.0,614.1,0 -2,401.00043,BRIT MOTOR AG SRL,0.0,374.85,0.0,6265.77,0.0,6640.62,0.0,6640.62,0 -2,401.00045,TRACOM IMPEX S.R.L.,0.0,0.0,0.0,1392.75,0.0,1392.75,0.0,1392.75,0 -2,401.00049,APA CANAL ILFOV,0.0,604.89,0.0,521.93,0.0,1126.82,0.0,1126.82,0 -2,401.00050,SC CEGIM SRL,0.0,5599.99,0.0,0.0,0.0,5599.99,0.0,5599.99,0 -2,401.00051,S.C. CRAMA SPARLENI SRL,0.0,420.0,0.0,0.0,0.0,420.0,0.0,420.0,0 -2,401.00053,POPESCU LUMINITA-MACOVICIUC VERONICA,0.0,298530.0,0.0,0.0,0.0,298530.0,0.0,298530.0,0 -2,401.00054,DEDEMAN SRL,0.0,0.0,0.0,190.0,0.0,190.0,0.0,190.0,0 -2,401.00055,AGROMECANIC IMPEX SRL,0.0,0.0,0.0,189.0,0.0,189.0,0.0,189.0,0 -2,409,FURNIZORI DEBITORI,-0.01,0.0,0.0,0.0,-0.01,0.0,-0.01,0.0,0 -2,4092,FUR.-DEBIT PT.PRESTARI SERVICII,809.33,0.0,0.0,0.0,809.33,0.0,809.33,0.0,0 -2,4092.1,HOSTVISION,547.65,0.0,0.0,0.0,547.65,0.0,547.65,0.0,0 -2,4092.2,ROMARG,217.7,0.0,0.0,0.0,217.7,0.0,217.7,0.0,0 -2,4092.3,HORVARTH ALINA GABRIELA PROMISIUNE VZ-C,43.98,0.0,0.0,0.0,43.98,0.0,43.98,0.0,0 -2,4111,CLIENTI,15126.48,0.0,0.0,0.0,15126.48,0.0,15126.48,0.0,0 -2,4111.00004,EM INTERNATIONAL SERVICES GROUP S.R.L.,15126.48,0.0,0.0,0.0,15126.48,0.0,15126.48,0.0,0 -2,4411,IMPOZITUL PE PROFIT,0.0,26986.0,0.0,0.0,0.0,26986.0,0.0,26986.0,0 -2,446,ALTE IMPOZITE SI TAXE,0.0,-485.0,0.0,0.0,0.0,-485.0,0.0,-485.0,0 -2,446.1,IMPOZIT TEREN,0.0,-485.0,0.0,0.0,0.0,-485.0,0.0,-485.0,0 -2,4482,ALTE CREANTE FATA DE BUGET,17264.38,0.0,0.0,0.0,17264.38,0.0,17264.38,0.0,0 -2,4551,ASOCIATI-CONTURI CURENTE,0.0,1571531.33,0.0,0.0,0.0,1571531.33,0.0,1571531.33,0 -2,4551.01,IONESCU VALENTIN GHEORGHE,0.0,1383131.33,0.0,0.0,0.0,1383131.33,0.0,1383131.33,0 -2,4551.02,EURO ANTR RO12 PIRB 4250 7735 C 0200 1000,0.0,188400.0,0.0,0.0,0.0,188400.0,0.0,188400.0,0 -3,461,DEBITORI DIVERSI,477938.37,0.0,0.0,0.0,477938.37,0.0,477938.37,0.0,0 -3,461.01,SISTEME INTERNATIONALE DE AFACERI,15031.49,0.0,0.0,0.0,15031.49,0.0,15031.49,0.0,0 -3,461.02,IMPRUMUT ASOC. NO-ADD,10800.0,0.0,0.0,0.0,10800.0,0.0,10800.0,0.0,0 -3,461.03,TEMPLUM,29445.23,0.0,0.0,0.0,29445.23,0.0,29445.23,0.0,0 -3,461.04,DEBITOR EURO ANTREPRENOR,417000.01,0.0,0.0,0.0,417000.01,0.0,417000.01,0.0,0 -3,461.06,ADRIAN CRISTESCU,5000.0,0.0,0.0,0.0,5000.0,0.0,5000.0,0.0,0 -3,461.07,FUNDATIA DOMENIU OTETELESANU,661.64,0.0,0.0,0.0,661.64,0.0,661.64,0.0,0 -3,462,CREDITORI DIVERSI,0.0,3400771.0,0.0,0.0,0.0,3400771.0,0.0,3400771.0,0 -3,462.01,TEMPLUM PLUS,0.0,988640.0,0.0,0.0,0.0,988640.0,0.0,988640.0,0 -3,462.04,CREDITOR EURO PRO CONSTRUCT,0.0,1869897.0,0.0,0.0,0.0,1869897.0,0.0,1869897.0,0 -3,462.05,MODREA ALAIN FLORIN,0.0,542234.0,0.0,0.0,0.0,542234.0,0.0,542234.0,0 -3,473,DEC. DIN OP. IN CURS DE CLARIF.,14094.02,0.0,0.0,0.0,14094.02,0.0,14094.02,0.0,0 -3,,Total sume clasa 4,525232.57,5304477.28,0.0,9029.63,525232.57,5313506.91,525232.57,5313506.91,1 -3,5121,CONTURI LA BANCA IN LEI,81966.8,0.0,0.0,0.0,81966.8,0.0,81966.8,0.0,0 -3,5121.01,RO73 BACX 0000 0008 7243 0001,81966.8,0.0,0.0,0.0,81966.8,0.0,81966.8,0.0,0 -3,5124,CONTURI LA BANCA IN DEVIZE,2492540.28,0.0,0.0,0.0,2492540.28,0.0,2492540.28,0.0,0 -3,5124.01,RO89 BACX 0000 0008 7243 0004 - EURO,2492540.28,0.0,0.0,0.0,2492540.28,0.0,2492540.28,0.0,0 -3,5125,SUME IN CURS DE DECONTARE,160509.43,0.0,0.0,0.0,160509.43,0.0,160509.43,0.0,0 -3,5125.03,SUME PROPRIRI ANAF,1490.02,0.0,0.0,0.0,1490.02,0.0,1490.02,0.0,0 -3,5125.04,SUME IN CURS DECONTARE,159019.41,0.0,0.0,0.0,159019.41,0.0,159019.41,0.0,0 -3,5311,CASA IN LEI,72433.56,0.0,0.0,0.0,72433.56,0.0,72433.56,0.0,0 -3,,Total sume clasa 5,2807450.07,0.0,0.0,0.0,2807450.07,0.0,2807450.07,0.0,1 -3,6024,CHELT. CU PIESELE DE SCHIMB,0.0,0.0,1392.75,1392.75,1392.75,1392.75,0.0,0.0,0 -3,6051,CHELT. PRIVIND CONSUMUL DE ENERGIE,0.0,0.0,521.93,521.93,521.93,521.93,0.0,0.0,0 -3,611,CHELT. CU INTRETINEREA SI REP.,0.0,0.0,6265.77,6265.77,6265.77,6265.77,0.0,0.0,0 -3,628,ALTE CHELT.CU SERV.LA TERTI,0.0,0.0,849.18,849.18,849.18,849.18,0.0,0.0,0 -3,6811,CHELT. DE EXPL.CU AMORT. IMOB.,0.0,0.0,121631.88,121631.88,121631.88,121631.88,0.0,0.0,0 -3,,Total sume clasa 6,0.0,0.0,130661.51,130661.51,130661.51,130661.51,0.0,0.0,1 -4,,Totaluri:,5923055.25,5923055.25,405482.58,405482.58,6328537.83,6328537.83,6053716.76,6053716.76,1 diff --git a/balanta_MASTERJOB.csv b/balanta_MASTERJOB.csv deleted file mode 100644 index 170a50f..0000000 --- a/balanta_MASTERJOB.csv +++ /dev/null @@ -1,89 +0,0 @@ -pagina,cont,denumire,prec_d,prec_c,rulaj_d,rulaj_c,total_d,total_c,sold_d,sold_c,este_total -1,1012,CAPITAL SUBSCRIS VARSAT,0.0,90000.0,0.0,0.0,0.0,90000.0,0.0,90000.0,0 -1,117,REZULTATUL REPORTAT,47431.79,0.0,0.0,0.0,47431.79,0.0,47431.79,0.0,0 -1,117.1,PIERDERE REPORTATA,4280.23,0.0,0.0,0.0,4280.23,0.0,4280.23,0.0,0 -1,117.2,PIERDERE REPORTATA,7743.14,0.0,0.0,0.0,7743.14,0.0,7743.14,0.0,0 -1,117.3,PIERDERE REPORTATA,7037.7,0.0,0.0,0.0,7037.7,0.0,7037.7,0.0,0 -1,117.4,PIERDERE 2007,7531.03,0.0,0.0,0.0,7531.03,0.0,7531.03,0.0,0 -1,117.5,PIERDERE 2008,5731.08,0.0,0.0,0.0,5731.08,0.0,5731.08,0.0,0 -1,117.6,PIERDERE 2009,3421.65,0.0,0.0,0.0,3421.65,0.0,3421.65,0.0,0 -1,117.7,PIERDERE 2010,2764.43,0.0,0.0,0.0,2764.43,0.0,2764.43,0.0,0 -1,117.8,PIERDERE 2011,6286.66,0.0,0.0,0.0,6286.66,0.0,6286.66,0.0,0 -1,1171,REZULTATUL REPORTAT - PROFITUL NEREP./ PIREDERE NEACOP.,106880.87,0.0,103625.3,0.0,210506.17,0.0,210506.17,0.0,0 -1,121,PROFIT SI PIERDERE,103625.3,0.0,100135.51,103625.3,203760.81,103625.3,100135.51,0.0,0 -1,,Total sume clasa 1,257937.96,90000.0,203760.81,103625.3,461698.77,193625.3,358073.47,90000.0,1 -1,201,CHELTUIELI DE CONSTITUIRE,581.27,0.0,0.0,0.0,581.27,0.0,581.27,0.0,0 -1,2111,TERENURI,439966.6,0.0,0.0,0.0,439966.6,0.0,439966.6,0.0,0 -1,2111.01,RISNOV- BRASOV,439966.6,0.0,0.0,0.0,439966.6,0.0,439966.6,0.0,0 -1,235,INVESTITII IMOBILIARE IN CURS DE EXECUTIE,292.0,0.0,0.0,0.0,292.0,0.0,292.0,0.0,0 -1,2813,"AMORT. INSTALATIILOR, MIJ. DE TRANSPORT",0.0,249314.46,0.0,87993.3,0.0,337307.76,0.0,337307.76,0 -1,,Total sume clasa 2,440839.87,249314.46,0.0,87993.3,440839.87,337307.76,440839.87,337307.76,1 -1,303,MAT. DE NATURA OB. DE INVENTAR,16802.46,0.0,0.0,0.0,16802.46,0.0,16802.46,0.0,0 -1,371,MARFURI,38850.0,0.0,0.0,0.0,38850.0,0.0,38850.0,0.0,0 -1,371.01,FURAJE PASTRAV,38850.0,0.0,0.0,0.0,38850.0,0.0,38850.0,0.0,0 -1,,Total sume clasa 3,55652.46,0.0,0.0,0.0,55652.46,0.0,55652.46,0.0,1 -1,401,FURNIZORI,0.0,534966.78,0.0,12142.21,0.0,547108.99,0.0,547108.99,0 -1,401.00001,MEDIAROM COMUNICATIONS,0.0,14293.76,0.0,0.0,0.0,14293.76,0.0,14293.76,0 -1,401.00003,TEMPLUM PLUS,0.0,500.0,0.0,0.0,0.0,500.0,0.0,500.0,0 -1,401.00006,BIROU INDIVIDUAL NOTARIAL GEORGE TRAGONE,0.0,952.0,0.0,0.0,0.0,952.0,0.0,952.0,0 -1,401.00007,ACVATIC TES S.R.L.,0.0,439966.6,0.0,0.0,0.0,439966.6,0.0,439966.6,0 -1,401.00008,DREAM FISH S.R.L.,0.0,38850.0,0.0,0.0,0.0,38850.0,0.0,38850.0,0 -1,401.00009,LEROY MERLIN ROMANIA SRL C,0.0,2902.91,0.0,0.0,0.0,2902.91,0.0,2902.91,0 -2,401.00010,SERVICE ONLAPTOP S.R.L.,0.0,1230.0,0.0,0.0,0.0,1230.0,0.0,1230.0,0 -2,401.00011,PET PRODUCT SRL,0.0,445.05,0.0,0.0,0.0,445.05,0.0,445.05,0 -2,401.00012,BIROU INDIVIDUAL NOTARIAL PIELEANU DANIELA,0.0,4879.0,0.0,0.0,0.0,4879.0,0.0,4879.0,0 -2,401.00013,CREATIV EXPERT CONSTRUCT S.R.L.,0.0,3300.0,0.0,0.0,0.0,3300.0,0.0,3300.0,0 -2,401.00014,,0.0,16802.46,0.0,0.0,0.0,16802.46,0.0,16802.46,0 -2,401.00015,ANDREEA BEATRICE VASILE,0.0,10000.0,0.0,0.0,0.0,10000.0,0.0,10000.0,0 -2,401.00017,TIRES AND PARTS SRL,0.0,845.0,0.0,400.0,0.0,1245.0,0.0,1245.0,0 -2,401.00023,ANGLO CARS S.R.L.,0.0,0.0,0.0,3700.9,0.0,3700.9,0.0,3700.9,0 -2,401.00024,DAW BENTA SRL,0.0,0.0,0.0,1948.01,0.0,1948.01,0.0,1948.01,0 -2,401.00025,ROMSYSTEMS S.R.L.,0.0,0.0,0.0,1380.4,0.0,1380.4,0.0,1380.4,0 -2,401.00026,UNIVERSAL GRUP SRL,0.0,0.0,0.0,1380.9,0.0,1380.9,0.0,1380.9,0 -2,401.00027,SEBASTIAN CONSTRUCTII MONTAJ SRL,0.0,0.0,0.0,3332.0,0.0,3332.0,0.0,3332.0,0 -2,4092,FURNIZORI — DEBITORI PT. PRESTARI DE SERVICII,4.0,0.0,0.0,0.0,4.0,0.0,4.0,0.0,0 -2,4092.01,ICI,4.0,0.0,0.0,0.0,4.0,0.0,4.0,0.0,0 -2,4111,CLIENTI,12000.0,0.0,0.0,0.0,12000.0,0.0,12000.0,0.0,0 -2,4111.00001,NEW HOME CONSTRUCT S.A.,12000.0,0.0,0.0,0.0,12000.0,0.0,12000.0,0.0,0 -2,421,PERSONAL - SALARII DATORATE,0.0,17689.0,0.0,0.0,0.0,17689.0,0.0,17689.0,0 -2,4315,CONTR. DE ASIGURARI SOCIALE,0.0,24297.0,0.0,0.0,0.0,24297.0,0.0,24297.0,0 -2,4316,CONTR. DE ASIGURARI SOCIALE DE SANATATE,0.0,10905.0,0.0,0.0,0.0,10905.0,0.0,10905.0,0 -2,436,CONTR. ASIGURATORIE DE MUNCA,0.0,1128.0,0.0,0.0,0.0,1128.0,0.0,1128.0,0 -2,4372,CONTRIBUTIA PERSONALULUI LA FONDUL DE SOMAJ,0.0,52.0,0.0,0.0,0.0,52.0,0.0,52.0,0 -2,441,IMPOZITUL PE PROFIT SI ALTE IMPOZITE,0.0,2567.0,0.0,0.0,0.0,2567.0,0.0,2567.0,0 -2,441.01,IMPOZIT FORFETAR,0.0,2567.0,0.0,0.0,0.0,2567.0,0.0,2567.0,0 -2,4411,IMPOZITUL PE PROFIT,0.0,7804.0,0.0,0.0,0.0,7804.0,0.0,7804.0,0 -2,4418,IMPOZITUL PE VENIT,0.0,360.0,0.0,0.0,0.0,360.0,0.0,360.0,0 -2,4424,TVA DE RECUPERAT,4669.56,0.0,0.0,0.0,4669.56,0.0,4669.56,0.0,0 -2,444,IMPOZITUL PE VENITURI DE NATURA SALARIILOR,0.0,3105.0,0.0,0.0,0.0,3105.0,0.0,3105.0,0 -2,447,FONDURI SPECIALE TAXE SI VARSAMINTE ASIMILATE,0.0,47.0,0.0,0.0,0.0,47.0,0.0,47.0,0 -2,447.01,COMISION ITM C,0.0,47.0,0.0,0.0,0.0,47.0,0.0,47.0,0 -3,4551,ACTIONARI/ASOCIATI - CONTURI CURENTE,0.0,751724.61,0.0,0.0,0.0,751724.61,0.0,751724.61,0 -3,4551.01,IONESCU VALENTIN,0.0,729308.61,0.0,0.0,0.0,729308.61,0.0,729308.61,0 -3,4551.02,TOMA IONEL VIOREL RO55 RZBR 0000 0600 1338 1717,0.0,22416.0,0.0,0.0,0.0,22416.0,0.0,22416.0,0 -3,461,DEBITORI DIVERSI,69289.0,0.0,0.0,0.0,69289.0,0.0,69289.0,0.0,0 -3,461.01,SISTEME INTARNATIONALE DE AF.,8289.0,0.0,0.0,0.0,8289.0,0.0,8289.0,0.0,0 -3,461.02,HABITAT SI AMBIENT,20000.0,0.0,0.0,0.0,20000.0,0.0,20000.0,0.0,0 -3,461.03,DEBITORI DIVERSI,41000.0,0.0,0.0,0.0,41000.0,0.0,41000.0,0.0,0 -3,462,CREDITORI DIVERSI,0.0,9810.0,0.0,0.0,0.0,9810.0,0.0,9810.0,0 -3,462.01,CREDITORI DIVERSI -IONESCU ALINA,0.0,3350.0,0.0,0.0,0.0,3350.0,0.0,3350.0,0 -3,462.02,CREDITORI DIVERSI - REZIDENT COM,0.0,700.0,0.0,0.0,0.0,700.0,0.0,700.0,0 -3,462.03,CREDITORI DIVERSI - IONESCU VALENTIN,0.0,760.0,0.0,0.0,0.0,760.0,0.0,760.0,0 -3,462.05,CREDITOR C.S.C.,0.0,5000.0,0.0,0.0,0.0,5000.0,0.0,5000.0,0 -3,473,DECONTARI DIN OPERATIUNI IN CURS DE CLARIFICARE,0.0,310905.94,0.0,0.0,0.0,310905.94,0.0,310905.94,0 -3,,Total sume clasa 4,85962.56,1675361.33,0.0,12142.21,85962.56,1687503.54,85962.56,1687503.54,1 -3,5121,CONTURI LA BANCA IN LEI,15730.61,0.0,0.0,0.0,15730.61,0.0,15730.61,0.0,0 -3,5121.01,PIRAEUS BANK - RON,0.0,20.8,0.0,0.0,0.0,20.8,0.0,20.8,0 -3,5121.02,RO45 PIRB 4250 7073 2600 1000 FIRST BANK,15599.25,0.0,0.0,0.0,15599.25,0.0,15599.25,0.0,0 -3,5124,CONTURI LA BANCA IN VALUTA,1060090.96,0.0,0.0,0.0,1060090.96,0.0,1060090.96,0.0,0 -3,5124.01,BANCA (EURO),5.71,0.0,0.0,0.0,5.71,0.0,5.71,0.0,0 -3,5124.02,RO11 PIRB 4250 7073 2600 2000 FIRST,1060085.25,0.0,0.0,0.0,1060085.25,0.0,1060085.25,0.0,0 -3,5125,SUME IN CURS DE DECONTARE,0.5,0.0,0.0,0.0,0.5,0.0,0.5,0.0,0 -3,5125.03,POPRIRE,0.5,0.0,0.0,0.0,0.5,0.0,0.5,0.0,0 -3,5311,CASA IN LEI,98460.87,0.0,0.0,0.0,98460.87,0.0,98460.87,0.0,0 -3,,Total sume clasa 5,1174282.94,0.0,0.0,0.0,1174282.94,0.0,1174282.94,0.0,1 -3,611,CHELT. CU INTRETINEREA SI REPARATIILE,0.0,0.0,2510.9,2510.9,2510.9,2510.9,0.0,0.0,0 -3,628,ALTE CHELT. CU SERVICIILE EXECUTATE DE TERTI,0.0,0.0,9631.31,9631.31,9631.31,9631.31,0.0,0.0,0 -3,6811,CHELT. DE EXPLOATARE CU AMORTIZAREA IMOBILIZARILOR,0.0,0.0,87993.3,87993.3,87993.3,87993.3,0.0,0.0,0 -3,,Total sume clasa 6,0.0,0.0,100135.51,100135.51,100135.51,100135.51,0.0,0.0,1 -4,,Totaluri:,2014675.79,2014675.79,303896.32,303896.32,2318572.11,2318572.11,2114811.3,2114811.3,1 diff --git a/docs/handoff_etapa2_convertor.md b/docs/handoff_etapa2_convertor.md new file mode 100644 index 0000000..02b23f2 --- /dev/null +++ b/docs/handoff_etapa2_convertor.md @@ -0,0 +1,167 @@ +# Handoff - etapa 2, convertorul reutilizabil SAGA -> ROA + +Scris 21.09.2026. Sesiunea urmatoare este **orchestrator**, nu executant: imparte pe lane-uri, +trimite sarcinile la agenti opencode, verifica portile. Nu scrie ea codul. + +**Stare: planul e gata si aprobat pe decizii; nu s-a scris nicio linie de cod pentru etapa 2.** +Marius nu a dat inca "porneste" pe lane-uri. Intreaba-l inainte sa lansezi primul lane. + +--- + +## 1. Ce sa citesti, in ordine, si de ce + +| fisier | de ce | +|---|---| +| `docs/plan_etapa2_convertor.md` | **contractul de lucru, v3.** Tot ce urmeaza se masoara fata de el | +| `docs/raport_sursa_saga_xlsx.md` | SAGA veche (VFP); conventiile `conturi_roa.dbf` (`.TOATE`, `.RESTUL`) | +| `docs/raport_exporturi_saga_noua.md` | SAGA noua (Firebird); structura exporturilor si a bazei | +| `docs/decizii_import.md` | regulile de mapare, **validate in productie - nu se redeschid** | +| `docs/review_ceo_etapa2.md`, `docs/review_eng_etapa2.md` | de unde vin corectiile din v3; reciteste doar daca vrei sa redeschizi o decizie | + +Nu reface cercetarea. Cele doua rapoarte au costat doua lane-uri si contin dovezi cu fisier si linie. + +## 2. Ce e gata + +- Etapele 1-6 din `docs/plan_solduri2roa.md`: cele doua xlsx au fost **importate in ROACONT si au + trecut**; scripturile `sql/update_cod_fiscal_*.sql` au fost **executate** de Marius. +- Cercetarea pe ambele formate SAGA + conectarea la Firebird, verificata live. +- Planul v3, dupa review de strategie si de arhitectura. +- `.gitignore` + `git rm --cached` pe iesirile de productie (fisierele raman pe disc). + +- **Etalonul de regresie este facut si pus in git** (21.09.2026): `tests/golden/` cu + `golden_FUNDATIA_2025_12.xlsx` si `golden_MASTER_2025_12.xlsx`, produse de + `tests/anonimizeaza_etalon.py`. Verificat: structura, numarul de randuri si sumele sunt + identice cu originalele (`SUM(totdeb) = SUM(totcred)` neatins), au fost schimbate doar + coloanele 10-14 pe randurile FACTURA plus IBAN-urile; zero IBAN si zero cod fiscal real + ramase. Tabela de corespondenta (`tests/_corespondenta_anonimizare.json`) e ignorata de git - + nu o comite, cu ea anonimizarea se desface. + +## 3. Ce NU e facut + +Tot ce e in tabelul de lane-uri din plan, mai putin etalonul. Niciun fisier de cod nou pentru +conversie, niciun test, niciun `config/conturi_parteneri.csv`. + +## 4. Deciziile deja luate - nu le redeschide + +1. **Conturile cu parteneri**: setul validat din `genereaza_xlsx.py:46-50` este autoritatea, tinut + in `config/conturi_parteneri.csv`. `CONFIG_CONT_IREG` e consultativ; unealta **raporteaza** + divergentele, nu le aplica. Motivul, cu dovada, e in plan - Oracle nu listeaza 5121/5124/5311. +2. **`acont` pentru conturi cu parteneri** ramane decis de `genereaza_xlsx.py`; maparea doar + suprascrie explicit. Randurile de diferenta raman calculate in cod. +3. **Maparea = doua fisiere**: `mapare_.xlsx` scris doar de unealta (regenerat complet), + `corectii_.xlsx` scris doar de Marius. +4. **Cititorul FDB se face acum**, marcat explicit "verificat doar pe cazul sintetic". +5. **Etalonul de regresie**: xlsx-uri intregi, **anonimizate** (sume si structura neatinse), in + `tests/golden/`. Tabela de corespondenta nume real -> nume fals **nu se comite**. +6. **Teste**: `unittest` din stdlib. Fara dependinte noi de test. +7. Sursa: PDF, foaie de calcul SAGA si Firebird, toate trei, egale. + +## 5. Ordinea lane-urilor + +`contract` singur, primul -> apoi `citire-xlsx` si `mapare` in paralel -> `citire-fdb` -> +`integrare` -> `regresie`. Criteriile de terminare sunt in plan, in tabelul de lane-uri si in cel de +testare. Etalonul din `tests/golden/` a fost fixat **inainte** sa se schimbe vreo linie de cod - +asta e tot rostul ordinii. Daca vreun lane modifica `genereaza_xlsx.py` si regresia incepe sa pice, +etalonul are dreptate, nu codul nou. + +## 6. Cum lansezi un lane (verificat azi, functioneaza) + +Ghidul complet: `D:\ROA\ROACONT\COMUN\docs\opencode_agenti_orchestrare.md`. + +- **Port pentru acest proiect: 4296.** Nu-l folosi pe 4196 (ROACONT) sau 4096 (ROAFACTURARE) - + oprirea serverului dintr-un proiect omoara lane-urile celuilalt. Portul 4296 **nu e inca trecut in + tabelul din ghid** - adauga-l acolo (COMUN are nevoie de aprobare inainte de commit). +- Pornire, din directorul proiectului: `opencode serve --port 4296` +- Model: `{"providerID":"opencode-go","modelID":"deepseek-v4.1-flash"}` +- Promptul de sistem, obligatoriu la fiecare sesiune, in campul `system`: + `D:\ROA\ROACONT\COMUN\docs\opencode_prompt_initial.md` +- Sesiune noua: `POST /session {title}` -> `id`; + sarcina: `POST /session/:id/prompt_async?directory=D:\ROA\IMPORT2ROA\solduri2roa` +- Stare: `GET /session/status` - sesiunea lipseste cand e libera, `{}` = toate libere. +- Oprire la final: + `Stop-Process -Id (Get-NetTCPConnection -LocalPort 4296 -State Listen).OwningProcess` + +Sablon PowerShell pentru trimitere (JSON pe UTF-8, altfel se strica diacriticele si ghilimelele): + +```powershell +$u='http://127.0.0.1:4296' +$sys = [IO.File]::ReadAllText('D:\ROA\ROACONT\COMUN\docs\opencode_prompt_initial.md') +$m = @{providerID='opencode-go'; modelID='deepseek-v4.1-flash'} +$sid = (Invoke-RestMethod -Method Post "$u/session" -ContentType 'application/json' -Body '{"title":"lane-X"}').id +$b = @{model=$m; system=$sys; parts=@(@{type='text';text=$task})} | ConvertTo-Json -Depth 5 +Invoke-RestMethod -Method Post "$u/session/$sid/prompt_async?directory=D:\ROA\IMPORT2ROA\solduri2roa" ` + -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($b)) +``` + +Asteptarea: nu face `Start-Sleep` lung in foreground (harness-ul il blocheaza). Foloseste Bash in +fundal: `until curl -s http://127.0.0.1:4296/session/status | grep -qv busy; do sleep 20; done`. + +**Ce trebuie sa contina fiecare sarcina de lane**: numele lane-ului, fisierele exacte de atins, +criteriul de terminare luat din plan, interdictiile (fara commit, fara `sed -i`, fara modificari in +`D:\ROA\ROACONT`, `D:\SAGA250909`, `saga2roa*`), unde scrie raportul, si ultimul mesaj +`GATA ` / `BLOCAT : motiv`. Agentii respecta asta; doi lane-uri au mers azi asa. + +## 7. Capcane deja platite - nu le redescoperi + +- `openpyxl` **nu** citeste `.xls` (BIFF). Pentru `.xls`: `xlrd` (instalat, 2.0.2) sau conversie + `soffice --headless --convert-to xlsx`. Ambele verificate. +- Firebird: **numai embedded**. `firebird-driver` (instalat, 2.0.3) + `fdb.load_api` catre + `C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll`, SYSDBA/masterkey, charset `WIN1250`, + `access_mode=READ`, `no_gc=True`. Pe TCP 3060 parola implicita **nu** merge. +- In `CONTURI` **soldurile stocate sunt 0**; balanta se obtine agregand `REGISTRU`. Cine citeste + `CONTURI.DEB_PREC` scoate o balanta goala fara nicio eroare. +- Coloanele `_1` din export (S..AF) seamana cu cele principale. Potrivirea numelor de coloana se + face **exact**, niciodata pe substring: `TOTAL_DEB` vs `TOTAL_DEB_1`. +- Numele foii difera intre surse (`balanta` / `xl` / `Sheet1`) - ia prima foaie, nu cauta dupa nume. +- `CONT` se citeste ca text. `401.00002` citit ca numar se strica. +- Heredoc-urile lungi in Bash au esuat pe fisierele mari de documentatie; scrie-le cu tool-ul de + scriere de fisiere. +- `python` direct din Bash e stub Windows Store si nu scrie nimic (iese 0 si fisierul ramane + neatins). Foloseste `py`, sau PowerShell. + +## 8. Starea pe disc - nimic periculos, dar necomis + +`git status` la predare (HEAD = `ceab089`, branch `main`): + +``` +M .gitignore +D balanta_FUNDATIA.csv +D balanta_MASTERJOB.csv + M docs/parteneri_cui_gasite.md + M docs/plan_solduri2roa.md +D export_balanta_fundatia_12_2025.xlsx +D init_FUNDATIA_2025_12.xlsx +D init_MASTER_2025_12.xlsx + M sql/update_cod_fiscal_FUNDATIA.sql + M sql/update_cod_fiscal_MASTER.sql +A tests/anonimizeaza_etalon.py +A tests/golden/golden_FUNDATIA_2025_12.xlsx +A tests/golden/golden_MASTER_2025_12.xlsx +?? docs/handoff_etapa2_convertor.md +?? docs/plan_etapa2_convertor.md +?? docs/raport_exporturi_saga_noua.md +?? docs/raport_sursa_saga_xlsx.md +?? docs/review_ceo_etapa2.md +?? docs/review_eng_etapa2.md +?? exemple/ +``` + +Verificari inainte de orice altceva: +- `init_FUNDATIA_2025_12.xlsx` si `init_MASTER_2025_12.xlsx` sunt pe disc dar nu mai sunt in git. + Etalonul anonimizat e deja generat din ele si comis, deci poarta de regresie nu mai depinde de + ele. Sunt necesare doar daca vrei sa **regenerezi** etalonul (`py testsnonimizeaza_etalon.py`). +- Nu se da commit fara ca Marius sa vada diff-ul (regula proiectului). +- Niciun proces ramas viu: serverul opencode de pe 4296 a fost oprit la predare. Il pornesti tu, + cu comanda din sectiunea 6. +- `D:\ROA\ROACONT`, `D:\SAGA250909` si `saga2roa*` nu au fost modificate si nu se modifica. + +## 9. Primul lucru de facut in sesiunea noua + +1. Citeste planul v3. +2. Intreaba-l pe Marius daca pornesti (nu a dat inca "da" pe lansarea lane-urilor). +3. La "da": lanseaza **doar** lane-ul `contract`. Etalonul exista deja, deci lane-ul are de facut + `config/conturi_parteneri.csv` + scriptul de divergenta Oracle, si de scris testul de regresie + care compara iesirea codului de azi cu `tests/golden/`, pe valori de celula (nu pe hash: un + xlsx rescris de alta versiune de openpyxl are alti octeti cu acelasi continut). Testul trebuie + sa treaca **cu codul de conversie neschimbat**. Daca nu trece, e bug in test sau in etalon, nu + in cod, si restul lane-urilor nu au voie sa porneasca. diff --git a/docs/parteneri_cui_gasite.md b/docs/parteneri_cui_gasite.md index c24cab4..ee49c03 100644 --- a/docs/parteneri_cui_gasite.md +++ b/docs/parteneri_cui_gasite.md @@ -77,3 +77,13 @@ Motivul fiecaruia; CUI-ul ramane provizoriu. - 401.00018 HOSTVISION SRL, 401.00003 TEMPLUM PLUS si 4111.00004 EM INTERNATIONAL SERVICES GROUP sunt singurele potriviri exacte, dar firmele sunt dizolvate/radiate/inactive. Sunt trecute la "gasite" cu starea notata; de verificat daca partenerul din balanta este chiar acesta. + +## Prefix RO (plătitori de TVA) + +Verificare ANAF 21.09.2026 (POST `https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva`, +câmpul `scpTVA`) pentru toate cele 17 CUI-uri găsite. Codul fiscal se scrie cu prefix `RO` +doar la plătitorii de TVA; neplătitorii rămân cu CUI-ul simplu. + +Neplătitori (fără prefix): 14446160 TEMPLUM PLUS (radiat 26.03.2021), 33048749 CREATIV EXPERT +CONSTRUCT, 3598139 HOSTVISION, 44234399 EM INTERNATIONAL SERVICES GROUP. +Restul de 13 au prefix `RO` în scripturile `sql/update_cod_fiscal_*.sql`. diff --git a/docs/plan_etapa2_convertor.md b/docs/plan_etapa2_convertor.md new file mode 100644 index 0000000..a57cc91 --- /dev/null +++ b/docs/plan_etapa2_convertor.md @@ -0,0 +1,248 @@ +# Plan etapa 2 - convertorul reutilizabil SAGA -> xlsx initializare ROA + +Stare: **v3, deciziile luate, lansarea lane-urilor neaprobata inca**. Versiunea v2 a trecut prin +review de strategie si de arhitectura (`docs/review_ceo_etapa2.md`, `docs/review_eng_etapa2.md`); ce urmeaza include corectiile. + +Premise: importurile FUNDATIA + MASTER au trecut in ROACONT, scripturile de coduri fiscale au fost +executate (confirmat 21.09.2026). Regulile din `decizii_import.md` sunt validate in productie si +**nu se redeschid**. + +Cercetarea pe care se sprijina planul: +- `docs/raport_sursa_saga_xlsx.md` - SAGA veche (VFP), conventiile tabelei `conturi_roa.dbf`; +- `docs/raport_exporturi_saga_noua.md` - SAGA noua (Firebird), exporturile xlsx si baza `.FDB`. + +## Ce ramane in scop si ce nu + +IN scop: **initializarea soldurilor la o luna**, adica fisierul pe care il inghite +`frm_initializare_facturi_balanta` din ROACONT. + +IN AFARA scopului (asta faceau programele `saga2roa*` vechi, noi nu): importul registrului jurnal, +generarea balantelor de verificare lunare, generarea nomenclatorului de parteneri la fiecare luna. + +## Corectia cea mai importanta fata de v2: cine decide conturile cu parteneri + +v2 spunea ca `CONFIG_CONT_IREG` din Oracle e sursa de adevar. **Este gresit si ar fi produs o +regresie tacuta.** Dovada: + +- `genereaza_xlsx.py:46-50` trateaza ca parteneri `5121`, `5124` (banca, cu analitic) si `5311` + (casa, fara analitic); +- in `docs/config_cont_ireg.md:89` (lista `CU_INREGISTRARI = 1`) **niciunul dintre ele nu apare**; +- invers, Oracle listeaza `404, 408, 409, 4091, 4093, 4094, 418, 419, 471, 472, 456, 457...` + pe care codul validat **nu** le trateaza ca parteneri. + +Daca unealta ar fi decis din Oracle, `5121.01 PIRAEUS BANK` de la MASTER - cazul din +`decizii_import.md` pct. 15, cu sold creditor pe cont de trezorerie - ar fi devenit cont obisnuit, +fara rand `FACTURA`, fara ca nimic sa dea eroare. + +**Regula, de acum:** setul operational este cel validat in productie, tinut intr-un fisier in repo, +`config/conturi_parteneri.csv`, cu o coloana de categorie (`BANCA` / `SINTETIC` / `FARA_ANALITICE`) +si una de motiv. `CONFIG_CONT_IREG` ramane **consultativ**: un script mic il exporta alaturi, iar +unealta **raporteaza divergentele** la fiecare rulare (cont in Oracle si nu la noi, sau invers) - +le raporteaza, nu le aplica. Divergenta constatata azi se scrie in `docs/decizii_import.md`, ca sa +nu fie redescoperita. + +## Arhitectura: un contract intern, trei cititoare + +``` + PDF balanta --\ + xlsx/xls SAGA --+--> [cititor] --> contractul intern --> mapare --> init___.xlsx + CONT_BAZA.FDB --/ +``` + +### Contractul intern - fixat aici, nu lasat la latitudinea cititoarelor + +Trei fisiere, nume si coloane exacte. Potrivirea numelor de coloana la citire este **exacta si +case-insensitive**, niciodata pe substring (`TOTAL_DEB` vs `TOTAL_DEB_1` se confunda usor). + +| fisier | coloane | scris de | +|---|---|---| +| `balanta_.csv` | `pagina, cont, denumire, prec_d, prec_c, rulaj_d, rulaj_c, total_d, total_c, sold_d, sold_c, este_total, tip` | toate cele 3 cititoare | +| `parteneri_.csv` | `cont_analitic, cod, denumire, cod_fiscal` | xlsx (fara `cod_fiscal`), FDB (complet) | +| `facturi_.csv` | `cont_analitic, numar, data, scadent, sold` | xlsx (fara `scadent`), FDB (complet) | + +Coloanele pe care o sursa nu le poate da raman **goale**, niciodata inventate. `tip` (A/P) exista +doar pe drumurile xlsx si FDB; consumatorul ei e stabilirea laturii soldului, in locul deducerii +dupa clasa contului. Ultimele doua fisiere lipsesc cu totul cand sursa e doar balanta - atunci se +cade pe comportamentul de azi (un `FACTURA` per partener, sold total). + +### Cititorul 1 - PDF (exista, nemodificat) +`extract_balanta.py`, pdfplumber pe coordonate. Singura sursa care merge pe orice versiune SAGA, +fiind raportul tiparit. Nu se atinge: e validat pe FUNDATIA si MASTER. + +### Cititorul 2 - foaie de calcul SAGA (`.xls` sau `.xlsx`), nou +`.xlsx` cu openpyxl, `.xls` cu `xlrd` (`openpyxl` nu citeste BIFF). Acelasi cititor pentru SAGA +veche si noua: au aceleasi 35 de coloane, in aceeasi ordine. +Maparea: `DEB_PREC/CRED_PREC -> prec_d/prec_c`, `RULAJ_D/RULAJ_C -> rulaj_d/rulaj_c`, +`TOTAL_DEB/TOTAL_CRED -> total_d/total_c`, `FIN_D/FIN_C -> sold_d/sold_c`, `TIP -> tip`. +`pagina` si `este_total` raman goale (exportul nu are rand de totaluri). +Foaia: prima foaie a registrului - numele difera (`balanta` la VFP, `xl` la `.xls` nou, `Sheet1` la +`.xlsx`), deci nu se cauta dupa nume. +`CONT` se citeste ca **text**: `401.00002` citit ca numar se strica. + +### Cititorul 3 - Firebird, nou +Parametru: calea catre o **copie** de `CONT_BAZA.FDB`. Niciodata baza vie a clientului. +Conexiune embedded (pe TCP 3060 parola implicita nu merge), `access_mode=READ`, `no_gc=True`, +doar SELECT, `rollback` la final. Charset `WIN1250`. + +**Algoritmul balantei, specificat aici pentru ca e inima corectitudinii.** Soldurile stocate in +`CONTURI` sunt 0 (`raport_exporturi_saga_noua.md` C.4); cifrele vin din agregarea `REGISTRU`. +Sunt necesare **trei intervale de data**, nu unul - exportul are patru marimi distincte: + +| marime | interval | +|---|---| +| `DEB_INIT/CRED_INIT` | tot ce e anterior inceputului anului fiscal | +| `prec_d/prec_c` | de la inceputul anului pana la inceputul lunii cerute | +| `rulaj_d/rulaj_c` | in luna ceruta | +| `total_*` | `prec + rulaj` ; `sold_d - sold_c = total_d - total_c` | + +**Rollup la sintetic**: `REGISTRU.CONT_D`/`CONT_C` contin analiticul (`401.00002`), iar exportul +SAGA are sinteticul (`401`) - `raport_exporturi_saga_noua.md` C.4. Agregarea grupeaza pe partea din +stanga punctului si emite si randul analitic, si sinteticul. Randul `%` din `CONTURI` este cont +colector si se exclude. + +Mai produce: `parteneri_.csv` din `FURNIZORI` + `CLIENTI` (**codul fiscal real vine de +aici**, deci pe acest drum etapa 5b - cautarea pe ANAF - dispare) si `facturi_.csv` din +`INTRD`/`INTRARI` si `IESIRI`/`IES_DET`, toate deodata, nu per partener. + +**Garzi pe NULL, nu presupuneri**: `INTRD.SCADENT` a fost NULL pe singurul rand existent, iar +`FURNIZORI.COD_FISCAL` era gol la unul din doi. Deci "scadenta reala" si "cod fiscal real" inseamna +*cand exista*: scadenta lipsa cade pe ultima zi a lunii (ca azi), codul fiscal lipsa cade pe codul +provizoriu `.` si partenerul intra in lista pentru cautarea ANAF. + +## Poarta de verificare a cititoarelor + +**Cititorul 3 (FDB) si cititorul 2 (xlsx) trebuie sa produca acelasi `balanta_.csv`** pentru +firma de joaca `D:\SAGA250909\0001\CONT_BAZA.FDB`, luna 09/2026, al carei export este in `exemple\`. +Daca difera, e bug de cititor, nu date gresite. Acesta inchide lane-ul `citire-fdb`, nu "ruleaza +fara eroare". + +**Limitele acestei porti, scrise si in raport, nu ascunse:** firma de joaca are 10 conturi, **toate +sintetice, niciun analitic**, 2 furnizori, 0 clienti, 1 factura. Deci poarta dovedeste rollup-ul si +periodizarea pe cazul sintetic si **nimic despre analitice** - exact partea grea. Pana la o baza +reala, cititorul FDB este **verificat partial** si se marcheaza ca atare in ajutorul uneltei; nu se +foloseste la un client fara rerularea portii pe datele lui. + +**Inlocuitorul verificarii "Totaluri:"**. Pe drumul PDF, poarta era randul "Totaluri:" din raport. +Exportul xlsx si baza nu au asa ceva. Nu se sterge verificarea, se inlocuieste cu una reala: +`SUM(REGISTRU)` pe fiecare latura, la data ceruta, = netul balantei generate. Pe drumul xlsx, unde +`REGISTRU` nu e disponibil, ramane `SUM(sold_d) = SUM(sold_c)` pe frunze plus relatiile +`total = prec + rulaj` si `sold_d - sold_c = total_d - total_c` pe fiecare cont (au trecut 341/341 +pe DANUBE si 10/10 pe firma de joaca). + +## Pasul de mapare + +Fiecare firma are propriile analitice, deci fiecare firma are propriul fisier. Implicit merge pe +propunerile automate; corectiile sunt posibile oricand, fara sa fie obligatorii. + +**Doua fisiere, cu proprietari diferiti** - asa regenerarea nu-ti poate distruge corectiile: + +| fisier | cine scrie | ce contine | +|---|---|---| +| `mapare_.xlsx` | **doar unealta**, regenerat complet la fiecare rulare | toate conturile din balanta, cu propunerea automata: `cont_saga`, `denumire`, `sold`, `cont`, `acont`, `sursa` (`AUTO` sau `CORECTAT`) | +| `corectii_.xlsx` | **doar tu** | numai randurile pe care le schimbi: `cont_saga`, `cont`, `acont`, `exclus`, `motiv` | + +La rulare: se citeste balanta, se propune automat, se suprapun corectiile, se scrie maparea +completa ca sa vezi rezultatul final. Fisierul de corectii se creeaza gol, cu antet si un exemplu +comentat, doar la prima rulare a firmei - pentru o firma simpla nu-l deschizi niciodata. + +Propunerea automata, cu regula de azi din `decizii_import.md`: `cont` = primele max 4 caractere, +`acont` = cifrele analiticului concatenate, max 4. + +**Conventii preluate din `conturi_roa.dbf`** (`raport_sursa_saga_xlsx.md` 2.3), ca sa nu ai sute de +randuri de corectat - se scriu in `corectii_.xlsx`: +- `.TOATE` - toate analiticele sinteticului merg pe acelasi `cont`/`acont` ROA; +- `.RESTUL` - la fel, dar doar pentru analiticele fara rand propriu. + +Ordinea de cautare: potrivire exacta -> `.RESTUL` -> `.TOATE`. +`PLANCONT` **nu** se preia: era fallback pentru planul de conturi, noi nu generam plan de conturi. + +Maparea acopera si **redenumirea sinteticului** (`409 -> 4091`, `431 -> 4311`), nu doar analiticul - +era jumatate din ce facea `conturi_roa.dbf` si lipsea din v2. + +**Ce NU se configureaza aici**: care conturi merg pe parteneri (vezi sectiunea de mai sus - fisierul +`config/conturi_parteneri.csv`). + +## Cine detine forma lui `acont` (corectie fata de v2) + +v2 spunea ca `genereaza` ia `cont`/`acont` din mapare in loc sa le calculeze. **Prea larg**, si ar +fi dublat o regula deja validata. Doua lucruri nu pot veni din mapare: + +- **randurile de diferenta** nu exista in balanta, sunt calculate (`genereaza_xlsx.py:204-212`, + `:243-249`) - raman in cod; +- **forma lui `acont` pentru conturile cu parteneri** depinde de categorie: `401` cere `acont` gol + pe ambele randuri, `5121` cere `acont` completat identic pe BALANTA si FACTURA + (`decizii_import.md` pct. 16). O regula generica de pre-completare ar pune `acont` pe `401` -> + chei diferite -> **dublare tacuta a sumei**, exact capcana din pct. 16. + +**Regula:** `genereaza_xlsx.py` ramane singurul care decide forma pentru conturile cu parteneri; +maparea doar suprascrie ce ii dai explicit. Restul fisierului - randuri BALANTA / FACTURA, sume +negative pe latura opusa, verificarile din etapa 5 - ramane neatins. + +Opreste-te cu eroare, nu cu ghicit, daca: `cont` sau `acont` depaseste 4 caractere; doua conturi +SAGA diferite produc acelasi `(cont, acont)` **fara** sa fie acoperite de un `.TOATE`/`.RESTUL` +comun (colapsarea intentionata e legala, coliziunea accidentala nu); un rand din `corectii` nu +corespunde niciunui cont din balanta (typo tacut). + +## Testare + +`unittest` din stdlib, fara dependinte noi. Proiectul nu are azi niciun test; fiecare lane lasa in +urma cel putin unul, altfel lane-ul nu e gata: + +| lane | testul pe care il lasa | +|---|---| +| `citire-xlsx` | citeste `exemple\saga-sqlite-balanta-09-2026.xlsx` si `saga2roa_danube\balanta.xls`; relatiile de sume trec pe toate randurile; `CONT` ramane text | +| `mapare` | o corectie supravietuieste regenerarii; `.TOATE`/`.RESTUL` se aplica in ordinea ceruta; un `cont_saga` inexistent in corectii da eroare | +| `citire-fdb` | CSV-ul din FDB identic cu cel din xlsx pe firma de joaca; `SCADENT` NULL cade pe ultima zi a lunii | +| `integrare` | rulare capat-la-capat pe firma de joaca, din ambele surse | +| `regresie` | FUNDATIA + MASTER reproduc etalonul | + +## Poarta de regresie si etalonul + +`tests/golden/` primeste copii **anonimizate** ale celor doua xlsx importate in productie: sumele, +conturile, structura randurilor si numarul lor raman **neatinse**; denumirile de parteneri devin +`PARTENER 001...` si codurile fiscale `CF000001...`, stabil (acelasi nume real -> acelasi nume fals, +ca relatiile dintre randuri sa se pastreze). Asa poarta compara orice celula si in git nu intra +numele niciunui client. Scriptul de anonimizare se comite; **tabela de corespondenta nu**. + +Comparatia se face pe **valori de celula**, nu pe hash de fisier: un xlsx rescris de alta versiune +de openpyxl are alti octeti cu acelasi continut. + +Regresia ruleaza **dupa fiecare lane care atinge generatorul**, nu doar la final - e cel mai ieftin +test care protejeaza zona deja validata in productie. + +## Git + +`.gitignore` ignora `init_*.xlsx`, `export_*.xlsx`, `mapare_*.xlsx`, `balanta_*.csv`. Se adauga: +`*.fdb`, `*.FDB` (copii de baze de client, zeci de MB), `parteneri_*.csv`, `facturi_*.csv`, +`corectii_*.xlsx`. Exceptii explicite: sablonul si `tests/golden/`. +PDF-urile sursa raman in git: sunt datele pe care ruleaza regresia. +`exemple\` ramane in git - firma de joaca, nu date de client. + +Dependintele noi (`xlrd`, `firebird-driver`) se trec in `CLAUDE.md`, la `Mediu`. + +## Ordinea de lucru (lane-uri opencode) + +| lane | livrabil | criteriu de terminare | depinde de | +|---|---|---|---| +| `contract` | `config/conturi_parteneri.csv` + scriptul de divergenta Oracle; anonimizatorul + `tests/golden/` | regresia ruleaza si trece pe etalonul anonimizat, cu codul de azi neschimbat | - | +| `citire-xlsx` | cititorul de foaie de calcul SAGA | vezi tabelul de testare | contract | +| `mapare` | propunerea automata + `corectii_.xlsx` + `.TOATE`/`.RESTUL` + redenumirea sinteticului | vezi tabelul de testare | contract | +| `citire-fdb` | balanta + parteneri + facturi din Firebird | CSV identic cu drumul xlsx pe firma de joaca | citire-xlsx | +| `integrare` | `extrage.py` / `genereaza.py` cu parametri | rulare capat-la-capat din ambele surse | toate | +| `regresie` | rerularea pe FUNDATIA + MASTER | identice cu etalonul, pe valori de celula | integrare | + +Lane-ul `contract` merge primul si singur: fixeaza etalonul **inainte** ca vreo linie de cod sa se +schimbe, altfel regresia masoara fata de un rezultat deja mutat. + +Poarta finala: `regresie`. Daca noul flux nu reproduce randurile celor doua xlsx-uri validate in +productie, nu e gata - indiferent cat de curat e codul. + +## Ce ramane pentru mai tarziu + +- **o baza `.FDB` reala + exportul ei de balanta**, ca sa se reruleze poarta pe date cu analitice si + cu volum. Pana atunci cititorul FDB e verificat partial (vezi mai sus); +- procedura pentru client: cum isi face copia bazei (`gbak` da o copie consistenta si mai mica decat + fisierul brut) si ca trebuie sa opreasca SAGA daca trimite fisierul direct. Se scrie odata cu + lane-ul `citire-fdb`, altfel livram un cititor care nu se poate folosi in teren; +- conectarea la un Firebird prin retea - azi nu merge cu parola implicita + (`raport_exporturi_saga_noua.md` C.1), deci cere credentiale de la client. diff --git a/docs/plan_solduri2roa.md b/docs/plan_solduri2roa.md index 6458a81..0592f7f 100644 --- a/docs/plan_solduri2roa.md +++ b/docs/plan_solduri2roa.md @@ -47,6 +47,18 @@ Iesire: `init_FUNDATIA_2025_12.xlsx`, `init_MASTER_2025_12.xlsx`, generate din s - `cont` <= 4 caractere, `acont` <= 4 caractere, fara dubluri `cont+acont` Raport: `docs\verificare_.md`, cu totalurile din balanta alaturi de totalurile din xlsx. +## Etapa 5b - codurile fiscale reale ale partenerilor +Pasul e obligatoriu, nu optional: partenerii intra in xlsx cu cod fiscal provizoriu +(`.` din SAGA) si trebuie corectati cu CUI-ul real. +1. Lista partenerilor fara CUI: `parteneri_de_cautat.md`. +2. Cautare pe demoanaf.ro (denumire -> CUI), rezultat in `parteneri_cui_gasite.md`. +3. Verificare TVA pe ANAF pentru fiecare CUI gasit (POST + `https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva`, campul `scpTVA`): + platitor de TVA -> cod fiscal cu prefix `RO`; neplatitor -> CUI fara prefix. +4. Scripturile de corectie, per schema: `sql/update_cod_fiscal_.sql` (neexecutate, + se ruleaza manual dupa import). +5. Partenerii negasiti raman cu codul provizoriu si se raporteaza explicit. + ## Etapa 6 - git `git init`, commit, push pe `gitea.romfast.ro:romfast/solduri2roa.git` (push-to-create). Se comit: scripturile, `docs\`, CSV-urile si xlsx-urile generate. PDF-urile: da (sunt sursa, 30 KB). diff --git a/docs/raport_exporturi_saga_noua.md b/docs/raport_exporturi_saga_noua.md new file mode 100644 index 0000000..bec1269 --- /dev/null +++ b/docs/raport_exporturi_saga_noua.md @@ -0,0 +1,326 @@ +# Raport exporturi SAGA noua (Firebird) - cercetare read-only + +Lane: `exporturi-saga-noua`. Sarcina de cercetare, fara modificari de cod. +Fisiere analizate in `D:\ROA\IMPORT2ROA\solduri2roa\exemple\`: +`saga-sqlite-balanta-09-2026.xlsx`, `saga-sqlite-balanta-09-2026.xls`, +`saga-sqlite-furnizori-09-2026.xlsx`, `saga-sqlite-furnizori-facturi-un-furnizor-09-2026.xlsx`. +Baza explorata: `D:\SAGA250909\0001\CONT_BAZA.FDB` (Firebird, NU SQLite). +Temporarele de lucru: `C:\Users\mmari\AppData\Local\Temp\opencode`. Nu s-a modificat nimic in +`D:\SAGA250909` (doar SELECT, tranzactie read-only). Fisierele nu sunt SQLite; numele contin +"sqlite" dar continutul sunt foi de calcul obisnuite (vezi mai jos). + +--- + +## PARTEA A - structura celor 4 exporturi + +### A.1 `saga-sqlite-balanta-09-2026.xlsx` + +- O singura foaie: `Sheet1`. Dimensiune `A1:AI11` = **11 randuri x 35 coloane** + (1 antet + 10 randuri de date). Fara foi ascunse, fara celule fuzionate. +- Antetul este pe **randul 1**. Datele incep la randul 2 si se termina la randul 11. +- **NU exista rand de totaluri** (ultimul rand este o data: contul `6461`). + +Coloanele, in ordine (litera = nume): + +| Lit | Nume | Lit | Nume | Lit | Nume | +|---|---|---|---|---|---| +| A | CONT | M | RULAJT_D | Y | RULAJ_D_1 | +| B | DENUMIRE | N | RULAJT_C | Z | RULAJ_C_1 | +| C | CATEGORIE | O | TOTAL_DEB | AA | RULAJT_D_1 | +| D | TIP | P | TOTAL_CRED | AB | RULAJT_C_1 | +| E | DEB_INIT | Q | FIN_D | AC | TOTAL_DEB_1 | +| F | CRED_INIT | R | FIN_C | AD | TOTAL_CRED_1 | +| G | DEB_PREC | S | DEB_INIT_1 | AE | FIN_D_1 | +| H | CRED_PREC | T | CRED_INIT_1 | AF | FIN_C_1 | +| I | SOLD_IN_D | U | DEB_PREC_1 | AG | ANALITIC | +| J | SOLD_IN_C | V | CRED_PREC_1 | AH | VALIDAT | +| K | RULAJ_D | W | SOLD_IN_D_1 | AI | LINIE | +| L | RULAJ_C | X | SOLD_IN_C_1 | | | + +Primele 8 randuri de date (rand Excel 2..9; sumele relevante, D/C separate): + +| Rand | A cont | B denumire | D tip | E/F init | G/H prec | K/L rulaj | O/P total | Q/R fin | AG | AH | AI | +|---|---|---|---|---|---|---|---|---|---|---|---| +| 2 | 371 | MARFURI | A | 0 / 0 | 2615 / 0 | 0 / 0 | 2615 / 0 | 2615 / 0 | 0 | 1 | 1 | +| 3 | 401 | FURNIZORI | P | 0 / 0 | 0 / 3164.15 | 0 / 0 | 0 / 3164.15 | 0 / 3164.15 | 0 | 1 | 2 | +| 4 | 421 | PERSONAL - SALARII DATORATE | P | 0 / 2574 | 0 / 2574 | 0 / 0 | 0 / 2574 | 0 / 2574 | 0 | 0 | 3 | +| 5 | 4315 | CONTR. DE ASIGURARI SOCIALE | P | 0 / 938 | 0 / 938 | 0 / 0 | 0 / 938 | 0 / 938 | 0 | 0 | 4 | +| 6 | 4316 | CONTR. DE ASIGURARI SOCIALE DE SANATATE | P | 0 / 375 | 0 / 375 | 0 / 0 | 0 / 375 | 0 / 375 | 0 | 0 | 5 | +| 7 | 436 | CONTR. ASIGURATORIE DE MUNCA | P | 0 / 84 | 0 / 84 | 0 / 0 | 0 / 84 | 0 / 84 | 0 | 0 | 6 | +| 8 | 4426 | TVA DEDUCTIBILA | A | 0 / 0 | 549.15 / 0 | 0 / 0 | 549.15 / 0 | 549.15 / 0 | 0 | 1 | 7 | +| 9 | 444 | IMPOZITUL PE VENITURI DE NATURA SALARIILOR | P | 0 / 163 | 0 / 163 | 0 / 0 | 0 / 163 | 0 / 163 | 0 | 0 | 8 | + +Ultimele 3 randuri (rand Excel 9..11): + +| Rand | A cont | B denumire | D tip | E/F init | G/H prec | O/P total | Q/R fin | AG | AH | AI | +|---|---|---|---|---|---|---|---|---|---|---| +| 9 | 444 | IMPOZIT... SALARIILOR | P | 0 / 163 | 0 / 163 | 0 / 163 | 0 / 163 | 0 | 0 | 8 | +| 10 | 641 | CHELT. CU SALARIILE PERSONALULUI | A | 4050 / 0 | 4050 / 0 | 4050 / 0 | 4050 / 0 | 0 | 0 | 9 | +| 11 | 6461 | CHELT. CU CONTRIB. ASIGURATORIE PT. MUNCA A SALARIATILOR | A | 84 / 0 | 84 / 0 | 84 / 0 | 84 / 0 | 0 | 0 | 10 | + +**Tipuri reale de valori** (openpyxl): +- `CONT` (A) = **text** (str), ex. `Sheet1!A2` = `'371'`. Nu e numar. `DENUMIRE` (B) = text. +- `TIP` (D) = text (`'A'` sau `'P'`); `CATEGORIE` (C) = gol pe toate randurile. +- Sumele (E..R si `_1`) = **numerice** (float la citire, dar stocate ca numar). +- `ANALITIC` (AG) = boolean (`False` pe toate randurile); `VALIDAT` (AH), `LINIE` (AI) = intregi. + +**Cont sintetic vs analitic**: in acest fisier **nu exista niciun cont analitic** - toate cele 10 +coduri sunt fara punct (371, 401, 421, 4315, 4316, 436, 4426, 444, 641, 6461), iar `ANALITIC` +(AG) este 0/False pe toate randurile. Deci din acest fisier singur nu se poate arata cum arata un +rand analitic. Formatul analitic se vede in schimb in exportul de furnizori: +`saga-sqlite-furnizori-09-2026.xlsx!H2` = `401.00002` (text). Rezulta: sintetic = cod fara punct +(`401`), analitic = `sintetic.cod` (`401.00002`). Daca si coloana `ANALITIC` distinge ceva ramane +neclar (aici e 0 pe toate randurile, inclusiv pe cele care in vechiul export VFP aveau 1 - vezi B). + +**Coloane de sume** (maparea ceruta): + +| Rol | Coloane | +|---|---| +| precedent D / C | `DEB_PREC` (G) / `CRED_PREC` (H) | +| rulaj D / C | `RULAJ_D` (K) / `RULAJ_C` (L) | +| total D / C | `TOTAL_DEB` (O) / `TOTAL_CRED` (P) | +| sold D / C | `FIN_D` (Q) / `FIN_C` (R) | + +`TOTAL_*` sunt **sume cumulative** (precedent + rulaj), NU solduri; soldul final este `FIN_*`. +Exista in plus: `DEB_INIT/CRED_INIT` (E/F) = sold la inceputul anului, `SOLD_IN_D/SOLD_IN_C` +(I/J) = sold la inceput de perioada, `RULAJT_D/RULAJT_C` (M/N) = rulaj total cumulat. + +Relatiile cerute, verificate pe toate cele 10 randuri (toleranta 0.005): + +| Verificare | Rezultat | +|---|---| +| `TOTAL_DEB = DEB_PREC + RULAJ_D` | **10/10 OK** | +| `TOTAL_CRED = CRED_PREC + RULAJ_C` | **10/10 OK** | +| `FIN_D - FIN_C = TOTAL_DEB - TOTAL_CRED` | **10/10 OK** | +| (suplimentar) `SOLD_IN_D - SOLD_IN_C = DEB_PREC - CRED_PREC` | 10/10 OK | +| (suplimentar) acelasi set `_1` (S..AF) identic cu setul principal | 10/10 OK | + +Nu exista rand care sa pice. Setul `_1` (S..AF) este **identic** cu setul principal pe toate +randurile - consecvent cu observatia din `raport_sursa_saga_xlsx.md` (pe conturile sintetice `_1` +este copia setului principal). + +### A.2 `saga-sqlite-balanta-09-2026.xls` vs `.xlsx` + +Fisierele au **acelasi continut** (aceleasi 35 coloane in aceeasi ordine si aceleasi 10 randuri de +date, cu aceleasi valori). Semnaturi: `.xls` = OLE2/BIFF8 (`d0cf11e0a1b11ae1...`), `.xlsx` = +OOXML/ZIP (`PK..`). Diferente constatate: + +| Aspect | `.xls` | `.xlsx` | +|---|---|---| +| Nume foaie | `xl` | `Sheet1` | +| Antet | litere mici (`cont`, `denumire`, ...) | litere mari (`CONT`, `DENUMIRE`, ...) | +| `CATEGORIE` goala | sir vid `''` | celula `None` | +| Restul valorilor | identice | identice | + +Comparatia programatica (valori numerice + text case-insensitive) a dat **0 diferente** in afara +celor de mai sus. Deci: acelasi export, doar alt format si alta conventie de nume/case. + +### A.3 `saga-sqlite-furnizori-09-2026.xlsx` + +- O foaie `Sheet1`, `A1:J2` = **2 randuri x 10 coloane** (1 antet + **1 rand de date**). +- Antet pe randul 1; datele pe randul 2. **Nu exista rand de totaluri.** +- Coloane: A `COD`, B `DENUMIRE`, C `NE_INIT`, D `TOTAL`, E `INCASARI`, F `NE_FIN`, + G `NEACHITAT`, H `ANALITIC`, I `SOLD`, J `GRUPA`. + +Randul de date (rand 2): `A2='00002'`, `B2='TRANSPORT'`, `C2=3164.15`, `D2=0`, `E2=0`, +`F2=3164.15`, `G2=3164.15`, `H2='401.00002'`, `I2=3164.15`, `J2` gol. + +Tipuri: `COD` (A) si `ANALITIC` (H) = **text**; `DENUMIRE` (B) = text; sumele C..G, I numerice; +`GRUPA` (J) gol. + +**Ce identifica partenerul**: in acest export partenerul este identificat prin: +- `COD` = codul SAGA al tertului (`00002`), text; +- `ANALITIC` = contul analitic `401.00002` (text) - **da, apare codul contului analitic**; +- `DENUMIRE` = `TRANSPORT`. +Exportul **NU contine codul fiscal** (desi tabelul `FURNIZORI` din baza il are - vezi Partea C). +Deci legatura cu partenerul se face pe `COD` si/sau pe `ANALITIC`, nu pe cod fiscal. + +### A.4 `saga-sqlite-furnizori-facturi-un-furnizor-09-2026.xlsx` + +- O foaie `Sheet1`, `A1:J2` = **2 randuri x 10 coloane** (1 antet + **1 rand de date**). +- Antet pe randul 1; datele pe randul 2. **Nu exista rand de totaluri.** +- Coloane: A `DATA`, B `NR`, C `CONT_FUR`, D `NE_INIT`, E `TOTAL`, F `PLATI`, G `NE_FIN`, + H `NEACHITAT`, I `INF_SUPLM`, J `DATA_DOC`. + +Randul de date (rand 2): `A2=2026-07-02` (datetime), `B2='123'` (text), `C2='401.00002'` (text), +`D2=3164.15`, `E2=0`, `F2=0`, `G2=3164.15`, `H2=3164.15`, `I2` gol, `J2` gol. + +**Campuri de factura prezente**: numar (`NR`), data operare (`DATA`), cont furnizor (`CONT_FUR`), +sold initial neachitat (`NE_INIT`), total factura (`TOTAL`), plati (`PLATI`), neachitat final +(`NE_FIN`), neachitat (`NEACHITAT`), informatii suplimentare (`INF_SUPLM`), data document +(`DATA_DOC`). +**Lipsesc**: **data scadenta** si **valuta** (desi in baza, tabelul de intrari, are `SCADENT` si +`COD_VALUTA` - exportul nu le include). Nu exista nici o coloana de serie. +**Legatura de furnizor**: prin `CONT_FUR = 401.00002` (acelasi analitic ca in A.3) - nu exista +`COD` de tert in acest export. + +--- + +## PARTEA B - comparatie cu ce stim deja + +### B.1 Cu CSV-ul din PDF (`extract_balanta.py`) + +CSV-ul din PDF are coloanele: +`pagina, cont, denumire, prec_d, prec_c, rulaj_d, rulaj_c, total_d, total_c, sold_d, sold_c, este_total`. + +Corespondenta cu exportul nou: + +| CSV (PDF) | Export nou (balanta) | +|---|---| +| `cont` | `CONT` (A) | +| `denumire` | `DENUMIRE` (B) | +| `prec_d` / `prec_c` | `DEB_PREC` (G) / `CRED_PREC` (H) | +| `rulaj_d` / `rulaj_c` | `RULAJ_D` (K) / `RULAJ_C` (L) | +| `total_d` / `total_c` | `TOTAL_DEB` (O) / `TOTAL_CRED` (P) | +| `sold_d` / `sold_c` | `FIN_D` (Q) / `FIN_C` (R) | +| `pagina` | **lipseste** | +| `este_total` | **lipseste** | + +**Se poate produce acelasi CSV din exportul SAGA nou?** Pentru partea de conturi + cele 8 sume: +**da**, maparea e 1:1 si relatiile sunt aceleasi (verificate 10/10 la A.1). Ce **lipseste**: +- `pagina` - proprie PDF-ului, depinde de paginare, nu are echivalent in export; +- `este_total` - exportul **nu are deloc randuri de totaluri**; in CSV-ul din PDF acest marker + distinge randurile "Total sume clasa N" si randul "Totaluri:", care in export nu exista + (ar trebui calculate, nu citite); +- setul de conturi poate diferi: in exemplul nou apar doar conturile cu miscare, la nivel sintetic. + +### B.2 Cu vechiul export SAGA-pe-VFP (`raport_sursa_saga_xlsx.md`, sectiunea 1) + +Structura este **identica** cu cea documentata acolo pentru `balanta.xls` (VFP): aceleasi **35 de +coloane, in aceeasi ordine, cu aceleasi nume** (`cont..fin_c`, `_1`, `analitic`, `validat`, +`linie`). Singurele diferente observate: +- numele foii: `xl`/`Sheet1` (nou) vs `balanta` (VFP); +- case-ul antetului (mic in `.xls`, mare in `.xlsx`) - in raportul VFP antetul era cu litere mici; +- valoarea coloanei `analitic`: in fisierul VFP vechi era **1 pe toate randurile**, in exportul nou + este **0/False pe toate randurile** (dar aici toate randurile sunt sintetice - nu se poate + decide daca diferenta e de semantica sau doar de firma/perioada). + +Concluzie: un convertor care stie deja sa citeasca exportul VFP (`conturi.dbf` / `balanta.xls`) +poate citi **neschimbat** si exportul SAGA nou Firebird - layout-ul de coloane este acelasi. + +Observatie importanta (dovada in Partea C): desi layout-ul e identic cu tabelul `CONTURI`, +valorile din export **nu sunt un dump brut** al tabelului - soldurile stocate in `CONTURI` sunt 0, +iar cifrele din export corespund agregarii registrului de note (`REGISTRU`). Deci exportul e un +raport calculat, nu o citire directa de tabel. + +--- + +## PARTEA C - baza Firebird `CONT_BAZA.FDB` (explorare, NUMAI CITIRE) + +### C.1 Ce client/server exista pe masina + +- Server Firebird **instalat si pornit**: serviciul `FirebirdServerFirebird30_Saga` + (`"C:\Program Files\Firebird\Firebird30_Saga\firebird.exe" -s Firebird30_Saga`, LocalSystem). +- `C:\Program Files\Firebird\Firebird30_Saga\` contine: `isql.exe`, `fbclient.dll`, + `firebird.exe`, `gsec.exe`, `gbak.exe`, `gfix.exe`, `fbsvcmgr.exe`, plus `plugins\engine12.dll` + (Firebird 3.0). In plus SAGA are clientul in `D:\SAGA250909\FbClient\` (`fbclient.dll`, + `plugins\engine12.dll`, `legacy_auth.dll`, etc.). +- Versiunea raportata de baza: **3.0.7** (via `rdb$get_context('SYSTEM','ENGINE_VERSION')`). +- Port de ascultare: doar **3060** (`RemoteServicePort = 3060` in `firebird.conf`; pe 3050 nu + asculta nimic). O conexiune TCP la `localhost/3060` ajunge la baza dar raspunde cu eroare de + autentificare (`Your user name and password are not defined`) - deci parola SYSDBA nu e + `masterkey`/`masterke` la server. + +### C.2 Cum s-a reusit conectarea (read-only) + +- S-a instalat `firebird-driver` (`py -m pip install firebird-driver` -> 2.0.3) si s-a indicat + explicit clientul: `fdb.load_api(r"C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll")`. +- Conexiunea **embedded** la fisier a functionat: + `fdb.connect(database=r"D:\SAGA250909\0001\CONT_BAZA.FDB", user="SYSDBA", password="masterkey", + charset="WIN1250", no_gc=True)` -> **OK, versiune 3.0.7**. Embedded accepta `masterkey`/`masterke` + (autentificare locala/trusted); TCP la 3060 nu. +- Respectarea "NUMAI CITIRE": s-a folosit o **tranzactie read-only** + (`fdb.tpb(Isolation.READ_COMMITTED, access_mode=TraAccessMode.READ)`) si `no_gc=True`; s-au rulat + **doar SELECT**-uri, apoi `rollback`. Nu s-a scris, nu s-a facut ALTER, nu s-a copiat baza. + +### C.3 Ce contine baza + +**191 de tabele**, **0 view-uri**. Cele relevante pentru conversie: + +| Tabel | Randuri | Ce este | +|---|---|---| +| `CONTURI` | 586 | planul de conturi + solduri stocate | +| `FURNIZORI` | 2 | furnizori | +| `CLIENTI` | 0 | clienti | +| `INTRARI` / `INTRD` | 0 / 1 | intrari = facturi furnizori (antet / detaliat) | +| `IESIRI` / `IES_DET` | 0 / - | iesiri = facturi clienti | +| `REGISTRU` | 8 | registru de note contabile (miscarile pe conturi) | +| `FFACT` | 5 | **NU** e tabel de facturi - e tabela de formulare/layout de tiparire | + +Structuri (campurile cheie; tipurile sunt cele din `rdb$relation_fields`/`rdb$fields`): + +**`CONTURI`** (586 randuri) - plan de conturi: +`CONT` VARCHAR(20) NOT NULL, `DENUMIRE` VARCHAR(64), `TIP` CHAR(1), +`DEB_INIT`/`CRED_INIT`/`DEB_PREC`/`CRED_PREC` NUMERIC(15,2), +`CONT_INCH` VARCHAR(20), `DEB_INIT_V`/`CRED_INIT_V`/`DEB_PREC_V`/`CRED_PREC_V` NUMERIC(14,2), +`COD_VALUTA` VARCHAR(3), `BLOCAT` SMALLINT. +Exemplu de rand: `('1012', 'CAPITAL SUBSCRIS VARSAT', 'P', 0.00, 0.00, 0.00, 0.00, ...)`. +Atentie: `CONT` e **text** (se citeste ca sir, deci `401.00002` nu se strica); +in acest exemplu **toate soldurile stocate sunt 0** - vezi C.4. + +**`FURNIZORI`** (2 randuri): +`COD` VARCHAR(8) NOT NULL, `DENUMIRE` VARCHAR(64), `COD_FISCAL` VARCHAR(20), `REG_COM` VARCHAR(16), +`ANALITIC` VARCHAR(20), `GRUPA` VARCHAR(16), plus adresa/IBAN/agent/etc. +Randuri: `('00001','ROMFAST','','401.00001')`, `('00002','TRANSPORT','1879855','401.00002')`. + +**`CLIENTI`** (0 randuri): aceeasi structura de tert ca `FURNIZORI` +(`COD`, `DENUMIRE`, `COD_FISCAL`, `ANALITIC`, `GRUPA`, adresa, agent, limita credit, e-Factura...). + +**`INTRARI`** (0) / **`INTRD`** (1 rand) - facturi furnizori: +`ID_INTRARE`, `NR_NIR`, `NR_INTRARE`, `COD`, `DENUMIRE`, `CONT_FUR`, `DATA`, `SCADENT`, +`NEACHITAT` NUMERIC(15,2), `NEACHITAT_VAL`, `COD_VALUTA`, `CURS`, `VAL_VAL`, `VAL_LEI`, +`BAZA_TVA`, `TOTAL`, `TVA`, `TRANSP_VAL/LEI`, `VALIDAT`, `DATA_DOC`, `INF_SUPLM`, ... +Randul existent: `ID_INTRARE=24, NR_INTRARE='123', COD='00001', CONT_FUR='401.00001', +DATA=2026-07-02, SCADENT=NULL, TOTAL=0, NEACHITAT=0, COD_VALUTA='EUR'`. + +**`IESIRI`** (0) - facturi clienti: +`NR_IESIRE`, `ID_IESIRE`, `COD`, `DENUMIRE`, `CONT_CLI`, `DATA`, `SCADENT`, `TOTAL`, +`NEACHITAT`, `BAZA_TVA`, `TVA`, `COMANDA`, `DATA_DOC`, `INF_SUPLM`, `IS_EF`, ... + +**`REGISTRU`** (8 randuri) - notele contabile (sursa miscarilor): +`ID_NOTA`, `CONT_D` VARCHAR(20), `CONT_C` VARCHAR(20), `SUMA` NUMERIC(15,2), `DATA`, +`EXPLICATIE`, `VALIDAT`, `COD`, `TIP_O`, `PK`. Aici se vede cheia: contul analitic apare in +`CONT_D`/`CONT_C` ca text (`401.00002`), iar `%` este un cont colector. + +Deci: planul de conturi cu solduri = `CONTURI`; furnizorii = `FURNIZORI`; clientii = `CLIENTI`; +facturile furnizorilor = `INTRARI`/`INTRD`; facturile clientilor = `IESIRI`/`IES_DET`. + +### C.4 Dovada ca exporturile sunt rapoarte calculate, nu dump de tabel + +- `CONTURI` are 585 randuri utile (fara randul `%`) si **0 randuri cu sold stocat nenul** + (`deb_prec/cred_prec/deb_init/cred_init` = 0 pentru toti), si totusi exportul de balanta are + cifre. Deci cifrele vin din agregarea `REGISTRU` la data raportului. +- Potriviri exacte: `REGISTRU` are pe `371` debit `2615.00` -> exportul are `371` `DEB_PREC=2615`; + pe `401.00002` credit `2615.00 + 549.15 = 3164.15` -> exportul are `401` `CRED_PREC=3164.15` + si exportul de furnizori `SOLD=3164.15`. In plus, miscarea veche pe `641` este din 2025-11-30 -> + exportul are `DEB_INIT=4050` (sold la inceput de an), ceea ce confirma semantica + `DEB_INIT/CRED_INIT` = inceput de an si `DEB_PREC/CRED_PREC` = inceput de perioada. +- In exportul de balanta contul apare ca `401` (sintetic), iar `401.00002` (analitic) nu apare ca + rand separat -> exportul ruleaza analiticele in sinteticul lor (sau a fost cerut la nivel + sintetic). Regula exacta de includere sintetic/analitic **nu se poate stabili** din acest singur + exemplu. + +### C.5 Ce NU se poate stabili + +- Nu se poate confirma exact din ce tabel/se selecteaza randul de factura din exportul + `saga-sqlite-furnizori-facturi-un-furnizor`: exportul are `CONT_FUR='401.00002'` si `NR='123'`, + dar singurul rand din `INTRD` are `CONT_FUR='401.00001'` (ROMFAST), tot cu `NR_INTRARE='123'`. + Coloanele `DATA/NR/CONT_FUR/TOTAL/NEACHITAT/INF_SUPLM/DATA_DOC` exista in `INTRD`, dar + `NE_INIT`, `PLATI`, `NE_FIN` **nu exista ca si coloane** in baza - sunt calculate (sold initial, + plati, sold final). Potrivirea exacta a randului nu se poate face cu datele disponibile. +- Semnificatia coloanei `ANALITIC` din balanta (0/False la toate randurile) nu se poate stabili. + +--- + +## Stare si verificari + +- Nu s-a modificat niciun fisier din proiect sau din `D:\SAGA250909`; nu s-a facut write-back; + nu s-a comis nimic (git/svn). Baza Firebird a fost accesata doar cu SELECT, in tranzactie + read-only, `no_gc=True`, fara copiere. +- Verificari rulate: relatiile de sume pe balanta (10/10 OK pentru toate cele trei relatii cerute); + comparatia `.xls` vs `.xlsx` (0 diferente reale); listarea tabelelor (191 tabele, 0 view-uri) si + a structurilor cheie; potrivirea soldurilor export vs `REGISTRU`. +- Temporare: `C:\Users\mmari\AppData\Local\Temp\opencode\` (`analiza.py`, `verif.py`, `fbtest*.py`, + `fblist.py`, `fbcols.py`, `fbdata.py`, `fbsearch.py` + dump-uri txt). Nu sunt in proiect. +- Fara stare periculoasa: niciun `.vc2` editat fara write-back, niciun proces lasat pornit, fara + date de test consumate. diff --git a/docs/raport_sursa_saga_xlsx.md b/docs/raport_sursa_saga_xlsx.md new file mode 100644 index 0000000..6ec3e33 --- /dev/null +++ b/docs/raport_sursa_saga_xlsx.md @@ -0,0 +1,351 @@ +# Raport sursa-saga: balanta.xls SAGA, tabela conturi_roa si exporturile folosite + +Cercetare read-only. Nu s-a modificat niciun fisier din `saga2roa`, `saga2roa_danube`, +`saga2roa_rompetrol`. Fisierele temporare sunt in +`C:\Users\mmari\AppData\Local\Temp\opencode\sursa_saga`. + +## 0. Cum am citit .xls-ul (ce a mers) + +Ordinea ceruta, cu rezultatul exact: + +1. `pandas` + `xlrd`: **pandas NU e instalat** (`ModuleNotFoundError: No module named 'pandas'`), + `xlrd` la fel. Am instalat doar `xlrd` (`py -m pip install xlrd` -> 2.0.2). Cu `xlrd.open_workbook` + fisierul se citeste corect. +2. `openpyxl` pe `.xls` **esueaza** (confirmat): + `openpyxl.utils.exceptions.InvalidFileException: openpyxl does not support the old .xls file format`. +3. LibreOffice exista (`C:\Program Files\LibreOffice\program\soffice.exe`) si conversia a mers: + `soffice --headless --convert-to xlsx --outdir balanta.xls` -> exit 0. Fidelitate verificata: + xlsx-ul convertit are tot 342 randuri x 35 coloane, sheet `balanta`, acelasi antet. +4. Excel COM din PowerShell: **nu a fost nevoie** (xlrd si LibreOffice au acoperit). + +Concluzie: se poate citi fie direct cu `xlrd`, fie prin conversie LibreOffice. + +## 1. Structura balantei SAGA exportate in Excel (`saga2roa_danube\balanta.xls`) + +- O singura foaie: `balanta`. 342 randuri x 35 coloane. +- **Randul 1 (index 0) = antetul**, direct pe prima linie; nu exista randuri de titlu deasupra. + Datele incep la randul 2 (index 1). Ultimul rand de date = randul 342 (index 341). +- **NU exista rand de totaluri.** Toate cele 341 de randuri de date au `cont` si `denumire` + completate; ultimul rand este contul `7651`. Randul "Totaluri:" exista doar in raportul PDF + "Balanta de verificare", nu in acest xls. + +### 1.1 Coloanele, in ordine (litera + nume) + +| Lit. | Nume | Lit. | Nume | Lit. | Nume | +|---|---|---|---|---|---| +| A | cont | M | rulajt_d | Y | rulaj_d_1 | +| B | denumire | N | rulajt_c | Z | rulaj_c_1 | +| C | categorie | O | total_deb | AA | rulajt_d_1 | +| D | tip | P | total_cred | AB | rulajt_c_1 | +| E | deb_init | Q | fin_d | AC | total_deb_1 | +| F | cred_init | R | fin_c | AD | total_cred_1 | +| G | deb_prec | S | deb_init_1 | AE | fin_d_1 | +| H | cred_prec | T | cred_init_1 | AF | fin_c_1 | +| I | sold_in_d | U | deb_prec_1 | AG | analitic | +| J | sold_in_c | V | cred_prec_1 | AH | validat | +| K | rulaj_d | W | sold_in_d_1 | AI | linie | +| L | rulaj_c | X | sold_in_c_1 | | | + +Aceasta lista coincide **exact** cu structura `conturi.dbf` documentata in cod la +`saga2roa_danube\saga2roa_danube.prg:25` (comentariul cu capul tabelei SAGA). Deci +`balanta.xls` este **exportul in Excel al tabelei SAGA `conturi` (plan de conturi cu solduri)**, +NU raportul "Balanta de verificare". Confirmare suplimentara: `saga2roa_danube.prg:30` citeste +direct `conturi.dbf` din directorul SAGA (`K:\MARIUS\SAGA C.3.0\0001\`). + +### 1.2 Primele 10 randuri de date (Excel rand 2..11) + +| cont | denumire | tip | deb_init | cred_init | deb_prec | cred_prec | rulaj_d | rulaj_c | total_deb | total_cred | fin_d | fin_c | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1012 | CAPITAL SUBSCRIS VARSAT | P | 0 | 2000.00 | 0 | 2000.00 | 0 | 0 | 0 | 2000.00 | 0 | 2000.00 | +| 105 | REZERVE DIN REEVALUARE | P | 0 | 3009833.83 | 0 | 3009833.83 | 0 | 0 | 0 | 3009833.83 | 0 | 3009833.83 | +| 105.10 | REZERVE DIN REEVALUARE CELULA INSILOZARE | P | 0 | 167629.29 | 0 | 167629.29 | 0 | 0 | 0 | 167629.29 | 0 | 167629.29 | +| 105.102 | REZERVA DIN REEVAL.TRANSPORTOR TORNADO ORIZONTAL | P | 0 | 4669.56 | 0 | 4669.56 | 0 | 0 | 0 | 4669.56 | 0 | 4669.56 | +| 105.103 | REZERVA DIN REEVAL.TRANSPOROTR TORNADO ORIZONTAL | P | 0 | 2263.31 | 0 | 2263.31 | 0 | 0 | 0 | 2263.31 | 0 | 2263.31 | +| 105.104 | REZERVA DIN REEVAL.TRANSPORTOR TORNADO ORIZONTAL | P | 0 | 4669.56 | 0 | 4669.56 | 0 | 0 | 0 | 4669.56 | 0 | 4669.56 | +| 105.105 | REZERVA DIN REEVAL TRANSPORTOR ELEVATOR TEE100 | P | 0 | 3788.75 | 0 | 3788.75 | 0 | 0 | 0 | 3788.75 | 0 | 3788.75 | +| 105.106 | REZERVA DIN REEVAL TRNASPORTOR ELEVATOR TEE100 | P | 0 | 3940.44 | 0 | 3940.44 | 0 | 0 | 0 | 3940.44 | 0 | 3940.44 | +| 105.11 | REZERVA DIN REEV.CELULA INSILOZARE | P | 0 | 167628.38 | 0 | 167628.38 | 0 | 0 | 0 | 167628.38 | 0 | 167628.38 | +| 105.17 | REZERVA DIN REEVAL.RETEA ELECTRICA | P | 0 | 12143.52 | 0 | 12143.52 | 0 | 0 | 0 | 12143.52 | 0 | 12143.52 | + +### 1.3 Ultimele 5 randuri (Excel rand 338..342) + +| cont | denumire | tip | deb_init | cred_init | deb_prec | cred_prec | rulaj_d | rulaj_c | total_deb | total_cred | fin_d | fin_c | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 6811 | CHELT. DE EXPLOATARE CU AMORTIZAREA IMOBILIZARILOR | A | 0 | 0 | 119978.76 | 119978.76 | 39992.94 | 39992.94 | 159971.70 | 159971.70 | 0 | 0 | +| 698 | CHELT. CU IMPOZITUL PE VENIT SI CU ALTE IMPOZITE | A | 0 | 0 | 1722.00 | 1722.00 | 0 | 0 | 1722.00 | 1722.00 | 0 | 0 | +| 704 | VEN. DIN SERVICII PRESTATE | P | 0 | 0 | 172156.50 | 172156.50 | 25185.81 | 25185.81 | 197342.31 | 197342.31 | 0 | 0 | +| 7584 | VEN. DIN SUBVENTII PT. INVESTITII | P | 0 | 0 | 6956.34 | 6956.34 | 2318.78 | 2318.78 | 9275.12 | 9275.12 | 0 | 0 | +| 7651 | VEN. DIN DIFERENTE FAVORABILE DE CURS VALUTAR | P | 0 | 0 | 14081.25 | 14081.25 | 7523.61 | 7523.61 | 21604.86 | 21604.86 | 0 | 0 | + +Nu exista rand de totaluri dupa acestea; randul 342 este ultimul. + +### 1.4 Sintetic vs analitic + +- **Nu exista indentare si nu exista o coloana separata** care sa spuna sintetic/analitic. + Coloana `analitic` (AG) este **1 pe toate cele 341 de randuri**, deci nu distinge nimic. +- Singurul criteriu real este **punctul in codul de cont**: + - 78 randuri fara punct (`0` puncte) = **conturi sintetice** (ex. `1012`, `105`, `1171`, `401`); + - 263 randuri cu exact 1 punct = **conturi analitice** (ex. `105.10`, `105.102`, `1171.100`, + `208.500`, `2813.114`, `401.00032`); + - nu exista coduri cu 2 sau mai multe puncte in acest fisier. +- Partea de analitic are lungime variabila 1..5 caractere si poate fi si alfanumerica: + in fisier apar si `105.RAI`, `4428.TP` (litere), pe langa `00001..00134` (5 cifre). +- Atentie: un cont sintetic fara punct poate fi si frunza (ex. `1012` nu are analitice), deci + "are punct" = analitic, dar "nu are punct" NU inseamna automat parinte. +- Codul de analitic arata exact ca in exemplu (`401.00027`), adica `sintetic.`. + +### 1.5 Coloanele de sume: ce sunt totaluri si ce sunt solduri + +Relatii verificate pe **toate cele 341 de randuri** (toleranta 0.005): + +| Verificare | Rezultat | +|---|---| +| `total_deb = deb_prec + rulaj_d` si `total_cred = cred_prec + rulaj_c` | 341/341 OK | +| `fin_d - fin_c = total_deb - total_cred` | 341/341 OK | +| `sold_in_d - sold_in_c = deb_prec - cred_prec` | 341/341 OK | + +Interpretare (sprijinita si de cod, `saga2roa\balanta.sql:11`: +`deb_init+deb_prec as debit, cred_init+cred_prec as credit`): +- `deb_prec` / `cred_prec` = **sume precedente** (rulaj cumulat pana la inceputul perioadei) — + corespund coloanelor "Sume precedente" din raportul PDF; +- `rulaj_d` / `rulaj_c` = **rulaj perioada**; +- `total_deb` / `total_cred` = **sume totale** = precedente + rulaj (NU sold); +- `fin_d` / `fin_c` = **sold final**; +- `deb_init` / `cred_init` = solduri de preluare (initial), separat de precedent; +- `sold_in_d` / `sold_in_c` = sold la inceput, pe netul precedentelor; +- `rulajt_d` / `rulajt_c` = rulaj total cumulat; in acest fisier **nu este egal cu `total_deb/cred`** + decat pe 95/341 randuri (deci nu e o simpla copie a lui `total_deb`). + +Setul `_1` (coloanele S..AF): pe 265/341 randuri este **complet zero**, iar pe 78/341 randuri este +**identic cu setul principal**. Cele 78 sunt exact randurile sintetice (fara punct), cele 265 sunt +analiticele + 2 randuri sintetice complet zero. Nu am putut stabili din fisierele disponibile ce +reprezinta setul `_1` (pare o a doua balanta / a doua perioada); **nu il folosesc ca fapt dovedit**. + +### 1.6 Comparatie cu CSV-ul produs de `extract_balanta.py` din PDF + +CSV-ul din PDF (`balanta_FUNDATIA.csv`) are coloanele: +`pagina, cont, denumire, prec_d, prec_c, rulaj_d, rulaj_c, total_d, total_c, sold_d, sold_c, este_total`. + +Corespondenta de semantica (raportul PDF "Balanta de verificare" = 8 coloane numerice, vezi +si `saga2roa\Balanta de verificare 16052018 1317.xls` rand 7-9: Sume precedente / Rulaje perioada / +Sume totale / Solduri finale): + +| CSV (PDF) | xls | +|---|---| +| prec_d / prec_c | deb_prec / cred_prec | +| rulaj_d / rulaj_c | rulaj_d / rulaj_c | +| total_d / total_c | total_deb / total_cred | +| sold_d / sold_c | fin_d / fin_c | + +Coloane in CSV care **lipsesc** din xls: `pagina` (numarul paginii PDF), `este_total` +(markerul randului de totaluri). +Coloane in xls care **nu exista** in CSV: `categorie`, `tip`, `deb_init`, `cred_init`, +`sold_in_d`, `sold_in_c`, `rulajt_d`, `rulajt_c`, cele 16 coloane `_1`, `analitic`, `validat`, `linie`. + +**Se poate produce EXACT acelasi CSV din xls?** Nu exact, dar foarte aproape: +- Coloanele de cont si de sume se pot mapa 1:1 si valorile sunt coerente (aceleasi relatii + `total = prec + rulaj`, `sold = total` pe net); +- `pagina` nu se poate reface din xls (e proprie PDF-ului, depinde de paginare si de + ruperile de denumire); +- `este_total`: xls-ul **nu are rand de totaluri**; s-ar putea doar calcula/adapta, nu citi; +- denumirea poate diferi: in PDF denumirea e recompusa din fragmente pe pagini + (`extract_balanta.py:124-126` lipeste randurile de continuare), in xls e o singura celula; +- setul de conturi poate diferi daca PDF-ul si xls-ul sunt din perioade/firme diferite + (in cazul de fata xls este DANUBE, CSV-urile existente sunt FUNDATIA/MASTERJOB). +Deci: **da pentru conturi + cele 8 sume, nu pentru `pagina` si `este_total`.** + +## 2. Tabela de corespondenta conturi SAGA -> conturi ROA + +### 2.1 Cele doua fisiere, structura DBF + +**`saga2roa\CONTURI_ROA.DBF`** (varianta DANUBE, 56 inregistrari, 4 campuri): + +| Camp | Tip | Lungime | +|---|---|---| +| CONT | C | 20 | +| ACONT | C | 20 | +| CONT2 | C | 4 | +| ACONT2 | C | 4 | + +Primele 15 randuri (CONT | ACONT | CONT2 | ACONT2): + +``` +2131 | TOATE | 2131 | (gol) +2133 | TOATE | 2133 | (gol) +214 | TOATE | 214 | (gol) +2813 | TOATE | 2813 | (gol) +2814 | TOATE | 2814 | (gol) +3024 | ATM | 3024 | 1 +3024 | ATUO | 3024 | 2 +3024 | (gol) | 3024 | 3 +303 | TOATE | 303 | (gol) +371 | TOATE | 371 | 5 +371 | MARFA | 371 | 6 +371 | MCKM4-5 | 371 | 7 +378 | MCKM4-5 | 378 | 7 +378 | MFEN | 378 | 1 +378 | (gol) | 378 | 1 +``` + +Observatii: `ACONT2` poate fi gol, o cifra, sau alfanumeric (`X`, `PG`, `PO`, `TXV`, `ANAF`, +`C4-5`). `CONT2` poate diferi de `CONT` (ex. `409 -> 4091`, `431 -> 4311`, `4314 -> 4313`, +`4374 -> 4371`, `4381 -> 4371`). Exista un export identic ca structura in +`saga2roa\conturi.csv` (antet `cont;acont;cont2;acont2`, 56 randuri) si `saga2roa\conturi.xls` +(foaie `Sheet1`, 57 randuri cu antet) — dar acestea doua NU sunt identice bit-cu-bit cu +`CONTURI_ROA.DBF`: in csv/xls codul analitic e lipit in `cont` (`707.ATM`) si `acont` e gol, +iar unele valori difera (`MCKM4-5` vs `MCK4-5`). Sunt variante, nu acelasi fisier. + +**`saga2roa_rompetrol\conturi_roa.dbf`** (varianta ROMPETROL, 135 inregistrari, 6 campuri): + +| Camp | Tip | Lungime | +|---|---|---| +| CONT2 | C | 4 | +| ACONT2 | C | 4 | +| DENUMIRE | C | 100 | +| CONT_ALT | C | 20 | +| COD | C | 20 | +| CONT | C | 20 | + +Primele 15 randuri (CONT2 | ACONT2 | DENUMIRE | CONT_ALT | COD | CONT): + +``` +121 | 3001 | PROFIT SI PIERDERE | (gol) | B1203010_NG_011 | 121 +167 | 5501 | GARANTIE DE BUNA EXECUTIE _NG | (gol) | B1203080_NG_081 | 167.01 +201 | 1101 | CHELTUIELI DE CONSTITUIRE_SOLD INITIAL | (gol) | B1301010_NG_011 | 201 +303 | 2201 | MAT. DE NATURA OB. DE INVENTAR | (gol) | B1301010_NG_022 | 303.TOATE +401 | 6301 | FURNIZORI NG | (gol) | B1301010_NG_032 | 401.RESTUL +401 | 6302 | FURNIZORI G | (gol) | B1307010_NG_011 | 401.00032 +401 | 6302 | FURNIZORI G | (gol) | B1307010_KMGR_022 | 401.00005 +401 | 6302 | FURNIZORI G | (gol) | B1307010_RSC_022 | 401.00018 +401 | 6302 | FURNIZORI G | (gol) | B1307080_NG_081 | 401.00002 +401 | 6302 | FURNIZORI G | (gol) | B1307080_NG_085 | 401.00001 +404 | 6301 | FURNIZORI DE IMOBILIZARI NG | (gol) | B1311010_NG_011 | 404.RESTUL +404 | 6302 | FURNIZORI DE IMOBILIZARI G | (gol) | B1311010_NG_022 | 404.0002 +404 | 6302 | FURNIZORI DE IMOBILIZARI G | (gol) | B1311010_RIV_022 | 404.00005 +404 | 6302 | FURNIZORI DE IMOBILIZARI G | (gol) | B1311010_NG_032 | 404.00012 +408 | 6305 | FURNIZORI - FACTURI NESOSITE IMOBILIZARI NG | (gol) | B1311090_NG_011 | PLANCONT +``` + +Aici `CONT` este codul SAGA complet (inclusiv analitic: `401.00032`, `611.1`) sau un marcaj +special (`303.TOATE`, `401.RESTUL`, `PLANCONT`); `CONT2`/`ACONT2` sunt contul sintetic/analitic +ROA; `CONT_ALT` = cont alternativ ROA; `COD` = codul suplimentar ROMPETROL (structura +`B.../I..._NG_...`). + +### 2.2 Unde se foloseste tabela in cele doua .prg + +Toate locurile (grep `conturi_roa` in `*.prg`): + +**`saga2roa_danube\saga2roa_danube.prg`** +- `:30-34` defineste caile; `:40-44` daca `conturi_roa.dbf` lipseste o creeaza **goala** + (`CREATE TABLE (cont C(20), acont C(20), cont2 C(4), acont2 C(4))`); +- `:79` `Select Cast(Alltrim(Cont) + '.' + Alltrim(acont) As C(20)) As Cont, cont2, acont2 From conturi_roa` + -> cheia de cautare este `cont + '.' + acont`; +- `:173-203` ("Corectare conturi analitice") este locul de fond: + ``` + lcContSintetic = Left(m.lcCont, m.lnPos - 1) + lcContAnalitic = Substr(m.lcCont, m.lnPos + 1) + lcContToate = m.lcContSintetic + '.TOATE' + ... + Locate For Alltrim(Cont) = m.lcCont + If !Found() + Locate For Alltrim(Cont) = m.lcContToate + ... + Replace cont2 With m.lcCont2, acont2 With m.lcAcont2 + ``` + +**`saga2roa_rompetrol\saga2roa_rompetrol3.prg`** +- `:59-73` caile + crearea goala (6 campuri); +- `:120-124` citeste tabela si comenteaza rolul coloanelor + (`cont = cont saga`, `cont2/acont2 = conturi ROA`, `cont_alt = cont alternativ ROA`, + `cod = cod suplimentar ROMPETROL`); +- `:126-169` construieste planul ROA (`cPlanROA`): face `Full Join` intre `conturi_roa` si + planul SAGA pe `cont`, plus un `Join` special pentru randurile cu `cont = 'PLANCONT'` + (`:134-138`), si sterge din plan conturile acoperite de un `X.TOATE` (`:152-157`); +- `:244-306` foloseste tabela la corectarea **registrului jurnal**, atat pe debit cat si pe + credit (`:262-273` si `:292-303`), cu `X.TOATE` **si** `X.RESTUL` (`:265`, `:295`); +- `:572-575` foloseste `cPlanROA` (deci indirect tabela) pentru planul de conturi analitic. + +In `saga2roa\balanta.prg` (versiune mai veche, firma COFFEE) tabela se foloseste identic ca in +danube (`:34`, `:40-44`, `:79`, `:188`); in `saga2roa\balanta.BAK:28` apare chiar o cale fixa +`E:\vali\COFFEE\conturi_roa.dbf`. + +### 2.3 Ce decide exact tabela + +Tabela **doar re-codeaza contul**: pentru un cod SAGA dat (`cont`, optional cu analitic) stabileste +`cont2` (sinteticul ROA, max 4 car.) si `acont2` (analiticul ROA, max 4 car.). Deci: +- **redenumirea sinteticului** (ex. SAGA `409` -> ROA `4091`, `431` -> `4311`); +- **analiticul** (`acont2`), care poate fi gol (cont fara analitic in ROA), o cifra, sau + alfanumeric; +- suporta **marcaje speciale** in coloana `cont`: `X.TOATE` (toate analiticele sinteticului X), + `X.RESTUL` (restul, doar in rompetrol), `PLANCONT` (fallback de plan pe `cont2`); +- in rompetrol mai decide **denumirea ROA** (`denumire`), **contul alternativ** (`cont_alt`) si + **codul suplimentar** (`cod`). + +Ce **NU** decide tabela: +- **nu decide `tip` (D/P)**: tipul vine din planul SAGA (`cPlan`, danube `:81`) si e transportat + prin join-uri (`:154`, rompetrol `:572`); tabela nu are coloana de tip. +- **nu decide conturile cu parteneri**: listele sunt **hardcodate in .prg** + (`lcListaConturiBalP` / `lcListaConturiIregP`, danube `:308-309`, rompetrol `:114-115`). + Partenerii vin din `clienti.dbf`/`furnizori.dbf`, nu din tabela. +- nu decide sume, doar maparea de coduri. + +### 2.4 Completata manual sau generata automat? + +Dovada: **niciunul dintre .prg nu scrie in tabela**. Singurele operatii pe `conturi_roa` sunt +`Use`, `Select`, `Locate` si `CREATE TABLE` (doar daca fisierul lipseste) — vezi +danube `:36-44`, `:79`, `:188-199` si rompetrol `:65-73`, `:120`, `:132-138`, `:262-303`. +Nu exista `INSERT`/`REPLACE`/`APPEND` catre `conturi_roa` in niciun .prg. + +Concluzie: tabela este **completata manual** (un nomenclator de corespondenta intretinut de om), +posibil pornita dintr-un export (in rompetrol coloana `COD` are coduri structurate de tip +`B1203010_NG_011`, care par generate din planul ROMPETROL) si apoi corectata/curatata manual +(valori de mana: `acont2` = `X`, `PG`, `PO`, `TXV`, `ANAF`; randuri `PLANCONT`, `TOATE`, `RESTUL`). + +## 3. Alte exporturi SAGA folosite in .prg (in afara de balanta) + +Ambele .prg citesc din directorul SAGA urmatoarele tabele (danube `:30-34`, rompetrol `:59-63`): + +| Fisier SAGA | Rol | Campuri folosite (din cod) | Exemplu in directoare | +|---|---|---|---| +| `conturi.dbf` | plan de conturi + solduri | `Cont, tip, denumire, deb_init, deb_prec, cred_init, cred_prec` (danube `:81`, `:103`; balanta.sql:11) | `saga2roa\conturi.dbf` (864 inreg.) | +| `registru.dbf` | registru jurnal / note contabile | danube: `cont_d, cont_c, explicatie, cod, suma, Data` (`:94-99`); rompetrol: + `id_nota, id_cont_d, id_cont_c, Curs, suma_val, cod_valuta, ndp, fel_d` (`:232`) | `saga2roa\REGISTRU.DBF` (2619 inreg.) | +| `clienti.dbf` | nomenclator clienti (parteneri) | `cod, denumire, cod_fiscal, reg_com, analitic, judet, adresa, tara, is_tva` (+ `banca, cont_banca, tel, email` in rompetrol) | `saga2roa\clienti.dbf` (11 inreg.) | +| `furnizori.dbf` | nomenclator furnizori (parteneri) | aceleasi campuri | `saga2roa\furnizori.dbf` (76 inreg.) | + +Structurile reale ale fisierelor-exemplu (format SAGA mai vechi): +- `conturi.dbf` (15 campuri): `CONT C(20), DENUMIRE C(60), TIP C(1), DEB_INIT N(15,2), + CRED_INIT N(15,2), DEB_PREC N(15,2), CRED_PREC N(15,2), CONT_INCH C(20), DEB_INIT_V N(14,2), + CRED_INIT_ N(14,2), DEB_PREC_V N(14,2), CRED_PREC_ N(14,2), COD_VALUTA C(3), BLOCAT N(1)`. + Atentie: aceasta difera de structura documentata in cod (`saga2roa_danube.prg:25`, 35 campuri), + care este formatul SAGA C.3.0 folosit de danube — si care coincide cu `balanta.xls`. +- `REGISTRU.DBF` (23 campuri): `ID_NOTA N(10), VALIDAT C(1), CONT_D C(20), CONT_C C(20), + SUMA N(15,2), COD_VALUTA C(3), CURS N(15,4), SUMA_VAL N(14,2), TVA N(15,2), EXPLICATIE C(70), + DATA D(8), ID_CONT_D C(20), ID_CONT_C C(20), CATEGORIE C(16), NDP C(16), FEL_D C(20), TIP C(1), + COD C(8), PLAN C(1), SECTOR C(1), SURSA C(1), CAPITOL C(1), ARTICOL C(1)`. +- `clienti.dbf` / `furnizori.dbf`: incep cu `COD C(8), DENUMIRE C(64), COD_FISCAL C(20), + REG_COM C(16), ANALITIC C(20)` (clienti are 31 campuri, furnizori 27). + +**Nu exista** exporturi separate de tip "solduri furnizori" sau "solduri clienti" sau +"facturi" folosite de aceste .prg. Soldurile per partener se calculeaza in .prg din +`registru.dbf` + nomenclatoarele `clienti/furnizori` (danube `:85-92`, `:311-343`), nu se citesc +dintr-un fisier dedicat. In directoare exista si fisiere `saga2roa\facturi_jurnal_tva_COFFEE*.xlsx` +(coloane `an, luna, cont, tip, numar, data, nume, cod_fiscal, sold, soldtvaneex, baza24, tva24, ...`), +dar **niciun .prg nu le refera** (grep pe `facturi_jurnal`, `balanta.xls`, `Balanta de verificare` +in toate .prg-urile: zero rezultate). Sunt exporturi folosite de alt flux, nu de `saga2roa*`. + +## 4. Raport de lucru + +1. **Facut**: cercetare read-only; citit `balanta.xls` (xlrd + conversie LibreOffice), structurile + DBF `saga2roa\CONTURI_ROA.DBF` si `saga2roa_rompetrol\conturi_roa.dbf`, toate locurile + `conturi_roa` din cele trei .prg, plus structurile `conturi/registru/clienti/furnizori.dbf`. + Scris acest fisier. +2. **Verificari**: `openpyxl` pe `.xls` -> `InvalidFileException` (confirmat); `xlrd 2.0.2` OK; + `soffice --headless --convert-to xlsx` -> exit 0, fidelitate 342x35. Relatii de sume pe + 341/341 randuri (tabel 1.5). Niciun fisier din `saga2roa*` modificat (doar citire); grep + `conturi_roa` = 19 potriviri, toate citite; grep exporturi nefolosite = 0. +3. **Nefacut / deschis**: semnificatia setului `_1` din `balanta.xls` (265 randuri zero, + 78 identice cu setul principal) nu e dovedibila din fisierele disponibile; perioada la care + se refera `balanta.xls` (nu exista in fisier un camp de an/luna); `este_total` nu are + echivalent in xls (nu exista rand de totaluri). +4. **Stare periculoasa**: niciuna. Fisierele de proiect nu au fost atinse; temporarele sunt in + `Temp\opencode\sursa_saga`; nu s-a facut write-back, nu au ramas procese, nu s-au consumat date. diff --git a/docs/review_ceo_etapa2.md b/docs/review_ceo_etapa2.md new file mode 100644 index 0000000..59669f0 --- /dev/null +++ b/docs/review_ceo_etapa2.md @@ -0,0 +1,424 @@ +# Review CEO - planul etapei 2 (`plan_etapa2_convertor.md`, PROPUNERE v2) + +Review de strategie, aplicat ca analiza (fara intrebari interactive). Nu s-a modificat planul. +Nu s-a scris cod. Scorurile si modurile de mai jos sunt o adaptare a skill-ului `plan-ceo-review` +la un plan intern, cu un singur utilizator (Marius). + +Documente citite integral: `plan_etapa2_convertor.md`, `plan_solduri2roa.md`, `decizii_import.md`, +`raport_sursa_saga_xlsx.md`, `raport_exporturi_saga_noua.md`, `raport_form_init_balanta.md`, +`config_cont_ireg.md`, `conturi_cu_analitice.md`, `verificare_FUNDATIA.md`, `CLAUDE.md`, +`extract_balanta.py`, `genereaza_xlsx.py`, `.gitignore`. + +## 0. Modul de review propus (Step 0F adaptat) + +Skill-ul cere alegerea unui mod. Nu pot intreba, deci il propun si il justific: +**SELECTIVE EXPANSION cu felie de HOLD** - planul e pe directia corecta, dar are 2-3 bucati care +nu-si justifica existenta acum (cititorul FDB fara date reale, complexitatea merge-ului de mapare) +si 1-2 bucati lipsa (contractul intermediar, sursa setului de parteneri). Nu e "go big": produsul +e intern, utilizatorul e unul singur, migrarile sunt rare. Nu e nici "reduction": cateva lucruri +din plan sunt chiar indispensabile (contractul de CSV, maparea per firma, regresia). +Motivul principal de a nu face HOLD pur: planul nu a evaluat nicio alternativa de arhitectura +(vezi C10), deci "scopul e acceptat" nu se poate sustine inca. + +--- + +## 1. Scoruri pe dimensiunile skill-ului + +| # | Dimensiune (skill) | Nota | Ce ar duce-o la 10 | +|---|---|---|---| +| 1 | Premisa corecta (0A) | 6/10 | Raspuns explicit la "cat de des migram?" si "PDF e suficient cat timp clientul nu da baza?". Planul presupune ca FDB e drumul viitor fara sa spuna cat de des ajunge acolo. | +| 2 | Refolosire cod existent (0B) | 8/10 | Nume per sub-problema la ce cod exista deja: reader PDF neatins; tot restul vine din `genereaza_xlsx.py`. Lipseste doar explicitarea setului de parteneri hardcodat (C1). | +| 3 | Alternative de implementare (0C-bis) | 3/10 | Planul are o singura arhitectura ("un format intern, trei cititoare"). Lipsesc varianta minima (un script `--sursa`) si varianta "FDB ca sursa primara". | +| 4 | Aliniere cu starea ideal 12 luni (0C) | 7/10 | O comanda `migreaza.py --firma --an --luna --sursa` cu mapare persistenta. Planul merge exact spre ea, dar nu scrie cum arata finalul (interfata, contract). | +| 5 | Arhitectura (S1) | 7/10 | Contractul intermediar este definit complet, negru pe alb (C4). Acum "acelasi CSV + optional doua fisiere" e o intentie, nu o specificatie. | +| 6 | Harta erorilor / rescue (S2) | 4/10 | 3 conditii de stop sunt numite (bun), dar lipsesc clasele de eroare pentru cititoare (fisier lipsa, antet schimbat, FDB blocat) si ce vede utilizatorul (C16). | +| 7 | Securitate / date de client (S3) | 6/10 | `.gitignore` extins pentru `.FDB`, `parteneri_*.csv`, `facturi_*.csv` (C8). Risc mic dar real: datele de partener sunt PII/financiar. | +| 8 | Data flow / edge cases (S4) | 6/10 | Regula de periodizare REGISTRU -> prec/rulaj/fin scrisa si testata pe o luna cu miscari inainte (C3); tratarea `scadent` NULL (C15). | +| 9 | Calitatea codului (S5) | 7/10 | Decizia despre complexitatea merge-ului de mapare (C6); altfel e curat si reutilizeaza ce exista. | +| 10 | Teste / porti de verificare (S6) | 6/10 | Poarta FDB-vs-xlsx declarata ca ceea ce este (nu inchide lane-ul pe date de joaca, C2) + testul de regresie rulat la fiecare lane, nu doar la final (C5). | +| 11 | Performanta (S7) | 8/10 | Nimic de facut; FDB e o copie citita o data, agregare unica. De notat doar ca firma de joaca nu spune nimic despre timp pe o baza reala. | +| 12 | Observabilitate (S8) | 6/10 | Raportul `verificare_.md` extins cu numar de parteneri/facturi, sursa folosita si invariantul care inlocuieste "Totaluri" pe sursele xlsx/fdb (C9). | +| 13 | Deploy / rulare la client (S9) | 5/10 | Procedura de copiere a bazei (gbak) mutata in Etapa 2 odata cu cititorul FDB, sau cititorul FDB declarat explicit "intern, nefolosibil la client" (C7/C12). | +| 14 | Traiectorie pe termen lung (S10) | 7/10 | E ok. Reversibilitate 5/5 (scripturi interne, git). Singura datorie: `conturi_cu_analitice.md` contrazice `decizii_import.md` pct.15 pe 5125 (C14). | +| 15 | Design / UX intern (S11) | 5/10 | `mapare_.xlsx` este interfata omului: header congelat, dropdown la `stare`, evidentiere `NOU`/`DISPARUT`. Planul nu spune nimic despre aspectul lui. | + +Scor mediu (nesponderat): ~6,1/10. Planul este peste media unui plan intern, dar isi asuma prea +mult pe incredere (FDB) si prea putin pe specificatie (contract, sursa parteneri). + +--- + +## 2. Constatari (fiecare: ce e in plan -> ce e in neregula -> ce propun) + +### C1. Conturile cu parteneri: planul spune "sursa de adevar = Oracle", dar codul le are hardcodate, iar cele doua liste nu coincid + +**In plan** (sectiunea "Pasul de mapare"): *"Ce NU se configureaza aici: care conturi merg pe +parteneri. Ramana `CONFIG_CONT_IREG` din Oracle ... e sursa de adevar a ROACONT ... Programele +vechi aveau lista hardcodata in `.prg`, exact ce nu repetam."* + +**In neregula:** planul nu spune NICIODATA cum ajunge configuratia Oracle in unealta. Iar codul de +azi o are hardcodata: `genereaza_xlsx.py:43-53` (`PARTENER_SINTETIC = {401,4092,4111,461,462,4551}`, +`PARTENER_BANCA = {5121,5124}`, `PARTENER_FARA_ANALITICE = {5311}`). Lista din Oracle +(`config_cont_ireg.md:89-91`) are **33 de conturi cu `CU_INREGISTRARI=1`** (403, 404, 408, 409, +4091, 4093, 4094, 4118, 413, 418, 419, 426, 4511, 456, 457, 471, 472, 4754, 542, 8051, 1621, 167, +232, 234, 261, 2678, 2691 ...). Daca unealta citeste fidel Oracle, comportamentul se schimba fata +de ce s-a validat in productie (unde doar lista mica a fost folosita). Nu e o eroare, dar este o +divergenta nerezolvata intre doua surse pe care planul le trateaza ca sinonime. + +**Propun:** alege o singura sursa si scrie-o in plan: +(a) snapshot local versionat `config/cont_ireg.csv` (coloanele `cont`, `fel_cont`, `cu_inregistrari`), +generat de un SQL documentat, citit de unealta - fara dependinta Oracle la rulare; +(b) pastrezi setul hardcodat (dar atunci scoti din plan afirmatia "Oracle e sursa de adevar"); +(c) citire live Oracle - de evitat (contra "fara alte dependinte"). +Recomand (a): o sursa, zero credentiale la rulare, si se vede in git cand se schimba. + +### C2. Cititorul FDB e cel mai valoros si cel mai putin verificat; poarta lui de terminare e aproape vida + +**In plan** (sectiunea "Poarta de verificare"): *"cititorul 3 (FDB) si cititorul 2 (xlsx) trebuie +sa produca acelasi CSV ... Acesta este criteriul care inchide lane-ul `citire-fdb`, nu 'ruleaza fara +eroare'."* + +**In neregula:** planul recunoaste singur (aceeasi sectiune) ca firma de joaca *"are doar conturi +sintetice, niciun analitic... 2 furnizori, 0 clienti, 1 factura"*. Deci poarta care "inchide" +lane-ul nu atinge exact partile riscante: analitice, parteneri, facturi per partener, cod fiscal, +scadenta. Pe 10 randuri sintetice, orice cititor plauzibil trece. Poarta e utila ca fumegatoare, +nu ca definitie de "gata". In plus, dovada din `raport_exporturi_saga_noua.md` C.3/C.5 arata ca +singurul rand din `INTRD` are `SCADENT=NULL` si `NEACHITAT=0` - deci nici "scadenta reala" nu e +demonstrata. + +**Propun:** muta obtinerea unei baze `.FDB` reale + exportul ei din Etapa 3 in Etapa 2, ca +**preconditie** pentru lane-ul `citire-fdb` (nu pentru tot planul). Cat timp nu exista, scrie in +plan ca `citire-fdb` se poate livra doar ca "pregatit, neverificat pe analitice". Nu numi poarta +"criteriu care inchide lane-ul". + +### C3. Semantica de periodizare REGISTRU -> prec/rulaj/fin nu este definita (inima corectitudinii FDB) + +**In plan** (sectiunea "Cititorul 3 - Firebird"): *"balanta: agregare pe `REGISTRU` la data ceruta. +... nu se citeste `CONTURI.DEB_PREC`, se agrega `REGISTRU`."* + +**In neregula:** planul nu defineste ce inseamna "la data ceruta" si cum se separa +`DEB_PREC` (inceput de perioada), `RULAJ_D` (perioada) si `FIN_D` (final). Exportul SAGA are +4 notiuni distincte (`raport_exporturi_saga_noua.md` A.1, C.4): `DEB_INIT` = inceput de an, +`DEB_PREC` = inceput de perioada, `RULAJ` = perioada, `FIN` = final. CSV-ul tinta cere +`prec_d/prec_c, rulaj_d/rulaj_c, total_d/total_c, sold_d/sold_c`. Daca intervalele nu sunt +definite exact, CSV-urile FDB si xlsx **nu vor fi identice pe date reale** - vor coincide doar pe +firma de joaca, unde toate miscarile sunt inainte de perioada (8 note in `REGISTRU`). + +**Propun:** scrie in plan formulele exacte, de obicei: +`prec = SUM(suma) unde DATA < prima_zi_a_lunii_cerute`, `rulaj = SUM unde luna_ceruta`, +`total = prec + rulaj`, `sold = total pe net`; si pentru `DEB_INIT` separat, pentru `sold_in`. +Adu ca test o luna cu miscari in 3 locuri: anul precedent, inainte de luna, in luna. + +### C4. Contractul intermediar (CSV + parteneri + facturi) nu este specificat + +**In plan** (sectiunea "Arhitectura"): *"Toate cele trei cititoare produc acelasi CSV ... plus - +optional - doua fisiere de parteneri."* Si la "Randurile FACTURA": parametrii +`--furnizori `, `--clienti `, `--facturi `, `--fdb `. + +**In neregula:** nu exista nume de fisiere, coloane si relatia dintre "fisierele de parteneri" ale +pasului de citire si flagurile generatorului. In plus, cititorul 2 propune `TIP` ca "a 13-a +coloana, optionala", in timp ce reader 1 (`extract_balanta.py`) ramane "nemodificat" si scrie +12 coloane (`pagina, cont, denumire, prec_d, prec_c, rulaj_d, rulaj_c, total_d, total_c, sold_d, +sold_c, este_total`). Rezulta doua contracte care trebuie tolerate simultan, fara sa fie scris. + +**Propun:** fixeaza in plan: +- `balanta_.csv` - exact 12 coloane, neschimbat; `TIP` nu se adauga (nu e nevoie in + generator; `genereaza_xlsx.py` nu citeste `tip`); +- `parteneri_.csv` - `cont_saga, denumire, cod_fiscal, analitic`; +- `facturi_.csv` - `cont_partener, numar, data, scadent, neachitat`; +- preconditii de potrivire nume de coloana: **exact, case-insensitive** (nu substring - + `TOTAL_DEB` este prefix al `TOTAL_DEB_1`, iar un match partial ar lua coloana gresita). + +### C5. Regresia pe FUNDATIA + MASTER trebuie rulata dupa fiecare lane care atinge generatorul, nu doar la final + +**In plan** (tabelul "Ordinea de lucru"): lane `regresie` depinde de `integrare`; *"Poarta finala: +lane-ul `regresie`. Daca noul flux nu reproduce randurile celor doua xlsx-uri validate in productie, +nu e gata."* + +**In neregula:** planul schimba doua lucruri in zona validata in productie: (1) `cont`/`acont` vin +din mapare in loc sa fie calculate, (2) setul de conturi cu parteneri (C1). Ambele pot muta cifre. +Daca regresia ruleaza doar la final, un bug introdus in lane-ul `mapare` se descopera dupa toate +celelalte lane-uri. + +**Propun:** poarta de regresie FUNDATIA + MASTER se ruleaza dupa `mapare` si dupa `integrare` +(de doua ori), nu o singura data la final. Este cel mai ieftin test de siguranta din tot planul: +doua PDF-uri in git, doua xlsx de referinta pe disc. + +### C6. Merge-ul de mapare (`MANUAL`/`NOU`/`DISPARUT` + `.TOATE`/`.RESTUL`) este cea mai mare bucata de cod pentru un utilizator unic + +**In plan** (sectiunea "Pasul de mapare"): *"Merge la rulare noua, nu suprascriere: randurile +`MANUAL` raman intacte; analiticele disparute din balanta se pastreaza, marcate `DISPARUT` ...; +analiticele noi se adauga cu `NOU`."* + +**In neregula:** nu e gresit, dar este cea mai complexa componenta a planului (stare persistenta, +reconciliere pe 3 tranzitii, marcaje) si se justifica doar daca Marius editeaza manual maparea +des. Planul nu spune cat de des se intampla asta. Alternativa: maparea se regenereaza complet la +fiecare rulare, iar corectiile omului traiesc intr-un fisier mic separat +(`mapare__manual.csv`, doar randurile atinse), peste care se aplica. + +**Propun:** decid: (a) merge complet ca in plan (accepti complexitatea, adaugi testele celor 3 +tranzitii - deja in criteriu), sau (b) overlay manual separat (mai simplu de testat, dar doua +fisiere). Nu am o preferinta puternica; depinde de cat de des editeaza Marius maparea. + +### C7. Dependinte noi nedeclarate in `CLAUDE.md` + +**In plan** (antet): *"Python 3.13, fara dependinte in afara de openpyxl/pdfplumber/xlrd/firebird-driver."* +`CLAUDE.md:6-7`: *"Instalate: pdfplumber, openpyxl. Fara alte dependinte."* + +**In neregula:** planul introduce `xlrd` si `firebird-driver` (si `fbclient.dll` dintr-o cale fixa +de masina: `C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll`, `raport_exporturi_saga_noua.md` +C.2). `CLAUDE.md` nu le mentioneaza, iar calea `fbclient` nu e portabila intre masini/clienti. + +**Propun:** adauga in `CLAUDE.md` (si intr-un scurt `README`/`--help`) lista de dependinte + pasul +de instalare + faptul ca `fbclient.dll` se descopera la rulare (nu cale fixa). Verificat: pe +masina asta toate cele 4 module se importa. + +### C8. `.gitignore` nu acopera copii `.FDB` si fisierele de parteneri/facturi (date de client) + +**In plan** (sectiunea "Git"): enumera ignorarea pentru `init_*.xlsx`, `export_*.xlsx`, +`mapare_*.xlsx`, `balanta_*.csv`. `.gitignore` real confirma exact acestea. + +**In neregula:** noile artefacte din plan - copii `.FDB`, `parteneri_*.csv`, `facturi_*.csv`, +fisiere `.xls` - nu sunt acoperite. O copie `.FDB` de client contine CUI, denumiri, IBAN, solduri. + +**Propun:** adauga in `.gitignore`: `*.FDB`, `*.fdb`, `parteneri_*.csv`, `facturi_*.csv`, +`config/cont_ireg.csv` (daca se decide snapshot-ul, vezi C1 - sau, dimpotriva, comite-l intentionat +daca nu contine date de client). Nu e o urgenta, dar e o scapare concreta. + +### C9. Raportul de verificare este orientat pe PDF; pentru xlsx/fdb, verificarea cu "Totaluri" dispare tacit + +**In plan** (sectiunea "Cititorul 2"): *"`este_total` = gol (exportul nu are rand de totaluri)"*. + +**In neregula:** `genereaza_xlsx.citeste_totaluri` cauta un rand cu "totaluri" in denumire si +intoarce `None` pe sursele xlsx/fdb (`genereaza_xlsx.py:89-94`). Verificarea "NET xlsx vs NET +Totaluri" (una din garzile din `CLAUDE.md`) nu se mai poate rula, fara ca planul sa spuna ce o +inlocuieste. Ramane doar `SUM(totdeb)=SUM(totcred)`, care e necesara dar nu suficienta. + +**Propun:** pentru sursele xlsx/fdb, invariantul de inlocuire se scrie in plan si in raport: +pe fiecare rand din CSV `total_d = prec_d + rulaj_d`, `total_c = prec_c + rulaj_c`, +`sold_d - sold_c = total_d - total_c` (exact verificarile deja rulate in cercetare, 10/10 pe +exportul nou; vezi `raport_exporturi_saga_noua.md` A.1). + +### C10. Planul nu evalueaza nicio alternativa si nu spune cat de des se migreaza (premisa) + +**In plan:** o singura arhitectura ("Arhitectura: un format intern, trei cititoare"), 5 lane-uri. + +**In neregula:** pentru un utilizator unic si 2 migrari facute pana acum, "5 lane-uri + 3 cititoare ++ mapare persistanta + parteneri/facturi din FDB" poate fi supradimensionat. Exista o varianta mult +mai mica: un singur script care citeste PDF sau xlsx (`--sursa`), fara FDB, fara mapare +persistanta, cu partenerii si cod fiscal provizoriu ca azi (etapa 5b ANAF exista deja si a +functionat). FDB-ul aduce valoare reala (cod fiscal real, scadenta, facturi per partener, dispare +etapa 5b) - dar numai dupa ce exista o baza reala de validat (C2). + +**Propun:** raspunde la doua intrebari in plan: cat de des se face o migrare si cat de des vine +clientul cu baza `.FDB`. Daca migrarile sunt rare sau FDB-ul e rar, varianta minima acopera cazul +real mai repede si mai sigur. + +### C11. Ordinea lane-urilor poate fi paralelizata, iar `citire-fdb` depinde de prea putin + +**In plan** (tabelul "Ordinea de lucru"): `citire-xlsx` fara dependinte, `mapare` fara dependinte, +`citire-fdb` depinde de `citire-xlsx`, `integrare` depinde de toate. + +**In neregula:** `citire-xlsx` si `mapare` nu depind una de alta si pot rula in paralel (planul le +pune amandoua "fara dependinte", deci e ok, dar nu o spune). `citire-fdb` depinde de `citire-xlsx` +pentru ca exportul e oracolul - corect ca tehnica, dar slab ca dovada pe date de joaca (C2). + +**Propun:** numeste explicit `citire-xlsx` + `mapare` ca lane-uri paralele si muta `citire-fdb` +dupa ce exista date reale sau dupa decizia D2. + +### C12. Etapa 3 contine procedura de client, fara de care cititorul FDB nu se poate folosi la client + +**In plan** (sectiunea "Etapa 3"): *"procedura pentru client: cum isi face copia bazei (`gbak` ... +) si ca trebuie sa opreasca SAGA daca trimite fisierul direct"*. + +**In neregula:** daca `citire-fdb` se livreaza in Etapa 2, dar procedura de obtinere a bazei ramane +in Etapa 3, unealta nu poate fi folosita la un client. Planul nu spune daca Etapa 2 FDB e "intern, +pe baza proprie" sau "folosibil la client". + +**Propun:** fie muti procedura `gbak`/opreste-SAGA in Etapa 2 odata cu cititorul, fie declari +explicit ca Etapa 2 FDB este doar pe baza proprie si procedura ramane in Etapa 3. + +### C13. Testul de regresie: "identice" trebuie definit pe date, nu pe octeti + +**In plan** (tabelul): *"xlsx-urile noi **identice** cu cele importate deja in productie"*. + +**In neregula:** `genereaza_xlsx.scrie_xlsx` re-salveaza sablonul cu `openpyxl`; metadate/formatari +pot diferi, deci "identic bit-cu-bit" e fragil ca test. Ce conteaza sunt valorile celulelor. + +**Propun:** defineste poarta ca "identic pe toate celulele de date (rand >= 2), pe toate coloanele", +nu pe hash de fisier. Compara cu `openpyxl.load_workbook` valorile celula cu celula. + +### C14. `conturi_cu_analitice.md` contrazice decizia finala pentru 5125 + +**In plan:** planul nu mentioneaza 5125. +**In neregula:** `conturi_cu_analitice.md:29,105` propune `5125 = PARTENER`; `decizii_import.md` +pct.15 (revizuit 20.09.2026) decide `5125 = doar analitice, fara parteneri`. Codul respecta pct.15 +(5125 nu e in `PARTENER`, `genereaza_xlsx.py:53`). Un om care citeste docul de conturi rămâne cu +informatia veche. Planul nu spune care doc castiga. + +**Propun:** o linie in plan ("pct.15 din `decizii_import.md` prevaleaza; `conturi_cu_analitice.md` +se actualizeaza") plus corectarea tabelului la urmatoarea atingere a acelui fisier. + +### C15. `scadent` din FDB poate fi NULL; planul il trateaza ca "scadenta reala" + +**In plan** (tabelul "Randurile FACTURA"): randul "baza `.FDB`" -> *"cu cod fiscal real si scadenta +reala"*. +**In neregula:** dovedit in `raport_exporturi_saga_noua.md` C.3, singurul rand din `INTRD` are +`SCADENT=NULL`; iar `NEACHITAT=0`. Deci "scadenta reala" si "facturi cu sold" nu sunt garantate de +baza. +**Propun:** in plan, regula pentru `SCADENT` NULL (fallback pe ultima zi a lunii, ca la exporturi) +si pentru selectia facturilor (ex. `NEACHITAT <> 0`), plus raportarea cazurilor. + +### C16. Harta erorilor nu numeste ce vede utilizatorul la esec de citire + +**In plan** (sectiunea "Ce se pastreaza neatins"): 3 conditii de oprire ("analitic fara rand in +mapare", "cont/acont > 4", "doua randuri cu acelasi (cont,acont)"). Bun, dar sunt doar in generator. +**In neregula:** nu exista erori numite pentru cititoare: fisier lipsa, foaie inexistenta/redenumita, +antet fara coloana asteptata, `cont` citit ca numar, FDB blocat/versiune greșita/`fbclient` +negasit. `raport_form_init_balanta.md` arata ca formularul insusi e lax (rand fara `an` = ignorat +tacut), deci un CSV gresit poate produce un xlsx "valid" dar incomplet. +**Propun:** tabel de erori in plan (codepath | ce poate merge prost | clasa/exceptie | ce vede +Marius). Minim: fiecare cititor valideaza antetul prin nume exacte si se opreste cu mesaj care +spune fisierul, foaia si coloana lipsa. + +--- + +## 3. DECIZII DE LUAT DE MARIUS + +### D1. Cat de mare e etapa 2: planul complet (5 lane-uri, 3 cititoare) sau o felie minima intai? +- A) **Felia minima intai**: un script cu `--sursa pdf|xlsx`, fara FDB si fara mapare persistanta + (cod fiscal provizoriu + etapa 5b ca azi); FDB cand apare un client cu baza. (uman: ~1 zi / CC: ~1h) +- B) **Planul complet ca acum** (5 lane-uri, inclusiv FDB + parteneri/facturi). +- C) **Planul complet, dar FDB ultimul si conditionat** de o baza reala (hibrid cu D2). +- **Recomandare: C** - pastrezi valoarea FDB, dar nu blochezi livrarea pe cea mai putin verificata + bucata. Rezultatul il simti imediat pe PDF/xlsx, iar FDB se adauga cand ai dovada. (Daca + migrarile sunt rare si clientii nu dau baza, A devine alegerea corecta.) + +### D2. Cititorul FDB: il construim acum sau dupa o baza reala? +- A) Mutam obtinerea unei baze `.FDB` reale + exportul ei in Etapa 2, ca preconditie. (recomandat) +- B) Il construim acum pe firma de joaca si acceptam ca "verificat, nu pe analitice". +- C) Il scoatem din etapa 2 complet (revine in etapa 3). +- **Recomandare: A** - poarta FDB-vs-xlsx pe 10 randuri sintetice nu poate inchide un lane care + promite analitice, parteneri, cod fiscal si scadenta reala. + +### D3. Sursa setului de conturi cu parteneri (C1) +- A) Snapshot local `config/cont_ireg.csv` generat de SQL, citit de unealta. (recomandat) +- B) Pastram setul hardcodat din `genereaza_xlsx.py` si scoatem afirmatia "Oracle e sursa de adevar". +- C) Citire live Oracle la rulare. +- **Recomandare: A** - o singura sursa, fara credentiale si fara client Oracle la rulare, si se + vede in git cand se schimba. Important: decide daca snapshot-ul include toate cele 33 de conturi + `CU_INREGISTRARI=1` sau doar setul validat - altfel rezultatul pe FUNDATIA/MASTER se poate schimba. + +### D4. Contractul intermediar (C4) +- A) Il fixezi in plan acum: 3 fisiere, nume si coloane exacte, fara `TIP`, match exact de antet. + (recomandat) +- B) Lasi cititoarele sa produca ce vor si adaptezi generatorul. +- **Recomandare: A** - e cel mai ieftin lucru de scris din tot planul si elimina cea mai frecventa + sursa de buguri tacite (potrivire de coloane). + +### D5. Merge-ul de mapare (C6) +- A) Merge complet (`MANUAL`/`NOU`/`DISPARUT` + `.TOATE`/`.RESTUL`) ca in plan. +- B) Overlay manual separat peste o mapare regenerata complet. (mai simplu de testat) +- **Recomandare: B** daca Marius editeaza maparea rar; A daca o editeaza des. Neutral - decizia + tine de cat de des corecteaza manual. + +### D6. Poarta de regresie (C5, C13) +- A) Rulezi regresia FUNDATIA + MASTER dupa `mapare` si dupa `integrare`, comparand valori de + celule (nu hash de fisier). (recomandat) +- B) Ramane o singura rulare la final, pe hash de fisier. +- **Recomandare: A** - cel mai ieftin test de siguranta, si protejeaza exact zona validata in + productie pe care planul o atinge. + +### D7. Procedura de client pentru baza FDB (C12) +- A) O muti in Etapa 2 odata cu cititorul FDB. (recomandat daca D2 = A/B) +- B) Ramane in Etapa 3; declari Etapa 2 FDB "intern, nefolosibil la client". +- **Recomandare: A** - altfel livrezi un cititor pe care nu-l poti folosi in teren. + +### D8. Cine e "adevarul" pe 5125 si pe docurile contradictorii (C14) +- A) `decizii_import.md` pct.15 prevaleaza; actualizezi `conturi_cu_analitice.md`. (recomandat) +- B) Lasat asa, se rezolva la urmatoarea atingere. +- **Recomandare: A** - o linie in plan si o corectie mica elimina o capcana pentru viitor. + +--- + +## 4. Ce am verificat si ce NU am putut verifica + +**Verificat (cu dovada):** +- Structura celor doua exporturi SAGA (VFP si Firebird) identica pe 35 de coloane si maparea + 1:1 pe cele 8 sume - `raport_sursa_saga_xlsx.md` 1.5/1.6 si `raport_exporturi_saga_noua.md` + A.1/B.2 (relatii de sume 341/341 si 10/10). +- `CONTURI` cu solduri 0, cifrele din agregarea `REGISTRU` - `raport_exporturi_saga_noua.md` C.4. +- Firma de joaca: FDB exista (`D:\SAGA250909\0001\CONT_BAZA.FDB`, 21 MB), exporturi in `exemple\`, + 0 analitice, 2 furnizori, 0 clienti, 1 factura - planul si raportul C.3 confirma. +- Setul hardcodat de parteneri din cod vs lista Oracle `CU_INREGISTRARI=1` (33 conturi) - C1: + `genereaza_xlsx.py:43-53` vs `config_cont_ireg.md:89-91`. +- `5125`: codul respecta `decizii_import.md` pct.15, docul `conturi_cu_analitice.md` nu - C14. +- `.gitignore` real nu acopera `.FDB`/`parteneri_*.csv`/`facturi_*.csv` - C8. +- Dependintele pe masina asta: `pdfplumber`, `openpyxl`, `xlrd`, `firebird.driver` se importa toate. +- `genereaza_xlsx.py` citeste `totaluri` din CSV, deci pe sursele xlsx/fdb verificarea scade - C9. +- `extract_balanta.py` scrie 12 coloane, deci `TIP` ca a 13-a coloana ar rupe contractul - C4. + +**NU am putut verifica (afirmatii neacoperite de un fisier citit):** +- Cat de des se face o migrare si cat de des vine clientul cu baza `.FDB` (premisa pentru D1). + Nu exista niciun document cu volumul de migrari. +- Daca `REGISTRU` permite reconstruirea exacta a `DEB_PREC`/`RULAJ`/`FIN` pe analitice pe o baza + reala - nu exista decat firma de joaca (8 note). C3 ramane o cerinta, nu o dovada. +- Daca vreun client are baza `.FDB` pe o versiune Firebird diferita (aici 3.0.7, `fbclient` dintr-o + cale fixa) - nerezolvat, risc de deploy nenumit in plan. +- Ordinea exacta de prioritate dintre `conturi_cu_analitice.md` (PARTENER pe 5125) si + `decizii_import.md` pct.15 (analitic) din partea lui Marius - am presupus ca pct.15, mai nou si + revizuit, este cel corect. +- Outside voice (Codex / subagent) NU a fost rulat: skill-ul il cere implicit, dar acest task este + o analiza fara aprobare interactiva, iar rezultatul ar fi informational. De reluat manual daca + Marius vrea a doua voce. + +**Anexa A - diagrama de arhitectura (cum e acum in plan; `[?]` = nespecificat)** + +``` + SURSE CITITOARE CONTRACT GENERATOR IESIRE + PDF balanta ------> citire-pdf (exista) -> balanta_.csv (12 col) --> genereaza -------> init___.xlsx + xlsx/xls SAGA -----> citire-xlsx (NOU) -> balanta_.csv + CONT_BAZA.FDB -----> citire-fdb (NOU) -> parteneri_.csv [nume/coloane ?] + mapare_.xlsx + facturi_.csv [nume/coloane ?] + docs/verificare_.md +``` + +**Anexa B - registru de erori propus (S2)** + +| Codepath | Ce poate merge prost | Tratament propus (acum lipseste) | +|---|---|---| +| citire-pdf | PDF fara text (scanat) | Stop, mesaj "PDF fara strat de text" | +| citire-xlsx | fisier lipsa / foaie redenumita / antet schimbat | Stop cu numele foii si coloanei asteptate | +| citire-xlsx | `cont` citit ca numar (`401.00002`) | citire ca text, verificat deja in cercetare | +| citire-fdb | `fbclient.dll` negasit / versiune Firebird diferita | Stop cu calea cautata si versiunea | +| citire-fdb | `CONTURI` cu solduri 0 (folosire gresita) | agregare `REGISTRU`, cu invariant de sume; de numit explicit | +| generator | analitic fara rand in mapare | deja in plan: Stop | +| generator | `(cont,acont)` duplicat | deja in plan: Stop | +| generator | rand fara `an` (formular il ignora tacit) | validare proprie, nu te baza pe formular | + +**Anexa C - registru de failure modes (S4)** + +| Codepath | Failure mode | Prins? | Test? | Marius vede? | Logat? | +|---|---|---|---|---|---| +| citire-fdb | CSV "corect" dar fara analitice (agregare sintetic) | N (C2) | N (date de joaca) | **Silent** | raport | +| citire-fdb | `SCADENT` NULL tratat ca data valida | N (C15) | N | partial | nu | +| citire-xlsx | antet cu coloana lipsa -> 0 pe toate | N (C16) | partial | posibil Silent | nu | +| generator | `Totaluri` absent -> verificare sarita | partial (C9) | da | notat in raport | da | +| deploy | `fbclient` lipsa la client | N (C7) | N | Stop (daca mesaj clar) | nu | + +Rândurile `RESCUED=N, TEST=N, USER SEES=Silent` sunt golurile critice: primul si al treilea. + +**Anexa D - "ce exista deja"** + +- `extract_balanta.py` = cititorul PDF (reused, nemodificat; corect). +- `genereaza_xlsx.py` = toti pasii de mapare si constructie a randurilor BALANTA/FACTURA, cu + verificarile din etapa 5 (reused; planul il extinde corect cu maparea si `exclus`). +- `decizii_import.md` pct.11-16 = regulile validate in productie (nu se redeschid; corect). +- Sablonul `init_facturi_balanta_note.xlsx` (rand 1 = cap de tabel, import incepe de la rand 2). + +**Anexa E - "NOT in scope" (confirmat de plan, cu o exceptie)** + +- importul registrului jurnal, balantele de verificare lunare, nomenclatorul de parteneri lunar + (programele `saga2roa*` vechi) - corect in afara scopului; +- planul de conturi syntetic generat de SAGA (`PLANCONT`) - corect nepreluat; +- conectarea Firebird prin retea - corect amanata. +- **Exceptie de re-verificat:** "procedura de client pentru copia bazei" este scoasa in Etapa 3, + dar daca FDB se livreaza in Etapa 2 (D2/D7), ea este in scop. diff --git a/docs/review_eng_etapa2.md b/docs/review_eng_etapa2.md new file mode 100644 index 0000000..c403ccb --- /dev/null +++ b/docs/review_eng_etapa2.md @@ -0,0 +1,395 @@ +# Review eng - plan etapa 2 (`docs/plan_etapa2_convertor.md`, PROPUNERE v2) + +Lane: `review-eng`. Analiza statica, read-only. Nu am modificat planul, nu am scris cod, nu am dat commit. +Am aplicat skill-ul `plan-eng-review` (SKILL.md + `sections/review-sections.md`) ca analiza: unde skill-ul +cere AskUserQuestion, am scris DECIZII DE LUAT (sectiunea 3). Sursa de adevar pentru fiecare afirmatie +de mai jos este un fisier citit; unde am presupus, scrie explicit "presupunere". + +Fisiere citite: planul; `raport_sursa_saga_xlsx.md`; `raport_exporturi_saga_noua.md`; `decizii_import.md`; +`plan_solduri2roa.md`; `config_cont_ireg.md`; `raport_form_init_balanta.md`; `conturi_cu_analitice.md`; +`extract_balanta.py`; `genereaza_xlsx.py`; `.gitignore`; CLAUDE.md; git status/log. + +--- + +## 0. Step 0 - Scope challenge (rezultat) + +- **Ce exista deja si e refolosit corect**: `extract_balanta.py` (PDF, nemodificat - bine), + `genereaza_xlsx.py` (constructia BALANTA/FACTURA, ramane), sablonul `init_facturi_balanta_note.xlsx`, + conventiile `.TOATE`/`.RESTUL` din `conturi_roa.dbf` (`raport_sursa_saga_xlsx.md` 2.1-2.3), regulile + validate `decizii_import.md`. Planul nu reconstruieste ce exista - punct bun. +- **Minimum set**: planul e rezonabil de minimal (3 cititoare + mapare + integrare + regresie). + Nu am gasit scope creep mare. Etapa 3 (baza reala, gbak, Firebird pe retea) e corect lasata afara + (`plan_etapa2_convertor.md:172-180`). +- **Complexity check** (skill: 8+ fisiere / 2+ clase noi = smell): planul atinge ~6-8 module noi + (`citire_xlsx`, `citire_fdb`, `mapare`, `extrage`, `genereaza`, test) - la limita, nu peste. + Nu declanshez reducere de scope pe acest motiv. +- **Completeness check**: planul propune varianta completa pe hartie (poarta de identitate CSV, + regresie pe productie) - dar doua dintre porti nu sunt executabile cu artefactele actuale + (vezi C2, C3). Deci "complet" e declarat, nu livrabil. +- **Distribution check**: unealta interna, rulata de Marius pe masina lui. Nu necesita pipeline de + publicare. Corect lasat afara. + +--- + +## 1. Scoruri pe dimensiunile skill-ului + +| Dimensiune (din skill) | Nota | Ce ar aduce-o la maxim | +|---|---|---| +| Scope challenge | 8/10 | Sa declare explicit ce se pierde cand "un singur CSV" nu e suficient (parteneri, totaluri, rollup) - vezi C5, C11 | +| Architecture / data flow | 5/10 | Contract intern real (balanta + parteneri + facturi), nu un singur CSV; regula de rollup FDB specificata (C2, C11) | +| Code quality / DRY | 6/10 | Clarifica cine detine cont/acont (mapare vs `genereaza`), evita re-encodarea pct. 16 in doua locuri (C4, C9) | +| Test coverage | 4/10 | Golden fixtures comise in git + framework de test; altfel poarta de regresie si poarta FDB nu ruleaza (C3, C5, sec. 2.5) | +| Performance | 7/10 | Agregare in SQL (GROUP BY pe 3 intervale de data), nu `SELECT *` in Python; charset WIN1250 (C15) | +| Failure modes / esecuri tacute | 5/10 | Trateaza explicit: verificarea Totaluri care dispare tacut, SCADENT NULL, cod fiscal gol (C5, C6) | +| Diagrame | 8/10 | Diagrama de pipeline exista (`:35-39`); lipseste diagrama de forma a datelor per cititor (ce coloane intra/ies) | +| Outside voice (cross-model) | n/a | Nu am putut rula Codex/subagent in acest lane; nu e o nota, e o limita de mediu | + +Verdict de ansamblu: **plan bun ca intentie, cu 3 gauri de executie care il opresc sa fie "gata"** +(poarta FDB fara regula, poarta de regresie fara fixture, sursa partenerilor care contrazice productia). + +--- + +## 2. Constatari + +Format: `[Px] (confidence N/10) ce e in plan / ce e in neregula / ce propun`. + +### C1 [P1] (confidence 9/10) - `CONFIG_CONT_IREG` nu reproduce setul de parteneri validat + +- **In plan**: `:119-122` - "Ce NU se configureaza aici: care conturi merg pe parteneri. Ramane + `CONFIG_CONT_IREG` din Oracle ... e sursa de adevar a ROACONT". +- **In neregula**: codul validat in productie foloseste alt set. `genereaza_xlsx.py:47-53`: + `PARTENER_BANCA={5121,5124}`, `PARTENER_SINTETIC={401,4092,4111,461,462,4551}`, + `PARTENER_FARA_ANALITICE={5311}`. In `config_cont_ireg.md:89-91` (CU_INREGISTRARI=1) **nu apar + 5121, 5124, 5311**, iar in lista Oracle apar 404,408,4091-4094,418,419 etc. pe care `genereaza` + NU le trateaza ca parteneri. Deci: daca unealta decide partenerii doar din Oracle, 5121/5124/5311 + (decisiuni validate, `decizii_import.md` pct. 14-15) devin conturi obisnuite -> regresie tacuta pe + MASTER (`5121.01 PIRAEUS`). +- **Propun**: sursa de adevar operationala = o lista in repo, derivata din setul validat + reconciliata + explicit cu `CONFIG_CONT_IREG`; Oracle ramane consultativ. Documenteaza divergenta (Oracle listeaza + trezoreriile ca `CU_INREGISTRARI=0`, dar `decizii_import.md:52-55` arata ca formularul are liste + hardcodate separate `:1104`/`:1107`). + +### C2 [P1] (confidence 9/10) - agregarea REGISTRU pentru balanta e sub-specificata; poarta de identitate nu e demonstrabila + +- **In plan**: `:62-64` - "balanta: agregare pe `REGISTRU` la data ceruta" si `:75` - "cititorul 3 (FDB) + si cititorul 2 (xlsx) trebuie sa produca acelasi CSV". +- **In neregula**: (a) balanta exportului are 4 marimi, nu una: `DEB_INIT/CRED_INIT` (inceput de an), + `DEB_PREC/CRED_PREC` (inceput de perioada), `RULAJ_*` (perioada), `FIN_*` (sold) - deci sunt necesare + **doua praguri de data**, nu unul (`raport_exporturi_saga_noua.md` C.4:288-297 o arata pe cazul + `641` din 2025-11-30 -> `DEB_INIT`). (b) exportul **ruleaza analiticul in sintetic**: `REGISTRU` are + `401.00002`, exportul are `401` (`C.4:294,298-301`), iar `C.5:303-304` spune explicit ca "regula exacta + de includere sintetic/analitic nu se poate stabili". Fara regula de rollup, agregarea din + `CONT_D/CONT_C` produce `401.00002`, iar CSV-ul nu e identic - testul pica din cauza regulii lipsa, + nu a unui bug de cititor. +- **Propun**: specifica algoritmul in plan (nu in cod): grupare pe sintetic (`stanga punctului`), + 3 intervale de data (an, perioada, in perioada) + `FIN=PREC+RULAJ`; declara explicit ca testul pe + firma de joaca dovedeste doar cazul sintetic (`:80-82`). Adauga in plan un al doilea prag (`an`). + +### C3 [P1] (confidence 9/10) - poarta `regresie` nu are fixture comis in git + +- **In plan**: `:167` - "xlsx-urile noi **identice** cu cele importate deja in productie"; + `:154-156` - `.gitignore` ignora `init_*.xlsx` si "fisierele de productie au fost scoase cu + `git rm --cached`". +- **In neregula**: `git status` arata `D init_FUNDATIA_2025_12.xlsx`, `D init_MASTER_2025_12.xlsx`, iar + `.gitignore:6` ignora `init_*.xlsx`. Criteriul de terminare al lane-ului de regresie compara cu + fisiere care nu sunt in repo -> pe alt checkout/masina poarta nu se poate rula, iar rezultatul nu e + reproductibil nici de autor peste o luna. +- **Propun**: comite o copie golden sub `tests/golden/` (exceptie in `.gitignore`), sau un manifest de + hash-uri + regulile de numarare; atunci "identice" devine verificabil automat. PDF-urile sunt deja in + git (bine), dar output-ul-tinta lipseste. + +### C4 [P1] (confidence 8/10) - "genereaza ia cont/acont din mapare" se bate cap in cap cu logica pastrata in cod + +- **In plan**: `:143-146` - "singura schimbare de fond este ca ia `cont`/`acont` din mapare in loc sa le + calculeze"; `:103-104` - pre-completarea e generica ("primele max 4 caractere" + "cifrele + analiticului concatenate, max 4"). +- **In neregula**: (a) randurile de diferenta NU exista in balanta, deci nu pot veni din mapare; + sunt calculate (`genereaza_xlsx.py:204-212,243-249`) - planul nu spune cine le mai emite. (b) forma + `acont` depinde de categorie: `401` -> acont gol, `5121` -> acont completat identic pe BALANTA si + FACTURA (`decizii_import.md` pct. 16; `genereaza_xlsx.py:219-249,252-283`). Regula generica de + pre-completare ar pune acont pe `401` -> chei diferite -> dublare tacuta (pct. 16). +- **Propun**: decide cine detine `acont`: ori pre-completarea din mapare e constienta de categorie + (partener -> pct. 16, analitic -> regula generica), ori `genereaza` ramane singurul care decide + forma pentru parteneri, iar maparea doar suprascrie. A doua e mai putin riscanta (codul deja validat). + +### C5 [P2] (confidence 8/10) - verificarea "Totaluri" dispare tacut pe drumurile xlsx/FDB + +- **In plan**: `:54` - `este_total = gol` pe exportul de foaie de calcul; `:56` - xls/xlsx nu au rand de + totaluri. +- **In neregula**: `genereaza_xlsx.py:89-94` cauta randul "Totaluri" si `:330,381-390` face cross-check-ul + net xlsx vs net Totaluri doar daca il gaseste. Pe drumurile noi `citeste_totaluri` intoarce `None`, + deci cea mai puternica verificare cap-la-cap se **omite silentios** - exact tiparul de esec tacut pe + care planul il interzice in alta parte. +- **Propun**: inlocuieste verificarea pierduta cu una echivalenta: pe FDB, `SUM(REGISTRU)` pe latura la + data ceruta = netul balantei (cifra de control reala); pe xlsx, re-calculeaza netul din coloanele + citite. Daca nu exista echivalent, scrie explicit in `verificare_.md` "verificare indisponibila", + nu o sari. + +### C6 [P2] (confidence 8/10) - "cod fiscal real" si "scadenta reala" nu sunt garantate de baza + +- **In plan**: `:65-66` - "Codul fiscal real vine de aici" si `:134` - FDB da "cod fiscal real si + scadenta reala ... nimic lipseste". +- **In neregula**: in baza de joaca, `FURNIZORI` are `***('00001','ROMFAST','','401.00001')***` - cod + fiscal **gol** (`raport_exporturi_saga_noua.md` C.3:264), iar singurul `INTRD` are `SCADENT=NULL` + (C.3:274). Deci "real" poate insemna gol/NULL pe date reale. +- **Propun**: pastreaza fallback-urile de azi (cod provizoriu `.`, scadenta = ultima zi + a lunii) ca default cand FDB da NULL/gol, si raporteaza cate randuri au cazut pe fallback. + +### C7 [P2] (confidence 7/10) - semantica `MANUAL` in merge e nedeclarata + +- **In plan**: `:100` `stare` e "nu" (need editabila); `:114-117` - "randurile `MANUAL` raman intacte". +- **In neregula**: nu se spune **cine** si **cum** face trecerea `AUTO -> MANUAL`. Daca utilizatorul + editeaza `cont`/`acont`, cine observa? Cum se distinge o editare de o re-preluare a aceleiasi valori? + La a doua rulare, daca regula auto ar produce exact valoarea editata, randul ramane AUTO si poate fi + suprascris mai tarziu. De asemenea: un MANUAL care devine `DISPARUT` si reapare - ce stare are? +- **Propun**: defineste regula (ex.: orice modificare a `cont`/`acont`/`exclus` fata de valoarea + pre-completata marcheaza `MANUAL`; `stare` devine `READONLY`), si trateaza revenirea `DISPARUT -> MANUAL`. + +### C8 [P2] (confidence 8/10) - regula de coliziune se bate cu `.TOATE`/`.RESTUL` + +- **In plan**: `:148-150` - opreste cu eroare daca "doua randuri produc acelasi `(cont, acont)` fara sa + fie acelasi cont SAGA"; `:106-109` - `.TOATE` = toate analiticele merg pe acelasi `(cont, + acont)` ROA. +- **In neregula**: `.TOATE`/`.RESTUL` sunt **agregare intentionata** a mai multor conturi SAGA pe aceeasi + cheie ROA, exact ce regula de mai sus declara eroare. Asa cum e scris, tool-ul se opreste pe cazul + suportat. In plus, pentru conturile-partener `acont` e irelevant (pct. 16) - nu trebuie sa intre in + verificarea de coliziune. +- **Propun**: scuteste agregarea intentionata (acelasi `cont_saga` de baza sub `.TOATE`/`.RESTUL`) si + exclude partenerii din verificarea de coliziune; raporteaza doar coliziunile neintentionate. + +### C9 [P2] (confidence 7/10) - redenumirea sinteticului (`409 -> 4091`) nu vine din noul mapare + +- **In plan**: `:106-112` preia doar `.TOATE`/`.RESTUL` din `conturi_roa.dbf`; pre-completarea `:103-104` + copiaza sinteticul SAGA. +- **In neregula**: `raport_sursa_saga_xlsx.md` 2.1/2.3 arata ca `conturi_roa.dbf` face si redenumire de + sintetic (`409 -> 4091`, `431 -> 4311`, `4374 -> 4371`). Regula de pre-completare nu o produce, deci + o firma cu `409` iese cu `cont=409` in loc de `4091`. Ramane corectabila manual, dar planul pretinde + reutilizare. +- **Propun**: semeneaza maparea din `conturi_roa.dbf` cand exista (CONT2/ACONT2), sau adauga tabela de + redenumire explicita. + +### C10 [P2] (confidence 8/10) - selectia foii nu e specificata si difera intre surse + +- **In plan**: `:48-56` (cititorul 2) nu spune cum alege foaia; `raport_sursa_saga_xlsx.md` spune ca + foaia VFP se numeste `balanta`, iar `raport_exporturi_saga_noua.md` A.2:110 spune `xl` (xls) vs + `Sheet1` (xlsx). +- **In neregula**: un cititor care cauta fix `Sheet1` pica pe exportul VFP; unul care ia `active` pica + pe fisiere cu foi ascunse. +- **Propun**: alege prima foaie, sau cauta dupa nume in lista cunoscuta, cu eroare clara daca nu exista. + +### C11 [P2] (confidence 7/10) - "un singur CSV intern" nu e suficient pentru nivelul 3-4 de FACTURA + +- **In plan**: `:41-42` - "Restul lantului nu stie din ce sursa vine"; dar `:136` parametri + `--furnizori/--clienti/--facturi/--fdb` intra in **generator**, nu in cititor. +- **In neregula**: numele partenerului, codul fiscal real si detaliul de factura nu stau in CSV-ul de + balanta; sunt intrari separate. Diagrama `:35-39` sugereaza un singur flux, iar contractul real are + 3 intrari. Nu e fatal, dar planul se contrazice si ascunde o dependenta. +- **Propun**: scrie contractul real: `balanta_.csv` + optional `parteneri_.csv` + + optional `facturi_.csv`; `genereaza` accepta cele doua optionale si degradeaza la nivelul + corespunzator (`:124-134`). + +### C12 [P3] (confidence 7/10) - coloana `TIP` (a 13-a) nu are consumator + +- **In plan**: `:55` - "Coloana `TIP` (A/P) se duce in CSV ca a 13-a coloana, optionala". +- **In neregula**: `genereaza_xlsx.py` nu citeste niciodata `TIP`; natura D/C vine din latura hardcodata + (`:43`) si din net. Deci coloana e greutate moarta sau scop nedeclarat. +- **Propun**: fie o lasi afara, fie ii dai un consumator concret (ex. validare latura/cont de partener). + +### C13 [P3] (confidence 6/10) - facturile de clienti din FDB sunt speculativ specificate + +- **In plan**: `:67-68` - `IESIRI`/`IES_DET` -> facturi clienti. +- **In neregula**: `raport_exporturi_saga_noua.md` C.3:280 da `IESIRI` (0 randuri) si `IES_DET` cu + structura "-" (necunoscuta); firma are 0 clienti. Deci SQL-ul de facturi clienti e presupunere. +- **Propun**: marcheaza partea de clienti ca neverificata si nu o promite ca livrabil pana nu ai o baza + reala (Etapa 3, `:175`). + +### C14 [P3] (confidence 6/10) - Oracle (retea + credentiale) intr-un flux de fisiere offline + +- **In plan**: `:119-122` - partenerii vin din `CONFIG_CONT_IREG` (Oracle local). +- **In neregula**: aduce o dependenta de DSN/VPN/tnsnames si de parola din + `D:\ROA\ROACONT\docs\oracle_parola_standard.secret` (`config_cont_ireg.md:7,17`) intr-o unealta care + altfel lucreaza doar pe fisiere. Esti pe acelasi LAN (`10.0.20.121`), deci e fezabil, dar adauga + moduri de esec (DNS, cont SQL*Plus, parola expirata) pentru un pas care se schimba rar. +- **Propun**: instantaneu versionat (`config/cont_ireg.csv`) regenerat de un script mic cand Oracle se + schimba; unealta citeste instantaneul offline. Vezi si C1. + +### C15 [P3] (confidence 6/10) - performanta FDB si charset + +- **In plan**: `:58-64` nu spune cum se agrega. +- **In neregula**: la clienti cu `REGISTRU` mare, `SELECT *` + agregare in Python e lent si consuma + memorie; iar `CONT_D/CONT_C` si denumirile au diacritice/CP1250. +- **Propun**: agregare in SQL (`GROUP BY cont, CASE pe intervale de data`), `charset=WIN1250` + (`raport_exporturi_saga_noua.md` C.2:230 foloseste deja WIN1250). + +### C16 [P3] (confidence 5/10) - potrivirea numelor de coloane trebuie exacta, nu substring + +- **In plan**: `:51-53` compara antetul case-insensitive. +- **In neregula**: exportul are si `DEB_PREC` si `DEB_PREC_1` (setul `_1`, `raport_sursa_saga_xlsx.md` + 1.1). O potrivire prin `in`/startswith poate alege coloana gresita tacut. +- **Propun**: normalizeaza si compara egalitate exacta dupa nume; ignora explicit coloanele `_1`. + +--- + +## 2.5 Test review (per lane - ce test automat lasa fiecare) + +Azi nu exista niciun test (`tests/` lipseste; `pytest` neinstalat, verificat). Skill-ul cere 100% +acoperire; propun concret pentru fiecare lane: + +| Lane | Test lasat in urma | Observatie | +|---|---|---| +| `citire-xlsx` | `tests/test_citire_xlsx.py`: CSV produs din `exemple/saga-sqlite-balanta-09-2026.xlsx` == CSV golden comis; + relatiile `total=prec+rulaj`, `sold=total` pe toate randurile | Fixture e deja in git (`exemple/`) | +| `mapare` | `tests/test_mapare.py` pe functia de merge: MANUAL supravietuieste, analitic nou -> `NOU`, disparut -> `DISPARUT`, coliziune neintentionata -> Stop, `.TOATE` NU e coliziune | Pur logic, fara dependinte | +| `citire-fdb` | `tests/test_citire_fdb.py`: CSV produs din `D:\SAGA250909\0001\CONT_BAZA.FDB` == CSV golden comis (inghetat o data). Live, doar daca baza exista (skip motivat) | Baza nu e in repo -> testul depinde de mediu; de aceea ingheata CSV-ul | +| `integrare` | rulare cap-la-cap pe firma de joaca, comparatie cu xlsx golden | Necesita C3 | +| `regresie` | `tests/test_regresie.py`: `init_FUNDATIA_2025_12.xlsx` si `init_MASTER_2025_12.xlsx` regenerate == golden comis | Necesita C3 (altfel nu ruleaza) | + +Problema transversala: **fara fixture comis (C3), 2 din 5 lane-uri nu pot demonstra criteriul de +terminare**. Recomand `unittest` din stdlib ca sa nu adaugi dependinte (CLAUDE.md: doar pdfplumber, +openpyxl, xlrd, firebird-driver), sau `pytest` acceptat explicit ca dependinta dev-only. + +Acoperire estimata azi: 0%. Dupa cele 5 testuri de mai sus, portile reale ale planului devin +automate. Cazurile pe care planul insusi le declara neacoperite (`:80-83`: 0 analitice, 2 furnizori, +0 clienti, 1 factura) raman neacoperite pana la o baza reala (Etapa 3) - corect de raportat, nu de ascuns. + +--- + +## 2.6 Failure modes (esec + test + eroare vizibila?) - cele fara test SI fara mesaj = critical gap + +| Failure mode | Test? | Eroare clara? | Verdict | +|---|---|---|---| +| Rollup FDB analitic->sintetic gresit (C2) | Nu (poarta nu are regula) | Nu, iese un CSV diferit | **CRITICAL GAP** | +| Verificarea Totaluri dispare tacut (C5) | Nu | Nu | **CRITICAL GAP** | +| Regresie fara golden -> cineva crede ca a trecut (C3) | Nu | Nu | **CRITICAL GAP** | +| Editare MANUAL suprascrisa la a 2-a rulare (C7) | Nu | Nu | **CRITICAL GAP** | +| Coliziune `.TOATE` oprita ca eroare / coliziune reala ratata (C8) | Nu | Da (Stop) / Nu | Risc mediu | +| `fbclient.dll` lipsa / cale gresita | Nu | Da (exceptie driver) | Risc mic | +| Foaie gresita aleasa (C10) | Nu | Depinde | Risc mediu | +| Cod fiscal gol / SCADENT NULL din FDB (C6) | Nu | Nu (valoare proasta) | Risc mediu | +| Facturi clienti FDB pe structura necunoscuta (C13) | Nu | Nu | Risc mediu, amanat | + +--- + +## 2.7 Required outputs (cerute de skill) + +**NOT in scope** (plan `:16-17`, `:172-180`): import registru jurnal; balante lunare; nomenclator +parteneri lunar; baza `.FDB` reala; procedura `gbak` pentru client; Firebird prin retea. Corect +delimitat. De adaugat explicit: pipeline de distributie (nu exista, unealta interna) si testarea pe +analitice reale (amanata la Etapa 3). + +**What already exists** (reutilizat): `extract_balanta.py` (nemodificat), `genereaza_xlsx.py` +(constructie + verificari), sablonul, `decizii_import.md`, `conturi_roa.dbf` (conventii), baza de joaca ++ exporturile din `exemple/`. Planul reutilizeaza bine; singurul lucru reconstruit inutil ar fi +`TIP` (C12) si, potential, setul de parteneri (C1). + +**Diagrams**: exista diagrama de pipeline (`:35-39`). Lipseste o diagrama de forma a datelor: pentru +fiecare cititor, ce coloane intra si ce iese in CSV (utila exact pentru C5/C10/C11/C16). + +**Worktree parallelization**: + +| Step | Module | Depinde de | +|---|---|---| +| citire-xlsx | `citire_xlsx` | - | +| mapare | `mapare` | - | +| citire-fdb | `citire_fdb` | citire-xlsx (contract CSV comun) | +| integrare | `extrage`, `genereaza` | toate | +| regresie | `tests`, golden | integrare | + +Lane A: citire-xlsx -> citire-fdb (secvential, contract comun). Lane B: mapare (independent). +Apoi integrare, apoi regresie. Fara conflict de module intre A si B. Planul are deja dependenta +`citire-fdb -> citire-xlsx` (`:165`), corect. + +**Implementation Tasks** (sinteza): +- T1 (P1) - scrie regula de agregare/rollup FDB in plan (C2). +- T2 (P1) - comite golden fixtures pentru regresie (C3). +- T3 (P1) - reconciliaza setul de parteneri cu productia (C1). +- T4 (P1) - decide proprietarul `cont/acont` si trata diferentele (C4). +- T5 (P2) - inlocuieste verificarea Totaluri pe drumurile noi (C5). +- T6 (P2) - specifica merge-ul `MANUAL`/coliziuni/.TOATE (C7, C8). +- T7 (P2) - selectie foaie + fallback cod fiscal/scadenta (C6, C10). + +--- + +## 3. DECIZII DE LUAT DE MARIUS + +### D1 - Sursa setului de conturi cu parteneri +- **A)** Interogare Oracle `CONFIG_CONT_IREG` la fiecare rulare (retea + credentiale). +- **B)** Instantaneu versionat in repo (`config/cont_ireg.csv`), regenerat de un script mic; setul + operational (5121/5124/5311 etc.) adaugat explicit langa el. (recomandat) +- **C)** Hardcodeaza setul validat in `genereaza_xlsx.py`. +- **Recomandare: B** - offline pentru flux, dar totusi auditat si actualizabil; A aduce esecuri de + retea intr-un pas care se schimba rar; C e exact "lista hardcodata in `.prg`" pe care planul (`:121`) + vrea sa o evite. Motiv: explicitudine + portabilitate. + +### D2 - Regula de agregare FDB (ca sa treaca poarta de identitate) +- **A)** Rollup la sintetic (stanga punctului) + 3 intervale de data; test pe firma de joaca dovedeste + doar sinteticul. (recomandat) +- **B)** Pastreaza analiticul ca atare si cere exportul SAGA tot la nivel analitic. +- **C)** Amaneaza FDB pana ai o baza reala cu analitice. +- **Recomandare: A** - e singurul mod in care poarta `:75` poate pica/trece pe datele existente; + C amana tot lane-ul, B muta problema la client. + +### D3 - Fixture pentru poarta de regresie +- **A)** Comite copiile `init_FUNDATIA_2025_12.xlsx` / `init_MASTER_2025_12.xlsx` sub `tests/golden/` + (exceptie in `.gitignore`). (recomandat) +- **B)** Manifest de hash-uri + un CSV golden al continutului. +- **C)** Renunta la poarta de regresie. +- **Recomandare: A** - e poarta finala a planului (`:167-170`); fara fixture nu exista; continutul e + date de test validate in productie, nu secrete. + +### D4 - Cine detine `cont`/`acont` si randurile de diferenta +- **A)** Maparea pre-completeaza constient de categorie (partener pct. 16 vs analitic), `genereaza` doar + citeste. (planul de azi) +- **B)** `genereaza` ramane singurul care decide forma pentru parteneri; maparea doar suprascrie; + randurile de diferenta raman calculate in cod. (recomandat) +- **C)** Reescrie `genereaza` complet pe mapare, inclusiv diferentele (rescriere mai mare). +- **Recomandare: B** - codul de parteneri e deja validat in productie (`decizii_import.md` pct. 16, + `genereaza_xlsx.py:219-303`); re-encodarea lui in generatorul de mapare dubleaza regula (DRY) si + risca regresia pe MASTER. + +### D5 - Verificarea echivalenta "Totaluri" pe drumurile xlsx/FDB +- **A)** Adauga un check real: `SUM(REGISTRU)` pe latura la data ceruta = netul balantei generate. (recomandat) +- **B)** Doar avertizare explicita in raport ca verificarea nu e disponibila. +- **C)** Ignora. +- **Recomandare: A** - planul declara ca nu ajusteaza cifre ca sa treaca verificari (`CLAUDE.md`); + verificarea trebuie inlocuita cu una reala, nu doar scoasa. + +### D6 - Framework de test +- **A)** `unittest` stdlib (fara dependinte noi). (recomandat) +- **B)** `pytest` (dependinta dev-only). +- **C)** Scripturi shell/py ad-hoc, fara framework. +- **Recomandare: A** - respecta "fara alte dependinte"; C nu da raportare de totaluri/esecuri. + +--- + +## 4. Ce am verificat si ce NU am putut verifica + +**Verificat (dovada in fisier):** +- `genereaza_xlsx.py` citeste din CSV doar `cont/denumire/total_d/total_c/este_total`; + nu citeste `sold_*`, nu citeste `TIP`, iar cross-check-ul Totaluri depinde de un rand care exista + doar in PDF (`:65-94,196-304,321-390`). +- `extract_balanta.py` scrie 12 coloane; `pagina`/`este_total` sunt coloanele care nu au echivalent + in export (`:232-237`, plan `:54`). +- Setul de parteneri din cod (5121/5124/5311) contrazice `CU_INREGISTRARI` din Oracle + (`genereaza_xlsx.py:47-53` vs `config_cont_ireg.md:89-91`). +- Baza de joaca: 0 analitice in export, `FURNIZORI.COD_FISCAL` gol pentru `ROMFAST`, + `INTRD.SCADENT=NULL`, regulile de rollup analitic->sintetic declarate necunoscute + (`raport_exporturi_saga_noua.md` A.1, C.3, C.4, C.5). +- `.gitignore` ignora `init_*.xlsx`; `git status` arata xlsx-urile de productie scoase din index. +- Mediu: `xlrd 2.0.2` si `firebird-driver` instalate; `pytest` NU; `tests/` NU. + +**Nu am putut verifica (si nu am inventat):** +- Daca `409 -> 4091` sau alte redenumiri sunt necesare pe firmele viitoare (nu apar in FUNDATIA/MASTER). +- Structura `IES_DET` din Firebird (raportul o da ca necunoscuta) - partea de facturi clienti e + presupunere pana la o baza reala. +- Daca `CONFIG_CONT_IREG` s-a schimbat fata de dump-ul din `config_cont_ireg.md` (nu am interogat Oracle). +- Regula exacta de includere sintetic/analitic in exportul SAGA (`C.5` o declara nedeterminabila). +- Comportamentul real al formularului pe combinatia "acont gol pe FACTURA + acont completat pe BALANTA" + pe alte conturi decat cele deja tratate (am luat de bun `decizii_import.md` pct. 16). + +**Nu am rulat** Codex/outside voice (mediu de subagent, fara Codex) si nici testul real de identitate +CSV (nu am voie sa rulez VFP/cod in acest lane; oricum planul nu are inca cititoarele). + +--- + +Stare: niciun fisier modificat in afara acestui raport; fara commit; fara procese pornite; +fara date de test consumate. diff --git a/exemple/saga-sqlite-balanta-09-2026.xls b/exemple/saga-sqlite-balanta-09-2026.xls new file mode 100644 index 0000000000000000000000000000000000000000..5f050825125b1f89084983fe7055d4751722adb3 GIT binary patch literal 10752 zcmeI2OK%-X700U|w_kP~J8^6$c9PCRf*FQ^Bqq!PW_;s#;7WeUj|h#BjK}s27mOV_ z4h#!~S+GPx@ClG+!49!OERbM1NI}2{KpOZ2vw$UI&i_=`sp=|+7+6K*j5_z$@xQC8 ztA2Irblv&WA4fj?*)K2sReAQhO4P^4rOF(CMBj0-4J#GX7nDCfK0ek?rReJmdyYon zpVU{(AFh-pu|pT;Jg2LmtE8)<3x6M^&ogul(RG$CLO_ksb&js{bR~3=?1eo?Be0_O z>Ayo&t2wntp9ku@G>fOVF(Sr1uEd6?+`jzo!o+WTe~H|9agW>o=2Px?w!2bsWZK8) zv+q!9fJ8s3Poq4Y=09!!bhVyd^}n?5-uvSF|KAA2L$rY1O+H}{!av)?@62cqHUneY zgROy0G}tJNX$_W|G3~)7VoZCmr5Mv5Y%s>O2ipysw1v&bnATt`ViOHECSzKIZHi4a zqsBv@T64j8L^EbQq8T?H(M%YRXf6hxOMz!H@Jt1s%f=&ZT`?Z|)P25cJf!Kw1K?Z&DGld`itOEv~J+T6b4_Us_-3t}U(f=IZ-% z^}_n%Y&WH5GR^^&N@HY-*#9H7j21G5WWt#CAhpo8&P-*bvZgh{oiZAqyNpwkc0E3K z8HadNx31aeF5^_Dt?{|bIL&Fd<#U&D3e?v4++~~|wKYC>nG){g*?sOZCESU|=Ppw| zcbW3J%aqSuMw4w#kC<>LZTZ|~O1L8pxXT>wGUaoZDa+jiaW}|mCgG!|r*9K@<5UKC zXVuz+E`fG`eRi#PuQ$)E&kQPGimUXbo7q1AFNQOkdu#tyNkB*4i40 zar76h@mb3Q)^Y{b+V%J`F6Sh=t&tcfZGpA6Mq-?3eAaS@HPdIX)~?60W;9@}t+A{b zjn7&hu$D7x?RtFHa%Qcqv8(;B?`R+=uH@~!kh*vli7H-5V z0tyj@8~fTeGl%tI4ZXxu_!>Xr6*l5YJwAm+Kw-fYn#y%sK7|ES*wzSz(v~0b3LEi6 z<5O5z3Ps~byuwC2(fARsun|u*K7~avKYu-lfFJ!wnWll};M zqVXeO$;S`eAJIsbC-n$>qVXeOsf0bz_z|#FK6_=rUKy}g2JDrVJ!wnWlRgW3qVXeO z>9AJ@?3E6C*Lk*#~mY6Z4@u92&D60U#5J;^#=6 z1z1L zdc3(?zq$Y3^1*)H*cX}Xn`-9rE5omG9g+GT|1rjltkXT{Z|-V;ME>9C zDADrAPthk&(TDYa*sR5W9M|f1H8-r_Lnp}h_hkL`=hYXf_E;(PsS}>< zr4xLwoZx%;1YdcAZ;0l^Z+EQa`)Qo2;iKdjpCd;Zf1W!^`Sbiy&Y$W?@#pAK=Xi|Q z-47|T`7^T>zqgAupn=wT!#$Gxjn*gpIElJJ*Z1hOgH18air_EIDxIn7B6C7vc*f- zlx%SlJCZFJ@e1Ov8|P5%SpH6!7Fm+>frJ2<~N{& zYrt`l3JnjEtI(=L$lyEU`kKxRk*`#T`-hvm8`RA-#`_(hsv74OJy06seR7z*9rcdx z9vma$pvrLHIld21i(%QI%oC~5dGZb@)FW<)M@5WLFyaeV?7GL;EyH8f<1IH=f~lAw zcWH(QS~xE{PFA85m&jSkFhL6Mr0bQgO_6uN4i&_RESJeW=nNM8n3OBz&-HK_*Hv+G zrI?eNT!q@)+uZFR_L~{L#s?=~<8?V^fCdr=~F3m_9>`oE-V1HxTnQ zK6HlmSwd`tieRo(#Mff)E&6!?kn#RWZa@|{knxWKaR6ByKo(obs0wr|X2|~V+ydSi1|gVt+8)}W_$pd(Ezg8fh@*e+?a+e zb|6#qB8H3$Mv&bm$ZpUVKsKq&aajFnb4FI*$8S>IHGU6Es2v!v=Ju2D;giWB)~2SuuG8kiRqE|v3v3UF9XDErk1<^`u!gxod(>c0e47h z8Y?mOH7(=PtF}iN@zHwcLru=PnJnO9SrGfV(^b literal 0 HcmV?d00001 diff --git a/exemple/saga-sqlite-balanta-09-2026.xlsx b/exemple/saga-sqlite-balanta-09-2026.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..cac1a6edfc283610ed2d41cf16f43d0c08a246d1 GIT binary patch literal 5352 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 literal 0 HcmV?d00001 diff --git a/exemple/saga-sqlite-furnizori-09-2026.xlsx b/exemple/saga-sqlite-furnizori-09-2026.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7e134ed6690af5b903e7faf4aee649d8d270ae71 GIT binary patch literal 3873 zcmZu!2{=^W8=kQgW5zyGcB1T*kfl&|vhx#)Vak?~Wvn4v5z-h-me82&`xc@!Vyr_Y z*_Db=mSU{=&*WdE&hyN3?tSLG-@V^^-t&F$HPoe~Vg&#IGypN=DVxF@^!1ITJ2@Z# zz(|@M5cY;12zO6OJ9l>p-)pWJ@v3*8OEE^wZ;0xAl{Ir<(l%jZBXC>K^WAsO%Amb( zG!%?5^&&p;Oe03#^&@z_k~g)$bJAK>7jc*Y{Xt9y#p|c%*f`9}<-u;e;pW}X3MzT7 zWD2V$(O5$%JPLdGoeEEFoQ#Q^h1p;C@4zHfd`fuKbeFrS{j!EiU*V&**HW~$A!S;K zEPv)&i#17^_@~=#ZMbw0A+;18Ih}!ye`Asw@#y9;%=QcsGg(Q~P?C0nVO#L<7RTTi z!0?Y$SGZfn*-2@BCjFT9rfQGyfRl5*cF)QH$IBi$zXgR5#j!SQnirATmavEI%(L&3 zo?X=yy9F5fGCu5Wm2o|J;mkOW&U7~8YXV|H)zU-&_3=1Rpvd%5ai4CbHo}ORUk*il zqY}Hq*qb_P`rOD)*|pJo0uve^!_SiEerg8h#X8BW(5vJiVAr~U^Y5N~{9PPr9k|5i zes4;*h|i;4pzWaD%oPCzVQfm`*G^)B3J9$1C?#!+V%qG~SWvL$N?}WD_A7 zGV^-UWm1)=&0`Y|i_5p;q$^)n$;%#aS5L`b%XWm7vV>|HCH*Z+0RSA?yUYjS;Q~V-T*!<|kb%Ybnw@b{ z;K=zKAk4;l2QK><`NkO4QmYj}tNHuh7TKgB3B={(Sn-Gx$Z2gYjqz=}{xpIU>p?vk zs?sbG9mZ+>vaoq*U{$G)0q^bVT7NJ%v`eeZ$$Xh3F7+FI9UltMXaM5$3`|1B;IvJc z_}4dIM#wnzT$_QvzLFb&Cc@W_zd`}KsZCsqL#wJ?7{72Xo}KIU4r@-fU19NWV&;pQ zlHRyup3807Y;oCv9zVO-FFtvf?+c!9skE7JyfZ_0Ik^ib4#$5EU&%0O`o5%_G>N!x zCN9}Cn!Znh`sV}bzmv$AAxV&Y21(p`I@)=_9ZbADoZPN@?x@hnB}|GnVw@8`oYb~)EasW;tAgeuT$2chZ{~)L-j6)8XL`#$r$eMeO+8hR$mn)%*r-B@tIj0?we zuI0`VW{39k-Xc1OK~C3c`uJFuIV3CN@62^kUp|djt56EJ&MATkK7Dun15Dp?y+hE- z?_{%wgx`^+0=$L&Q}lAw*17j*Mc06TO6UB9*r5RuLyMFE0ObEHdHK1*f6|kf(CL;g z#TrSx0qPWh_s0PBH6g%<$C-^~N4-S6`(lBV=7D@Q+F1tU_7W^ImD+>pX@sfqHX%Ru z92G>@eUw=<%R7yh4G9NEJ+&`~C}t!Q_^Dqq0nKdwQViAZu-p zw=`dk>-`*Lsp6w7V;6z^uwI!KWMp5*x{$CIE0rd{u!XNsOT{Mmyz#e}KDF2+F7GE3 zp$*qzc1Jr`>x_na)k0qKDfN254Vs~smR>p*y3`)z?DOu{MHVl`jWF@Tqh{a|8S2=t ziMrRlE@TfXSx7Zc0x_2J2_-76-7cl0TitFnq0{bXH5*DBY|0oY^m@Xt>6$SKx6_RC zysjxnmsk5%YO=0rRN&iD7YiB8_&5i7WP^;>Z9X=ANcdJDRvz5P0gb24?ap`b+&oV$ z3C?`>t)tyn;SlUxCW|G-E1#}BPKv74+;Ylg&{%uS{Wtmtq#|UYN|oY{7vNF0)nNy4 zX>Y7@72n_KX~sHezi`4pq1<&yF?w5Fp_cjen5_eEv&Xtb7HYHElC4>BxSD9kfv81I zNxCabpB0{Tb#k-+2qsbuh~EvB)2Z-g^@8*@WRFHcL~ntYvD1@NrU`JFi5r_mgJ=I# zfRDT9SiGi!6 zZsV@jXDGjXJ$iVSQ7ED&D5zKNh=+9TkAQ^}l716!rm}5wK=<))l5K~Ff?QWBiyA3^ zCHi+UrQc1|(-98$+FwzpPlk;ykO*O>1_0RqTKf|sO)emb=wY`>UdG6IqA(*7Qse1p zKUh+)|0>5A+wbf%PunUREzK66`w@OdN4;oKm*EsfoQOpJ;k+a{ z6%bainsEk|V(ytryyc+(kT-=oVc=%KsqZGbTqohej4WEkmIo-gDIwG5XcCLt3blM9szme1=) zj%kI6Q!dJK2s+SV%LO*RR+JMS*vKvpcEO2f0!zO2lOMZL=S#i#`-t?>b#MHC@JWK_hJ8`P|@5>adB2i^+s%n~_7akQ#T< zCkxJglOw}VJ>zb))TN1wuPOFp9l-^DeGSOF_s)zg1YI*5E}f_;MOW)QcnIR=AFz>W z$usPD5c-ubBlzmzAP`JI4?DlO1P;202Y!;tj;0ayx?wLmrecL;hr{RffSQU zD9O{Q6SqK}R;(QLc?BXj)pFNty}nFdngk4<{iM1{muaf^<9;^fI`si)30 z45xV@qOWxUKJ=(Arr=WEY5efJg$r+*uP)T#GY;}38}#O3yXXA$+lD!5#XMFXUz__6 zY9W2mq)^YhZfQNlc#b9%eS@J!=zYkM$4WZWBHNV6dO80T2br>boi(%5Gbk6)r)n1i zBg+_b_2Y8D`u5Jf*_RYhG%>X3RyJNm(RDL<_~wMtQ-sE#Bu4b$;6`TqO+R~JDR?~f zfk3Uu%^*Tl>-QO8P(Wsf>bet%$tjD)n*04#n4&jRJB_hyE~4oeYWd@{FspOK!!pxP z=hBQd=wq9Xs^X+r*-NdK;DbEf6LEQxztyk$$N)t5&*# zq1W{kt+tqbfYmzjn{63&y@Y1x%U218-YL08jh?p~Q0)|NgWfF#`Ysc*zrK?Elc%kKXUr$(SAV>|XRQ@4gHFyRed7>dqj4 z;hZ4t(jL$H=R5Crtz_8FQ!V$x_BNxR`@H-3o!s{uz1=u&BvbP*+qXZ$eiO7C0ZlsD zcJd$fY=40L=W#c{dD8LRb2<%msY!&84=o*_g0#PuVDjDn0IfDNN&o-= literal 0 HcmV?d00001 diff --git a/exemple/saga-sqlite-furnizori-facturi-un-furnizor-09-2026.xlsx b/exemple/saga-sqlite-furnizori-facturi-un-furnizor-09-2026.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..355f06836979830f0afcf01e9c59ae7870300f74 GIT binary patch literal 3866 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 cum se falsifica +COL_NUME = 10 +COL_COD_FISCAL = 11 +COL_REG_COM = 12 +COL_ADRESA = 13 +COL_EXPLICATIA = 14 +COL_TIP = 5 + +# IBAN romanesc, cu sau fara spatii: RO + 2 cifre + 4 litere + 16 caractere alfanumerice +IBAN = re.compile(r"RO\s?\d{2}(?:\s?[A-Z0-9]{4}){5}", re.IGNORECASE) + + +def anonimizeaza(): + GOLDEN.mkdir(parents=True, exist_ok=True) + coresp = {"nume": {}, "cod_fiscal": {}, "iban": {}} + raport = [] + + for sursa, tinta in SURSE: + cale = RADACINA / sursa + if not cale.exists(): + raise SystemExit( + "Lipseste %s. Etalonul nu se poate construi - vezi docs/handoff_etapa2_convertor.md" + % sursa + ) + shutil.copy(cale, GOLDEN / tinta) + wb = openpyxl.load_workbook(GOLDEN / tinta) + ws = wb.active + schimbate = 0 + + for rand in range(2, ws.max_row + 1): + if (ws.cell(rand, COL_TIP).value or "").strip().upper() != "FACTURA": + continue + schimbate += 1 + + nume = ws.cell(rand, COL_NUME).value + if nume: + if nume not in coresp["nume"]: + coresp["nume"][nume] = "PARTENER %03d" % (len(coresp["nume"]) + 1) + ws.cell(rand, COL_NUME).value = coresp["nume"][nume] + + cf = ws.cell(rand, COL_COD_FISCAL).value + if cf: + cf = str(cf) + if cf not in coresp["cod_fiscal"]: + coresp["cod_fiscal"][cf] = "CF%06d" % (len(coresp["cod_fiscal"]) + 1) + ws.cell(rand, COL_COD_FISCAL).value = coresp["cod_fiscal"][cf] + + for col in (COL_REG_COM, COL_ADRESA, COL_EXPLICATIA): + if ws.cell(rand, col).value: + ws.cell(rand, col).value = None + + # IBAN-urile apar si in afara randurilor FACTURA (denumirea analiticului de banca) + ibanuri = 0 + for rand in ws.iter_rows(): + for celula in rand: + if not isinstance(celula.value, str) or not IBAN.search(celula.value): + continue + def fals(m, _c=coresp): + real = m.group(0) + if real not in _c["iban"]: + _c["iban"][real] = "RO00 IBAN %04d" % (len(_c["iban"]) + 1) + return _c["iban"][real] + + celula.value = IBAN.sub(fals, celula.value) + ibanuri += 1 + + wb.save(GOLDEN / tinta) + raport.append((tinta, ws.max_row - 1, schimbate, ibanuri)) + + CORESP.write_text(json.dumps(coresp, ensure_ascii=False, indent=1), encoding="utf-8") + + for tinta, randuri, schimbate, ibanuri in raport: + print( + "%-34s %4d randuri, %3d FACTURA anonimizate, %2d celule cu IBAN" + % (tinta, randuri, schimbate, ibanuri) + ) + print( + "%d nume, %d coduri fiscale si %d IBAN-uri inlocuite. Corespondenta: %s (ignorata de git)" + % (len(coresp["nume"]), len(coresp["cod_fiscal"]), len(coresp["iban"]), CORESP.name) + ) + + +if __name__ == "__main__": + anonimizeaza() diff --git a/tests/golden/golden_FUNDATIA_2025_12.xlsx b/tests/golden/golden_FUNDATIA_2025_12.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9bc7f9643b45d6fdcdf08397beb2f60e01a44e54 GIT binary patch literal 10878 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 literal 0 HcmV?d00001