Files
solduri2roa/tests/test_lacerta.py
Marius Mutu 99d9cf0aff Cititorul FDB: sume totale ca SAGA, analitice pastrate, baza oprita acceptata
Proba pe LACERTA (06/2026, firma reala) a gasit doua defecte in citire_fdb:
- precedentul netea si rulajele din an, deci conturile de cheltuieli/venituri
  pierdeau rulajul (CPP gresit). Acum: preluare netata + rulaj an brut;
  potriveste 44/44 sintetice din importul facut din exportul SAGA (inainte 19/44).
- analiticele se rulau in sintetic, deci facturile per document nu se foloseau
  si analiticele nepartener disparea. Acum balanta are toate nivelurile.

Baza primita se copiaza in temporar si copia se repune online (clientul o
trimite si in full shutdown). tests/test_lacerta.py: 48/53 frunze identice cu
REF, restul explicate; se sare fara fisierele clientului, care sunt ignorate
de git. README nou; analiza in docs/analiza_lacerta_fdb.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
2026-09-25 16:14:14 +03:00

156 lines
6.2 KiB
Python

"""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"],
["4428.TOATE", "4428", None, None, "ca in REF"],
]
# frunza -> (REF, NOI), cu (totdeb, totcred); None = frunza lipseste
DIFERENTE_ASTEPTATE = {
# postarile direct pe sinteticul 401 intra pe net (regula stabilita), deci rulajul scade
("401", ""): ((8991.55, 57253.22), (5536.75, 53798.42)),
# 4092 e cont cu parteneri in config; REF l-a pus pe analitic si a pierdut 1563.37
("4092", ""): (None, (4170.23, 0.0)),
("4092", "1"): ((2606.86, 0.0), None),
# nota de 900 direct pe 419: analitic de diferenta; REF a pierdut-o
("419", "9999"): (None, (0.0, 900.0)),
# 4428: postarile direct pe sintetic -> analitic de diferenta; totalul coincide
("4428", ""): ((1749.68, 0.0), (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)),
# 542 nu e cont cu parteneri in config: analitice + diferenta
("542", ""): ((2243.57, 1824.07), None),
("542", "01"): (None, (1828.77, 1824.07)),
("542", "02"): (None, (124.74, 0.0)),
("542", "99"): (None, (290.06, 0.0)),
# 891 are rulaj zero; REF nu-l scrie
("891", ""): (None, (0.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)})
if __name__ == "__main__":
unittest.main()