- genereaza_xlsx: coloana data = ultima zi a lunii; etalonul de regresie actualizat doar pe aceasta coloana (verificat celula cu celula). - config: 542 cont cu parteneri (acum identic cu REF); descrierea lui 4092 corectata. - extrage/citire_fdb: .fbk se restaureaza cu gbak -c in temporar; test ca da aceleasi fisiere ca FDB-ul. - test_lacerta: 49/53 frunze identice cu REF, 4428.TOATE -> 4428/01. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
595 lines
25 KiB
Python
595 lines
25 KiB
Python
"""Generatorul xlsx-urilor de initializare ROACONT din balantele SAGA (CSV).
|
|
|
|
Biblioteca folosita de `genereaza.py` (parametri de linie de comanda); produce:
|
|
init_<FIRMA>_<an>_<luna>.xlsx - un sheet, randul 1 = capul de tabel,
|
|
datele de la randul 2 in jos
|
|
verificare_<FIRMA>.md - verificarile obligatorii
|
|
|
|
Reguli: docs/decizii_import.md (pct. 11-15) si docs/conturi_cu_analitice.md.
|
|
|
|
Forma randurilor (revizuit 20.09.2026, vezi pct. 14-15):
|
|
- cont obisnuit: doar BALANTA per frunza (acont completat).
|
|
- 401/4092/4111/461/462/4551: un BALANTA agregat pe sintetic (acont gol) + cate
|
|
un FACTURA per analitic (acont gol).
|
|
- 5121/5124: analiticele raman analitice; per analitic un BALANTA cu acont SI un
|
|
FACTURA cu ACELASI acont.
|
|
- 5311: BALANTA + FACTURA pe sintetic (acont gol), partener CENTRALA.
|
|
- 5125: cont obisnuit cu analitice (fara parteneri).
|
|
- diferenta neta parinte/copii (postare directa pe sintetic): la nepartener si la
|
|
5121/5124 devine rand BALANTA de diferenta (analitic nou, fara FACTURA); la
|
|
ceilalti parteneri se include in BALANTA agregat. NU se mai emite FACTURA de
|
|
diferenta (o rezolva formularul, prin NEREPARTIZAT).
|
|
- analiticul cu sold pe latura opusa celei fixate de formular (':379') primeste
|
|
FACTURA, cu suma scrisa cu semn negativ pe latura formularului (atat in BALANTA
|
|
cat si in sold-ul FACTURA); soldul ramane pe latura corecta si nu apare NEREPARTIZAT.
|
|
"""
|
|
|
|
import csv
|
|
import os
|
|
from calendar import monthrange
|
|
|
|
from openpyxl import load_workbook
|
|
|
|
AN = 2025
|
|
LUNA = 12
|
|
SERIE = None
|
|
VALUTA = None
|
|
|
|
# Sablonul sta langa script, nu langa directorul de lucru: iesirile pot merge in temp.
|
|
TEMPLATE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"sablon", "init_facturi_balanta_note.xlsx")
|
|
|
|
# Latura pe care formularul pune randurile FACTURA (frm...sc2:379/'383'):
|
|
# conturile din lista merg pe CREDIT, tot restul pe DEBIT.
|
|
LATURA_CREDIT = {"401", "404", "462", "419", "408", "4551"}
|
|
|
|
# Setul operational de conturi cu parteneri: config/conturi_parteneri.csv (validat
|
|
# in productie; CONFIG_CONT_IREG e doar consultativ, vezi docs/decizii_import.md).
|
|
# Categoriile CSV corespund celor trei seturi: BANCA, SINTETIC, FARA_ANALITICE.
|
|
CONFIG_PARTENERI = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"config", "conturi_parteneri.csv")
|
|
|
|
|
|
class Stop(Exception):
|
|
"""Conditie neprevazuta: nu ghicim, raportam."""
|
|
|
|
|
|
def citeste_conturi_parteneri(path=CONFIG_PARTENERI):
|
|
"""(BANCA, SINTETIC, FARA_ANALITICE) din CSV; fisier lipsa = eroare, nu set gol."""
|
|
if not os.path.exists(path):
|
|
raise Stop(f"lipseste fisierul de configurare a partenerilor: {path}")
|
|
seturi = {"BANCA": set(), "SINTETIC": set(), "FARA_ANALITICE": set()}
|
|
with open(path, encoding="utf-8-sig", newline="") as f:
|
|
for r in csv.DictReader(f):
|
|
cont = (r.get("cont") or "").strip()
|
|
categorie = (r.get("categorie") or "").strip()
|
|
if not cont:
|
|
continue
|
|
if categorie not in seturi:
|
|
raise Stop(f"categorie necunoscuta in {path}: {categorie!r}")
|
|
seturi[categorie].add(cont)
|
|
return seturi["BANCA"], seturi["SINTETIC"], seturi["FARA_ANALITICE"]
|
|
|
|
|
|
# BANCA (5121/5124): analiticele raman analitice SI primesc partener.
|
|
# SINTETIC: un singur BALANTA agregat pe sintetic + FACTURA per analitic.
|
|
# FARA_ANALITICE (5311): fara analitice, BALANTA + FACTURA pe sintetic.
|
|
(PARTENER_BANCA, PARTENER_SINTETIC,
|
|
PARTENER_FARA_ANALITICE) = citeste_conturi_parteneri()
|
|
|
|
PARTENER = PARTENER_BANCA | PARTENER_SINTETIC | PARTENER_FARA_ANALITICE
|
|
|
|
|
|
def citeste_header():
|
|
wb = load_workbook(TEMPLATE, read_only=True)
|
|
ws = wb.active
|
|
hdr = [c.value for c in ws[1]]
|
|
wb.close()
|
|
return hdr
|
|
|
|
|
|
def citeste_balanta(path):
|
|
rows = []
|
|
with open(path, encoding="utf-8-sig", newline="") as f:
|
|
for r in csv.DictReader(f):
|
|
cont = (r["cont"] or "").strip()
|
|
if r["este_total"] == "1" or cont == "":
|
|
continue
|
|
rows.append({
|
|
"cont": cont,
|
|
"denumire": (r["denumire"] or "").strip(),
|
|
"total_d": float(r["total_d"]),
|
|
"total_c": float(r["total_c"]),
|
|
})
|
|
return rows
|
|
|
|
|
|
def citeste_totaluri(path):
|
|
with open(path, encoding="utf-8-sig", newline="") as f:
|
|
for r in csv.DictReader(f):
|
|
if "totaluri" in (r["denumire"] or "").lower():
|
|
return float(r["total_d"]), float(r["total_c"])
|
|
return None
|
|
|
|
|
|
def arbore(rows):
|
|
by = {r["cont"]: r for r in rows}
|
|
children = {}
|
|
for c in by:
|
|
p = ".".join(c.split(".")[:-1])
|
|
if p in by:
|
|
children.setdefault(p, []).append(c)
|
|
return by, children
|
|
|
|
|
|
def este_partener(cont):
|
|
return cont.split(".")[0] in PARTENER
|
|
|
|
|
|
def latura_factura(cont):
|
|
"""Latura fixa pe care formularul pune randurile FACTURA ale contului."""
|
|
return "C" if cont.split(".")[0] in LATURA_CREDIT else "D"
|
|
|
|
|
|
def split_cont(cont):
|
|
seg = cont.split(".")
|
|
return seg[0], "".join(seg[1:])
|
|
|
|
|
|
def diferente(by, children):
|
|
"""(cont_parinte, diferenta_net) pentru parintii cu postare directa."""
|
|
out = []
|
|
for cont, r in by.items():
|
|
kids = children.get(cont)
|
|
if not kids:
|
|
continue
|
|
pnet = round(r["total_d"] - r["total_c"], 2)
|
|
cnet = round(sum(by[k]["total_d"] - by[k]["total_c"] for k in kids), 2)
|
|
d = round(pnet - cnet, 2)
|
|
if abs(d) > 0.005:
|
|
out.append((cont, d))
|
|
return out
|
|
|
|
|
|
def acont_diferenta(parent, kids):
|
|
"""Primul cod liber de forma 99 (segmente scurte) / 9999 (segmente lungi)."""
|
|
seg = parent.split(".")
|
|
base = "".join(seg[1:])
|
|
style = 2 if max(len(k.split(".")[-1]) for k in kids) <= 2 else 4
|
|
cod = "99" if style == 2 else "9999"
|
|
ac = base + cod
|
|
if len(ac) > 4:
|
|
raise Stop(f"acont diferenta prea lung pentru {parent}: '{ac}'")
|
|
frati = {split_cont(k)[1] for k in kids}
|
|
if ac in frati:
|
|
raise Stop(f"acont diferenta '{ac}' deja folosit sub {parent}")
|
|
return ac
|
|
|
|
|
|
def _datascad(an, luna):
|
|
"""Ultima zi a lunii, in formatul din sablon (DD.MM.YYYY)."""
|
|
return "%02d.%02d.%04d" % (monthrange(int(an), int(luna))[1],
|
|
int(luna), int(an))
|
|
|
|
|
|
# data soldului = ultima zi a lunii (decizie 27.09.2026; inainte, prima zi)
|
|
_data = _datascad
|
|
|
|
|
|
def rand(header, an, luna, **kw):
|
|
r = {h: None for h in header}
|
|
r.update(an=an, luna=luna, data=_data(an, luna), datascad=_datascad(an, luna),
|
|
serie=SERIE, valuta=VALUTA)
|
|
r.update(kw)
|
|
return r
|
|
|
|
|
|
def _mapare_cont(cont_saga, cont_def, acont_def, mapare, partener):
|
|
"""Cont/acont final: maparea suprascrie doar ce i s-a dat explicit.
|
|
|
|
La parteneri `acont` ramane al generatorului (o pre-completare generica ar
|
|
pune acont pe 401 -> chei diferite -> dublare tacuta, decizii_import.md pct. 16);
|
|
o corectie explicita de `acont` se aplica totusi, pe ambele randuri.
|
|
"""
|
|
if not mapare:
|
|
return cont_def, acont_def
|
|
m = mapare.get(cont_saga)
|
|
if m is None:
|
|
return cont_def, acont_def
|
|
cont = m.get("cont") or cont_def
|
|
if partener and m.get("acont") is None:
|
|
return cont, acont_def
|
|
return cont, m.get("acont")
|
|
|
|
|
|
def este_latura_formular(net, side):
|
|
return (net > 0) == (side == "D")
|
|
|
|
|
|
def _text(v):
|
|
return "" if v is None else str(v).strip()
|
|
|
|
|
|
def _iso(s):
|
|
"""'YYYY-MM-DD' -> 'DD.MM.YYYY'; altceva -> None."""
|
|
p = _text(s).split("-")
|
|
if len(p) == 3 and len(p[0]) == 4:
|
|
return "%s.%s.%s" % (p[2], p[1], p[0])
|
|
return None
|
|
|
|
|
|
def genereaza(rows, header, an=AN, luna=LUNA, mapare=None, parteneri=None,
|
|
facturi=None):
|
|
by, children = arbore(rows)
|
|
diffs = diferente(by, children)
|
|
diff_by_parent = dict(diffs)
|
|
|
|
# parteneri/facturi reale (doar pe drumul FDB); lipsa = comportamentul de azi
|
|
part_map = {}
|
|
for r in (parteneri or []):
|
|
part_map[_text(r.get("cont_analitic"))] = r
|
|
fact_map = {}
|
|
for r in (facturi or []):
|
|
fact_map.setdefault(_text(r.get("cont_analitic")), []).append(r)
|
|
|
|
# verificarile de structura care nu se ghicesc (pe valorile finale, dupa mapare)
|
|
for cont in by:
|
|
if este_partener(cont):
|
|
continue
|
|
if cont not in children:
|
|
cont4, ac = split_cont(cont)
|
|
cont4, ac = _mapare_cont(cont, cont4, ac, mapare, partener=False)
|
|
if len(cont4) > 4 or len(ac or "") > 4:
|
|
raise Stop(f"cont/acont prea lung: {cont} -> ({cont4},{ac})")
|
|
for parent in diff_by_parent:
|
|
if not este_partener(parent) or parent.split(".")[0] in PARTENER_BANCA:
|
|
acont_diferenta(parent, children[parent])
|
|
for p in {c.split(".")[0] for c in by if este_partener(c)}:
|
|
for k in children.get(p, []):
|
|
if k in children:
|
|
raise Stop(f"analitic partener cu sub-analitice: {k}")
|
|
|
|
out = []
|
|
leaf_map = {} # frunza -> randurile dedicate (folosite la dubluri)
|
|
leaf_rep = set() # toate frunzele reprezentate (inclusiv in BALANTA agregat)
|
|
negative = [] # (cont, analitic, sold_semnat, latura_formular)
|
|
|
|
def add(r, leaf=None):
|
|
out.append(r)
|
|
if leaf is not None:
|
|
leaf_map.setdefault(leaf, []).append(len(out) - 1)
|
|
leaf_rep.add(leaf)
|
|
|
|
def factura(cod_saga, cont_out, acont, nume_def, side, net, sold, leaf):
|
|
"""Un FACTURA agregat (azi) sau, cand exista documente reale, unul per factura."""
|
|
info = part_map.get(cod_saga) or {}
|
|
cod_fisc = _text(info.get("cod_fiscal")) or cod_saga
|
|
nume = nume_def or _text(info.get("denumire")) or ("FURNIZOR %s" % cod_saga)
|
|
docs = fact_map.get(cod_saga)
|
|
if docs and sold > 0 and este_latura_formular(net, side):
|
|
total = round(sum(float(d.get("sold") or 0) for d in docs), 2)
|
|
if abs(total - abs(net)) <= 0.005:
|
|
primul = True
|
|
for d in docs:
|
|
v = round(float(d.get("sold") or 0), 2)
|
|
if v == 0:
|
|
continue
|
|
r = rand(header, an, luna, TIP="FACTURA", cont=cont_out,
|
|
acont=acont, numar=_text(d.get("numar")), nume=nume,
|
|
cod_fiscal=cod_fisc, sold=v, explicatia=side)
|
|
data, scad = _iso(d.get("data")), _iso(d.get("scadent"))
|
|
if data:
|
|
r["data"] = data
|
|
if scad:
|
|
r["datascad"] = scad
|
|
if primul:
|
|
add(r, leaf=leaf)
|
|
primul = False
|
|
else:
|
|
add(r)
|
|
if leaf is not None:
|
|
leaf_rep.add(leaf)
|
|
if not primul:
|
|
return
|
|
add(rand(header, an, luna, TIP="FACTURA", cont=cont_out, acont=acont,
|
|
numar=cod_saga.replace(".", ""), nume=nume, cod_fiscal=cod_fisc,
|
|
sold=sold, explicatia=side), leaf=leaf)
|
|
|
|
# A. conturi nepartener: cate un BALANTA per frunza
|
|
for r in rows:
|
|
if este_partener(r["cont"]) or r["cont"] in children:
|
|
continue
|
|
cont4, ac = split_cont(r["cont"])
|
|
cont4, ac = _mapare_cont(r["cont"], cont4, ac, mapare, partener=False)
|
|
add(rand(header, an, luna, TIP="BALANTA", cont=cont4, acont=ac, numar=1,
|
|
nume=r["denumire"], totdeb=round(r["total_d"], 2),
|
|
totcred=round(r["total_c"], 2)), leaf=r["cont"])
|
|
|
|
# A. analitic de diferenta pe parinte nepartener (postare directa pe sintetic).
|
|
# Randurile de diferenta raman calculate aici, nu trec prin mapare.
|
|
for cont, d in diffs:
|
|
if este_partener(cont):
|
|
continue
|
|
ac = acont_diferenta(cont, children[cont])
|
|
add(rand(header, an, luna, TIP="BALANTA", cont=cont.split(".")[0], acont=ac,
|
|
numar=1, nume="DIFERENTA SINTETIC SAGA",
|
|
totdeb=d if d > 0 else None,
|
|
totcred=-d if d < 0 else None))
|
|
|
|
parteneri_activi = sorted({c.split(".")[0] for c in by if este_partener(c)})
|
|
for p in parteneri_activi:
|
|
direct = children.get(p, [])
|
|
side = latura_factura(p)
|
|
|
|
if p in PARTENER_BANCA and direct:
|
|
# 5121/5124: BALANTA cu acont + FACTURA cu ACELASI acont.
|
|
for k in direct:
|
|
kd = by[k]
|
|
cont_k, kac = _mapare_cont(k, p, split_cont(k)[1], mapare, True)
|
|
net = round(kd["total_d"] - kd["total_c"], 2)
|
|
if net != 0 and not este_latura_formular(net, side):
|
|
neg = -abs(net)
|
|
negative.append((p, k, neg, side))
|
|
if side == "D":
|
|
td, tc = neg, None
|
|
else:
|
|
td, tc = None, neg
|
|
else:
|
|
td, tc = round(kd["total_d"], 2), round(kd["total_c"], 2)
|
|
add(rand(header, an, luna, TIP="BALANTA", cont=cont_k, acont=kac,
|
|
numar=1, nume=kd["denumire"], totdeb=td, totcred=tc),
|
|
leaf=k)
|
|
if net == 0:
|
|
continue
|
|
sold = abs(net) if este_latura_formular(net, side) else -abs(net)
|
|
factura(k, cont_k, kac, kd["denumire"], side, net, sold, None)
|
|
if p in diff_by_parent:
|
|
d = diff_by_parent[p]
|
|
ac = acont_diferenta(p, direct)
|
|
add(rand(header, an, luna, TIP="BALANTA", cont=p, acont=ac, numar=1,
|
|
nume="DIFERENTA SINTETIC SAGA",
|
|
totdeb=d if d > 0 else None,
|
|
totcred=-d if d < 0 else None))
|
|
continue
|
|
|
|
# 401/4092/4111/461/462/4551 si 5311
|
|
if direct:
|
|
base_d = 0.0
|
|
base_c = 0.0
|
|
for k in direct:
|
|
net = round(by[k]["total_d"] - by[k]["total_c"], 2)
|
|
if net != 0 and not este_latura_formular(net, side):
|
|
neg = -abs(net)
|
|
negative.append((p, k, neg, side))
|
|
if side == "D":
|
|
base_d += neg
|
|
else:
|
|
base_c += neg
|
|
else:
|
|
base_d += by[k]["total_d"]
|
|
base_c += by[k]["total_c"]
|
|
d = diff_by_parent.get(p, 0.0)
|
|
cont_p, ac_p = _mapare_cont(p, p, None, mapare, True)
|
|
if cont_p == p: # .TOATE/.RESTUL poate redenumi fara rand pe sintetic
|
|
# BALANTA agregat si randurile FACTURA trebuie sa cada pe acelasi
|
|
# cont. Daca analiticele sunt mapate pe conturi diferite, agregatul
|
|
# nu mai corespunde detaliului: oprim, nu luam primul analitic.
|
|
variante = {_mapare_cont(k, p, None, mapare, True)[0] for k in direct}
|
|
if len(variante) > 1:
|
|
raise Stop(f"analiticele lui {p} sunt mapate pe conturi diferite "
|
|
f"({sorted(variante)}); BALANTA agregat nu mai poate "
|
|
f"corespunde randurilor FACTURA")
|
|
if variante:
|
|
cont_p = variante.pop()
|
|
add(rand(header, an, luna, TIP="BALANTA", cont=cont_p, acont=ac_p,
|
|
numar=1, nume=by[p]["denumire"],
|
|
totdeb=round(base_d + (d if d > 0 else 0.0), 2),
|
|
totcred=round(base_c + (-d if d < 0 else 0.0), 2)))
|
|
for k in direct:
|
|
net = round(by[k]["total_d"] - by[k]["total_c"], 2)
|
|
if net == 0:
|
|
leaf_rep.add(k)
|
|
continue
|
|
sold = abs(net) if este_latura_formular(net, side) else -abs(net)
|
|
cont_k, kac = _mapare_cont(k, cont_p, ac_p, mapare, True)
|
|
factura(k, cont_k, kac, by[k]["denumire"], side, net, sold, k)
|
|
else:
|
|
net = round(by[p]["total_d"] - by[p]["total_c"], 2)
|
|
if net != 0 and not este_latura_formular(net, side):
|
|
neg = -abs(net)
|
|
negative.append((p, p, neg, side))
|
|
if side == "D":
|
|
td, tc = neg, None
|
|
else:
|
|
td, tc = None, neg
|
|
else:
|
|
td = round(by[p]["total_d"], 2)
|
|
tc = round(by[p]["total_c"], 2)
|
|
cont_p, ac_p = _mapare_cont(p, p, None, mapare, True)
|
|
add(rand(header, an, luna, TIP="BALANTA", cont=cont_p, acont=ac_p,
|
|
numar=1, nume=by[p]["denumire"], totdeb=td, totcred=tc),
|
|
leaf=p)
|
|
if net != 0:
|
|
nume = "CENTRALA" if p == "5311" else by[p]["denumire"]
|
|
sold = abs(net) if este_latura_formular(net, side) else -abs(net)
|
|
factura(p, cont_p, ac_p, nume, side, net, sold, None)
|
|
return out, diffs, leaf_map, leaf_rep, negative
|
|
|
|
|
|
def scrie_xlsx(header, rows, path):
|
|
wb = load_workbook(TEMPLATE)
|
|
ws = wb.active
|
|
if ws.max_row > 1:
|
|
ws.delete_rows(2, ws.max_row - 1)
|
|
for i, r in enumerate(rows, start=2):
|
|
for j, h in enumerate(header, start=1):
|
|
v = r.get(h)
|
|
if h == "explicatia": # coloana temporara de control
|
|
v = None
|
|
ws.cell(i, j, v)
|
|
wb.save(path)
|
|
|
|
|
|
def verifica(firma, csv_path, xlsx_path, rows, out, diffs, leaf_map, leaf_rep,
|
|
negative, header, dir_verificare="docs"):
|
|
by, children = arbore(rows)
|
|
leaves = [c for c in by if c not in children]
|
|
diff_by_parent = dict(diffs)
|
|
|
|
totdeb = round(sum(r["totdeb"] or 0 for r in out), 2)
|
|
totcred = round(sum(r["totcred"] or 0 for r in out), 2)
|
|
net = round(totdeb - totcred, 2)
|
|
toturi = citeste_totaluri(csv_path)
|
|
|
|
pierdute = sorted(set(leaves) - leaf_rep)
|
|
duplicate_leaf = sorted(c for c, v in leaf_map.items() if len(v) != 1)
|
|
|
|
# lungimi + dubluri (cont,acont) pe BALANTA
|
|
lungimi = []
|
|
bal_pairs = {}
|
|
for r in out:
|
|
if len(r["cont"]) > 4:
|
|
lungimi.append(("cont", r["cont"]))
|
|
if r["acont"] and len(r["acont"]) > 4:
|
|
lungimi.append(("acont", r["acont"]))
|
|
if r["TIP"] == "BALANTA":
|
|
bal_pairs.setdefault((r["cont"], r["acont"]), []).append(r)
|
|
dubluri_bal = {k: v for k, v in bal_pairs.items() if len(v) > 1}
|
|
|
|
# parteneri: SUM(sold FACTURA cu semn) + NEREPARTIZAT asteptat = net sintetic
|
|
part_rows = {}
|
|
for r in out:
|
|
if r["TIP"] == "FACTURA":
|
|
semn = 1 if latura_factura(r["cont"]) == "D" else -1
|
|
part_rows[r["cont"]] = part_rows.get(r["cont"], 0.0) + semn * (r["sold"] or 0)
|
|
|
|
part_verif = []
|
|
for p in sorted({c.split(".")[0] for c in by if este_partener(c)}):
|
|
net_sint = round(by[p]["total_d"] - by[p]["total_c"], 2)
|
|
suma = round(part_rows.get(p, 0.0), 2)
|
|
direct = round(diff_by_parent.get(p, 0.0), 2)
|
|
asteptat = direct
|
|
diferenta = round(net_sint - suma, 2)
|
|
ok = abs(diferenta - asteptat) <= 0.005
|
|
part_verif.append((p, suma, net_sint, direct, asteptat, diferenta, ok))
|
|
|
|
ok_bal = abs(net) <= 0.005
|
|
ok_leaves = not pierdute and not duplicate_leaf
|
|
ok_part = all(x[6] for x in part_verif)
|
|
ok_lung = not lungimi and not dubluri_bal
|
|
|
|
L = []
|
|
L.append(f"# Verificare `{os.path.basename(xlsx_path)}`")
|
|
L.append("")
|
|
L.append(f"Generat de `genereaza_xlsx.py` din `{os.path.basename(csv_path)}`. "
|
|
f"Randuri de date: {len(out)} ({sum(1 for r in out if r['TIP']=='BALANTA')} BALANTA, "
|
|
f"{sum(1 for r in out if r['TIP']=='FACTURA')} FACTURA).")
|
|
L.append("")
|
|
L.append("## 1. Balanta generala (SUM totdeb = SUM totcred)")
|
|
L.append("")
|
|
L.append(f"- SUM(totdeb) = {totdeb}")
|
|
L.append(f"- SUM(totcred) = {totcred}")
|
|
L.append(f"- diferenta (totdeb - totcred) = {net} -> {'OK' if ok_bal else '**ESEC**'}")
|
|
if toturi:
|
|
td, tc = toturi
|
|
L.append(f"- Totaluri balanta: total_d={td} total_c={tc} "
|
|
f"net={round(td - tc, 2)}")
|
|
L.append(f"- NET xlsx ({net}) vs NET Totaluri ({round(td - tc, 2)}) -> "
|
|
f"{'OK' if abs(net - (td - tc)) <= 0.005 else '**ESEC**'}")
|
|
L.append(f"- Nota: SUM(totdeb)={totdeb} difera de Totaluri.total_d={td} cu "
|
|
f"{round(totdeb - td, 2)}; suma pe laturi a PDF-ului nu se poate "
|
|
f"reconstrui din frunze acolo unde SAGA a netat sinteticul (gross-ul "
|
|
f"parintelui e netat). Netul se pastreaza exact.")
|
|
L.append("")
|
|
L.append("## 2. Conturi frunza din CSV, reprezentate o singura data")
|
|
L.append("")
|
|
L.append(f"- frunze in CSV: {len(leaves)}")
|
|
L.append(f"- reprezentate in xlsx: {len(leaf_rep)}")
|
|
L.append(f"- conturi pierdute: {pierdute if pierdute else '[] (gol)'} -> "
|
|
f"{'OK' if not pierdute else '**ESEC**'}")
|
|
L.append(f"- frunze cu mai putin/mai mult de un rand dedicat: "
|
|
f"{duplicate_leaf if duplicate_leaf else '[] (gol)'} -> "
|
|
f"{'OK' if not duplicate_leaf else '**ESEC**'}")
|
|
L.append("")
|
|
L.append("## 3. Conturi partener: FACTURA + NEREPARTIZAT = net sintetic")
|
|
L.append("")
|
|
L.append("Formularul scade din randul BALANTA suma randurilor FACTURA de pe "
|
|
"acelasi `(cont, acont, latura)` si lasa restul pe partenerul "
|
|
"`NEREPARTIZAT`. Deci: `net sintetic - SUM(sold FACTURA cu semn) = "
|
|
"postare directa pe sintetic`.")
|
|
L.append("")
|
|
L.append("| cont | SUM(sold FACTURA) | net sintetic | postare directa | NEREPARTIZAT asteptat | diferenta | rezultat |")
|
|
L.append("|---|---|---|---|---|---|---|")
|
|
for p, suma, net_sint, direct, asteptat, diferenta, ok in part_verif:
|
|
L.append(f"| {p} | {suma} | {net_sint} | {direct} | {asteptat} "
|
|
f"| {diferenta} | {'OK' if ok else '**ESEC**'} |")
|
|
L.append("")
|
|
L.append(f"Rezultat: {'OK' if ok_part else '**ESEC**'}")
|
|
L.append("")
|
|
L.append("## 4. Analitice cu semn negativ (sold pe latura opusa formularului)")
|
|
L.append("")
|
|
L.append("Latura FACTURA e fixa in cod (`:379`): `401,404,462,419,408,4551` -> "
|
|
"CREDIT, orice alt cont -> DEBIT. Un analitic cu sold pe latura opusa "
|
|
"primeste totusi rand FACTURA, iar suma se scrie cu **semn negativ pe "
|
|
"latura formularului**, atat in randul BALANTA cat si in `sold`-ul "
|
|
"randului FACTURA. Soldul ramane pe latura corecta, balanta ramane "
|
|
"inchisa, iar randul FACTURA consuma exact randul BALANTA (fara "
|
|
"`NEREPARTIZAT`).")
|
|
L.append("")
|
|
if not negative:
|
|
L.append("Niciunul.")
|
|
else:
|
|
L.append("| cont | analitic | sold (semnat) | latura formularului |")
|
|
L.append("|---|---|---|---|")
|
|
for cont, k, s, side in negative:
|
|
L.append(f"| {cont} | {k} | {s} | {side} |")
|
|
L.append("")
|
|
L.append("## 5. Lungimi si dubluri")
|
|
L.append("")
|
|
L.append(f"- cont > 4 sau acont > 4: {lungimi if lungimi else '[] (gol)'} -> "
|
|
f"{'OK' if not lungimi else '**ESEC**'}")
|
|
L.append(f"- perechi (cont, acont) duplicate la BALANTA: "
|
|
f"{list(dubluri_bal) if dubluri_bal else '[] (gol)'} -> "
|
|
f"{'OK' if not dubluri_bal else '**ESEC**'}")
|
|
L.append("")
|
|
L.append("## 6. Randuri de diferenta generate (pct. 13-15)")
|
|
L.append("")
|
|
if not diffs:
|
|
L.append("Niciunul (toate diferentele nete sunt zero).")
|
|
for cont, d in diffs:
|
|
base = cont.split(".")[0]
|
|
if base in PARTENER_BANCA:
|
|
kind = "BALANTA (analitic de diferenta, fara FACTURA)"
|
|
elif este_partener(cont):
|
|
kind = "inclus in BALANTA agregat al sinteticului (fara FACTURA)"
|
|
else:
|
|
kind = "BALANTA (analitic de diferenta)"
|
|
L.append(f"- {cont}: diferenta neta {d} -> {kind}")
|
|
L.append("")
|
|
L.append("Nota: randurile FACTURA de tip `DIFERENTA SINTETIC` au fost eliminate; "
|
|
"diferenta nu se mai inventa, o rezolva formularul prin `NEREPARTIZAT`.")
|
|
L.append("")
|
|
L.append("## Verdict")
|
|
L.append("")
|
|
toate = ok_bal and ok_leaves and ok_part and ok_lung
|
|
L.append(f"{'TOATE verificarile au trecut.' if toate else '**UNA SAU MAI MULTE VERIFICARI AU PICAT - datele NU au fost ajustate.**'}")
|
|
L.append("")
|
|
L.append("Nota: coloana `explicatia` (D/C la FACTURA) a fost folosita doar ca "
|
|
"control intern si este goala in xlsx-ul final (capul de tabel o pastreaza).")
|
|
L.append("")
|
|
|
|
if dir_verificare:
|
|
os.makedirs(dir_verificare, exist_ok=True)
|
|
with open(os.path.join(dir_verificare, f"verificare_{firma}.md"), "w",
|
|
encoding="utf-8") as f:
|
|
f.write("\n".join(L))
|
|
|
|
return {"bal": ok_bal, "leaves": ok_leaves, "part": ok_part, "lung": ok_lung,
|
|
"totdeb": totdeb, "totcred": totcred, "net": net,
|
|
"pierdute": pierdute, "dubluri_leaf": duplicate_leaf,
|
|
"dubluri_bal": list(dubluri_bal), "lungimi": lungimi,
|
|
"part_verif": part_verif, "negative": negative}
|
|
|
|
|
|
|