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
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 tabeleiconturi_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-50trateaza ca parteneri5121,5124(banca, cu analitic) si5311(casa, fara analitic);- in
docs/config_cont_ireg.md:89(listaCU_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 acelasicont/acontROA;<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
acontpentru conturile cu parteneri depinde de categorie:401cereacontgol pe ambele randuri,5121cereacontcompletat identic pe BALANTA si FACTURA (decizii_import.mdpct. 16). O regula generica de pre-completare ar puneacontpe401-> 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
.FDBreala + 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 (
gbakda o copie consistenta si mai mica decat fisierul brut) si ca trebuie sa opreasca SAGA daca trimite fisierul direct. Se scrie odata cu lane-ulcitire-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.mdC.1), deci cere credentiale de la client.