From b5773c98225b4a28ac37d6c7c2eeb3e411006679 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Sun, 27 Sep 2026 16:43:54 +0300 Subject: [PATCH] Decizii LACERTA: data = ultima zi, 542 cu parteneri, 4428/01, backup .fbk - genereaza_xlsx: coloana data = ultima zi a lunii; etalonul de regresie actualizat doar pe aceasta coloana (verificat celula cu celula). - config: 542 cont cu parteneri (acum identic cu REF); descrierea lui 4092 corectata. - extrage/citire_fdb: .fbk se restaureaza cu gbak -c in temporar; test ca da aceleasi fisiere ca FDB-ul. - test_lacerta: 49/53 frunze identice cu REF, 4428.TOATE -> 4428/01. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2 --- README.md | 10 +++++---- citire_fdb.py | 17 +++++++++++---- config/conturi_parteneri.csv | 3 ++- docs/analiza_lacerta_fdb.md | 24 +++++++++++++--------- docs/decizii_import.md | 13 +++++++++++- extrage.py | 3 ++- genereaza_xlsx.py | 9 ++++---- tests/golden/golden_FUNDATIA_2025_12.xlsx | Bin 10878 -> 10867 bytes tests/golden/golden_MASTER_2025_12.xlsx | Bin 11035 -> 11022 bytes tests/test_citire_fdb.py | 10 +++++++++ tests/test_lacerta.py | 17 ++++++++------- 11 files changed, 71 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index fd8907f..9da4c8c 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ Rezultatul este `iesiri/init_FIRMA_2026_6.xlsx`, care se importa in ROACONT. | sursa | fisier | ce se obtine | |---|---|---| -| SAGA noua (Firebird) | `CONT_BAZA.FDB` (o **copie**, nu baza vie) | balanta + parteneri cu CUI + facturi per document | +| SAGA noua (Firebird) | `CONT_BAZA.FDB` (o **copie**, nu baza vie) sau backup `.fbk` | balanta + parteneri cu CUI + facturi per document | | export SAGA | `.xls` / `.xlsx` | doar balanta | | balanta tiparita | `.pdf` | doar balanta | Tipul se deduce din extensie. **FDB-ul e sursa recomandata.** Clientul face copia dupa `docs/procedura_copie_fdb_client.md`. Poate fi si oprita (`shutdown`): unealta -lucreaza pe o copie a ei si nu atinge fisierul primit. Backupul `.fbk` nu e acceptat -inca. +lucreaza pe o copie a ei si nu atinge fisierul primit. Un `.fbk` se restaureaza +automat in temporar. ## Pasii @@ -54,7 +54,8 @@ Coloane: `cont_saga`, `cont`, `acont`, `exclus`, `motiv`. `cont` are maxim 4 car | `4428.TOATE` | toate analiticele lui 4428 | | `4428.RESTUL` | analiticele lui 4428 fara corectie exacta | -Exemple: `419.NETOPIA -> 419 / 2`, `4428.TOATE -> 4428 / (gol)` = strange tot pe sintetic. +Exemple: `419.NETOPIA -> 419 / 2`, `4428.TOATE -> 4428 / 01` = strange toate analiticele +pe unul singur (lasa `acont` gol doar daca sinteticul nu are si postari directe). Cand un analitic nu incape in 4 caractere, genereaza se opreste si iti cere corectia. ## Reguli de stiut @@ -62,6 +63,7 @@ Cand un analitic nu incape in 4 caractere, genereaza se opreste si iti cere core - Conturile cu parteneri sunt in `config/conturi_parteneri.csv` (autoritatea). Primesc un rand `BALANTA` pe sintetic si randuri `FACTURA` per partener sau document. - In xlsx intra doar frunzele (ultimul nivel de analitic). +- Data soldurilor este ultima zi a lunii (`30.06.2026` pentru `--luna 6`). - Diferenta dintre sintetic si suma analiticelor (note puse direct pe sintetic) iese pe un analitic de diferenta (`9999`, `99`) sau, la parteneri, pe `NEREPARTIZAT` in ROA. - Nu ajusta cifre ca sa treaca o verificare. Raporteaza. diff --git a/citire_fdb.py b/citire_fdb.py index 15eb343..89f9b74 100644 --- a/citire_fdb.py +++ b/citire_fdb.py @@ -22,7 +22,7 @@ colector `%` se exclude. Baza primita se copiaza in temporar si copia se repune online (clientul o poate opri cu `full shutdown` inainte de copiere); fisierul primit nu se atinge. -VERIFICARI: firma de joaca din `D:\SAGA250909\0001` (sinteticele = exportul .xlsx, +VERIFICARI: firma de joaca din `D:/SAGA250909/0001` (sinteticele = exportul .xlsx, la octet) si LACERTA 06/2026 (tests/test_lacerta.py: 48 din 53 de frunze identice cu importul facut din exportul SAGA, restul explicate in docs/analiza_lacerta_fdb.md; facturile per document insumeaza netul fiecarui partener). La un client nou se @@ -43,6 +43,7 @@ from datetime import date FBCLIENT = r"C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll" GFIX = os.path.join(os.path.dirname(FBCLIENT), "gfix.exe") +GBAK = os.path.join(os.path.dirname(FBCLIENT), "gbak.exe") UTILIZATOR = "SYSDBA" PAROLA = "masterkey" CHARSET = "WIN1250" @@ -292,12 +293,20 @@ def scrie_csv(rows, cale, coloane): def copie_online(cale_fdb, dir_tmp): """Copiaza baza in dir_tmp si o repune online, daca clientul a oprit-o inainte de - copiere (`full shutdown`). Fisierul primit nu se atinge. Pe o baza deja online - gfix raspunde cu eroare, inofensiva: daca repunerea chiar esueaza, conectarea - urmatoare pica cu mesajul real (`database ... shutdown`).""" + copiere (`full shutdown`). Un backup `.fbk` se restaureaza cu gbak in dir_tmp. + Fisierul primit nu se atinge. Pe o baza deja online gfix raspunde cu eroare, + inofensiva: daca repunerea chiar esueaza, conectarea urmatoare pica cu mesajul + real (`database ... shutdown`).""" if not os.path.exists(cale_fdb): raise FileNotFoundError("lipseste baza Firebird: %s" % cale_fdb) copie = os.path.join(dir_tmp, "CONT_BAZA.FDB") + if cale_fdb.lower().endswith(".fbk"): + rez = subprocess.run([GBAK, "-c", cale_fdb, copie, "-user", UTILIZATOR, + "-password", PAROLA], capture_output=True, text=True) + if rez.returncode != 0: + raise RuntimeError("restaurarea %s a esuat: %s" + % (cale_fdb, (rez.stderr or rez.stdout).strip())) + return copie shutil.copyfile(cale_fdb, copie) if os.path.exists(GFIX): subprocess.run([GFIX, "-online", "normal", copie, "-user", UTILIZATOR, diff --git a/config/conturi_parteneri.csv b/config/conturi_parteneri.csv index 67db95a..e5e61c8 100644 --- a/config/conturi_parteneri.csv +++ b/config/conturi_parteneri.csv @@ -2,9 +2,10 @@ cont,categorie,motiv 5121,BANCA,Cont bancar in lei: analiticele raman analitice si primesc partener. 5124,BANCA,Cont bancar in valuta: analiticele raman analitice si primesc partener. 401,SINTETIC,Furnizori: BALANTA agregat pe sintetic plus cate un FACTURA per analitic. -4092,SINTETIC,Furnizori de imobilizari: BALANTA agregat plus FACTURA per analitic. +4092,SINTETIC,Furnizori-debitori pt. prestari de servicii: BALANTA agregat plus FACTURA per analitic. 4111,SINTETIC,Clienti: BALANTA agregat plus FACTURA per analitic. 461,SINTETIC,Debitori diversi: BALANTA agregat plus FACTURA per analitic. 462,SINTETIC,Creditori diversi: BALANTA agregat plus FACTURA per analitic. 4551,SINTETIC,Actionari: BALANTA agregat plus FACTURA per analitic. +542,SINTETIC,Avansuri de trezorerie: BALANTA agregat plus FACTURA per persoana (analitic). 5311,FARA_ANALITICE,Casa in lei: BALANTA plus FACTURA pe sintetic cu partener CENTRALA. diff --git a/docs/analiza_lacerta_fdb.md b/docs/analiza_lacerta_fdb.md index 14486b8..960049f 100644 --- a/docs/analiza_lacerta_fdb.md +++ b/docs/analiza_lacerta_fdb.md @@ -118,18 +118,22 @@ P1-P5 sunt facute. Rulare pe LACERTA dupa ele (`tests/test_lacerta.py`): Corectiile folosite (in test, ca in REF): `419.NETOPIA -> 419 / 2`, `4428.TOATE -> 4428`. -## Intrebari deschise +## Intrebari - raspunse 26.09.2026 -1. **4428 iese pe doua randuri**: `4428` fara acont (373,14, analiticele M/TI/TP stranse - prin `.TOATE`) si `4428 / 99` (1.376,54, postarile direct pe sintetic). Nu stiu cum - trateaza importul un cont cu rand si fara acont, si cu acont. Varianta sigura: - corectia `4428.TOATE -> 4428 / 01`, ca sa iasa doua frunze curate. Decizia e a ta. -2. `.fbk`: procedura clientului recomanda backupul `.fbk` ca varianta preferata, dar - `extrage.py` nu il accepta (nu il restaureaza cu `gbak -c`). Deocamdata clientul - trebuie sa trimita `.FDB`. -3. Deciziile de config de mai sus (`542`, `461`, `4092`) si coloana `data`. +1. 4428 -> corectia `4428.TOATE -> 4428 / 01`; doua frunze curate, `01` si `99`. +2. `.fbk` e acceptat: `extrage.py` il restaureaza cu `gbak -c` in temporar + (`tests/test_citire_fdb.py::test_fbk_identic_cu_fdb`). +3. `542` e adaugat in config (acum identic cu REF: BALANTA 2.243,57 / 1.824,07 si 2 FACTURA + care insumeaza 129,44). Descrierea lui `4092` e corectata. +4. Coloana `data` = ultima zi a lunii. + +Frunze BALANTA identice cu REF dupa aceste decizii: **49 din 53**. + +**Ramas deschis:** `461` si `4092` sunt conturi cu parteneri, dar pot avea analitice +functionale sau geografice (`461.SGR` "AMBALAJE SGR", `4092.1`). Azi un astfel de analitic +devine un pseudo-partener cu cod fiscal provizoriu (`461.SGR`). ## Starea pe disc Cod: `citire_fdb.py` (P1-P3), `tests/test_citire_fdb.py` (poarta restransa la sintetice), `tests/test_lacerta.py` (P4), `.gitignore` (P5). Suita: `py -m unittest discover -s tests`, -37 de teste, toate trec. Fisierele LACERTA din `exemple/` sunt ignorate de git. +38 de teste, toate trec. Fisierele LACERTA din `exemple/` sunt ignorate de git. diff --git a/docs/decizii_import.md b/docs/decizii_import.md index 8743c60..a05dfd6 100644 --- a/docs/decizii_import.md +++ b/docs/decizii_import.md @@ -119,9 +119,20 @@ Consecinte: ambele randuri cu acelasi `acont`. - Latura conteaza si ea: un rand FACTURA pe debit nu atinge randul BALANTA de pe credit. +## 17. Decizii din proba LACERTA (26.09.2026) + +- Coloana `data` = **ultima zi a lunii** (inainte, prima zi). Etalonul de regresie a fost + actualizat doar pe aceasta coloana. +- `542` (avansuri de trezorerie) este cont cu parteneri: BALANTA pe sintetic + FACTURA per + persoana. +- Analitice nenumerice stranse pe sintetic (`4428.M/TI/TP` prin `.TOATE`) primesc un acont + (`01`), ca sa nu stea langa analiticul de diferenta (`99`) un rand fara acont. +- `461` si `4092` raman conturi cu parteneri, desi pot avea si analitice functionale sau + geografice (`461.SGR`, `4092.1`). Tratarea acestora e deschisa (docs/analiza_lacerta_fdb.md). + ## Divergenta fata de CONFIG_CONT_IREG (constatata 21.09.2026) -Setul operational de conturi cu parteneri este `config/conturi_parteneri.csv` (9 conturi), validat +Setul operational de conturi cu parteneri este `config/conturi_parteneri.csv` (10 conturi; `542` adaugat 26.09.2026), validat in productie. `CONFIG_CONT_IREG` din Oracle este doar **consultativ**; divergentele se raporteaza, nu se aplica. Comparatia se reface oricand cu: diff --git a/extrage.py b/extrage.py index ee3b17c..23b4ace 100644 --- a/extrage.py +++ b/extrage.py @@ -27,6 +27,7 @@ SURSE_DUPA_EXTENSIE = { ".xls": "xlsx", ".xlsx": "xlsx", ".fdb": "fdb", + ".fbk": "fdb", # backup gbak; se restaureaza in temporar (citire_fdb.copie_online) } @@ -35,7 +36,7 @@ def deduce_sursa(fisier): sursa = SURSE_DUPA_EXTENSIE.get(ext) if sursa is None: raise SystemExit( - "extensie necunoscuta pentru %s: %r (astept .pdf, .xls/.xlsx sau .fdb)" + "extensie necunoscuta pentru %s: %r (astept .pdf, .xls/.xlsx, .fdb sau .fbk)" % (fisier, ext)) return sursa diff --git a/genereaza_xlsx.py b/genereaza_xlsx.py index 5f1cb75..fc07c95 100644 --- a/genereaza_xlsx.py +++ b/genereaza_xlsx.py @@ -166,17 +166,16 @@ def acont_diferenta(parent, kids): return ac -def _data(an, luna): - """Prima zi a lunii, in formatul din sablon (DD.MM.YYYY).""" - return "01.%02d.%04d" % (int(luna), int(an)) - - def _datascad(an, luna): """Ultima zi a lunii, in formatul din sablon (DD.MM.YYYY).""" return "%02d.%02d.%04d" % (monthrange(int(an), int(luna))[1], int(luna), int(an)) +# data soldului = ultima zi a lunii (decizie 27.09.2026; inainte, prima zi) +_data = _datascad + + def rand(header, an, luna, **kw): r = {h: None for h in header} r.update(an=an, luna=luna, data=_data(an, luna), datascad=_datascad(an, luna), diff --git a/tests/golden/golden_FUNDATIA_2025_12.xlsx b/tests/golden/golden_FUNDATIA_2025_12.xlsx index 9bc7f9643b45d6fdcdf08397beb2f60e01a44e54..3942651e89d6596dfde0213b2c5b4bea375e14dc 100644 GIT binary patch delta 6312 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 G@&5&&3jI<5 diff --git a/tests/golden/golden_MASTER_2025_12.xlsx b/tests/golden/golden_MASTER_2025_12.xlsx index 37d93cd9c5f59f1d622eb0ec59ecdb2241be47b5..6ad489dd750f60957b39b367675464feb9e9b804 100644 GIT binary patch delta 6249 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 MHz!dy{2%B40MmoL7XSbN diff --git a/tests/test_citire_fdb.py b/tests/test_citire_fdb.py index 54b0da0..04bfd6d 100644 --- a/tests/test_citire_fdb.py +++ b/tests/test_citire_fdb.py @@ -11,6 +11,7 @@ sar, nu esueaza. Rulare: py -m unittest discover -s tests -v """ import os +import subprocess import pathlib import shutil import sys @@ -110,6 +111,15 @@ class TestCitireFdb(unittest.TestCase): # singura factura din firma de joaca are SCADENT NULL in INTRD self.assertEqual(rows[0]["scadent"], asteptat) + def test_fbk_identic_cu_fdb(self): + """Backupul gbak (.fbk) se restaureaza in temporar si da aceleasi fisiere.""" + fbk = os.path.join(self.dir, "firma.fbk") + subprocess.run([citire_fdb.GBAK, "-b", self.baza, fbk, "-user", + citire_fdb.UTILIZATOR, "-password", citire_fdb.PAROLA], check=True) + a = citire_fdb.produce(self.baza, "A", AN, LUNA, os.path.join(self.dir, "a")) + b = citire_fdb.produce(fbk, "B", AN, LUNA, os.path.join(self.dir, "b")) + self.assertEqual(a, b) + def test_cont_colector_exclus(self): rows = citire_fdb.citeste_balanta(self.baza, AN, LUNA) self.assertNotIn("%", [r["cont"] for r in rows]) diff --git a/tests/test_lacerta.py b/tests/test_lacerta.py index 168286f..5fd2d02 100644 --- a/tests/test_lacerta.py +++ b/tests/test_lacerta.py @@ -37,7 +37,8 @@ AN, LUNA = 2026, 6 # deciziile de mapare din REF, pentru analiticele care nu intra in acont CORECTII = [ ["419.NETOPIA", "419", "2", None, "ca in REF"], - ["4428.TOATE", "4428", None, None, "ca in REF"], + # REF are un singur rand 4428; 01 ca sa iasa frunze curate langa diferenta 4428/99 + ["4428.TOATE", "4428", "01", None, "decizie 26.09.2026"], ] # frunza -> (REF, NOI), cu (totdeb, totcred); None = frunza lipseste @@ -49,18 +50,15 @@ DIFERENTE_ASTEPTATE = { ("4092", "1"): ((2606.86, 0.0), None), # nota de 900 direct pe 419: analitic de diferenta; REF a pierdut-o ("419", "9999"): (None, (0.0, 900.0)), - # 4428: postarile direct pe sintetic -> analitic de diferenta; totalul coincide - ("4428", ""): ((1749.68, 0.0), (373.14, 0.0)), + # 4428: analiticele M/TI/TP pe 01 (corectie), postarile direct pe sintetic pe 99; + # totalul coincide cu randul unic din REF + ("4428", ""): ((1749.68, 0.0), None), + ("4428", "01"): (None, (373.14, 0.0)), ("4428", "99"): (None, (1376.54, 0.0)), # 5121: regula de banca (BALANTA cu acont per cont bancar), validata la MASTER ("5121", ""): ((40801.41, 35876.25), None), ("5121", "1"): (None, (40601.41, 35876.25)), ("5121", "2"): (None, (200.0, 0.0)), - # 542 nu e cont cu parteneri in config: analitice + diferenta - ("542", ""): ((2243.57, 1824.07), None), - ("542", "01"): (None, (1828.77, 1824.07)), - ("542", "02"): (None, (124.74, 0.0)), - ("542", "99"): (None, (290.06, 0.0)), # 891 are rulaj zero; REF nu-l scrie ("891", ""): (None, (0.0, 0.0)), } @@ -148,7 +146,8 @@ class TestLacerta(unittest.TestCase): s[str(r["cont"])] += _v(r["sold"]) self.assertEqual({k: (c[k], round(s[k], 2)) for k in c}, { "401": (12, 9220.75), "4092": (1, 2606.86), "4111": (1, 90.0), - "4551": (3, 3023583.0), "461": (1, 2250.0), "5121": (2, 4925.16)}) + "4551": (3, 3023583.0), "461": (1, 2250.0), "5121": (2, 4925.16), + "542": (2, 129.44)}) if __name__ == "__main__":