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
156 lines
6.2 KiB
Python
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()
|