Files
roafacturare/docs/cercetare/parametru_cont_contabilizeaza_articol.md
2026-09-09 22:19:22 +03:00

15 KiB

Verificare adversariala — proiectarea parametrului de cont (canal_cont_venit_fara_politica.md, sectiunea "Proiectarea parametrului de cont contabil")

Mandat: nu re-proiecta — proiectarea exista deja in canal_cont_venit_fara_politica.md:13-227 (citita integral). Aici: incercare de a o sparge + inchiderea celor 4 goluri semnalate de autor. Sursa PL/SQL: aceeasi, D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (17217 linii). Oracle: doar SELECT, schema MARIUSM_AUTO/ROA_CENTRAL. VFP: doar vfp_symbols.ps1 (index, read-only). Niciun fisier de cod atins.

Verdict, in patru randuri

Proiectarea rezista la verificarea adversariala — nu am gasit nicio eroare care sa-i invalideze concluzia centrala. Am gasit un rand din tabel mai slab decat descris (CU_TVA=1 hardcodat NU e complet inofensiv — are un efect colateral masurabil, minor, prin nproc_tva_max), un rand mai tare decat descris (IN_VALUTA din nin_valuta e o sursa solida, nu doar plauzibila — parametru obligatoriu, fara DEFAULT), o excludere confirmata corecta prin structura schemei (articolul "compus" nu poate exista pe o linie fara politica — nu o gaura), si un gol raportat de autor acum inchis cu fapt (cele doua clase de la :14069/:18089 sunt distincte, nu o duplicare a aceleiasi metode — 2 locuri VFP de atins, nu 1). Pe decizia 35 (regenerare = acelasi cod): DA, calea Oracle e identica, dar exista un obstacol real, deja proiectat separat (idfact_refolosire_si_documente.md), in alt pachet (PACK_CONTAFIN), nu in pack_facturare — nu invalideaza design-ul curent, dar regenerarea nu e completa fara acea a doua bucata de lucru.


1. Decizia 35 — regenerarea foloseste identic pack_facturare?

DA pe calea Oracle relevanta pentru parametrul de cont, cu o exceptie deja cunoscuta si separata.

Verificat direct: scrie_factura2 reseteaza starea de sesiune la fiecare apel prin initializeaza_date_factura (PACK_FACTURARE:1808-1846), care face DELETE FROM VANZARI_DETALII_TEMP; (:1835) si pack_facturare.nid_act := 0; (:1836) — contorul folosit de scrie_nota/scrie_discount/scrie_tva la fiecare INSERT INTO ACT_TEMP porneste curat la fiecare emitere, inclusiv la o a doua (regenerare). Nimic in contabilizeaza_articol, ramura noua inclusa, citeste vreo stare care ar presupune "documentul e nou" — toate variabilele folosite (nid_venchelt, nid_sectie_stoc, nin_valuta, nid_set, nid_util) sunt citite, nu verificate contra unei stari anterioare.

Obstacolul real e in PACK_CONTAFIN, nu in pack_facturare: reemiterea cu acelasi ID_FACT (ca sa nu se dubleze documentul contabil la regenerare) ar da azi ORA-00001 pe PK_DOCUMENTE, pentru ca SET_IDFACT ia mereu SEQ_IdFact.NEXTVAL si documentul vechi ramane in tabel doar soft-sters (STERS=1), nu disparut — analiza completa, cu solutia (variabila de pachet noua in PACK_CONTAFIN, MERGE ... WHEN MATCHED pe DOCUMENTE), e deja facuta integral in idfact_refolosire_si_documente.md (sectiunile A-C). E exact "ceva care ar cere cod separat" — dar separat inseamna alt pachet/alta procedura (SET_IDFACT, scrierea in DOCUMENTE), nu o ramura in plus in contabilizeaza_articol sau adauga_articol_factura. Ramane o bucata de lucru distincta, necesara pentru ca regenerarea sa fie completa, dar nu intersecteaza si nu invalideaza design-ul parametrului de cont.

Un gol neadresat de design, gasit acum: ramura pack_facturare.ntip = 4 ("facturare din avize", :7520-7537) apeleaza scrie_fact_aviz_custodie in loc de descarca_gestiune+discount — design-ul nu spune ce se intampla pe aceasta ramura in cazul fallback. In practica, probabil nu conteaza: "facturare din aviz" cere azi ca linia sursa sa aiba deja id_pol (adauga_articol_factura:5096, AND A.ID_POL = V_ID_POL pe VANZARI_DETALII a avizului sursa) — un aviz fara politica n-ar fi ajuns niciodata pana la factura pe acest drum, deci scenariul "fallback pe ntip=4" e putin probabil sa apara. Dar design-ul nu spune asta explicit — de adaugat o linie care sa acopere/exclude explicit acest caz inainte de implementare, nu de presupus tacit.

2. Cele 4 goluri semnalate de autor

2a. ofacturare.vc2:14069/:18089 — doua metode sau o duplicare?

vfp_symbols.ps1 -Where 'ofacturare.vc2:14069' -> frm_facturare_articole.do_scrie_articole (ofacturare.vc2:13967-14195). -Where 'ofacturare.vc2:18089' -> frm_facturare_articole2.do_scrie_articole (ofacturare.vc2:18003-18221). Confirmat: doua clase distincte, frm_facturare_articole si frm_facturare_articole2, fiecare cu propria metoda do_scrie_articole (acelasi nume de metoda, nu aceeasi clasa) — nu o duplicare literala a unui singur cod. Consecinta pentru implementare: parametrul nou trebuie cablat in doua locuri VFP, nu unul — ambele clase construiesc separat apelul RPC catre adauga_articol_factura. Nu schimba verdictul de fezabilitate, dar schimba suprafata de lucru VFP fata de impresia "un singur loc de atins".

2b. scrie_tva la cota 0% cu CU_TVA hardcodat 1 — efect real, nu inofensiv

Verificat corpul scrie_nota (:12537-12558): actualizarea nproc_tva_max/nid_jtva_coloana/ nTaxCode nu e neconditionata — e in interiorul IF V_CU_TVA = 1 THEN, deci exact conditionata de flagul pe care design-ul propune sa-l hardcodeze. In interior insa, comparatia IF pack_facturare.nproc_tva_max < V_PTVA THEN nu se uita la suma, doar la rata — deci daca articolul fallback are proc_tvav real 0% (scutit) si CU_TVA e fortat la 1, rata 0% intra in comparatia de maxim si, daca e prima/singura linie a documentului (nproc_tva_max initial -1, :1885), castiga — seteaza nid_jtva_coloana/nTaxCode la valorile acelei linii scutite. Aceste doua variabile sunt consumate mai departe in aceeasi procedura scrie_factura2, la discountul global pe factura (:6164-6184, V_DISCOUNT_FACTURA <> 0): rata/coloana/taxcode ale liniei scutite ar ajunge sa descrie linia de discount a intregii facturi, chiar daca alte linii au TVA real. Verdict: CU_TVA=1 hardcodat nu e "inofensiv" in toate cazurile cum spune design-ul — are un efect colateral real, dar restrans la o combinatie specifica (linie fallback cu TVA 0% + discount global pe factura + acea linie e cea cu rata "maxima" vazuta pana atunci). Nu invalideaza alegerea CU_TVA=1 ca implicit (majoritatea liniilor reale au TVA nenul), dar intareste, nu slabeste, cererea deja facuta de autor ("de confirmat cu Marius") — motivul de confirmat e mai concret decat "linie de TVA cu suma 0, inofensiv".

2c. INSERT INTO VANZARI_DETALII ... FROM VANZARI_DETALII_TEMP — linie exacta, confirmata

PACK_FACTURARE:13705-13757, in scrie_in_vanzari (spec :13488, apelata din finalizeaza_factura :14787, care e apelata la finalul fluxului de verificare — nu din scrie_factura2, alta faza a aceluiasi pipeline). Confirmat: lista de coloane e explicita, 24 coloane (ID_VANZARE, ID_ARTICOL, LOT, SERIE, ID_RATA, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD, PRET, PROC_TVAV, ID_JTVA_COLOANA, ID_JTVA_COLOANA_EX, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, EXPLICATIE, PRET_CU_TVA, DIFERENTA, CUSTODIE, ID_VANZARE_SET, ID_CTR, TAXCODE) — nu SELECT *. Exact ce presupunea design-ul (sectiunea 2, citand nota_contabila_fara_politica.md fara re-verificare): daca se doreste ca CONT_VENIT sa ajunga si in VANZARI_DETALII (trasabilitate), aceasta lista trebuie extinsa explicit — fara acest pas, coloana noua ramane doar pe VANZARI_DETALII_TEMP si ACT_TEMP, invizibila in VANZARI_DETALII dupa fapt. Confirmarea nu schimba verdictul design-ului (deja marcase pasul ca "recomandat, nu strict necesar"), doar il transforma din presupunere in fapt verificat.

2d. Apelanti adauga_articol_factura din restul suitei

Sarit, cum a cerut team-lead-ul — alt agent lucreaza pe suprafata de regresie.


3. Verificare adversariala pe tabelul din design (sectiunea 3 a raportului sursa)

Rand din tabel Incercare de infirmare Rezultat
IN_VALUTA <- pack_facturare.nin_valuta E setat pe toate fluxurile relevante, sau ramane nul si azi nu conteaza? Mai solid decat descris. nin_valuta := V_IN_VALUTA (:1902) e in initializeaza_date_factura, apelata o data la inceputul fiecarei emiteri, cu V_IN_VALUTA IN NUMBER fara DEFAULT in semnatura (:1827) — VFP e obligat sa trimita o valoare, nu poate omite parametrul. Nu e un fallback de sesiune care "poate ramane nesetat" (ca nid_venchelt), e un flag de document obligatoriu, mereu curent.
ID_VENCHELT/ID_SECTIE <- variabile de sesiune Cine le seteaza si cand? Confirmat, cu nuanta. nid_venchelt := V_ID_VENCHELT si nid_sectie_stoc := V_ID_SECTIE (:1876-1877), tot in initializeaza_date_factura, din parametri fara DEFAULT insa VFP poate trimite NULL pe ei (sunt opționale ca valoare, nu ca prezenta in apel) — deci pot ramane NULL pe tot documentul. Nu e o regresie: pe ramura veche, NVL(nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT)) ar da tot NULL daca nici politica nu are aceste campuri — acelasi rezultat posibil, aceeasi cauza (sesiune nesetata), nu unul nou introdus de fallback.
Garda descarca_gestiune copiata identic Acopera id_gestiune=-1000 si in_stoc? Da, fara diferenta. Garda foloseste detalii_articol.id_gestiune/.in_stoc — campuri populate identic de adauga_articol_factura indiferent de ramura (nu depind de politica azi, nici in design). Copierea literala a conditiei (:7473-7475) e corecta prin constructie.
ASCD/ASCC <- GetAnaliticByGrupUtilizatori Ce intoarce pe NO_DATA_FOUND, e acceptabil? Confirmat NULL (corp citit :16704-16723: lcAcont declarat fara valoare implicita, EXCEPTION WHEN NO_DATA_FOUND THEN NULL;, RETURN lcAcont). Acceptabil — e exact fallback-ul deja folosit azi, necondiționat, pe ramurile de aviz (:7416-7417,7421-7422) si ACT_TEMP.ASCD/ASCC sunt nullable (confirmat DDL, canal_cont_venit_fara_politica.md DDL section). Nu e un risc nou.
Articol "compus" (V_COMPUS=1) lasat in afara scopului Poate un articol ales ad-hoc din nomenclator fi compus? NU — exclus prin structura schemei, nu prin presupunere. Interogat direct ALL_VIEWS.VCRM_POLITICI_PRET_ART: coloana COMPUS citita de contabilizeaza_articol (SELECT COMPUS, ID_POL_ART ... FROM VCRM_POLITICI_PRET_ART, :7279-7283) e definita in view ca CASE WHEN PA.ID_POL_ART IN (SELECT DISTINCT ID_PACHET FROM CRM_PACHETE_ARTICOLE WHERE STERS=0) THEN 1 ELSE 0 END — o proprietate a perechii (articol, politica), identificata prin ID_POL_ART (cheia surogat a randului din CRM_POLITICI_PRET_ART). O linie fara politica nu are un ID_POL_ART — deci intrebarea "e articolul compus" nu se poate pune structural pe aceasta ramura. (NOM_ARTICOLE.COMPUS exista ca alta coloana, aliasata art_compus in acelasi view, dar nu e citita de contabilizeaza_articol — irelevanta aici.) Excluderea din design e corecta, nu o gaura.
RETURN V_INCASAT_CALCUL Se calculeaza corect pe ramura noua sau intoarce 0? Corect, prin constructie. Design-ul specifica explicit aceeasi acumulare ca azi (V_INCASAT_CALCUL := V_INCASAT_CALCUL + scrie_nota(...), apoi - scrie_discount(...)), cu V_INCASAT_CALCUL initializat 0 la declaratie (:7178), inainte de IF-ul care selecteaza ramura — identic cu azi. Niciun risc de RETURN 0 gasit.

4. Hardcodarile SCD='4111' / CU_TVA=1 — exista o sursa mai buna?

Cautare directa in tot PACK_FACTURARE pentru orice sursa alternativa care nu trece prin NOTE_CONTABILE: config de firma (getoptiunefirma('CONT...')), cont implicit pe partener/client (PARTENERI.CONT*), flag de scutire TVA pe articol/client (SCUTIT, EXCEPTAT) — zero rezultate pentru toate cele trei cautari. Singurul camp inrudit gasit, ACT_TEMP.NEIMPOZAB, e folosit in alt scop (raportare sume neimpozabile), nu ca sursa pentru CU_TVA. Concluzie: nu exista o sursa mai buna in cod — hardcodarea (sau parametrul explicit trimis de VFP) ramane singura optiune. Asta intareste recomandarea deja facuta de autor: de decis explicit cu Marius, nu de dedus din date, mai ales pentru CU_TVA dupa gasirea de la punctul 2b (efectul via nproc_tva_max nu mai e "pur cosmetic").


5. Completare: goExecutor.oExecuta si cursorul de verificare — fals pozitiv, nu bug

Verdict: fals pozitiv, confirmat cu argumente, nu doar presupus. oExecuta (funcție-wrapper, COMUN\programe\oproceduri_comune.prg:121-159) deleagă la oExecute (:173-504), care la rândul ei face SQLExec(lnHandle, lcSql, lcCursor) (:330) — lcSql e folosit exact cum a fost construit de apelant, fără nicio rescriere care să adauge un bind lipsă. Textul construit în COMUN\clase\ofacturare.vc2:14345-14359 (și identic la :14373-14387, :18343-18371) e sintaxă ODBC escape {call pack_facturare.scrie_factura2(...)}, cu 16 argumente poziționale (15 IN + ?@poDate.nid_vanzare pentru V_ID_VANZARE OUT NUMBER, al 16-lea parametru declarat), paranteza se închide imediat după — al 17-lea parametru, V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare (spec PACK_FACTURARE:656), nu are niciun placeholder în text, nici legat, nici nelegat. Asta e exact tiparul cunoscut al driverului ODBC Oracle pe care VFP îl folosește prin SQLExec: cand ultimul parametru declarat al unei proceduri e un REF CURSOR OUT, driverul îl detectează din catalogul Oracle (nu din textul apelului) și întoarce automat rândurile lui ca result set al apelului — al treilea argument al SQLExec/oExecute (numele de cursor VFP, aici lcCursorVerificare) e exact mecanismul de captare a acelui result set, fără sa fie nevoie de bind explicit. Codul imediat după apel (ofacturare.vc2:14394-14397, llReturn = goExecutor.oExecuta(lcSql,lcCursorVerificare) urmat de If Reccount(lcCursorVerificare)>0) tratează cursorul ca fiind deja populat cu rânduri — comportament incompatibil cu un apel eșuat sau cu un parametru nelegat (care ar da eroare Oracle, nu un cursor gol interpretabil). Nu pot confirma mecanismul din interiorul driverului însuși (e extern codebase-ului, verificabil doar prin comportament) — dar dovada indirectă e puternică: același tipar apare identic în toate cele 4 locuri de apel găsite, neschimbat de-a lungul mai multor versiuni (v 2.0.13 -> v 2.0.93, comentarii de istoric vizibile în cod), iar ecranul de verificare (funcție activă, folosită la fiecare emitere) depinde de acest cursor populat — dacă apelul ar eșua silențios, ecranul de verificare n-ar arăta niciodată note propuse, ceea ce ar fi fost observat imediat. Concluzie pentru plan: anomalia nu există — nu trebuie tratată ca risc pe cele 7 produse.

Ce ramane neverificat

  • Comportamentul exact al ramurii ntip=4/scrie_fact_aviz_custodie in scenariul fallback (punctul
    1. — argumentat ca improbabil, nu testat/exclus explicit in design.
  • Daca vreun raport Oracle activ presupune ACT_TEMP.ID_VENCHELT/ID_SECTIE populate pe liniile de venit (relevant doar daca sesiunea nu seteaza nid_venchelt/nid_sectie_stoc) — in afara scopului acestei verificari (cod PL/SQL only).
  • Suprafata de regresie la nivelul apelantilor adauga_articol_factura din restul suitei — explicit lasata altui agent.