Pe drumul FDB sinteticul e suma bruta a notelor, deci diferenta fata de analitice se scrie cu D si C separat, nu pe net: rulajul 401 LACERTA iese ca in SAGA (8.991,55 / 57.253,22). PDF/xls raman pe net (etalonul trece). Conturile nepartener fara sold si fara rulaj (891) nu mai intra in xlsx. LACERTA: 51/53 frunze identice cu REF; restul sunt asezari alese (5121, 4428), cu totaluri egale pe cont. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
153 lines
6.1 KiB
Python
153 lines
6.1 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"],
|
|
# 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()
|