Files
solduri2roa/genereaza.py
Marius Mutu 9a83e367d8 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
2026-09-21 16:42:25 +03:00

128 lines
4.9 KiB
Python

"""Punct de intrare: de la contractul intern la xlsx-ul de import ROA.
Citeste balanta_<FIRMA>.csv, aplica maparea (mapare.py) si scrie:
init_<FIRMA>_<an>_<luna>.xlsx - xlsx-ul de import
mapare_<FIRMA>.xlsx - propunerea completa (doar unealta scrie)
corectii_<FIRMA>.xlsx - creat gol la prima rulare a firmei
docs/verificare_<FIRMA>.md - verificarile obligatorii
Maparea hotaraste cont/acont pentru conturile obisnuite. La conturile cu parteneri
forma lui acont ramane a generatorului: maparea suprascrie doar ce i se da explicit
in corectii_<FIRMA>.xlsx (docs/decizii_import.md pct. 16). Randurile de diferenta
raman calculate in cod.
Daca exista parteneri_<FIRMA>.csv / facturi_<FIRMA>.csv (drumul FDB), se folosesc:
cod fiscal real si un rand FACTURA per document. Lipsa lor = comportamentul de azi
(un FACTURA per partener, sold total).
Utilizare:
py genereaza.py --firma FIRMA --an 2025 --luna 12 [--balanta balanta_FIRMA.csv]
"""
import argparse
import csv
import os
import genereaza_xlsx as generator
import mapare
def _citeste_csv(path):
if not os.path.exists(path):
return []
with open(path, encoding="utf-8-sig", newline="") as f:
return list(csv.DictReader(f))
def _mapare_si_exclusi(rezultat):
"""{cont_saga: {cont, acont}} pentru corectiile explicite + multimea exclusilor."""
override = {}
exclusi = set()
for r in rezultat:
if r["exclus"]:
exclusi.add(r["cont_saga"])
elif r["sursa"] == mapare.SURSA_CORECTAT:
override[r["cont_saga"]] = {"cont": r["cont"], "acont": r["acont"]}
return override, exclusi
def _fara_exclusi(rows, exclusi):
"""Un cont exclus iese cu tot cu subarborele lui (un parinte fara copii ar rupe arborele)."""
if not exclusi:
return rows
out = []
for r in rows:
c = r["cont"]
if any(c == e or c.startswith(e + ".") for e in exclusi):
continue
out.append(r)
return out
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--firma", required=True)
parser.add_argument("--an", type=int, required=True)
parser.add_argument("--luna", type=int, required=True)
parser.add_argument("--balanta",
help="implicit balanta_<FIRMA>.csv din --dir")
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()
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)
if not os.path.exists(balanta):
raise SystemExit("lipseste balanta: %s" % balanta)
header = generator.citeste_header()
if len(header) != 39:
raise SystemExit("cap de tabel neasteptat: %d coloane" % len(header))
rows = generator.citeste_balanta(balanta)
rezultat = mapare.genereaza_mapare(rows, a.firma, a.dir)
override, exclusi = _mapare_si_exclusi(rezultat)
rows = _fara_exclusi(rows, exclusi)
parteneri = _citeste_csv(os.path.join(a.dir, "parteneri_%s.csv" % a.firma))
facturi = _citeste_csv(os.path.join(a.dir, "facturi_%s.csv" % a.firma))
out, diffs, leaf_map, leaf_rep, negative = generator.genereaza(
rows, header, an=a.an, luna=a.luna, mapare=override,
parteneri=parteneri, facturi=facturi)
xlsx_path = os.path.join(a.dir_iesire, "init_%s_%d_%d.xlsx"
% (a.firma, a.an, a.luna))
generator.scrie_xlsx(header, out, xlsx_path)
rez = generator.verifica(a.firma, balanta, xlsx_path, rows, out, diffs,
leaf_map, leaf_rep, negative, header,
dir_verificare=a.dir_iesire)
print("=== %s ===" % a.firma)
print(" rows: %d totdeb=%s totcred=%s net=%s"
% (len(out), rez["totdeb"], rez["totcred"], rez["net"]))
print(" balanta=%s frunze=%s parteneri=%s lungimi=%s"
% ("OK" if rez["bal"] else "ESEC",
"OK" if rez["leaves"] else "ESEC",
"OK" if rez["part"] else "ESEC",
"OK" if rez["lung"] else "ESEC"))
print(" scris %s si %s" % (xlsx_path,
os.path.join(a.dir_iesire,
"verificare_%s.md" % a.firma)))
if rez["negative"]:
print(" analitice cu semn negativ: %s" % (rez["negative"],))
if rez["pierdute"] or rez["dubluri_leaf"] or rez["dubluri_bal"] or rez["lungimi"]:
print(" ATENTIE: %s %s %s %s"
% (rez["pierdute"], rez["dubluri_leaf"], rez["dubluri_bal"],
rez["lungimi"]))
toate = rez["bal"] and rez["leaves"] and rez["part"] and rez["lung"]
raise SystemExit(0 if toate else 1)
if __name__ == "__main__":
main()