Files
solduri2roa/docs/plan_etapa2_convertor.md
Marius Mutu 15bb26ac15 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
2026-09-21 15:44:57 +03:00

14 KiB

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_<FIRMA>_<an>_<luna>.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_<FIRMA>.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_<FIRMA>.csv cont_analitic, cod, denumire, cod_fiscal xlsx (fara cod_fiscal), FDB (complet)
facturi_<FIRMA>.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_<FIRMA>.csv din FURNIZORI + CLIENTI (codul fiscal real vine de aici, deci pe acest drum etapa 5b - cautarea pe ANAF - dispare) si facturi_<FIRMA>.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 <cont>.<analitic> 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_<FIRMA>.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_<FIRMA>.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_<FIRMA>.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_<FIRMA>.xlsx:

  • <sintetic>.TOATE - toate analiticele sinteticului merg pe acelasi cont/acont ROA;
  • <sintetic>.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_<FIRMA>.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.