Lane contract: config/conturi_parteneri.csv, divergenta Oracle, poarta de regresie
Setul de conturi cu parteneri iese din literali si trece in config/conturi_parteneri.csv, cu categorie si motiv. Comportamentul e neschimbat; lipsa fisierului opreste cu eroare, nu cade pe set gol. CONFIG_CONT_IREG ramane consultativ: tools/divergenta_parteneri.py raporteaza divergenta, nu o aplica. Constatarea (27 conturi in Oracle si nu la noi, 3 la noi si nu in Oracle - 5121, 5124, 5311) e scrisa in docs/decizii_import.md. tests/test_regresie.py compara iesirea codului de azi cu tests/golden/ pe valori de celula, prin anonimizatorul refolosit ca functie. Trece pe FUNDATIA si MASTER; verificat ca pica la o schimbare reala de set de conturi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
This commit is contained in:
37
config/config_cont_ireg_snapshot.csv
Normal file
37
config/config_cont_ireg_snapshot.csv
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Snapshot consultativ CONFIG_CONT_IREG (MARIUSM_AUTO@ROA_CENTRAL, Oracle local), conturi cu CU_INREGISTRARI = 1.
|
||||||
|
# Construit o singura data la 21.09.2026 din docs/config_cont_ireg.md (sectiunea "Conturi cu CU_INREGISTRARI = 1").
|
||||||
|
# Pentru reimprospatare din Oracle: sqlplus ... @sql/export_config_cont_ireg.sql (suprascrie acest fisier).
|
||||||
|
cont
|
||||||
|
1621
|
||||||
|
167
|
||||||
|
232
|
||||||
|
234
|
||||||
|
261
|
||||||
|
2678
|
||||||
|
2691
|
||||||
|
401
|
||||||
|
403
|
||||||
|
404
|
||||||
|
408
|
||||||
|
409
|
||||||
|
4091
|
||||||
|
4092
|
||||||
|
4093
|
||||||
|
4094
|
||||||
|
4111
|
||||||
|
4118
|
||||||
|
413
|
||||||
|
418
|
||||||
|
419
|
||||||
|
426
|
||||||
|
4511
|
||||||
|
4551
|
||||||
|
456
|
||||||
|
457
|
||||||
|
461
|
||||||
|
462
|
||||||
|
471
|
||||||
|
472
|
||||||
|
4754
|
||||||
|
542
|
||||||
|
8051
|
||||||
|
Can't render this file because it contains an unexpected character in line 2 and column 82.
|
10
config/conturi_parteneri.csv
Normal file
10
config/conturi_parteneri.csv
Normal file
@@ -0,0 +1,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.
|
||||||
|
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.
|
||||||
|
5311,FARA_ANALITICE,Casa in lei: BALANTA plus FACTURA pe sintetic cu partener CENTRALA.
|
||||||
|
@@ -118,3 +118,24 @@ Consecinte:
|
|||||||
ambele randuri cu `acont` gol. Cont cu parteneri **si** analitice in ROA (5121, 5124) ->
|
ambele randuri cu `acont` gol. Cont cu parteneri **si** analitice in ROA (5121, 5124) ->
|
||||||
ambele randuri cu acelasi `acont`.
|
ambele randuri cu acelasi `acont`.
|
||||||
- Latura conteaza si ea: un rand FACTURA pe debit nu atinge randul BALANTA de pe credit.
|
- Latura conteaza si ea: un rand FACTURA pe debit nu atinge randul BALANTA de pe credit.
|
||||||
|
|
||||||
|
## Divergenta fata de CONFIG_CONT_IREG (constatata 21.09.2026)
|
||||||
|
|
||||||
|
Setul operational de conturi cu parteneri este `config/conturi_parteneri.csv` (9 conturi), validat
|
||||||
|
in productie. `CONFIG_CONT_IREG` din Oracle este doar **consultativ**; divergentele se raporteaza,
|
||||||
|
nu se aplica. Comparatia se reface oricand cu:
|
||||||
|
|
||||||
|
```
|
||||||
|
py tools/divergenta_parteneri.py config/config_cont_ireg_snapshot.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Rezultatul de azi (snapshot: conturile cu `CU_INREGISTRARI = 1` din `docs/config_cont_ireg.md`):
|
||||||
|
|
||||||
|
- **In Oracle, nu la noi (27):** `1621, 167, 232, 234, 261, 2678, 2691, 403, 404, 408, 409, 4091,
|
||||||
|
4093, 4094, 4118, 413, 418, 419, 426, 4511, 456, 457, 471, 472, 4754, 542, 8051`
|
||||||
|
- **La noi, nu in Oracle (3):** `5121, 5124, 5311`
|
||||||
|
|
||||||
|
Interpretare: Oracle listeaza conturi de parteneri pe care codul validat nu le trateaza ca parteneri
|
||||||
|
(inclusiv 404, 408, 409, 419 si derivatele 409x). Invers, `5121`, `5124` (banca) si `5311` (casa)
|
||||||
|
sunt tratate ca parteneri de `genereaza_xlsx.py` desi Oracle nu le listeaza (pct. 11-15). Daca vreodata
|
||||||
|
se schimba setul operational, se schimba constient in CSV, nu pe baza acestui export.
|
||||||
|
|||||||
95
docs/raport_lane_contract.md
Normal file
95
docs/raport_lane_contract.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Raport lane `contract` - etapa 2
|
||||||
|
|
||||||
|
## 1. Facut
|
||||||
|
|
||||||
|
### 1.1 `config/conturi_parteneri.csv`
|
||||||
|
- `config/conturi_parteneri.csv:1-10` - 9 conturi, antet exact `cont,categorie,motiv`.
|
||||||
|
Categoriile (`BANCA` / `SINTETIC` / `FARA_ANALITICE`) corespund exact seturilor
|
||||||
|
`PARTENER_BANCA` / `PARTENER_SINTETIC` / `PARTENER_FARA_ANALITICE` de dinainte
|
||||||
|
(`genereaza_xlsx.py:46-50`).
|
||||||
|
|
||||||
|
### 1.2 `genereaza_xlsx.py` (singura modificare permisa)
|
||||||
|
- `genereaza_xlsx.py:48-49` - `CONFIG_PARTENERI`, cale absoluta fata de fisierul scriptului.
|
||||||
|
- `genereaza_xlsx.py:56-70` - `citeste_conturi_parteneri()`: citeste CSV-ul la pornire; fisier
|
||||||
|
lipsa sau categorie necunoscuta -> `Stop` cu mesaj clar (nu cade pe set gol).
|
||||||
|
- `genereaza_xlsx.py:77-78` - cele trei seturi se incarca din CSV la import.
|
||||||
|
- `LATURA_CREDIT` (`:43`) **nu** s-a mutat in CSV - neatins.
|
||||||
|
- Verificat bit-identic: regenerare FUNDATIA/MASTER => 0 diferente de celula fata de
|
||||||
|
`init_FUNDATIA_2025_12.xlsx` / `init_MASTER_2025_12.xlsx` din radacina (76x39, respectiv 81x39).
|
||||||
|
|
||||||
|
### 1.3 Script divergenta fata de Oracle
|
||||||
|
- `sql/export_config_cont_ireg.sql` - `SELECT cont FROM config_cont_ireg WHERE cu_inregistrari = 1`,
|
||||||
|
spool CSV cu o coloana (`CONT`); nume de coloane luate din `docs/config_cont_ireg.md`.
|
||||||
|
- `config/config_cont_ireg_snapshot.csv` - snapshot construit o singura data (21.09.2026) din
|
||||||
|
`docs/config_cont_ireg.md:91`, cu comentariu in antet despre sursa si data.
|
||||||
|
- `tools/divergenta_parteneri.py:17` citeste config-ul, `:30` snapshot-ul (ignora liniile `#` si
|
||||||
|
antetul `cont`/`CONT`), `:51` tipareste cele doua liste. Nu scrie in config, nu se conecteaza la
|
||||||
|
Oracle, doar stdlib.
|
||||||
|
|
||||||
|
### 1.4 `docs/decizii_import.md`
|
||||||
|
- `docs/decizii_import.md:122` - sectiune noua "Divergenta fata de CONFIG_CONT_IREG (constatata
|
||||||
|
21.09.2026)" cu ambele liste complete si comanda de refacere.
|
||||||
|
|
||||||
|
### 1.5 Anonimizator + poarta de regresie
|
||||||
|
- `tests/anonimizeaza_etalon.py:49` `citeste_corespondenta`, `:54` `anonimizeaza_foaie`,
|
||||||
|
`:99` `anonimizeaza_fisier` - functii importabile; `:112` `anonimizeaza()` refoloseste acelasi nucleu.
|
||||||
|
Verificat: golden regenerat din `init_*.xlsx` = golden comis pe valori de celula (0 diferente),
|
||||||
|
tabela de corespondenta neschimbata.
|
||||||
|
- `tests/test_regresie.py:44` `setUpClass` (skip daca lipseste tabela, `:46`), `:78` `_verifica`
|
||||||
|
(comparatie pe valori de celula), `:115/:118` cele doua teste. Scrie xlsx-urile intr-un director
|
||||||
|
temporar, nu peste cele din radacina.
|
||||||
|
|
||||||
|
## 2. Verificari
|
||||||
|
|
||||||
|
- Cens octeti: toate fisierele atinse raman **ASCII pur** (0 octeti > 0x7F inainte si dupa).
|
||||||
|
Nu s-a atins niciun `.vc2/.sc2/.prg`.
|
||||||
|
- Terminatoare de linie: `sql/export_config_cont_ireg.sql` scris **CRLF** (27 linii). Fisierele
|
||||||
|
`.py`/`.csv` LF, ca restul surselor Python existente.
|
||||||
|
- Comanda de test (output exact):
|
||||||
|
|
||||||
|
```
|
||||||
|
> py -m unittest discover -s tests -v
|
||||||
|
test_fundatia (test_regresie.TestRegresieEtalon.test_fundatia) ... ok
|
||||||
|
test_master (test_regresie.TestRegresieEtalon.test_master) ... ok
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 2 tests in 0.416s
|
||||||
|
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
- Comanda de divergenta (output exact):
|
||||||
|
|
||||||
|
```
|
||||||
|
> py tools/divergenta_parteneri.py config/config_cont_ireg_snapshot.csv
|
||||||
|
Conturi comparate: la noi 9, in Oracle 33
|
||||||
|
In Oracle, nu la noi (27): 1621, 167, 232, 234, 261, 2678, 2691, 403, 404, 408, 409, 4091, 4093, 4094, 4118, 413, 418, 419, 426, 4511, 456, 457, 471, 472, 4754, 542, 8051
|
||||||
|
La noi, nu in Oracle (3): 5121, 5124, 5311
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verificat ca mesajul de esec al testului contine fisier, rand, coloana, asteptat si gasit
|
||||||
|
(simulat cu un golden modificat): `g.xlsx: rand 3, coloana 2: asteptat 'TAMPER', gasit 12`.
|
||||||
|
- Verificat ca `SkipTest` se declanseaza cand lipseste `tests/_corespondenta_anonimizare.json`.
|
||||||
|
- Netinse: `init_FUNDATIA_2025_12.xlsx`, `init_MASTER_2025_12.xlsx` (mtime 00:13), `tests/golden/*`,
|
||||||
|
`tests/_corespondenta_anonimizare.json`, `docs/verificare_*.md`, `init_facturi_balanta_note.xlsx`.
|
||||||
|
|
||||||
|
## 3. Divergenta constatata fata de Oracle
|
||||||
|
|
||||||
|
- **In Oracle, nu la noi (27):** `1621, 167, 232, 234, 261, 2678, 2691, 403, 404, 408, 409, 4091,
|
||||||
|
4093, 4094, 4118, 413, 418, 419, 426, 4511, 456, 457, 471, 472, 4754, 542, 8051`
|
||||||
|
- **La noi, nu in Oracle (3):** `5121, 5124, 5311`
|
||||||
|
|
||||||
|
Aceeasi divergenta e scrisa in `docs/decizii_import.md:122`.
|
||||||
|
|
||||||
|
## 4. Nefacut / blocat
|
||||||
|
|
||||||
|
- SQL-ul **nu s-a rulat pe Oracle** (interzis in acest lane). Snapshot-ul e construit din
|
||||||
|
`docs/config_cont_ireg.md`. `tools/divergenta_parteneri.py` citeste si formatul produs de SQL
|
||||||
|
(antet `CONT`), deci refacerea din Oracle nu cere modificari.
|
||||||
|
- Nimic blocat. Singura modificare in `genereaza_xlsx.py` este mutarea seturilor de conturi in CSV.
|
||||||
|
|
||||||
|
## 5. Stare periculoasa
|
||||||
|
|
||||||
|
Niciuna: fara commit / `git add`, fara write-back in binar, fara procese ramase, fisierele
|
||||||
|
`init_*.xlsx` din radacina si etalonul neatinse. Toate iesirile de test sunt intr-un director
|
||||||
|
temporar, sters la finalul testului. Un `bash.exe.stackdump` aparut in timpul sesiunii a fost sters.
|
||||||
@@ -42,13 +42,39 @@ TEMPLATE = "init_facturi_balanta_note.xlsx"
|
|||||||
# conturile din lista merg pe CREDIT, tot restul pe DEBIT.
|
# conturile din lista merg pe CREDIT, tot restul pe DEBIT.
|
||||||
LATURA_CREDIT = {"401", "404", "462", "419", "408", "4551"}
|
LATURA_CREDIT = {"401", "404", "462", "419", "408", "4551"}
|
||||||
|
|
||||||
# 5121/5124: analiticele raman analitice SI primesc partener (BALANTA + FACTURA
|
# Setul operational de conturi cu parteneri: config/conturi_parteneri.csv (validat
|
||||||
# pe acelasi acont).
|
# in productie; CONFIG_CONT_IREG e doar consultativ, vezi docs/decizii_import.md).
|
||||||
PARTENER_BANCA = {"5121", "5124"}
|
# Categoriile CSV corespund celor trei seturi: BANCA, SINTETIC, FARA_ANALITICE.
|
||||||
# Conturi cu parteneri, un singur BALANTA agregat pe sintetic + FACTURA per analitic.
|
CONFIG_PARTENERI = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
PARTENER_SINTETIC = {"401", "4092", "4111", "461", "462", "4551"}
|
"config", "conturi_parteneri.csv")
|
||||||
# Casa: fara analitice, BALANTA + FACTURA pe sintetic.
|
|
||||||
PARTENER_FARA_ANALITICE = {"5311"}
|
|
||||||
|
class Stop(Exception):
|
||||||
|
"""Conditie neprevazuta: nu ghicim, raportam."""
|
||||||
|
|
||||||
|
|
||||||
|
def citeste_conturi_parteneri(path=CONFIG_PARTENERI):
|
||||||
|
"""(BANCA, SINTETIC, FARA_ANALITICE) din CSV; fisier lipsa = eroare, nu set gol."""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise Stop(f"lipseste fisierul de configurare a partenerilor: {path}")
|
||||||
|
seturi = {"BANCA": set(), "SINTETIC": set(), "FARA_ANALITICE": set()}
|
||||||
|
with open(path, encoding="utf-8-sig", newline="") as f:
|
||||||
|
for r in csv.DictReader(f):
|
||||||
|
cont = (r.get("cont") or "").strip()
|
||||||
|
categorie = (r.get("categorie") or "").strip()
|
||||||
|
if not cont:
|
||||||
|
continue
|
||||||
|
if categorie not in seturi:
|
||||||
|
raise Stop(f"categorie necunoscuta in {path}: {categorie!r}")
|
||||||
|
seturi[categorie].add(cont)
|
||||||
|
return seturi["BANCA"], seturi["SINTETIC"], seturi["FARA_ANALITICE"]
|
||||||
|
|
||||||
|
|
||||||
|
# BANCA (5121/5124): analiticele raman analitice SI primesc partener.
|
||||||
|
# SINTETIC: un singur BALANTA agregat pe sintetic + FACTURA per analitic.
|
||||||
|
# FARA_ANALITICE (5311): fara analitice, BALANTA + FACTURA pe sintetic.
|
||||||
|
(PARTENER_BANCA, PARTENER_SINTETIC,
|
||||||
|
PARTENER_FARA_ANALITICE) = citeste_conturi_parteneri()
|
||||||
|
|
||||||
PARTENER = PARTENER_BANCA | PARTENER_SINTETIC | PARTENER_FARA_ANALITICE
|
PARTENER = PARTENER_BANCA | PARTENER_SINTETIC | PARTENER_FARA_ANALITICE
|
||||||
|
|
||||||
@@ -58,10 +84,6 @@ FIRME = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Stop(Exception):
|
|
||||||
"""Conditie neprevazuta: nu ghicim, raportam."""
|
|
||||||
|
|
||||||
|
|
||||||
def citeste_header():
|
def citeste_header():
|
||||||
wb = load_workbook(TEMPLATE, read_only=True)
|
wb = load_workbook(TEMPLATE, read_only=True)
|
||||||
ws = wb.active
|
ws = wb.active
|
||||||
|
|||||||
27
sql/export_config_cont_ireg.sql
Normal file
27
sql/export_config_cont_ireg.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- solduri2roa - export consultativ CONFIG_CONT_IREG
|
||||||
|
-- Schema : MARIUSM_AUTO@ROA_CENTRAL (Oracle local, NU productie)
|
||||||
|
-- Data : 2026-09-21
|
||||||
|
-- Ce face : listeaza conturile cu CU_INREGISTRARI = 1, ca CSV cu o
|
||||||
|
-- singura coloana (cont). Rezultatul e comparat consultativ
|
||||||
|
-- cu config/conturi_parteneri.csv de tools/divergenta_parteneri.py;
|
||||||
|
-- divergentele se RAPORTEAZA, nu se aplica.
|
||||||
|
-- Sursa : docs/config_cont_ireg.md (coloanele CONT, CU_INREGISTRARI).
|
||||||
|
-- Rulare : sqlplus -S "MARIUSM_AUTO/...@ROA_CENTRAL" @sql/export_config_cont_ireg.sql
|
||||||
|
-- Atentie : spool-ul suprascrie config/config_cont_ireg_snapshot.csv (fisierul
|
||||||
|
-- citit de tools/divergenta_parteneri.py). Ruleaza din radacina proiectului.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
set heading on
|
||||||
|
set feedback off
|
||||||
|
set pagesize 500
|
||||||
|
set linesize 100
|
||||||
|
set trimspool on
|
||||||
|
set colsep ,
|
||||||
|
spool config/config_cont_ireg_snapshot.csv
|
||||||
|
select cont
|
||||||
|
from config_cont_ireg
|
||||||
|
where cu_inregistrari = 1
|
||||||
|
order by cont;
|
||||||
|
spool off
|
||||||
|
exit;
|
||||||
@@ -46,6 +46,69 @@ COL_TIP = 5
|
|||||||
IBAN = re.compile(r"RO\s?\d{2}(?:\s?[A-Z0-9]{4}){5}", re.IGNORECASE)
|
IBAN = re.compile(r"RO\s?\d{2}(?:\s?[A-Z0-9]{4}){5}", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def citeste_corespondenta(cale=CORESP):
|
||||||
|
"""Tabela real -> fals folosita la construirea etalonului. NU se regenereaza."""
|
||||||
|
return json.loads(pathlib.Path(cale).read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def anonimizeaza_foaie(ws, coresp):
|
||||||
|
"""Anonimizeaza foaia `ws` in loc, completand tabela `coresp`. NU salveaza.
|
||||||
|
|
||||||
|
Intoarce (randuri FACTURA atinse, celule cu IBAN inlocuite).
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
return schimbate, ibanuri
|
||||||
|
|
||||||
|
|
||||||
|
def anonimizeaza_fisier(sursa, tinta, coresp=None):
|
||||||
|
"""Scrie in `tinta` o copie anonimizata a lui `sursa`, cu aceleasi reguli.
|
||||||
|
|
||||||
|
Tabela implicita e cea comisa (CORESP); nu o regenereaza, doar o citeste.
|
||||||
|
"""
|
||||||
|
if coresp is None:
|
||||||
|
coresp = citeste_corespondenta()
|
||||||
|
wb = openpyxl.load_workbook(sursa)
|
||||||
|
schimbate, ibanuri = anonimizeaza_foaie(wb.active, coresp)
|
||||||
|
wb.save(tinta)
|
||||||
|
return schimbate, ibanuri
|
||||||
|
|
||||||
|
|
||||||
def anonimizeaza():
|
def anonimizeaza():
|
||||||
GOLDEN.mkdir(parents=True, exist_ok=True)
|
GOLDEN.mkdir(parents=True, exist_ok=True)
|
||||||
coresp = {"nume": {}, "cod_fiscal": {}, "iban": {}}
|
coresp = {"nume": {}, "cod_fiscal": {}, "iban": {}}
|
||||||
@@ -61,44 +124,7 @@ def anonimizeaza():
|
|||||||
shutil.copy(cale, GOLDEN / tinta)
|
shutil.copy(cale, GOLDEN / tinta)
|
||||||
wb = openpyxl.load_workbook(GOLDEN / tinta)
|
wb = openpyxl.load_workbook(GOLDEN / tinta)
|
||||||
ws = wb.active
|
ws = wb.active
|
||||||
schimbate = 0
|
schimbate, ibanuri = anonimizeaza_foaie(ws, coresp)
|
||||||
|
|
||||||
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)
|
wb.save(GOLDEN / tinta)
|
||||||
raport.append((tinta, ws.max_row - 1, schimbate, ibanuri))
|
raport.append((tinta, ws.max_row - 1, schimbate, ibanuri))
|
||||||
|
|||||||
123
tests/test_regresie.py
Normal file
123
tests/test_regresie.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
"""Poarta de regresie: codul de conversie de azi trebuie sa reproduca etalonul.
|
||||||
|
|
||||||
|
Ruleaza convertorul pe balantele CSV de pe disc (FUNDATIA, MASTER), scrie xlsx-urile
|
||||||
|
de initializare intr-un director temporar (nu peste cele din radacina), le
|
||||||
|
anonimizeaza cu ACELEASI reguli si ACEIASI tabela ca tests/anonimizeaza_etalon.py si
|
||||||
|
le compara cu tests/golden/ pe VALORI DE CELULA.
|
||||||
|
|
||||||
|
Comparatia nu se face niciodata pe hash de fisier: un xlsx rescris de alta versiune
|
||||||
|
de openpyxl are alti octeti cu acelasi continut.
|
||||||
|
|
||||||
|
Rulare: py -m unittest discover -s tests -v
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import openpyxl
|
||||||
|
|
||||||
|
RADACINA = pathlib.Path(__file__).resolve().parent.parent
|
||||||
|
TESTS = pathlib.Path(__file__).resolve().parent
|
||||||
|
for cale in (str(RADACINA), str(TESTS)):
|
||||||
|
if cale not in sys.path:
|
||||||
|
sys.path.insert(0, cale)
|
||||||
|
|
||||||
|
import genereaza_xlsx as generator
|
||||||
|
import anonimizeaza_etalon as anonim
|
||||||
|
|
||||||
|
CORESP = TESTS / "_corespondenta_anonimizare.json"
|
||||||
|
GOLDEN = TESTS / "golden"
|
||||||
|
|
||||||
|
FIRME = [
|
||||||
|
("FUNDATIA", "balanta_FUNDATIA.csv", "golden_FUNDATIA_2025_12.xlsx"),
|
||||||
|
("MASTER", "balanta_MASTERJOB.csv", "golden_MASTER_2025_12.xlsx"),
|
||||||
|
]
|
||||||
|
|
||||||
|
MAX_DIFERENTE_RAPORTATE = 20
|
||||||
|
|
||||||
|
|
||||||
|
class TestRegresieEtalon(unittest.TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
if not CORESP.exists():
|
||||||
|
raise unittest.SkipTest(
|
||||||
|
"Lipseste %s (e ignorata de git). Etalonul nu se poate reproduce "
|
||||||
|
"fara tabela de corespondenta - vezi docs/handoff_etapa2_convertor.md, "
|
||||||
|
"sectiunea despre etalon." % CORESP
|
||||||
|
)
|
||||||
|
cls.coresp = anonim.citeste_corespondenta(CORESP)
|
||||||
|
|
||||||
|
cls._cwd = os.getcwd()
|
||||||
|
os.chdir(str(RADACINA)) # TEMPLATE si CSV-urile sunt relative la radacina
|
||||||
|
cls._tmp = tempfile.TemporaryDirectory(prefix="regresie_")
|
||||||
|
tmpdir = pathlib.Path(cls._tmp.name)
|
||||||
|
cls.iesiri = {}
|
||||||
|
try:
|
||||||
|
header = generator.citeste_header()
|
||||||
|
for firma, csv_nume, golden_nume in FIRME:
|
||||||
|
rows = generator.citeste_balanta(str(RADACINA / csv_nume))
|
||||||
|
out, _, _, _, _ = generator.genereaza(rows, header)
|
||||||
|
brut = tmpdir / ("init_%s_2025_12.xlsx" % firma)
|
||||||
|
generator.scrie_xlsx(header, out, str(brut))
|
||||||
|
anonimizat = tmpdir / ("anon_%s.xlsx" % firma)
|
||||||
|
anonim.anonimizeaza_fisier(str(brut), str(anonimizat), cls.coresp)
|
||||||
|
cls.iesiri[firma] = (anonimizat, GOLDEN / golden_nume)
|
||||||
|
except Exception:
|
||||||
|
os.chdir(cls._cwd)
|
||||||
|
cls._tmp.cleanup()
|
||||||
|
raise
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
os.chdir(cls._cwd)
|
||||||
|
cls._tmp.cleanup()
|
||||||
|
|
||||||
|
def _verifica(self, firma):
|
||||||
|
anonimizat, golden = self.iesiri[firma]
|
||||||
|
self.assertTrue(golden.exists(), "lipseste etalonul %s" % golden)
|
||||||
|
wb_gasit = openpyxl.load_workbook(anonimizat)
|
||||||
|
wb_asteptat = openpyxl.load_workbook(golden)
|
||||||
|
ws_gasit, ws_asteptat = wb_gasit.active, wb_asteptat.active
|
||||||
|
|
||||||
|
nr_g = ws_gasit.max_row
|
||||||
|
nc_g = ws_gasit.max_column
|
||||||
|
nr_a = ws_asteptat.max_row
|
||||||
|
nc_a = ws_asteptat.max_column
|
||||||
|
self.assertEqual(
|
||||||
|
(nr_g, nc_g), (nr_a, nc_a),
|
||||||
|
"Dimensiuni diferite pentru %s: asteptat %d randuri x %d coloane, "
|
||||||
|
"gasit %d randuri x %d coloane" % (golden.name, nr_a, nc_a, nr_g, nc_g),
|
||||||
|
)
|
||||||
|
|
||||||
|
diferente = []
|
||||||
|
for rand in range(1, max(nr_g, nr_a) + 1):
|
||||||
|
for col in range(1, max(nc_g, nc_a) + 1):
|
||||||
|
gasit = ws_gasit.cell(rand, col).value
|
||||||
|
asteptat = ws_asteptat.cell(rand, col).value
|
||||||
|
if gasit != asteptat:
|
||||||
|
diferente.append((rand, col, asteptat, gasit))
|
||||||
|
|
||||||
|
if diferente:
|
||||||
|
linii = ["%d celule difera fata de %s:" % (len(diferente), golden.name)]
|
||||||
|
for rand, col, asteptat, gasit in diferente[:MAX_DIFERENTE_RAPORTATE]:
|
||||||
|
linii.append(
|
||||||
|
" %s: rand %d, coloana %d: asteptat %r, gasit %r"
|
||||||
|
% (golden.name, rand, col, asteptat, gasit)
|
||||||
|
)
|
||||||
|
if len(diferente) > MAX_DIFERENTE_RAPORTATE:
|
||||||
|
linii.append(" ... si alte %d celule"
|
||||||
|
% (len(diferente) - MAX_DIFERENTE_RAPORTATE))
|
||||||
|
self.fail("\n".join(linii))
|
||||||
|
|
||||||
|
def test_fundatia(self):
|
||||||
|
self._verifica("FUNDATIA")
|
||||||
|
|
||||||
|
def test_master(self):
|
||||||
|
self._verifica("MASTER")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
67
tools/divergenta_parteneri.py
Normal file
67
tools/divergenta_parteneri.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"""Compara setul operational de conturi cu parteneri cu snapshot-ul CONFIG_CONT_IREG.
|
||||||
|
|
||||||
|
RAPORTEAZA divergentele, nu le aplica: nu scrie nimic in config/conturi_parteneri.csv
|
||||||
|
si nu se conecteaza la Oracle. Snapshot-ul vine din sql/export_config_cont_ireg.sql
|
||||||
|
sau, in lipsa unei rulari pe Oracle, din config/config_cont_ireg_snapshot.csv.
|
||||||
|
|
||||||
|
Rulare: py tools/divergenta_parteneri.py config/config_cont_ireg_snapshot.csv
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
RADACINA = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
CONFIG = os.path.join(RADACINA, "config", "conturi_parteneri.csv")
|
||||||
|
|
||||||
|
|
||||||
|
def citeste_config(path=CONFIG):
|
||||||
|
"""Conturile cu parteneri tinute de noi (coloana cont din config)."""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise SystemExit("lipseste configuratorul de parteneri: %s" % path)
|
||||||
|
conturi = set()
|
||||||
|
with open(path, encoding="utf-8-sig", newline="") as f:
|
||||||
|
for r in csv.DictReader(f):
|
||||||
|
cont = (r.get("cont") or "").strip()
|
||||||
|
if cont:
|
||||||
|
conturi.add(cont)
|
||||||
|
return conturi
|
||||||
|
|
||||||
|
|
||||||
|
def citeste_snapshot(path):
|
||||||
|
"""Conturile CU_INREGISTRARI = 1 din snapshot; ignora comentariile si antetul."""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise SystemExit("lipseste snapshot-ul: %s" % path)
|
||||||
|
conturi = set()
|
||||||
|
primul = True
|
||||||
|
with open(path, encoding="utf-8-sig") as f:
|
||||||
|
for linie in f:
|
||||||
|
linie = linie.strip()
|
||||||
|
if not linie or linie.startswith("#"):
|
||||||
|
continue
|
||||||
|
valoare = linie.split(",")[0].strip().strip('"').strip()
|
||||||
|
if primul and valoare.lower() == "cont":
|
||||||
|
primul = False
|
||||||
|
continue
|
||||||
|
primul = False
|
||||||
|
if valoare:
|
||||||
|
conturi.add(valoare)
|
||||||
|
return conturi
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) != 2:
|
||||||
|
raise SystemExit("Utilizare: py tools/divergenta_parteneri.py <snapshot.csv>")
|
||||||
|
noi = citeste_config()
|
||||||
|
oracle = citeste_snapshot(argv[1])
|
||||||
|
doar_oracle = sorted(oracle - noi)
|
||||||
|
doar_noi = sorted(noi - oracle)
|
||||||
|
print("Conturi comparate: la noi %d, in Oracle %d" % (len(noi), len(oracle)))
|
||||||
|
print("In Oracle, nu la noi (%d): %s"
|
||||||
|
% (len(doar_oracle), ", ".join(doar_oracle) if doar_oracle else "(niciunul)"))
|
||||||
|
print("La noi, nu in Oracle (%d): %s"
|
||||||
|
% (len(doar_noi), ", ".join(doar_noi) if doar_noi else "(niciunul)"))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
Reference in New Issue
Block a user