"""Generatorul xlsx-urilor de initializare ROACONT din balantele SAGA (CSV). Biblioteca folosita de `genereaza.py` (parametri de linie de comanda); produce: init___.xlsx - un sheet, randul 1 = capul de tabel, datele de la randul 2 in jos verificare_.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 diferente_dc(by, children, diffs, brut): """{parinte: (D, C)} al postarilor direct pe sintetic. Pe net (ca pana acum), sau, cu brut=True (drumul FDB, unde sinteticul e suma bruta a tuturor notelor), cu D si C separat, ca rulajul sa ramana cel din SAGA. Daca vreo latura iese negativa (netarea soldului initial pe analitic), se revine la net.""" out = {} for cont, d in diffs: out[cont] = (d, 0.0) if d > 0 else (0.0, -d) if brut: for cont, kids in children.items(): dd = round(by[cont]["total_d"] - sum(by[k]["total_d"] for k in kids), 2) dc = round(by[cont]["total_c"] - sum(by[k]["total_c"] for k in kids), 2) if dd >= 0 and dc >= 0 and (dd > 0.005 or dc > 0.005): out[cont] = (dd, dc) return out def genereaza(rows, header, an=AN, luna=LUNA, mapare=None, parteneri=None, facturi=None, brut=False): by, children = arbore(rows) diffs = diferente(by, children) diff_by_parent = dict(diffs) dif = diferente_dc(by, children, diffs, brut) # 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 if round(r["total_d"], 2) == 0 and round(r["total_c"], 2) == 0: leaf_rep.add(r["cont"]) # fara sold si fara rulaj: nimic de importat 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, (dd, dc) in dif.items(): 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=dd or None, totcred=dc or 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 dif: dd, dc = dif[p] ac = acont_diferenta(p, direct) add(rand(header, an, luna, TIP="BALANTA", cont=p, acont=ac, numar=1, nume="DIFERENTA SINTETIC SAGA", totdeb=dd or None, totcred=dc or None)) continue # 401/4092/4111/461/462/4551 si 5311 if direct: dd, dc = dif.get(p, (0.0, 0.0)) cont_p, ac_p = _mapare_cont(p, p, None, mapare, True) def baza(kids): """Totalurile BALANTA pentru analiticele date; netul pe latura opusa intra negativ (vezi `negative`).""" bd = bc = 0.0 for k in kids: net = round(by[k]["total_d"] - by[k]["total_c"], 2) if net != 0 and not este_latura_formular(net, side): negative.append((p, k, -abs(net), side)) if side == "D": bd -= abs(net) else: bc -= abs(net) else: bd += by[k]["total_d"] bc += by[k]["total_c"] return bd, bc def facturi(kids, cont_f, ac_f): for k in kids: 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_f, ac_f, mapare, True) factura(k, cont_k, kac, by[k]["denumire"], side, net, sold, k) # Analitice cu acont dat prin corectii (461.SGR functional, 461.RESTUL -> # 461/1 pentru parteneri): cate un BALANTA per (cont, acont) si FACTURA cu # ACELASI acont (pct. 16); postarile direct pe sintetic -> acont de diferenta. grupuri = {} for k in direct: grupuri.setdefault(_mapare_cont(k, cont_p, ac_p, mapare, True), []).append(k) if any(ac != ac_p for _, ac in grupuri): for (cont_g, ac_g), kids in sorted(grupuri.items(), key=lambda g: str(g[0])): bd, bc = baza(kids) nume = by[kids[0]]["denumire"] if len(kids) == 1 else by[p]["denumire"] add(rand(header, an, luna, TIP="BALANTA", cont=cont_g, acont=ac_g, numar=1, nume=nume, totdeb=round(bd, 2), totcred=round(bc, 2))) facturi(kids, cont_g, ac_g) if dd or dc: add(rand(header, an, luna, TIP="BALANTA", cont=cont_p, acont=acont_diferenta(p, direct), numar=1, nume="DIFERENTA SINTETIC SAGA", totdeb=dd or None, totcred=dc or None)) continue base_d, base_c = baza(direct) 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 + dd, 2), totcred=round(base_c + dc, 2))) facturi(direct, cont_p, ac_p) 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}