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
249 lines
14 KiB
Markdown
249 lines
14 KiB
Markdown
# 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.
|