Lane integrare: extrage.py si genereaza.py cu parametri, maparea legata
Firmele si lunile nu mai sunt hardcodate. extrage.py dispecerizeaza catre cele trei cititoare (pdf / xlsx / fdb, dedus din extensie, extensie necunoscuta = eroare) si scrie contractul intern; genereaza.py aplica maparea si scrie init_<FIRMA>_<an>_<luna>.xlsx plus mapare_<FIRMA>.xlsx. genereaza_xlsx.py devine biblioteca: an/luna parametri, data si scadenta calculate din ele, sablonul cautat langa script. Cand exista parteneri_ si facturi_ (drumul FDB) se emite cate un FACTURA per document, cu cod fiscal real, si numai daca suma documentelor da netul contului; altfel ramane comportamentul de azi, un FACTURA agregat. Corectat in orchestrare: la redenumirea unui cont cu parteneri, generatorul lua primul analitic ca sa afle contul sintetic. Daca analiticele erau mapate pe conturi diferite, BALANTA agregat si randurile FACTURA cadeau pe conturi diferite, tacut. Acum se opreste cu eroare; colapsarea pe acelasi cont ramane legala. Doua teste acopera ambele cazuri. 29 de teste trec, inclusiv regresia pe etalon cu generatorul modificat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
This commit is contained in:
119
docs/raport_lane_integrare.md
Normal file
119
docs/raport_lane_integrare.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Raport lane `integrare` - solduri2roa etapa 2
|
||||
|
||||
Data: 21.09.2026. Director: `D:\ROA\IMPORT2ROA\solduri2roa`. Singurul lane activ.
|
||||
|
||||
## 1. Facut
|
||||
|
||||
### 1.1 Fisiere noi
|
||||
|
||||
- `extrage.py` (nou) - punct de intrare sursa -> contractul intern:
|
||||
- `extrage.py:25-30` `SURSE_DUPA_EXTENSIE` (`.pdf`, `.xls`, `.xlsx`, `.fdb`).
|
||||
- `extrage.py:33-40` `deduce_sursa` - deducere din extensie; extensie necunoscuta = `SystemExit`.
|
||||
- `extrage.py:43-52` `extrage_pdf` / `extrage_xlsx` - importa `extract_balanta.parse_pdf` si
|
||||
`citire_xlsx.citeste_balanta`; scrie CSV-ul cu writer-ul existent `citire_xlsx.scrie_csv`
|
||||
(adauga coloana `tip` goala pe drumul PDF, conform contractului).
|
||||
- `extrage.py:55-59` `extrage_fdb` - cheama `citire_fdb.produce` (ca si `--an`/`--luna`).
|
||||
- `extrage.py:62-85` CLI `--sursa/--fisier/--firma/--an/--luna/--dir`.
|
||||
- `genereaza.py` (nou) - punct de intrare contractul intern -> xlsx:
|
||||
- `genereaza.py:30-34` citire parteneri/facturi CSV (daca exista).
|
||||
- `genereaza.py:37-46` `_mapare_si_exclusi` - din rezultatul `mapare.genereaza_mapare` iese
|
||||
`override` (doar `sursa == CORECTAT`, cu `cont`/`acont` explicit) si `exclusi`.
|
||||
- `genereaza.py:49-59` `_fara_exclusi` - un cont exclus iese cu tot cu subarbore (un parinte
|
||||
fara copii ar rupe `arbore()`).
|
||||
- `genereaza.py:62-112` fluxul: mapare -> generare -> scriere `init_<FIRMA>_<an>_<luna>.xlsx`
|
||||
-> `verifica`; iese cu 1 doar cand verificarile de structura pica.
|
||||
- `tests/test_integrare.py` (nou) - 4 teste (vezi 2).
|
||||
|
||||
### 1.2 `genereaza_xlsx.py` (singurul fisier existent modificat)
|
||||
|
||||
Parametrizare, nu rescriere: logica de constructie BALANTA/FACTURA si verificarile din etapa 5
|
||||
raman cu aceleasi formule.
|
||||
|
||||
- `genereaza_xlsx.py:27-40` - `monthrange`; `TEMPLATE` devine cale absoluta langa script (iesirile
|
||||
pot merge in orice `--dir`, inclusiv temp). Am scos constanta `FIRME` (hardcodarea ceruta) si
|
||||
`main()`-ul vechi; modulul e acum biblioteca.
|
||||
- `genereaza_xlsx.py:169-185` - `_data`/`_datascad` calculeaza `data`/`datascad` din `an`/`luna`
|
||||
(prima/ultima zi); `rand(header, an, luna, ...)` primeste perioada ca parametru. Pentru
|
||||
`2025/12` iese exact `01.12.2025` / `31.12.2025`, ca inainte.
|
||||
- `genereaza_xlsx.py:188-203` - `_mapare_cont`: maparea suprascrie doar ce i s-a dat explicit;
|
||||
la **parteneri** un `acont` lipsa (None) lasa forma generatorului. Asta e garda anti-dublare
|
||||
din `decizii_import.md` pct. 16: maparea nu pre-completa `acont` generic pe `401`.
|
||||
- `genereaza_xlsx.py:210-219` - `_text`/`_iso` (data ISO -> `DD.MM.YYYY` pentru facturile FDB).
|
||||
- `genereaza_xlsx.py:222-410` - `genereaza(rows, header, an, luna, mapare, parteneri, facturi)`:
|
||||
- conturile obisnuite iau `cont`/`acont` prin `_mapare_cont` (`:303-304`);
|
||||
- partenerii BANCA pastreaza `acont`-ul analiticului, identic pe BALANTA si FACTURA
|
||||
(`:329`, `:340-346`, `:388-389`) - corectia explicita de `acont` se aplica pe ambele;
|
||||
- randurile de diferenta raman calculate in cod, fara mapare (`:309-318`, `:347-353`);
|
||||
- `factura()` (`:264-297`) foloseste `parteneri`/`facturi` cand exista (cod fiscal real + un
|
||||
rand FACTURA per document, doar cand suma documentelor = netul analiticului si soldul e pe
|
||||
latura formularului), altfel cade pe comportamentul de azi (un FACTURA per partener, sold
|
||||
total). Fara parametri, calea de azi e neatinsa.
|
||||
- `genereaza_xlsx.py:427-428`, `:575-576` - `verifica(..., dir_verificare)` scrie
|
||||
`verificare_<FIRMA>.md` in directorul cerut si il creeaza daca lipseste.
|
||||
|
||||
## 2. Verificari
|
||||
|
||||
### 2.1 Teste
|
||||
|
||||
Comanda:
|
||||
|
||||
```
|
||||
py -m unittest discover -s tests -v
|
||||
```
|
||||
|
||||
Rezultat (exact, la final):
|
||||
|
||||
```
|
||||
Ran 27 tests in 3.226s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
27 = cele 23 existente (nemodificate: 2 regresie + 6 citire_xlsx + 5 citire_fdb + 10 mapare) +
|
||||
2 capat-la-capata (xlsx si fdb) + 2 dispecer. `EXIT=0`. `tests/test_regresie.py` trece
|
||||
**neschimbat**, pe FUNDATIA si MASTER.
|
||||
|
||||
Fluxurile au fost rulate si separat, cu `--dir` in temp (nu in radacina):
|
||||
|
||||
- `py extrage.py --sursa xlsx --fisier exemple\saga-sqlite-balanta-09-2026.xlsx --firma JOACA
|
||||
--an 2026 --luna 9 --dir <temp>` -> `balanta_JOACA.csv` (10 randuri);
|
||||
`py genereaza.py --firma JOACA --an 2026 --luna 9 --dir <temp>` -> `rows: 11
|
||||
totdeb=7298.15 totcred=7298.15 net=0.0`, toate cele 4 verificari OK (exit 0).
|
||||
- `py extrage.py --sursa fdb --fisier <copie CONT_BAZA.FDB> --firma JOACA --an 2026 --luna 9
|
||||
--dir <temp>` -> `balanta_JOACA.csv` (10) + `parteneri_JOACA.csv` (2) + `facturi_JOACA.csv` (1);
|
||||
`genereaza.py` -> aceleasi `rows: 11`, `totdeb=totcred=7298.15`, exit 0.
|
||||
- `py extrage.py --sursa pdf --fisier "IBB 31.12.2025.pdf" ...` -> 88 randuri;
|
||||
`genereaza.py --firma FUNDATIA --an 2025 --luna 12` -> `rows: 75 totdeb=totcred=7068369.5`, OK.
|
||||
- deducere fara `--sursa` (`.xlsx` -> xlsx, `.FDB` -> fdb) si extensie `.csv` -> eroare, exit 1.
|
||||
|
||||
### 2.2 Cens octeti si linii
|
||||
|
||||
Toate fisierele atinse sunt ASCII strict, LF (ca restul `.py` din proiect). Zero octeti > 0x7F
|
||||
inainte si dupa: `genereaza_xlsx.py` 0, `genereaza.py` 0, `extrage.py` 0, `tests/test_integrare.py`
|
||||
0. `genereaza_xlsx.py` are `CRLF=0`/`LF=588` pe disc, identic cu HEAD (`CRLF=0`, LF), deci nu am
|
||||
schimbat terminatorii de linie.
|
||||
|
||||
## 3. Nefacut / blocat / intrebari deschise
|
||||
|
||||
- **`exclus` din `corectii_<FIRMA>.xlsx`**: onorat la nivel de rand + subarbore in `genereaza.py`
|
||||
(nu era cerut explicit de sarcina). Nu exista test dedicat - mapare nu are coloana `exclus` in
|
||||
`mapare_<FIRMA>.xlsx`, deci nu se poate verifica automat din fisierul de mapare.
|
||||
- **`parteneri_`/`facturi_` de pe drumul FDB**: calea "factura per document" e implementata, dar pe
|
||||
firma de joaca **nu se poate exercita**: `citire_fdb` ruleaza analiticele in sintetic (decizia
|
||||
lane-ului `citire-fdb`, raport sectiunea 3.1), deci balanta nu are `401.00001` si documentele nu
|
||||
au carui analitic sa se lege; cade corect pe comportamentul de azi. Necesita o baza reala cu
|
||||
analitice, ca si poarta cititorului FDB.
|
||||
- O corectie de `cont` per analitic la un partener SINTETIC (nu BANCA) ar pune FACTURA pe alt cont
|
||||
decat BALANTA agregat; `mapare._verifica_coliziuni` sare peste parteneri. Nu e cazul periculos
|
||||
din sarcina (acela e `acont`, acoperit), dar merita stiut.
|
||||
- Nu am actualizat `CLAUDE.md` (tabelul de fisiere) cu `extrage.py`/`genereaza.py`; nu era in
|
||||
livrabil si nu sunt dependinte noi.
|
||||
|
||||
## 4. Stare periculoasa
|
||||
|
||||
Niciuna. Fara binar VFP editat/write-back, fara procese lasate pornite, fara fisiere in
|
||||
`D:\ROA\ROACONT`, `D:\SAGA250909` sau `saga2roa*` (baza Firebird a fost doar copiata in temp, in
|
||||
test si in verificarea manuala; copiile au fost sterse). In timpul verificarii manuale am scris
|
||||
din greseala `balanta_JOACA.csv`, `parteneri_JOACA.csv`, `facturi_JOACA.csv` in radacina (rule
|
||||
fara `workdir`); le-am sters, iar testele trimit explicit `--dir` in temp. Fara commit, fara
|
||||
`git add`.
|
||||
86
extrage.py
Normal file
86
extrage.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Punct de intrare: de la sursa (PDF / foaie SAGA / Firebird) la contractul intern.
|
||||
|
||||
Dispecerizeaza catre cititoarele existente, fara sa le dubleze logica:
|
||||
pdf -> extract_balanta.parse_pdf (raportul tiparit SAGA)
|
||||
xlsx -> citire_xlsx.citeste_balanta (.xls / .xlsx SAGA)
|
||||
fdb -> citire_fdb.produce (copia unei baze CONT_BAZA.FDB)
|
||||
|
||||
Scrie balanta_<FIRMA>.csv si, pe drumul fdb, parteneri_<FIRMA>.csv si
|
||||
facturi_<FIRMA>.csv. Daca --sursa lipseste, se deduce din extensie; o extensie
|
||||
necunoscuta da eroare, nu ghicit.
|
||||
|
||||
Utilizare:
|
||||
py extrage.py --sursa pdf --fisier "<balanta.pdf>" --firma FIRMA --an 2025 --luna 12
|
||||
py extrage.py --sursa xlsx --fisier "<balanta.xlsx>" --firma FIRMA --an 2026 --luna 9
|
||||
py extrage.py --sursa fdb --fisier "<copie CONT_BAZA.FDB>" --firma FIRMA --an 2026 --luna 9
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import citire_fdb
|
||||
import citire_xlsx
|
||||
import extract_balanta
|
||||
|
||||
SURSE_DUPA_EXTENSIE = {
|
||||
".pdf": "pdf",
|
||||
".xls": "xlsx",
|
||||
".xlsx": "xlsx",
|
||||
".fdb": "fdb",
|
||||
}
|
||||
|
||||
|
||||
def deduce_sursa(fisier):
|
||||
ext = os.path.splitext(fisier)[1].lower()
|
||||
sursa = SURSE_DUPA_EXTENSIE.get(ext)
|
||||
if sursa is None:
|
||||
raise SystemExit(
|
||||
"extensie necunoscuta pentru %s: %r (astept .pdf, .xls/.xlsx sau .fdb)"
|
||||
% (fisier, ext))
|
||||
return sursa
|
||||
|
||||
|
||||
def extrage_pdf(fisier, cale):
|
||||
rows = extract_balanta.parse_pdf(fisier)
|
||||
citire_xlsx.scrie_csv(rows, cale)
|
||||
return len(rows)
|
||||
|
||||
|
||||
def extrage_xlsx(fisier, cale):
|
||||
rows = citire_xlsx.citeste_balanta(fisier)
|
||||
citire_xlsx.scrie_csv(rows, cale)
|
||||
return len(rows)
|
||||
|
||||
|
||||
def extrage_fdb(fisier, firma, an, luna, dir_iesire):
|
||||
if an is None or luna is None:
|
||||
raise SystemExit("--sursa fdb cere --an si --luna")
|
||||
rez = citire_fdb.produce(fisier, firma, an, luna, dir_iesire)
|
||||
return len(rez["balanta"])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--sursa", choices=("pdf", "xlsx", "fdb"),
|
||||
help="lipsa = dedusa din extensia --fisier")
|
||||
parser.add_argument("--fisier", required=True)
|
||||
parser.add_argument("--firma", required=True)
|
||||
parser.add_argument("--an", type=int)
|
||||
parser.add_argument("--luna", type=int)
|
||||
parser.add_argument("--dir", default=".", help="directorul de iesire")
|
||||
a = parser.parse_args()
|
||||
|
||||
sursa = a.sursa or deduce_sursa(a.fisier)
|
||||
os.makedirs(a.dir, exist_ok=True)
|
||||
|
||||
if sursa == "fdb":
|
||||
n = extrage_fdb(a.fisier, a.firma, a.an, a.luna, a.dir)
|
||||
else:
|
||||
cale = os.path.join(a.dir, "balanta_%s.csv" % a.firma)
|
||||
n = extrage_pdf(a.fisier, cale) if sursa == "pdf" \
|
||||
else extrage_xlsx(a.fisier, cale)
|
||||
print("scris balanta_%s.csv: %d randuri (sursa %s)" % (a.firma, n, sursa))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
123
genereaza.py
Normal file
123
genereaza.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Punct de intrare: de la contractul intern la xlsx-ul de import ROA.
|
||||
|
||||
Citeste balanta_<FIRMA>.csv, aplica maparea (mapare.py) si scrie:
|
||||
init_<FIRMA>_<an>_<luna>.xlsx - xlsx-ul de import
|
||||
mapare_<FIRMA>.xlsx - propunerea completa (doar unealta scrie)
|
||||
corectii_<FIRMA>.xlsx - creat gol la prima rulare a firmei
|
||||
docs/verificare_<FIRMA>.md - verificarile obligatorii
|
||||
|
||||
Maparea hotaraste cont/acont pentru conturile obisnuite. La conturile cu parteneri
|
||||
forma lui acont ramane a generatorului: maparea suprascrie doar ce i se da explicit
|
||||
in corectii_<FIRMA>.xlsx (docs/decizii_import.md pct. 16). Randurile de diferenta
|
||||
raman calculate in cod.
|
||||
|
||||
Daca exista parteneri_<FIRMA>.csv / facturi_<FIRMA>.csv (drumul FDB), se folosesc:
|
||||
cod fiscal real si un rand FACTURA per document. Lipsa lor = comportamentul de azi
|
||||
(un FACTURA per partener, sold total).
|
||||
|
||||
Utilizare:
|
||||
py genereaza.py --firma FIRMA --an 2025 --luna 12 [--balanta balanta_FIRMA.csv]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
|
||||
import genereaza_xlsx as generator
|
||||
import mapare
|
||||
|
||||
|
||||
def _citeste_csv(path):
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
with open(path, encoding="utf-8-sig", newline="") as f:
|
||||
return list(csv.DictReader(f))
|
||||
|
||||
|
||||
def _mapare_si_exclusi(rezultat):
|
||||
"""{cont_saga: {cont, acont}} pentru corectiile explicite + multimea exclusilor."""
|
||||
override = {}
|
||||
exclusi = set()
|
||||
for r in rezultat:
|
||||
if r["exclus"]:
|
||||
exclusi.add(r["cont_saga"])
|
||||
elif r["sursa"] == mapare.SURSA_CORECTAT:
|
||||
override[r["cont_saga"]] = {"cont": r["cont"], "acont": r["acont"]}
|
||||
return override, exclusi
|
||||
|
||||
|
||||
def _fara_exclusi(rows, exclusi):
|
||||
"""Un cont exclus iese cu tot cu subarborele lui (un parinte fara copii ar rupe arborele)."""
|
||||
if not exclusi:
|
||||
return rows
|
||||
out = []
|
||||
for r in rows:
|
||||
c = r["cont"]
|
||||
if any(c == e or c.startswith(e + ".") for e in exclusi):
|
||||
continue
|
||||
out.append(r)
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--firma", required=True)
|
||||
parser.add_argument("--an", type=int, required=True)
|
||||
parser.add_argument("--luna", type=int, required=True)
|
||||
parser.add_argument("--balanta",
|
||||
help="implicit balanta_<FIRMA>.csv din --dir")
|
||||
parser.add_argument("--dir", default=".", help="directorul de iesire")
|
||||
a = parser.parse_args()
|
||||
|
||||
os.makedirs(a.dir, exist_ok=True)
|
||||
balanta = a.balanta or os.path.join(a.dir, "balanta_%s.csv" % a.firma)
|
||||
if not os.path.exists(balanta):
|
||||
raise SystemExit("lipseste balanta: %s" % balanta)
|
||||
|
||||
header = generator.citeste_header()
|
||||
if len(header) != 39:
|
||||
raise SystemExit("cap de tabel neasteptat: %d coloane" % len(header))
|
||||
rows = generator.citeste_balanta(balanta)
|
||||
|
||||
rezultat = mapare.genereaza_mapare(rows, a.firma, a.dir)
|
||||
override, exclusi = _mapare_si_exclusi(rezultat)
|
||||
rows = _fara_exclusi(rows, exclusi)
|
||||
|
||||
parteneri = _citeste_csv(os.path.join(a.dir, "parteneri_%s.csv" % a.firma))
|
||||
facturi = _citeste_csv(os.path.join(a.dir, "facturi_%s.csv" % a.firma))
|
||||
|
||||
out, diffs, leaf_map, leaf_rep, negative = generator.genereaza(
|
||||
rows, header, an=a.an, luna=a.luna, mapare=override,
|
||||
parteneri=parteneri, facturi=facturi)
|
||||
|
||||
xlsx_path = os.path.join(a.dir, "init_%s_%d_%d.xlsx"
|
||||
% (a.firma, a.an, a.luna))
|
||||
generator.scrie_xlsx(header, out, xlsx_path)
|
||||
rez = generator.verifica(a.firma, balanta, xlsx_path, rows, out, diffs,
|
||||
leaf_map, leaf_rep, negative, header,
|
||||
dir_verificare=os.path.join(a.dir, "docs"))
|
||||
|
||||
print("=== %s ===" % a.firma)
|
||||
print(" rows: %d totdeb=%s totcred=%s net=%s"
|
||||
% (len(out), rez["totdeb"], rez["totcred"], rez["net"]))
|
||||
print(" balanta=%s frunze=%s parteneri=%s lungimi=%s"
|
||||
% ("OK" if rez["bal"] else "ESEC",
|
||||
"OK" if rez["leaves"] else "ESEC",
|
||||
"OK" if rez["part"] else "ESEC",
|
||||
"OK" if rez["lung"] else "ESEC"))
|
||||
print(" scris %s si %s" % (xlsx_path,
|
||||
os.path.join(a.dir, "docs",
|
||||
"verificare_%s.md" % a.firma)))
|
||||
if rez["negative"]:
|
||||
print(" analitice cu semn negativ: %s" % (rez["negative"],))
|
||||
if rez["pierdute"] or rez["dubluri_leaf"] or rez["dubluri_bal"] or rez["lungimi"]:
|
||||
print(" ATENTIE: %s %s %s %s"
|
||||
% (rez["pierdute"], rez["dubluri_leaf"], rez["dubluri_bal"],
|
||||
rez["lungimi"]))
|
||||
|
||||
toate = rez["bal"] and rez["leaves"] and rez["part"] and rez["lung"]
|
||||
raise SystemExit(0 if toate else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Genereaza fisierele xlsx de initializare ROACONT din balantele SAGA (CSV).
|
||||
"""Generatorul xlsx-urilor de initializare ROACONT din balantele SAGA (CSV).
|
||||
|
||||
Produce (pentru fiecare firma):
|
||||
init_<FIRMA>_2025_12.xlsx - un sheet, randul 1 = capul de tabel,
|
||||
datele de la randul 2 in jos
|
||||
docs/verificare_<FIRMA>.md - verificarile obligatorii
|
||||
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.
|
||||
|
||||
@@ -26,17 +26,18 @@ Forma randurilor (revizuit 20.09.2026, vezi pct. 14-15):
|
||||
|
||||
import csv
|
||||
import os
|
||||
from calendar import monthrange
|
||||
|
||||
from openpyxl import load_workbook
|
||||
|
||||
AN = 2025
|
||||
LUNA = 12
|
||||
DATA = "01.12.2025"
|
||||
DATASCAD = "31.12.2025"
|
||||
SERIE = None
|
||||
VALUTA = None
|
||||
|
||||
TEMPLATE = "init_facturi_balanta_note.xlsx"
|
||||
# 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__)),
|
||||
"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.
|
||||
@@ -78,11 +79,6 @@ def citeste_conturi_parteneri(path=CONFIG_PARTENERI):
|
||||
|
||||
PARTENER = PARTENER_BANCA | PARTENER_SINTETIC | PARTENER_FARA_ANALITICE
|
||||
|
||||
FIRME = [
|
||||
("FUNDATIA", "balanta_FUNDATIA.csv"),
|
||||
("MASTER", "balanta_MASTERJOB.csv"),
|
||||
]
|
||||
|
||||
|
||||
def citeste_header():
|
||||
wb = load_workbook(TEMPLATE, read_only=True)
|
||||
@@ -170,30 +166,81 @@ def acont_diferenta(parent, kids):
|
||||
return ac
|
||||
|
||||
|
||||
def rand(header, **kw):
|
||||
def _data(an, luna):
|
||||
"""Prima zi a lunii, in formatul din sablon (DD.MM.YYYY)."""
|
||||
return "01.%02d.%04d" % (int(luna), int(an))
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
def rand(header, an, luna, **kw):
|
||||
r = {h: None for h in header}
|
||||
r.update(an=AN, luna=LUNA, data=DATA, datascad=DATASCAD,
|
||||
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 genereaza(rows, header):
|
||||
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)
|
||||
|
||||
# verificarile de structura care nu se ghicesc
|
||||
# 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)
|
||||
if len(cont4) > 4 or len(ac) > 4:
|
||||
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:
|
||||
@@ -214,27 +261,64 @@ def genereaza(rows, header):
|
||||
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"])
|
||||
add(rand(header, TIP="BALANTA", cont=cont4, acont=ac, numar=1,
|
||||
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)
|
||||
# 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, TIP="BALANTA", cont=cont.split(".")[0], acont=ac,
|
||||
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 = sorted({c.split(".")[0] for c in by if este_partener(c)})
|
||||
for p in parteneri:
|
||||
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)
|
||||
|
||||
@@ -242,7 +326,7 @@ def genereaza(rows, header):
|
||||
# 5121/5124: BALANTA cu acont + FACTURA cu ACELASI acont.
|
||||
for k in direct:
|
||||
kd = by[k]
|
||||
kac = split_cont(k)[1]
|
||||
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)
|
||||
@@ -253,19 +337,17 @@ def genereaza(rows, header):
|
||||
td, tc = None, neg
|
||||
else:
|
||||
td, tc = round(kd["total_d"], 2), round(kd["total_c"], 2)
|
||||
add(rand(header, TIP="BALANTA", cont=p, acont=kac, numar=1,
|
||||
nume=kd["denumire"], totdeb=td, totcred=tc), leaf=k)
|
||||
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)
|
||||
add(rand(header, TIP="FACTURA", cont=p, acont=kac,
|
||||
numar=k.replace(".", ""),
|
||||
nume=kd["denumire"] or f"FURNIZOR {k}",
|
||||
cod_fiscal=k, sold=sold, explicatia=side))
|
||||
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, TIP="BALANTA", cont=p, acont=ac, numar=1,
|
||||
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))
|
||||
@@ -288,8 +370,20 @@ def genereaza(rows, header):
|
||||
base_d += by[k]["total_d"]
|
||||
base_c += by[k]["total_c"]
|
||||
d = diff_by_parent.get(p, 0.0)
|
||||
add(rand(header, TIP="BALANTA", cont=p, acont=None, numar=1,
|
||||
nume=by[p]["denumire"],
|
||||
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:
|
||||
@@ -298,11 +392,8 @@ def genereaza(rows, header):
|
||||
leaf_rep.add(k)
|
||||
continue
|
||||
sold = abs(net) if este_latura_formular(net, side) else -abs(net)
|
||||
add(rand(header, TIP="FACTURA", cont=p, acont=None,
|
||||
numar=k.replace(".", ""),
|
||||
nume=by[k]["denumire"] or f"FURNIZOR {k}",
|
||||
cod_fiscal=k, sold=sold, explicatia=side),
|
||||
leaf=k)
|
||||
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):
|
||||
@@ -315,14 +406,14 @@ def genereaza(rows, header):
|
||||
else:
|
||||
td = round(by[p]["total_d"], 2)
|
||||
tc = round(by[p]["total_c"], 2)
|
||||
add(rand(header, TIP="BALANTA", cont=p, acont=None, numar=1,
|
||||
nume=by[p]["denumire"], totdeb=td, totcred=tc), leaf=p)
|
||||
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)
|
||||
add(rand(header, TIP="FACTURA", cont=p, acont=None,
|
||||
numar=p, nume=nume, cod_fiscal=p, sold=sold,
|
||||
explicatia=side))
|
||||
factura(p, cont_p, ac_p, nume, side, net, sold, None)
|
||||
return out, diffs, leaf_map, leaf_rep, negative
|
||||
|
||||
|
||||
@@ -341,7 +432,7 @@ def scrie_xlsx(header, rows, path):
|
||||
|
||||
|
||||
def verifica(firma, csv_path, xlsx_path, rows, out, diffs, leaf_map, leaf_rep,
|
||||
negative, header):
|
||||
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)
|
||||
@@ -488,7 +579,9 @@ def verifica(firma, csv_path, xlsx_path, rows, out, diffs, leaf_map, leaf_rep,
|
||||
"control intern si este goala in xlsx-ul final (capul de tabel o pastreaza).")
|
||||
L.append("")
|
||||
|
||||
with open(os.path.join("docs", f"verificare_{firma}.md"), "w",
|
||||
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))
|
||||
|
||||
@@ -499,29 +592,4 @@ def verifica(firma, csv_path, xlsx_path, rows, out, diffs, leaf_map, leaf_rep,
|
||||
"part_verif": part_verif, "negative": negative}
|
||||
|
||||
|
||||
def main():
|
||||
header = citeste_header()
|
||||
assert len(header) == 39, f"cap de tabel neasteptat: {len(header)} coloane"
|
||||
for firma, csv_path in FIRME:
|
||||
rows = citeste_balanta(csv_path)
|
||||
out, diffs, leaf_map, leaf_rep, negative = genereaza(rows, header)
|
||||
xlsx_path = f"init_{firma}_2025_12.xlsx"
|
||||
scrie_xlsx(header, out, xlsx_path)
|
||||
rez = verifica(firma, csv_path, xlsx_path, rows, out, diffs, leaf_map,
|
||||
leaf_rep, negative, header)
|
||||
print(f"=== {firma} ===")
|
||||
print(f" rows: {len(out)} totdeb={rez['totdeb']} totcred={rez['totcred']} net={rez['net']}")
|
||||
print(f" balanta={'OK' if rez['bal'] else 'ESEC'} "
|
||||
f"frunze={'OK' if rez['leaves'] else 'ESEC'} "
|
||||
f"parteneri={'OK' if rez['part'] else 'ESEC'} "
|
||||
f"lungimi={'OK' if rez['lung'] else 'ESEC'}")
|
||||
print(f" scris {xlsx_path} si docs/verificare_{firma}.md")
|
||||
if rez["negative"]:
|
||||
print(f" analitice cu semn negativ: {rez['negative']}")
|
||||
if rez["pierdute"] or rez["dubluri_leaf"] or rez["dubluri_bal"] or rez["lungimi"]:
|
||||
print(" ATENTIE:", rez["pierdute"], rez["dubluri_leaf"],
|
||||
rez["dubluri_bal"], rez["lungimi"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
164
tests/test_integrare.py
Normal file
164
tests/test_integrare.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""Test de integrare: capat-la-capat pe firma de joaca, din ambele surse.
|
||||
|
||||
Ruleaza `extrage.py` apoi `genereaza.py` in directoare temporare (nu scrie nimic in
|
||||
radacina proiectului), din:
|
||||
- exportul de foaie de calcul SAGA (exemple/saga-sqlite-balanta-09-2026.xlsx);
|
||||
- copia bazei Firebird D:\\SAGA250909\\0001\\CONT_BAZA.FDB.
|
||||
|
||||
Verifica: xlsx-ul de import exista, are randuri de date si SUM(totdeb) = SUM(totcred).
|
||||
|
||||
Pe o masina fara baza / fbclient.dll / driver, teste FDB se sar, nu esueaza.
|
||||
|
||||
Rulare: py -m unittest discover -s tests -v
|
||||
"""
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
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 extrage
|
||||
import genereaza_xlsx
|
||||
|
||||
EXTRAGE = RADACINA / "extrage.py"
|
||||
GENEREAZA = RADACINA / "genereaza.py"
|
||||
XLSX_EXEMPLU = RADACINA / "exemple" / "saga-sqlite-balanta-09-2026.xlsx"
|
||||
FBCLIENT = pathlib.Path(r"C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll")
|
||||
BAZA = pathlib.Path(r"D:\SAGA250909\0001\CONT_BAZA.FDB")
|
||||
|
||||
FIRMA = "JOACA"
|
||||
AN, LUNA = 2026, 9
|
||||
TOL = 0.01
|
||||
|
||||
|
||||
def _driver_disponibil():
|
||||
try:
|
||||
import firebird.driver # noqa: F401
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _ruleaza(script, arg, dir_iesire):
|
||||
"""Ruleaza un punct de intrare cu `--dir` in directorul temporar si verificat."""
|
||||
return subprocess.run(
|
||||
[sys.executable, str(script), "--firma", FIRMA,
|
||||
"--an", str(AN), "--luna", str(LUNA), "--dir", str(dir_iesire)] + arg,
|
||||
cwd=str(dir_iesire), capture_output=True, text=True)
|
||||
|
||||
|
||||
def _sume_xlsx(cale):
|
||||
wb = openpyxl.load_workbook(cale)
|
||||
try:
|
||||
ws = wb.active
|
||||
header = [c.value for c in ws[1]]
|
||||
i_deb = header.index("totdeb")
|
||||
i_cred = header.index("totcred")
|
||||
deb = cred = 0.0
|
||||
if ws.max_row > 1:
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
deb += row[i_deb] or 0
|
||||
cred += row[i_cred] or 0
|
||||
return ws.max_row, deb, cred
|
||||
finally:
|
||||
wb.close()
|
||||
|
||||
|
||||
class TestIntegrare(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="integrare_")
|
||||
self.dir = pathlib.Path(self._tmp.name)
|
||||
|
||||
def tearDown(self):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _flux(self, sursa, fisier):
|
||||
r = _ruleaza(EXTRAGE, ["--sursa", sursa, "--fisier", str(fisier)], self.dir)
|
||||
self.assertEqual(r.returncode, 0, "extrage.py a picat:\n%s\n%s"
|
||||
% (r.stdout, r.stderr))
|
||||
self.assertTrue((self.dir / ("balanta_%s.csv" % FIRMA)).exists(),
|
||||
"extrage.py nu a scris balanta")
|
||||
|
||||
r = _ruleaza(GENEREAZA, [], self.dir)
|
||||
self.assertEqual(r.returncode, 0, "genereaza.py a picat:\n%s\n%s"
|
||||
% (r.stdout, r.stderr))
|
||||
|
||||
xlsx = self.dir / ("init_%s_%d_%d.xlsx" % (FIRMA, AN, LUNA))
|
||||
self.assertTrue(xlsx.exists(), "lipseste xlsx-ul de import: %s" % xlsx)
|
||||
n, deb, cred = _sume_xlsx(xlsx)
|
||||
self.assertGreater(n, 1, "xlsx-ul de import nu are randuri de date")
|
||||
self.assertAlmostEqual(deb, cred, delta=TOL,
|
||||
msg="SUM(totdeb)=%s != SUM(totcred)=%s" % (deb, cred))
|
||||
return xlsx
|
||||
|
||||
def test_capat_la_capat_xlsx(self):
|
||||
if not XLSX_EXEMPLU.exists():
|
||||
self.skipTest("lipseste exportul %s" % XLSX_EXEMPLU)
|
||||
self._flux("xlsx", XLSX_EXEMPLU)
|
||||
|
||||
def test_capat_la_capat_fdb(self):
|
||||
if not FBCLIENT.exists():
|
||||
self.skipTest("lipseste fbclient.dll: %s" % FBCLIENT)
|
||||
if not BAZA.exists():
|
||||
self.skipTest("lipseste baza Firebird: %s" % BAZA)
|
||||
if not _driver_disponibil():
|
||||
self.skipTest("lipseste pachetul firebird-driver")
|
||||
copie = self.dir / "CONT_BAZA.FDB"
|
||||
shutil.copy2(str(BAZA), str(copie))
|
||||
self._flux("fdb", copie)
|
||||
|
||||
|
||||
class TestDispecerSursa(unittest.TestCase):
|
||||
|
||||
def test_deducere_din_extensie(self):
|
||||
self.assertEqual(extrage.deduce_sursa("balanta.pdf"), "pdf")
|
||||
self.assertEqual(extrage.deduce_sursa("balanta.xls"), "xlsx")
|
||||
self.assertEqual(extrage.deduce_sursa("balanta.XLSX"), "xlsx")
|
||||
self.assertEqual(extrage.deduce_sursa("CONT_BAZA.FDB"), "fdb")
|
||||
|
||||
def test_extensie_necunoscuta_da_eroare(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
extrage.deduce_sursa("balanta.csv")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class TestMapareSinteticPartener(unittest.TestCase):
|
||||
"""Analiticele unui cont cu parteneri nu au voie sa cada pe conturi diferite:
|
||||
BALANTA e agregat pe sintetic, FACTURA e per analitic, iar o redenumire
|
||||
partiala ar rupe tacit corespondenta dintre ele."""
|
||||
|
||||
ROWS = [
|
||||
{"cont": "401", "denumire": "FURNIZORI", "total_d": 0.0, "total_c": 300.0},
|
||||
{"cont": "401.0001", "denumire": "A", "total_d": 0.0, "total_c": 100.0},
|
||||
{"cont": "401.0002", "denumire": "B", "total_d": 0.0, "total_c": 200.0},
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
self.header = genereaza_xlsx.citeste_header()
|
||||
|
||||
def test_conturi_diferite_dau_eroare(self):
|
||||
mapare = {"401.0001": {"cont": "4011", "acont": None},
|
||||
"401.0002": {"cont": "4012", "acont": None}}
|
||||
with self.assertRaises(genereaza_xlsx.Stop) as ctx:
|
||||
genereaza_xlsx.genereaza(self.ROWS, self.header, 2025, 12, mapare=mapare)
|
||||
self.assertIn("conturi diferite", str(ctx.exception))
|
||||
|
||||
def test_acelasi_cont_pe_toate_analiticele_e_legal(self):
|
||||
mapare = {"401.0001": {"cont": "4011", "acont": None},
|
||||
"401.0002": {"cont": "4011", "acont": None}}
|
||||
out, _, _, _, _ = genereaza_xlsx.genereaza(self.ROWS, self.header, 2025, 12,
|
||||
mapare=mapare)
|
||||
conturi = {r["cont"] for r in out}
|
||||
self.assertEqual({"4011"}, conturi)
|
||||
Reference in New Issue
Block a user