Cititorul FDB: sume totale ca SAGA, analitice pastrate, baza oprita acceptata
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
This commit is contained in:
104
citire_fdb.py
104
citire_fdb.py
@@ -12,17 +12,21 @@ COPIE a bazei (gbak sau fisier oprit), niciodata baza vie a clientului.
|
||||
|
||||
Sumele nu vin din soldurile stocate in CONTURI (sunt 0); se agrega REGISTRU pe trei
|
||||
intervale: inainte de inceputul anului (sold de preluare), de la inceputul anului pana
|
||||
la inceputul lunii cerute si luna ceruta. Precedentul (preluare + an) se neteaza pe
|
||||
latura, ca in exportul SAGA; rulajul lunii se ia brut, iar total = prec + rulaj.
|
||||
Analiticul din REGISTRU (401.00002) se ruleaza in sinteticul lui (401); contul
|
||||
la inceputul lunii cerute si luna ceruta. Precedentul = soldul de preluare netat pe
|
||||
latura + rulajele din an, BRUTE (ca SAGA: netarea lor ar anula rulajul conturilor de
|
||||
cheltuieli si venituri); rulajul lunii se ia brut, iar total = prec + rulaj.
|
||||
Fiecare cont din REGISTRU intra in toate nivelurile lui: 401.00002 -> 401 si 401.00002,
|
||||
deci balanta are sinteticele si analiticele, ca exportul SAGA cu analitice. Contul
|
||||
colector `%` se exclude.
|
||||
|
||||
LIMITA VERIFICARII (scrisa si in raport): cititorul este verificat DOAR pe cazul
|
||||
sintetic al firmei de joaca din `D:\\SAGA250909\\0001` (10 conturi, toate sintetice,
|
||||
fara analitice in iesire, 2 furnizori, 0 clienti, 1 factura, luna 09/2026). Poarta
|
||||
(balanta FDB identica cu exportul .xlsx) dovedeste rollup-ul la sintetic si
|
||||
periodizarea pe acest caz; NU dovedeste nimic despre conturile analitice. Nu se
|
||||
foloseste la un client fara rerularea portii pe datele lui.
|
||||
Baza primita se copiaza in temporar si copia se repune online (clientul o poate opri
|
||||
cu `full shutdown` inainte de copiere); fisierul primit nu se atinge.
|
||||
|
||||
VERIFICARI: firma de joaca din `D:\SAGA250909\0001` (sinteticele = exportul .xlsx,
|
||||
la octet) si LACERTA 06/2026 (tests/test_lacerta.py: 48 din 53 de frunze identice cu
|
||||
importul facut din exportul SAGA, restul explicate in docs/analiza_lacerta_fdb.md;
|
||||
facturile per document insumeaza netul fiecarui partener). La un client nou se
|
||||
reruleaza o poarta pe datele lui.
|
||||
|
||||
Utilizare:
|
||||
py citire_fdb.py "<copie CONT_BAZA.FDB>" "<FIRMA>" <an> <luna> [dir_iesire]
|
||||
@@ -30,11 +34,15 @@ Utilizare:
|
||||
|
||||
import csv
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from calendar import monthrange
|
||||
from datetime import date
|
||||
|
||||
FBCLIENT = r"C:\Program Files\Firebird\Firebird30_Saga\fbclient.dll"
|
||||
GFIX = os.path.join(os.path.dirname(FBCLIENT), "gfix.exe")
|
||||
UTILIZATOR = "SYSDBA"
|
||||
PAROLA = "masterkey"
|
||||
CHARSET = "WIN1250"
|
||||
@@ -45,30 +53,23 @@ COLOANE_BALANTA = ["pagina", "cont", "denumire", "prec_d", "prec_c", "rulaj_d",
|
||||
COLOANE_PARTENERI = ["cont_analitic", "cod", "denumire", "cod_fiscal"]
|
||||
COLOANE_FACTURI = ["cont_analitic", "numar", "data", "scadent", "sold"]
|
||||
|
||||
# Prefix pe partea din stanga punctului (401.00002 -> 401). TRIM inainte, coloanele
|
||||
# SAGA sunt VARCHAR(20) cu spatii la dreapta.
|
||||
_SINT = ("CASE WHEN POSITION('.' IN TRIM(%s)) > 0 "
|
||||
"THEN SUBSTRING(TRIM(%s) FROM 1 FOR POSITION('.' IN TRIM(%s)) - 1) "
|
||||
"ELSE TRIM(%s) END")
|
||||
|
||||
_SQL_BALANTA = """
|
||||
SELECT SINT, LATURA,
|
||||
SELECT CONT, LATURA,
|
||||
SUM(CASE WHEN DATA < ? THEN SUMA ELSE 0 END) AS INIT,
|
||||
SUM(CASE WHEN DATA >= ? AND DATA < ? THEN SUMA ELSE 0 END) AS AN_PREC,
|
||||
SUM(CASE WHEN DATA >= ? AND DATA < ? THEN SUMA ELSE 0 END) AS LUNA
|
||||
FROM (
|
||||
SELECT {sint_d} AS SINT, 'D' AS LATURA, SUMA, DATA
|
||||
SELECT TRIM(CONT_D) AS CONT, 'D' AS LATURA, SUMA, DATA
|
||||
FROM REGISTRU
|
||||
WHERE TRIM(CONT_D) NOT IN ('', '%') AND DATA < ?
|
||||
UNION ALL
|
||||
SELECT {sint_c} AS SINT, 'C' AS LATURA, SUMA, DATA
|
||||
SELECT TRIM(CONT_C) AS CONT, 'C' AS LATURA, SUMA, DATA
|
||||
FROM REGISTRU
|
||||
WHERE TRIM(CONT_C) NOT IN ('', '%') AND DATA < ?
|
||||
) A
|
||||
GROUP BY SINT, LATURA
|
||||
ORDER BY SINT, LATURA
|
||||
""".format(sint_d=_SINT % (("CONT_D",) * 4),
|
||||
sint_c=_SINT % (("CONT_C",) * 4))
|
||||
GROUP BY CONT, LATURA
|
||||
ORDER BY CONT, LATURA
|
||||
"""
|
||||
|
||||
_API_INCARCAT = False
|
||||
|
||||
@@ -164,28 +165,33 @@ def citeste_balanta(cale, an, luna):
|
||||
con.close()
|
||||
|
||||
sume = {}
|
||||
for sint, latura, init, an_prec, luna_v in brute:
|
||||
s = sume.setdefault(_text(sint), {"i_d": 0, "i_c": 0, "a_d": 0,
|
||||
"a_c": 0, "l_d": 0, "l_c": 0})
|
||||
if latura == "D":
|
||||
s["i_d"] += init or 0
|
||||
s["a_d"] += an_prec or 0
|
||||
s["l_d"] += luna_v or 0
|
||||
else:
|
||||
s["i_c"] += init or 0
|
||||
s["a_c"] += an_prec or 0
|
||||
s["l_c"] += luna_v or 0
|
||||
for cont, latura, init, an_prec, luna_v in brute:
|
||||
seg = _text(cont).split(".")
|
||||
# contul intra in fiecare nivel al lui: 215.1 -> 215 si 215.1
|
||||
for k in range(1, len(seg) + 1):
|
||||
s = sume.setdefault(".".join(seg[:k]), {"i_d": 0, "i_c": 0, "a_d": 0,
|
||||
"a_c": 0, "l_d": 0, "l_c": 0})
|
||||
if latura == "D":
|
||||
s["i_d"] += init or 0
|
||||
s["a_d"] += an_prec or 0
|
||||
s["l_d"] += luna_v or 0
|
||||
else:
|
||||
s["i_c"] += init or 0
|
||||
s["a_c"] += an_prec or 0
|
||||
s["l_c"] += luna_v or 0
|
||||
|
||||
rows = []
|
||||
for cont in sorted(sume):
|
||||
s = sume[cont]
|
||||
# precedent = soldul la inceputul lunii (preluare + an), pe net
|
||||
prec_d, prec_c = _net(s["i_d"] + s["a_d"], s["i_c"] + s["a_c"])
|
||||
# precedent = soldul de preluare pe net + rulajele din an, brute (ca SAGA:
|
||||
# netarea rulajelor ar sterge rulajul conturilor de cheltuieli/venituri)
|
||||
prec_d, prec_c = _net(s["i_d"], s["i_c"])
|
||||
prec_d, prec_c = prec_d + s["a_d"], prec_c + s["a_c"]
|
||||
rulaj_d, rulaj_c = s["l_d"], s["l_c"]
|
||||
total_d = prec_d + rulaj_d
|
||||
total_c = prec_c + rulaj_c
|
||||
sold_d, sold_c = _net(total_d, total_c)
|
||||
denumire, tip = conturi.get(cont, ("", ""))
|
||||
denumire, tip = conturi.get(cont) or ("", conturi.get(cont.split(".")[0], ("", ""))[1])
|
||||
rows.append({
|
||||
"pagina": "", "cont": cont, "denumire": denumire,
|
||||
"prec_d": _num(prec_d), "prec_c": _num(prec_c),
|
||||
@@ -284,13 +290,31 @@ def scrie_csv(rows, cale, coloane):
|
||||
wr.writerows(rows)
|
||||
|
||||
|
||||
def copie_online(cale_fdb, dir_tmp):
|
||||
"""Copiaza baza in dir_tmp si o repune online, daca clientul a oprit-o inainte de
|
||||
copiere (`full shutdown`). Fisierul primit nu se atinge. Pe o baza deja online
|
||||
gfix raspunde cu eroare, inofensiva: daca repunerea chiar esueaza, conectarea
|
||||
urmatoare pica cu mesajul real (`database ... shutdown`)."""
|
||||
if not os.path.exists(cale_fdb):
|
||||
raise FileNotFoundError("lipseste baza Firebird: %s" % cale_fdb)
|
||||
copie = os.path.join(dir_tmp, "CONT_BAZA.FDB")
|
||||
shutil.copyfile(cale_fdb, copie)
|
||||
if os.path.exists(GFIX):
|
||||
subprocess.run([GFIX, "-online", "normal", copie, "-user", UTILIZATOR,
|
||||
"-password", PAROLA], capture_output=True)
|
||||
return copie
|
||||
|
||||
|
||||
def produce(cale_fdb, firma, an, luna, dir_iesire="."):
|
||||
"""Scrie cele trei fisiere ale contractului intern; intoarce randurile."""
|
||||
"""Scrie cele trei fisiere ale contractului intern; intoarce randurile.
|
||||
Citeste dintr-o copie temporara, repusa online (vezi copie_online)."""
|
||||
if not os.path.isdir(dir_iesire):
|
||||
os.makedirs(dir_iesire)
|
||||
balanta = citeste_balanta(cale_fdb, an, luna)
|
||||
parteneri = citeste_parteneri(cale_fdb)
|
||||
facturi = citeste_facturi(cale_fdb, an, luna)
|
||||
with tempfile.TemporaryDirectory(prefix="citire_fdb_") as tmp:
|
||||
copie = copie_online(cale_fdb, tmp)
|
||||
balanta = citeste_balanta(copie, an, luna)
|
||||
parteneri = citeste_parteneri(copie)
|
||||
facturi = citeste_facturi(copie, an, luna)
|
||||
scrie_csv(balanta, os.path.join(dir_iesire, "balanta_%s.csv" % firma),
|
||||
COLOANE_BALANTA)
|
||||
scrie_csv(parteneri, os.path.join(dir_iesire, "parteneri_%s.csv" % firma),
|
||||
|
||||
Reference in New Issue
Block a user