citire_xlsx.py citeste .xlsx (openpyxl) si .xls (xlrd) in acelasi contract intern
ca extract_balanta.py. Prima foaie, nu dupa nume; potrivire exacta a coloanelor,
niciodata pe substring (TOTAL_DEB vs TOTAL_DEB_1); CONT ramane text.
mapare.py: propunere automata, corectii_<FIRMA>.xlsx detinut de utilizator si
mapare_<FIRMA>.xlsx regenerat de unealta, .TOATE/.RESTUL in ordinea exact ->
RESTUL -> TOATE, redenumirea sinteticului. Se opreste cu eroare la cont/acont
peste 4 caractere, coliziune accidentala si cont_saga inexistent in corectii.
Corectat in orchestrare: .xls-ul SAGA nu are inregistrare CODEPAGE, deci xlrd
cadea pe iso-8859-1 si strica denumirile ("FURNIZORI \x97 DEBITORI"). Citirea se
face cu encoding_override="cp1250", cu o verificare in test care pica daca revine.
18 teste trec, inclusiv poarta de regresie pe etalon.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
144 lines
5.8 KiB
Python
144 lines
5.8 KiB
Python
"""Teste pentru citire_xlsx: contractul intern produs din foaia de calcul SAGA.
|
|
|
|
Rulare: py -m unittest discover -s tests -v
|
|
"""
|
|
import os
|
|
import pathlib
|
|
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 citire_xlsx
|
|
|
|
EXEMPLE = RADACINA / "exemple"
|
|
XLSX = EXEMPLE / "saga-sqlite-balanta-09-2026.xlsx"
|
|
XLS = EXEMPLE / "saga-sqlite-balanta-09-2026.xls"
|
|
DANUBE = RADACINA.parent / "saga2roa_danube" / "balanta.xls"
|
|
|
|
TOL = 0.01
|
|
NUMERICE = ["prec_d", "prec_c", "rulaj_d", "rulaj_c",
|
|
"total_d", "total_c", "sold_d", "sold_c"]
|
|
|
|
|
|
def frunze(rows):
|
|
"""Randurile care nu sunt parintele niciunui alt cont din balanta."""
|
|
conts = [r["cont"] for r in rows if r["cont"]]
|
|
parinti = {c.rsplit(".", 1)[0] for c in conts if "." in c}
|
|
return [r for r in rows if r["cont"] and r["cont"] not in parinti]
|
|
|
|
|
|
def scrie_xlsx(cale, antet, randuri):
|
|
wb = openpyxl.Workbook()
|
|
ws = wb.active
|
|
ws.append(antet)
|
|
for rand in randuri:
|
|
ws.append(rand)
|
|
wb.save(cale)
|
|
wb.close()
|
|
|
|
|
|
class TestCitireXlsx(unittest.TestCase):
|
|
|
|
def _verifica_relatii(self, rows, eticheta):
|
|
self.assertTrue(rows, "%s: nicio linie citita" % eticheta)
|
|
for r in rows:
|
|
self.assertAlmostEqual(
|
|
r["total_d"], r["prec_d"] + r["rulaj_d"], delta=TOL,
|
|
msg="%s %s: total_d != prec_d + rulaj_d" % (eticheta, r["cont"]))
|
|
self.assertAlmostEqual(
|
|
r["total_c"], r["prec_c"] + r["rulaj_c"], delta=TOL,
|
|
msg="%s %s: total_c != prec_c + rulaj_c" % (eticheta, r["cont"]))
|
|
self.assertAlmostEqual(
|
|
r["sold_d"] - r["sold_c"], r["total_d"] - r["total_c"], delta=TOL,
|
|
msg="%s %s: sold_d - sold_c != total_d - total_c" % (eticheta, r["cont"]))
|
|
|
|
def _verifica_frunze(self, rows, eticheta):
|
|
f = frunze(rows)
|
|
sd = sum(r["sold_d"] for r in f)
|
|
sc = sum(r["sold_c"] for r in f)
|
|
self.assertAlmostEqual(sd, sc, delta=TOL,
|
|
msg="%s: SUM(sold_d)=%s != SUM(sold_c)=%s" % (eticheta, sd, sc))
|
|
|
|
def test_xlsx_si_xls_identice(self):
|
|
rows_xlsx = citire_xlsx.citeste_balanta(str(XLSX))
|
|
rows_xls = citire_xlsx.citeste_balanta(str(XLS))
|
|
self.assertTrue(rows_xlsx)
|
|
self.assertEqual(len(rows_xlsx), len(rows_xls))
|
|
for a, b in zip(rows_xlsx, rows_xls):
|
|
self.assertEqual(a["cont"], b["cont"])
|
|
self.assertEqual(a["denumire"], b["denumire"])
|
|
self.assertEqual(a["tip"], b["tip"])
|
|
for col in ("pagina", "este_total"):
|
|
self.assertEqual(a[col], b[col])
|
|
for col in NUMERICE:
|
|
self.assertAlmostEqual(a[col], b[col], delta=TOL,
|
|
msg="%s %s: %s xlsx=%s xls=%s"
|
|
% (a["cont"], col, a["cont"], a[col], b[col]))
|
|
|
|
def test_relatii_si_frunze(self):
|
|
for eticheta, cale in (("xlsx", XLSX), ("xls", XLS)):
|
|
rows = citire_xlsx.citeste_balanta(str(cale))
|
|
self._verifica_relatii(rows, eticheta)
|
|
self._verifica_frunze(rows, eticheta)
|
|
|
|
def test_cont_ramane_text(self):
|
|
for eticheta, cale in (("xlsx", XLSX), ("xls", XLS)):
|
|
rows = citire_xlsx.citeste_balanta(str(cale))
|
|
for r in rows:
|
|
self.assertIsInstance(r["cont"], str, "%s: cont nu e text" % eticheta)
|
|
|
|
def test_danube_analitice(self):
|
|
if not DANUBE.exists():
|
|
self.skipTest("lipseste %s" % DANUBE)
|
|
rows = citire_xlsx.citeste_balanta(str(DANUBE))
|
|
self._verifica_relatii(rows, "danube")
|
|
self._verifica_frunze(rows, "danube")
|
|
analitice = [r for r in rows if "." in r["cont"]]
|
|
self.assertTrue(analitice, "danube ar trebui sa aiba conturi analitice")
|
|
for r in analitice:
|
|
self.assertIsInstance(r["cont"], str, "cont analitic nu e text")
|
|
# .xls-ul SAGA nu are CODEPAGE: fara encoding_override, xlrd cade pe iso-8859-1
|
|
# si denumirile ies cu caractere de control in loc de diacritice.
|
|
stricate = [r["denumire"] for r in rows
|
|
if any(ord(c) < 32 or 127 <= ord(c) < 160 for c in str(r["denumire"]))]
|
|
self.assertEqual([], stricate, "denumiri decodate gresit (codepage)")
|
|
|
|
cautat = [r for r in rows if r["cont"] == "105.10"]
|
|
self.assertTrue(cautat, "lipseste contul analitic 105.10")
|
|
self.assertIsInstance(cautat[0]["cont"], str)
|
|
self.assertEqual(cautat[0]["cont"], "105.10")
|
|
|
|
def test_cont_numeric_devine_text(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
cale = os.path.join(d, "numeric.xlsx")
|
|
scrie_xlsx(
|
|
cale,
|
|
["CONT", "DENUMIRE", "TIP", "DEB_PREC", "CRED_PREC", "RULAJ_D",
|
|
"RULAJ_C", "TOTAL_DEB", "TOTAL_CRED", "FIN_D", "FIN_C"],
|
|
[[401.00002, "PARTENER", "P", 0, 0, 0, 0, 0, 100, 0, 100]],
|
|
)
|
|
rows = citire_xlsx.citeste_balanta(cale)
|
|
self.assertEqual(len(rows), 1)
|
|
self.assertIsInstance(rows[0]["cont"], str)
|
|
self.assertEqual(rows[0]["cont"], "401.00002")
|
|
|
|
def test_coloana_lipsa_si_fara_substring(self):
|
|
antet = ["CONT", "DENUMIRE", "TIP", "DEB_PREC", "CRED_PREC", "RULAJ_D",
|
|
"RULAJ_C", "TOTAL_DEB_1", "TOTAL_CRED", "FIN_D", "FIN_C"]
|
|
with tempfile.TemporaryDirectory() as d:
|
|
cale = os.path.join(d, "lipsa.xlsx")
|
|
scrie_xlsx(cale, antet, [["401", "FURNIZORI", "P", 0, 0, 0, 0, 0, 0, 0, 0]])
|
|
with self.assertRaises(ValueError) as cm:
|
|
citire_xlsx.citeste_balanta(cale)
|
|
self.assertIn("TOTAL_DEB", str(cm.exception))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|