diff --git a/config/config_cont_ireg_snapshot.csv b/config/config_cont_ireg_snapshot.csv new file mode 100644 index 0000000..7d4f262 --- /dev/null +++ b/config/config_cont_ireg_snapshot.csv @@ -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 diff --git a/config/conturi_parteneri.csv b/config/conturi_parteneri.csv new file mode 100644 index 0000000..67db95a --- /dev/null +++ b/config/conturi_parteneri.csv @@ -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. diff --git a/docs/decizii_import.md b/docs/decizii_import.md index a8e7266..8743c60 100644 --- a/docs/decizii_import.md +++ b/docs/decizii_import.md @@ -118,3 +118,24 @@ Consecinte: ambele randuri cu `acont` gol. Cont cu parteneri **si** analitice in ROA (5121, 5124) -> ambele randuri cu acelasi `acont`. - 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. diff --git a/docs/raport_lane_contract.md b/docs/raport_lane_contract.md new file mode 100644 index 0000000..5cf682c --- /dev/null +++ b/docs/raport_lane_contract.md @@ -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. diff --git a/genereaza_xlsx.py b/genereaza_xlsx.py index 7fb410d..86cce46 100644 --- a/genereaza_xlsx.py +++ b/genereaza_xlsx.py @@ -42,13 +42,39 @@ TEMPLATE = "init_facturi_balanta_note.xlsx" # conturile din lista merg pe CREDIT, tot restul pe DEBIT. LATURA_CREDIT = {"401", "404", "462", "419", "408", "4551"} -# 5121/5124: analiticele raman analitice SI primesc partener (BALANTA + FACTURA -# pe acelasi acont). -PARTENER_BANCA = {"5121", "5124"} -# Conturi cu parteneri, un singur BALANTA agregat pe sintetic + FACTURA per analitic. -PARTENER_SINTETIC = {"401", "4092", "4111", "461", "462", "4551"} -# Casa: fara analitice, BALANTA + FACTURA pe sintetic. -PARTENER_FARA_ANALITICE = {"5311"} +# Setul operational de conturi cu parteneri: config/conturi_parteneri.csv (validat +# in productie; CONFIG_CONT_IREG e doar consultativ, vezi docs/decizii_import.md). +# Categoriile CSV corespund celor trei seturi: BANCA, SINTETIC, FARA_ANALITICE. +CONFIG_PARTENERI = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "config", "conturi_parteneri.csv") + + +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 @@ -58,10 +84,6 @@ FIRME = [ ] -class Stop(Exception): - """Conditie neprevazuta: nu ghicim, raportam.""" - - def citeste_header(): wb = load_workbook(TEMPLATE, read_only=True) ws = wb.active diff --git a/sql/export_config_cont_ireg.sql b/sql/export_config_cont_ireg.sql new file mode 100644 index 0000000..f6bcc64 --- /dev/null +++ b/sql/export_config_cont_ireg.sql @@ -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; diff --git a/tests/anonimizeaza_etalon.py b/tests/anonimizeaza_etalon.py index e61e877..640c29f 100644 --- a/tests/anonimizeaza_etalon.py +++ b/tests/anonimizeaza_etalon.py @@ -46,6 +46,69 @@ COL_TIP = 5 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(): GOLDEN.mkdir(parents=True, exist_ok=True) coresp = {"nume": {}, "cod_fiscal": {}, "iban": {}} @@ -61,44 +124,7 @@ def anonimizeaza(): shutil.copy(cale, GOLDEN / tinta) wb = openpyxl.load_workbook(GOLDEN / tinta) ws = wb.active - 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 + schimbate, ibanuri = anonimizeaza_foaie(ws, coresp) wb.save(GOLDEN / tinta) raport.append((tinta, ws.max_row - 1, schimbate, ibanuri)) diff --git a/tests/test_regresie.py b/tests/test_regresie.py new file mode 100644 index 0000000..1a6a6e8 --- /dev/null +++ b/tests/test_regresie.py @@ -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() diff --git a/tools/divergenta_parteneri.py b/tools/divergenta_parteneri.py new file mode 100644 index 0000000..52c10b5 --- /dev/null +++ b/tools/divergenta_parteneri.py @@ -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 ") + 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))