Muta datele din radacina in date/, iesiri/ si sablon/

date/    - intrari de la client (PDF-urile, exportul SAGA) si balanta intermediara
iesiri/  - xlsx-urile generate si verificare_<FIRMA>.md (ignorat de git)
sablon/  - sablonul ROA

Punctele de intrare scriu acolo implicit: extrage.py --dir=date, genereaza.py
citeste din date/ si scrie in iesiri/ (--dir-iesire), ca radacina sa nu se
murdareasca la fiecare rulare. Sablonul e cautat langa script, in sablon/.

Actualizate: TEMPLATE, cele trei teste care tin caile, .gitignore, CLAUDE.md si
handoff-ul. Poarta capat-la-capat incepuse sa sara tacut dupa mutarea PDF-urilor
(skipTest pe "lipsesc PDF-urile"); acum arata din nou catre date/ si ruleaza.
33 de teste trec, zero skip-uri.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
This commit is contained in:
2026-09-21 16:42:25 +03:00
parent c6c6ebe1fe
commit 9a83e367d8
13 changed files with 48 additions and 22 deletions

4
.gitignore vendored
View File

@@ -3,11 +3,13 @@ __pycache__/
.gstack/ .gstack/
# iesiri si date de firme de productie - raman pe disc, nu in git # iesiri si date de firme de productie - raman pe disc, nu in git
# (docs/verificare_*.md raman in git: sunt dovada importurilor validate in productie)
iesiri/
init_*.xlsx init_*.xlsx
export_*.xlsx export_*.xlsx
mapare_*.xlsx mapare_*.xlsx
balanta_*.csv balanta_*.csv
!init_facturi_balanta_note.xlsx !sablon/init_facturi_balanta_note.xlsx
# etalonul de regresie e anonimizat si se comite; tabela de corespondenta NU # etalonul de regresie e anonimizat si se comite; tabela de corespondenta NU
tests/_corespondenta_anonimizare.json tests/_corespondenta_anonimizare.json

View File

@@ -18,6 +18,9 @@ Convertor balante SAGA (PDF) -> xlsx de initializare solduri ROACONT.
| `citire_fdb.py` | CONT_BAZA.FDB -> balanta + parteneri + facturi (verificat doar pe sintetic) | | `citire_fdb.py` | CONT_BAZA.FDB -> balanta + parteneri + facturi (verificat doar pe sintetic) |
| `mapare.py` | propunere automata + `corectii_<FIRMA>.xlsx`, .TOATE/.RESTUL | | `mapare.py` | propunere automata + `corectii_<FIRMA>.xlsx`, .TOATE/.RESTUL |
| `config/conturi_parteneri.csv` | **conturile cu parteneri** - autoritatea, nu CONFIG_CONT_IREG | | `config/conturi_parteneri.csv` | **conturile cu parteneri** - autoritatea, nu CONFIG_CONT_IREG |
| `date/` | intrari de la client (PDF, export SAGA) + balanta intermediara |
| `iesiri/` | xlsx-urile generate + `verificare_<FIRMA>.md` (ignorat de git) |
| `sablon/init_facturi_balanta_note.xlsx` | sablonul ROA (rand 1 = coloane) |
| `genereaza_xlsx.py` | CSV -> `init_<FIRMA>_<an>_<luna>.xlsx` | | `genereaza_xlsx.py` | CSV -> `init_<FIRMA>_<an>_<luna>.xlsx` |
| `init_facturi_balanta_note.xlsx` | sablonul ROA (rand 1 = coloane, 2-13 instructiuni, 20+ exemple) | | `init_facturi_balanta_note.xlsx` | sablonul ROA (rand 1 = coloane, 2-13 instructiuni, 20+ exemple) |
| `docs/decizii_import.md` | **regulile de mapare - citeste intai** | | `docs/decizii_import.md` | **regulile de mapare - citeste intai** |

View File

@@ -24,6 +24,7 @@ Actualizat 21.09.2026, la finalul executiei. Versiunea anterioara a acestui fisi
| `tests/` | 33 de teste, `unittest` din stdlib | | `tests/` | 33 de teste, `unittest` din stdlib |
| `docs/procedura_copie_fdb_client.md` | cum isi face clientul copia bazei | | `docs/procedura_copie_fdb_client.md` | cum isi face clientul copia bazei |
| `docs/raport_lane_*.md` | raportul fiecarui lane | | `docs/raport_lane_*.md` | raportul fiecarui lane |
| `date/`, `iesiri/`, `sablon/` | intrari de la client / xlsx-uri generate / sablonul ROA |
Commit-uri: `15bb26a` (plan + etalon), `f23b336` (contract), `ba4cbbf` (citire-xlsx + mapare), Commit-uri: `15bb26a` (plan + etalon), `f23b336` (contract), `ba4cbbf` (citire-xlsx + mapare),
`6b37d37` (citire-fdb), `82511c8` (integrare), plus commit-ul final de regresie. `6b37d37` (citire-fdb), `82511c8` (integrare), plus commit-ul final de regresie.
@@ -84,15 +85,23 @@ de pe 4296 a fost oprit), `D:\ROA\ROACONT`, `D:\SAGA250909` si `saga2roa*` neati
joaca deschisa numai prin copie in temporar, `tests/_corespondenta_anonimizare.json` ramasa ignorata joaca deschisa numai prin copie in temporar, `tests/_corespondenta_anonimizare.json` ramasa ignorata
de git. de git.
Fisierele `init_FUNDATIA_2025_12.xlsx` si `init_MASTER_2025_12.xlsx` sunt pe disc dar nu in git; Fisierele `iesiri/init_FUNDATIA_2025_12.xlsx` si `iesiri/init_MASTER_2025_12.xlsx` sunt pe
sunt necesare doar daca vrei sa **regenerezi** etalonul (`py tests\anonimizeaza_etalon.py`). disc dar nu in git; sunt necesare doar daca vrei sa **regenerezi** etalonul
(`py tests\anonimizeaza_etalon.py`, care le citeste din `iesiri/`).
## 8. Cum rulezi acum unealta ## 8. Cum rulezi acum unealta
``` ```
py extrage.py --fisier "IBB 31.12.2025.pdf" --firma FUNDATIA --an 2025 --luna 12 py extrage.py --fisier "date\IBB 31.12.2025.pdf" --firma FUNDATIA --an 2025 --luna 12
py genereaza.py --firma FUNDATIA --an 2025 --luna 12 py genereaza.py --firma FUNDATIA --an 2025 --luna 12
``` ```
Implicit `extrage.py` scrie in `date/`, iar `genereaza.py` citeste din `date/` si scrie in
`iesiri/` (`--dir` / `--dir-iesire` le schimba). Sablonul e in `sablon/`.
**Capcana de nume**: balanta firmei MASTER se numeste `balanta_MASTERJOB.csv`, nu
`balanta_MASTER.csv`, deci pentru ea trebuie dat explicit
`--balanta date\balanta_MASTERJOB.csv`. Eroarea e clara, nu tacuta.
Sursa se deduce din extensie (`.pdf`, `.xls`/`.xlsx`, `.fdb`); o extensie necunoscuta da eroare. Sursa se deduce din extensie (`.pdf`, `.xls`/`.xlsx`, `.fdb`); o extensie necunoscuta da eroare.
Pentru Firebird, da calea catre o **copie** a bazei, niciodata baza vie a clientului. Pentru Firebird, da calea catre o **copie** a bazei, niciodata baza vie a clientului.

View File

@@ -67,7 +67,8 @@ def main():
parser.add_argument("--firma", required=True) parser.add_argument("--firma", required=True)
parser.add_argument("--an", type=int) parser.add_argument("--an", type=int)
parser.add_argument("--luna", type=int) parser.add_argument("--luna", type=int)
parser.add_argument("--dir", default=".", help="directorul de iesire") parser.add_argument("--dir", default="date",
help="unde se scrie contractul intern (implicit date/)")
a = parser.parse_args() a = parser.parse_args()
sursa = a.sursa or deduce_sursa(a.fisier) sursa = a.sursa or deduce_sursa(a.fisier)

View File

@@ -66,10 +66,14 @@ def main():
parser.add_argument("--luna", type=int, required=True) parser.add_argument("--luna", type=int, required=True)
parser.add_argument("--balanta", parser.add_argument("--balanta",
help="implicit balanta_<FIRMA>.csv din --dir") help="implicit balanta_<FIRMA>.csv din --dir")
parser.add_argument("--dir", default=".", help="directorul de iesire") parser.add_argument("--dir", default="date",
help="de unde se citeste balanta (implicit date/)")
parser.add_argument("--dir-iesire", dest="dir_iesire", default="iesiri",
help="unde se scriu xlsx-urile (implicit iesiri/)")
a = parser.parse_args() a = parser.parse_args()
os.makedirs(a.dir, exist_ok=True) os.makedirs(a.dir, exist_ok=True)
os.makedirs(a.dir_iesire, exist_ok=True)
balanta = a.balanta or os.path.join(a.dir, "balanta_%s.csv" % a.firma) balanta = a.balanta or os.path.join(a.dir, "balanta_%s.csv" % a.firma)
if not os.path.exists(balanta): if not os.path.exists(balanta):
raise SystemExit("lipseste balanta: %s" % balanta) raise SystemExit("lipseste balanta: %s" % balanta)
@@ -90,12 +94,12 @@ def main():
rows, header, an=a.an, luna=a.luna, mapare=override, rows, header, an=a.an, luna=a.luna, mapare=override,
parteneri=parteneri, facturi=facturi) parteneri=parteneri, facturi=facturi)
xlsx_path = os.path.join(a.dir, "init_%s_%d_%d.xlsx" xlsx_path = os.path.join(a.dir_iesire, "init_%s_%d_%d.xlsx"
% (a.firma, a.an, a.luna)) % (a.firma, a.an, a.luna))
generator.scrie_xlsx(header, out, xlsx_path) generator.scrie_xlsx(header, out, xlsx_path)
rez = generator.verifica(a.firma, balanta, xlsx_path, rows, out, diffs, rez = generator.verifica(a.firma, balanta, xlsx_path, rows, out, diffs,
leaf_map, leaf_rep, negative, header, leaf_map, leaf_rep, negative, header,
dir_verificare=os.path.join(a.dir, "docs")) dir_verificare=a.dir_iesire)
print("=== %s ===" % a.firma) print("=== %s ===" % a.firma)
print(" rows: %d totdeb=%s totcred=%s net=%s" print(" rows: %d totdeb=%s totcred=%s net=%s"
@@ -106,7 +110,7 @@ def main():
"OK" if rez["part"] else "ESEC", "OK" if rez["part"] else "ESEC",
"OK" if rez["lung"] else "ESEC")) "OK" if rez["lung"] else "ESEC"))
print(" scris %s si %s" % (xlsx_path, print(" scris %s si %s" % (xlsx_path,
os.path.join(a.dir, "docs", os.path.join(a.dir_iesire,
"verificare_%s.md" % a.firma))) "verificare_%s.md" % a.firma)))
if rez["negative"]: if rez["negative"]:
print(" analitice cu semn negativ: %s" % (rez["negative"],)) print(" analitice cu semn negativ: %s" % (rez["negative"],))

View File

@@ -37,7 +37,7 @@ VALUTA = None
# Sablonul sta langa script, nu langa directorul de lucru: iesirile pot merge in temp. # Sablonul sta langa script, nu langa directorul de lucru: iesirile pot merge in temp.
TEMPLATE = os.path.join(os.path.dirname(os.path.abspath(__file__)), TEMPLATE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"init_facturi_balanta_note.xlsx") "sablon", "init_facturi_balanta_note.xlsx")
# Latura pe care formularul pune randurile FACTURA (frm...sc2:379/'383'): # Latura pe care formularul pune randurile FACTURA (frm...sc2:379/'383'):
# conturile din lista merg pe CREDIT, tot restul pe DEBIT. # conturile din lista merg pe CREDIT, tot restul pe DEBIT.

View File

@@ -115,10 +115,10 @@ def anonimizeaza():
raport = [] raport = []
for sursa, tinta in SURSE: for sursa, tinta in SURSE:
cale = RADACINA / sursa cale = RADACINA / "iesiri" / sursa
if not cale.exists(): if not cale.exists():
raise SystemExit( raise SystemExit(
"Lipseste %s. Etalonul nu se poate construi - vezi docs/handoff_etapa2_convertor.md" "Lipseste iesiri/%s. Etalonul nu se poate construi - vezi docs/handoff_etapa2_convertor.md"
% sursa % sursa
) )
shutil.copy(cale, GOLDEN / tinta) shutil.copy(cale, GOLDEN / tinta)

View File

@@ -48,10 +48,15 @@ def _driver_disponibil():
def _ruleaza(script, arg, dir_iesire): def _ruleaza(script, arg, dir_iesire):
"""Ruleaza un punct de intrare cu `--dir` in directorul temporar si verificat.""" """Ruleaza un punct de intrare cu `--dir` in directorul temporar si verificat.
genereaza.py scrie implicit in iesiri/; in test totul sta in acelasi temporar.
"""
iesire = ["--dir-iesire", str(dir_iesire)] if script.name == "genereaza.py" else []
return subprocess.run( return subprocess.run(
[sys.executable, str(script), "--firma", FIRMA, [sys.executable, str(script), "--firma", FIRMA,
"--an", str(AN), "--luna", str(LUNA), "--dir", str(dir_iesire)] + arg, "--an", str(AN), "--luna", str(LUNA), "--dir", str(dir_iesire)]
+ iesire + arg,
cwd=str(dir_iesire), capture_output=True, text=True) cwd=str(dir_iesire), capture_output=True, text=True)

View File

@@ -58,7 +58,7 @@ class TestRegresieEtalon(unittest.TestCase):
try: try:
header = generator.citeste_header() header = generator.citeste_header()
for firma, csv_nume, golden_nume in FIRME: for firma, csv_nume, golden_nume in FIRME:
rows = generator.citeste_balanta(str(RADACINA / csv_nume)) rows = generator.citeste_balanta(str(RADACINA / "date" / csv_nume))
out, _, _, _, _ = generator.genereaza(rows, header) out, _, _, _, _ = generator.genereaza(rows, header)
brut = tmpdir / ("init_%s_2025_12.xlsx" % firma) brut = tmpdir / ("init_%s_2025_12.xlsx" % firma)
generator.scrie_xlsx(header, out, str(brut)) generator.scrie_xlsx(header, out, str(brut))

View File

@@ -12,7 +12,7 @@ reguli si ACEIASI tabela ca tests/anonimizeaza_etalon.py si se compara cu
tests/golden/ pe VALORI DE CELULA, nu pe hash de fisier. tests/golden/ pe VALORI DE CELULA, nu pe hash de fisier.
Asocierea PDF -> firma nu se presupune: fiecare balanta extrasa se compara cu Asocierea PDF -> firma nu se presupune: fiecare balanta extrasa se compara cu
balanta de referinta din radacina (balanta_FUNDATIA.csv / balanta_MASTERJOB.csv), balanta de referinta din date/ (balanta_FUNDATIA.csv / balanta_MASTERJOB.csv),
care e cea folosita la constructia etalonului. care e cea folosita la constructia etalonului.
Daca iesirea difera de etalon, etalonul are dreptate: se raporteaza, nu se ajusteaza. Daca iesirea difera de etalon, etalonul are dreptate: se raporteaza, nu se ajusteaza.
@@ -39,13 +39,14 @@ import anonimizeaza_etalon as anonim
CORESP = TESTS / "_corespondenta_anonimizare.json" CORESP = TESTS / "_corespondenta_anonimizare.json"
GOLDEN = TESTS / "golden" GOLDEN = TESTS / "golden"
DATE = RADACINA / "date"
EXTRAGE = RADACINA / "extrage.py" EXTRAGE = RADACINA / "extrage.py"
GENEREAZA = RADACINA / "genereaza.py" GENEREAZA = RADACINA / "genereaza.py"
AN, LUNA = 2025, 12 AN, LUNA = 2025, 12
MAX_DIFERENTE_RAPORTATE = 20 MAX_DIFERENTE_RAPORTATE = 20
# PDF din radacina -> firma -> balanta de referinta din radacina -> etalon. # PDF din date/ -> firma -> balanta de referinta din date/ -> etalon.
# Asocierea e verificata de test_pdf_corespunde_referintei, nu doar afirmata. # Asocierea e verificata de test_pdf_corespunde_referintei, nu doar afirmata.
FIRME = [ FIRME = [
("FUNDATIA", "IBB 31.12.2025.pdf", "balanta_FUNDATIA.csv", ("FUNDATIA", "IBB 31.12.2025.pdf", "balanta_FUNDATIA.csv",
@@ -100,9 +101,9 @@ class TestRegresieCapatLaCapat(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
lipsa = [pdf for _, pdf, _, _ in FIRME if not (RADACINA / pdf).exists()] lipsa = [pdf for _, pdf, _, _ in FIRME if not (DATE / pdf).exists()]
if lipsa: if lipsa:
raise unittest.SkipTest("lipsesc PDF-urile din radacina: %s" raise unittest.SkipTest("lipsesc PDF-urile din date/: %s"
% ", ".join(lipsa)) % ", ".join(lipsa))
if not CORESP.exists(): if not CORESP.exists():
raise unittest.SkipTest( raise unittest.SkipTest(
@@ -116,14 +117,15 @@ class TestRegresieCapatLaCapat(unittest.TestCase):
cls.iesiri = {} cls.iesiri = {}
try: try:
for firma, pdf, _, _ in FIRME: for firma, pdf, _, _ in FIRME:
_ruleaza(EXTRAGE, ["--sursa", "pdf", "--fisier", str(RADACINA / pdf), _ruleaza(EXTRAGE, ["--sursa", "pdf", "--fisier", str(DATE / pdf),
"--firma", firma, "--an", AN, "--luna", LUNA, "--firma", firma, "--an", AN, "--luna", LUNA,
"--dir", str(tmpdir)], tmpdir) "--dir", str(tmpdir)], tmpdir)
cls.balante[firma] = tmpdir / ("balanta_%s.csv" % firma) cls.balante[firma] = tmpdir / ("balanta_%s.csv" % firma)
for firma, _, _, _ in FIRME: for firma, _, _, _ in FIRME:
_ruleaza(GENEREAZA, ["--firma", firma, "--an", AN, "--luna", LUNA, _ruleaza(GENEREAZA, ["--firma", firma, "--an", AN, "--luna", LUNA,
"--dir", str(tmpdir)], tmpdir) "--dir", str(tmpdir),
"--dir-iesire", str(tmpdir)], tmpdir)
for firma, _, _, golden_nume in FIRME: for firma, _, _, golden_nume in FIRME:
brut = tmpdir / ("init_%s_%d_%d.xlsx" % (firma, AN, LUNA)) brut = tmpdir / ("init_%s_%d_%d.xlsx" % (firma, AN, LUNA))
@@ -140,7 +142,7 @@ class TestRegresieCapatLaCapat(unittest.TestCase):
def _verifica_referinta(self, firma): def _verifica_referinta(self, firma):
"""Balanta extrasa din PDF = balanta de referinta (verifica asocierea PDF->firma).""" """Balanta extrasa din PDF = balanta de referinta (verifica asocierea PDF->firma)."""
referinta = RADACINA / dict( referinta = DATE / dict(
(f, r) for f, _, r, _ in FIRME)[firma] (f, r) for f, _, r, _ in FIRME)[firma]
if not referinta.exists(): if not referinta.exists():
self.skipTest("lipseste referinta %s" % referinta) self.skipTest("lipseste referinta %s" % referinta)