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
141 lines
5.5 KiB
Python
141 lines
5.5 KiB
Python
"""Teste pentru citire_fdb: balanta din Firebird identica cu exportul .xlsx.
|
|
|
|
Poarta: `balanta` produsa din copia unei baze `CONT_BAZA.FDB` trebuie sa fie
|
|
identica (pana la octet) cu cea produsa de `citire_xlsx.py` din exportul de foaie
|
|
de calcul, pe firma de joaca, luna 09/2026. Baza originala NU se deschide: se
|
|
copiaza intr-un director temporar.
|
|
|
|
Pe o masina fara SAGA (fara fbclient.dll, baza, driver sau export) testele se
|
|
sar, nu esueaza.
|
|
|
|
Rulare: py -m unittest discover -s tests -v
|
|
"""
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
RADACINA = pathlib.Path(__file__).resolve().parent.parent
|
|
if str(RADACINA) not in sys.path:
|
|
sys.path.insert(0, str(RADACINA))
|
|
|
|
import citire_fdb
|
|
import citire_xlsx
|
|
|
|
FBCLIENT = pathlib.Path(r"C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll")
|
|
BAZA = pathlib.Path(r"D:\SAGA250909\0001\CONT_BAZA.FDB")
|
|
XLSX = RADACINA / "exemple" / "saga-sqlite-balanta-09-2026.xlsx"
|
|
|
|
AN, LUNA = 2026, 9
|
|
TOL = 0.01
|
|
|
|
|
|
def _driver_disponibil():
|
|
try:
|
|
import firebird.driver # noqa: F401
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
class TestCitireFdb(unittest.TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if not FBCLIENT.exists():
|
|
raise unittest.SkipTest("lipseste fbclient.dll: %s" % FBCLIENT)
|
|
if not BAZA.exists():
|
|
raise unittest.SkipTest("lipseste baza Firebird: %s" % BAZA)
|
|
if not _driver_disponibil():
|
|
raise unittest.SkipTest("lipseste pachetul firebird-driver")
|
|
cls._tmp = tempfile.TemporaryDirectory(prefix="citire_fdb_")
|
|
cls.baza = os.path.join(cls._tmp.name, "CONT_BAZA.FDB")
|
|
shutil.copy2(str(BAZA), cls.baza)
|
|
cls.dir = cls._tmp.name
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls._tmp.cleanup()
|
|
|
|
def test_poarta_identica_cu_xlsx(self):
|
|
if not XLSX.exists():
|
|
self.skipTest("lipseste exportul %s" % XLSX)
|
|
rows_xlsx = citire_xlsx.citeste_balanta(str(XLSX))
|
|
# exportul firmei de joaca e fara analitice; cititorul le emite si pe ele
|
|
rows_fdb = [r for r in citire_fdb.citeste_balanta(self.baza, AN, LUNA)
|
|
if "." not in r["cont"]]
|
|
self.assertTrue(rows_xlsx, "exportul xlsx nu are randuri")
|
|
self.assertEqual(rows_xlsx, rows_fdb,
|
|
"balanta din FDB difera de exportul xlsx")
|
|
|
|
# comparatie stricta: acelasi fisier CSV, la octet
|
|
cale_xlsx = os.path.join(self.dir, "din_xlsx.csv")
|
|
cale_fdb = os.path.join(self.dir, "din_fdb.csv")
|
|
citire_xlsx.scrie_csv(rows_xlsx, cale_xlsx)
|
|
citire_fdb.scrie_csv(rows_fdb, cale_fdb, citire_fdb.COLOANE_BALANTA)
|
|
with open(cale_xlsx, "rb") as f:
|
|
a = f.read()
|
|
with open(cale_fdb, "rb") as f:
|
|
b = f.read()
|
|
self.assertEqual(a, b, "CSV-urile difera la nivel de octet")
|
|
|
|
def test_sum_registru_egal_netul_balantei(self):
|
|
d_an, d_luna, d_urm = citire_fdb._limite(AN, LUNA)
|
|
con = citire_fdb._conecteaza(self.baza)
|
|
try:
|
|
deb, cred = citire_fdb._select(
|
|
con,
|
|
"SELECT "
|
|
"SUM(CASE WHEN TRIM(CONT_D) NOT IN ('', '%') THEN SUMA ELSE 0 END), "
|
|
"SUM(CASE WHEN TRIM(CONT_C) NOT IN ('', '%') THEN SUMA ELSE 0 END) "
|
|
"FROM REGISTRU WHERE DATA < ?", (d_urm,))[0]
|
|
finally:
|
|
con.rollback()
|
|
con.close()
|
|
self.assertAlmostEqual(float(deb), float(cred), delta=TOL,
|
|
msg="SUM(REGISTRU) nu e echilibrat pe laturi")
|
|
|
|
rows = citire_fdb.citeste_balanta(self.baza, AN, LUNA)
|
|
net_balanta = sum(r["total_d"] - r["total_c"] for r in rows
|
|
if "." not in r["cont"])
|
|
self.assertAlmostEqual(float(deb) - float(cred), net_balanta, delta=TOL,
|
|
msg="netul din REGISTRU != netul balantei")
|
|
|
|
def test_scadent_null_cade_pe_ultima_zi_a_lunii(self):
|
|
rows = citire_fdb.citeste_facturi(self.baza, AN, LUNA)
|
|
self.assertTrue(rows, "nu s-au citit facturi")
|
|
asteptat = citire_fdb._ultima_zi(AN, LUNA).isoformat()
|
|
# singura factura din firma de joaca are SCADENT NULL in INTRD
|
|
self.assertEqual(rows[0]["scadent"], asteptat)
|
|
|
|
def test_cont_colector_exclus(self):
|
|
rows = citire_fdb.citeste_balanta(self.baza, AN, LUNA)
|
|
self.assertNotIn("%", [r["cont"] for r in rows])
|
|
self.assertTrue(all(r["cont"] for r in rows), "rand fara cont")
|
|
|
|
def test_parteneri_si_facturi(self):
|
|
parteneri = citire_fdb.citeste_parteneri(self.baza)
|
|
self.assertTrue(parteneri, "nu s-au citit parteneri")
|
|
for r in parteneri:
|
|
self.assertTrue(r["cont_analitic"])
|
|
self.assertTrue(r["cod_fiscal"],
|
|
"cod fiscal gol pentru %s" % r["cont_analitic"])
|
|
# Codul fiscal lipsa in SAGA cade pe contul analitic (citire_fdb.py). Pe firma
|
|
# de joaca ROMFAST (401.00001) nu are COD_FISCAL, TRANSPORT (401.00002) are CUI
|
|
# real. Asta dovedeste ca drumul FDB NU scuteste de etapa 5b (cautarea ANAF).
|
|
provizorii = {r["cont_analitic"] for r in parteneri
|
|
if r["cod_fiscal"] == r["cont_analitic"]}
|
|
self.assertEqual(provizorii, {"401.00001"},
|
|
"s-a schimbat setul de parteneri fara CUI real")
|
|
|
|
facturi = citire_fdb.citeste_facturi(self.baza, AN, LUNA)
|
|
for r in facturi:
|
|
self.assertTrue(r["cont_analitic"])
|
|
self.assertTrue(r["scadent"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|