"""Poarta pe o firma reala cu analitice: LACERTA, drumul FDB, 06/2026. Ruleaza ca subprocese, pornind de la copia bazei primita de la client (in stare `full shutdown`, deci exerseaza si repunerea online pe copia temporara): py extrage.py --fisier exemple/lacerta_CONT_BAZA-20-08_2026.FDB --firma LACERTA --an 2026 --luna 6 py genereaza.py --firma LACERTA --an 2026 --luna 6 si compara frunzele BALANTA cu importul facut din exportul SAGA (REF, in exemple/). Analiza completa: docs/analiza_lacerta_fdb.md. REF nu e un etalon curat. Diferentele de mai jos sunt explicate acolo si scrise aici explicit; orice alta diferenta pica testul. Nu se adauga exceptii ca sa treaca. Fisierele clientului nu sunt in git (nume, CNP-uri): lipsa lor = test sarit. """ import collections import pathlib import subprocess import sys import tempfile import unittest import openpyxl RADACINA = pathlib.Path(__file__).resolve().parent.parent if str(RADACINA) not in sys.path: sys.path.insert(0, str(RADACINA)) import mapare EXEMPLE = RADACINA / "exemple" BAZA = EXEMPLE / "lacerta_CONT_BAZA-20-08_2026.FDB" REF = EXEMPLE / "lacerta_init_facturi_balanta_note-20-08_2026.xlsx" 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"], # 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"], # analitic functional pe cont cu parteneri: ramane analitic (decizie 27.09.2026). # 461.SGR exista in plan, dar fara note: 461 e postat doar pe sintetic. ["4092.1", "4092", "1", None, "analitic functional"], ] # frunza -> (REF, NOI), cu (totdeb, totcred); None = frunza lipseste DIFERENTE_ASTEPTATE = { # 4092: note direct pe sintetic -> acont de diferenta; REF le-a pierdut ("4092", "99"): (None, (1563.37, 0.0)), # nota de 900 direct pe 419: analitic de diferenta; REF a pierdut-o ("419", "9999"): (None, (0.0, 900.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)), } def _randuri(cale): ws = openpyxl.load_workbook(cale, data_only=True).worksheets[0] h = [c.value for c in ws[1]] return [d for d in (dict(zip(h, r)) for r in ws.iter_rows(min_row=2, values_only=True)) if d.get("TIP") in ("BALANTA", "FACTURA")] def _v(x): return round(float(x or 0), 2) def _frunze(randuri): """{(cont, acont): (totdeb, totcred)}. REF are pe 215/2815/4091 si sinteticul langa analitice, cu suma lor (dublura): acela se scoate, oricare altul ramane.""" d = {} for r in randuri: if r["TIP"] == "BALANTA": d[(str(r["cont"]), str(r["acont"] or ""))] = (_v(r["totdeb"]), _v(r["totcred"])) suma = collections.defaultdict(lambda: [0.0, 0.0]) for (c, a), (td, tc) in d.items(): if a: suma[c][0] += td suma[c][1] += tc dubluri = {(c, "") for c, (td, tc) in suma.items() if d.get((c, "")) == (round(td, 2), round(tc, 2))} return {k: v for k, v in d.items() if k not in dubluri} @unittest.skipUnless(BAZA.exists() and REF.exists(), "lipsesc fisierele LACERTA din exemple/") class TestLacerta(unittest.TestCase): @classmethod def setUpClass(cls): cls._tmp = tempfile.TemporaryDirectory(prefix="lacerta_") d = pathlib.Path(cls._tmp.name) wb = openpyxl.Workbook() wb.active.append(mapare.COLOANE_CORECTII) for r in CORECTII: wb.active.append(r) wb.save(d / "corectii_LACERTA.xlsx") for cmd in (["extrage.py", "--fisier", str(BAZA), "--dir", str(d)], ["genereaza.py", "--dir", str(d), "--dir-iesire", str(d)]): subprocess.run([sys.executable, str(RADACINA / cmd[0])] + cmd[1:] + ["--firma", "LACERTA", "--an", str(AN), "--luna", str(LUNA)], check=True, capture_output=True, cwd=str(RADACINA)) cls.noi = _randuri(d / ("init_LACERTA_%d_%d.xlsx" % (AN, LUNA))) cls.ref = _randuri(REF) @classmethod def tearDownClass(cls): cls._tmp.cleanup() def test_frunze_balanta_ca_in_ref(self): ref, noi = _frunze(self.ref), _frunze(self.noi) gasite = {} for k in sorted(set(ref) | set(noi)): if ref.get(k) != noi.get(k): gasite[k] = (ref.get(k), noi.get(k)) self.assertEqual(gasite, DIFERENTE_ASTEPTATE) def test_echilibru(self): bal = [r for r in self.noi if r["TIP"] == "BALANTA"] self.assertAlmostEqual(sum(_v(r["totdeb"]) for r in bal), sum(_v(r["totcred"]) for r in bal), delta=0.005) def test_facturi_per_document_cu_cod_fiscal_real(self): fac = [r for r in self.noi if r["TIP"] == "FACTURA" and str(r["cont"]) == "401"] # un rand per document nesoldat; suma = cea din REF self.assertEqual(len(fac), 12) self.assertAlmostEqual(sum(_v(r["sold"]) for r in fac), 9220.75, delta=0.005) provizorii = [r["cod_fiscal"] for r in fac if str(r["cod_fiscal"]).startswith("401.")] self.assertEqual(provizorii, [], "furnizor fara cod fiscal real") def test_facturi_per_cont(self): c = collections.Counter() s = collections.defaultdict(float) for r in self.noi: if r["TIP"] == "FACTURA": c[str(r["cont"])] += 1 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), "542": (2, 129.44)}) if __name__ == "__main__": unittest.main()