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

17 KiB

Golul ntip=4 (facturare din avize) fata de proiectarea parametrului de cont contabil

Cercetare read-only, continua canal_cont_venit_fara_politica.md (sectiunea "Proiectarea parametrului de cont contabil", liniile 13-227). Confirma si extinde golul deja semnalat, mai succint, in parametru_cont_contabilizeaza_articol.md:49-56,167-168.

Sursa Oracle: D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (17217 linii). Toate liniile citate mai jos sunt verificate direct pe acest fisier in aceasta runda — numerele din cererea initiala (7520-7537, 5096) s-au dovedit identice, fara offset, fata de fisierul curent (nu +17 cum se anticipa).

Verdict, in cinci randuri

Ramura ntip=4 e EXCLUSA STRUCTURAL, dar nu prin mecanismul presupus initial (nu prin garda id_pol din cursor_articol/FACT-024 a lui contabilizeaza_articol). Blocajul real e cu o functie mai devreme: adauga_articol_factura, ramura WHEN pack_facturare.ntip = 4 (:5080-5103), cauta randul original din avizul sursa prin A.ID_POL = V_ID_POL fara niciun handler de exceptie — daca V_ID_POL e NULL (exact cazul "articol fara politica"), NULL = NULL nu se potriveste niciodata in SQL, deci SELECT ... INTO cade mereu cu NO_DATA_FOUND neprins, la momentul adaugarii articolului in factura (adauga_articol_factura), cu mult inainte ca linia sa ajunga vreodata la contabilizeaza_articol. Practic, un articol fara politica nu poate fi adaugat deloc pe un document ntip=4 — eroarea apare la primul pas, nu la scriere. Gol sora mai important, gasit in aceasta cercetare: ramurile de aviz (ntip in afara bucket-ului "factura") — 28/29 (SCD hardcodat 461) si toate celelalte (SCD hardcodat 418) — raman perfect accesibile cu cont_venit populat, iar ramura noua propusa hardcodeaza SCD='4111' necondiționat de ntip, ceea ce ar scrie contul gresit pe orice aviz cu articol fara politica. Vezi sectiunea "Ramuri sora" mai jos — mai grav decat golul ntip=4 insusi, pentru ca e efectiv atins, nu doar teoretic.

1. Lantul de dovezi pentru "ntip=4 e exclus" — pas cu pas

1.1. ntip e o variabila de pachet, setata o singura data per document

ff_...:165   ntip NUMBER(2);                                  -- declarata in spec, variabila publica de pachet
ff_...:1882  pack_facturare.ntip := V_TIP;                    -- in initializeaza_date_factura (una din cele 4 supraincarcari, ~1650-1918)

V_TIP vine din poDate.Tip la apelul VFP pack_facturare.initializeaza_date_factura(...) (COMUN\clase\ofacturare.vc2:13981-13999, argumentul Alltrim(Str(poDate.Tip)) pozitionat ca V_TIP). O data setat, pack_facturare.ntip ramane valabil pentru toate apelurile ulterioare din aceeasi tranzactie (adauga_articol_factura, apoi scrie_factura2/scrie_factura_avize_retur/ scrie_aviz_retur -> contabilizeaza_articol) — e o singura valoare per document, nu per linie.

1.2. Apelanti VFP care produc poDate.Tip = 4

Comentat consecvent && factura din aviz / && aviz in tot codul VFP:

COMUN\clase\ofacturare_comun.vc2:4347        If poDate.tip = 4 && factura din aviz
COMUN\programe\oproceduri_facturare.prg:1253 If poDate.tip = 4 && factura din aviz
COMUN\programe\ofacturare.prg:1512           Case poDate.tip = 4
COMUN\clase\ofacturare.vc2:9534,9636,9674,14301,15151,18299,19022  Case poDate.tip = 4 (afisare/validare)

ofacturare.prg:1268,1488,1504 trateaza si combinatia poDate.tip = 4 And Not Empty(poDate.id_comanda_aviz) alaturi de ntip IN (3,21,28,42,47) — confirma ca ntip=4 e categoria distincta "facturare pe baza de avize existente" (formularul dedicat de facturare din avize), separata de facturarea directa sau din comenzi.

1.3. adauga_articol_factura, ramura ntip=4 — blocajul real

ff_...:4989-5015  PROCEDURE adauga_articol_factura(..., V_ID_POL IN NUMBER, ...)

V_ID_POL e parametru direct (nu derivat), trimis de VFP la COMUN\clase\ofacturare.vc2:14072 (si identic :18107):

Nvl(Alltrim(Str(poArt.id_pol)),[NULL])

Daca poArt.id_pol e .NULL. in VFP (cazul "articol fara politica" — premiza intregului proiect #13), Str() propaga .NULL., iar Nvl(...) trimite literalul SQL NULL — deci V_ID_POL ajunge NULL in Oracle exact cand articolul n-are politica.

In corpul adauga_articol_factura, CASE-ul care determina pretul/TVA/valuta ramurii (:5052-5220) are, pentru ntip=4:

ff_...:5080-5103
      WHEN pack_facturare.ntip = 4 THEN
        -- facturare din avize
        SELECT DISTINCT A.PRET, A.PROC_TVAV, A.ID_VALUTA, A.PRET_CU_TVA, B.IN_STOC
          INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
          FROM VANZARI_DETALII A
          LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
         WHERE A.ID_ARTICOL = V_ID_ARTICOL
           AND A.ID_POL = V_ID_POL
           AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE
           AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR
           AND NVL(A.CONT, 'XXXX') = V_CONT
           AND A.ID_VANZARE IN (SELECT X FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab)));

Fara niciun EXCEPTION WHEN NO_DATA_FOUND — spre deosebire de ramurile vecine din acelasi CASE: ntip=45 are handler cu FACT-018 (:5140-5145), V_OPT_FACTURARE=3 are handler cu fallback + FACT-012 (:5167-5185), ELSE are handler cu FACT-013 (:5188-5198). Doar ramurile ntip IN (3,21,28,42,47) (:5053-5078, cauta in COMENZI_ELEMENTE dupa comanda, nu dupa politica) si ntip=4 sunt fara plasa de siguranta.

Cu V_ID_POL = NULL, A.ID_POL = V_ID_POL nu se poate potrivi niciodata (semantica SQL: orice comparatie cu NULL da UNKNOWN, niciodata TRUE, indiferent ce e stocat in A.ID_POL) — SELECT ... INTO (fara DISTINCT+agregat care sa tolereze zero randuri) arunca mereu NO_DATA_FOUND. Neprinsa aici, exceptia se propaga necaptata pana la apelantul PL/SQL (blocul anonim generat de VFP), care se intoarce la goExecutor ca eroare Oracle bruta (ORA-01403: no data found), nu ca FACT-024 — vezi punctul 3 mai jos pentru diferenta.

Concluzie pas 1.3: un articol fara politica (V_ID_POL=NULL, exact scenariul cont_venit) nu poate fi adaugat pe un document ntip=4 — apelul adauga_articol_factura insusi esueaza, inainte ca VANZARI_DETALII_TEMP sa primeasca randul, deci cu mult inainte ca contabilizeaza_articol (unde e ramura noua cont_venit) sa vada vreodata acea linie.

1.4. Blocajul secundar (daca 1.3 n-ar exista): contabilizeaza_articol insusi

Chiar daca ipotetic linia ar ajunge in VANZARI_DETALII_TEMP cu id_pol=NULL, funcia care scrie efectiv factura are propria garda, inaintea oricarui ntip:

ff_...:7278-7302  BEGIN
                     SELECT COMPUS, ID_POL_ART INTO V_COMPUS, V_ID_POL_ART
                       FROM VCRM_POLITICI_PRET_ART
                      WHERE ID_ARTICOL = detalii_articol.id_articol
                        AND ID_POL = detalii_articol.id_pol;
                   EXCEPTION
                     WHEN NO_DATA_FOUND THEN
                       ... RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)');
                   END;

Aceasta ruleaza la inceputul functiei, inainte de OPEN cursor_articol (:7393) si deci inainte de CASE-ul pe ntip (:7398-7423) si de IF-ul ntip<>4 (:7472). Cu id_pol=NULL, FACT-024 cade aici, indiferent de ntip — confirma independent ca ramura ntip=4 de la :7520-7537 (scrie_fact_aviz_custodie) e neatinsa azi de o linie fara politica, e a doua plasa de siguranta, redundanta cu 1.3 dar pe alt strat.

Ramura propusa cont_venit IS NOT NULL (proiectare canal_cont_venit_fara_politica.md sectiunea 3) se planteaza "inainte de :7278" — deci inlocuieste ambele garda de mai sus cand cont_venit e populat. Asta e sigur (design-ul insusi), dar devine irelevant pentru ntip=4 pentru ca linia nu ajunge niciodata aici (blocata la 1.3).

2. Ce se intampla defensiv daca cineva ajunge totusi acolo

Doua scenarii distincte, cu raspunsuri diferite:

(a) Scenariul normal — cont_venit populat, id_pol NULL (cum cere premiza proiectului): esec la adaugarea articolului, nu la scrierea facturii. Eroare Oracle bruta (ORA-01403: no data found), aparuta din adauga_articol_factura (:5080-5103), propagata prin goExecutor catre VFP ca oPrelucrareEroare() — nu FACT-024 (acela e alt cod, alta functie, alt moment). Utilizatorul vede un mesaj Oracle generic, nu un mesaj de business explicativ. Nu exista azi niciun cod care sa traduca acest caz intr-un mesaj prietenos — e un gol de UX, nu de integritate a datelor (nu se scrie nimic gresit, doar esueaza fara explicatie buna).

(b) Scenariul defensiv real — cont_venit SI id_pol populate simultan pe aceeasi linie (nimic in schema nu impune exclusivitate reciproca; VANZARI_DETALII_TEMP.CONT_VENIT e o coloana noua, fara CHECK care sa lege de ID_POL). Daca cineva (bug in VFP, sau folosire deliberata combinata pe viitor) ar trimite ambele populate pe un document ntip=4: pasul 1.3 ar reusi (V_ID_POL nu mai e NULL, deci SELECT din VANZARI_DETALII isi gaseste randul), linia intra in VANZARI_DETALII_TEMP cu cont_venit si id_pol completate. La contabilizeaza_articol, ramura noua (detalii_articol.cont_venit IS NOT NULL) preia controlul complet — fara sa verifice ntip — si executa necondiționat scrie_nota + descarca_gestiune + discount, exact ca pentru o factura normala. Nu cade cu nicio eroare. Scrie tacut date gresite: sare complet peste scrie_fact_aviz_custodie (:7521-7536), care exista tocmai pentru ca marfa a iesit deja din gestiune cand s-a scris avizul sursa — rularea descarca_gestiune in loc de acea functie descarca stocul a doua oara pentru aceeasi marfa, fara nicio garda care sa prinda dubla scadere. Acesta e raspunsul relevant pentru textul de plan: nu un crash, un defect tacut de date daca vreodata cele doua canale se intalnesc pe aceeasi linie.

3. Toate valorile ntip relevante in zona contabilizeaza_articol (:7173-7547) — verdict per valoare

Ramura noua (cont_venit IS NOT NULL) inlocuieste tot ce e listat mai jos, necondiționat de ntip (design-ul, sectiunea 3, nu mentioneaza ntip deloc in continutul ramurii noi). Verdictul de mai jos e "atinsa" = poate ajunge cu cont_venit populat prin adauga_articol_factura fara sa esueze la pasul 1.3-echivalent; "neatinsa" = blocata structural inainte de a ajunge aici (ca ntip=4); "ambigua" = depinde de alt cod neverificat complet in aceasta runda.

ntip Comportament azi in contabilizeaza_articol Atinsa de linie cont_venit? Verdict fata de ramura noua
<=20 sau IN (43,44,45,46,48,49,51,52) (:7400-7412) SCD/ASCD din politica (crs_rand_articol.scd) — bucket "factura" DA — adauga_articol_factura pentru aceste ntip foloseste calea generica (ELSE, :5187-5203, fara cerinta de id_pol) sau ramuri proprii (2,6,26,52→contract, 45→restaurant) care oricum accepta V_PRET_TEMP cand nu gasesc potrivire E cazul acoperit de design — SCD='4111' hardcodat e gandit exact pentru acest bucket. Fara probleme noi, cu exceptia sub-cazurilor de mai jos.
= 46 (nTipNotaPlata), sub-caz al bucketului "factura" scrie_nota NU se apeleaza (:7441, IF ntip <> nTipNotaPlata) DA (e in bucketul de mai sus) GOL SORA nou, neconsemnat in proiectare: ramura noua apeleaza scrie_nota necondiționat — pentru un document NotaPlata cu linie cont_venit, s-ar scrie o nota care azi e sarita intentionat. De adaugat aceeasi garda in ramura noua.
= 7, sub-caz al bucketului "factura" EXPLICATIE primeste sufix ' INVOICE:' + cdescriere (:7431-7433) DA Gol cosmetic: ramura noua foloseste direct detalii_articol.explicatia, fara sufix. Probabil acceptabil (design-ul il numeste "mai bun"), dar e o schimbare de continut pe documente de export, de confirmat.
IN (8,9), sub-caz al bucketului "factura" EXPLICATIE primeste sufix ' RETUR FACTURA:' + cdescriere (:7434-7436) DA Acelasi gol cosmetic ca mai sus.
IN (28,29) (:7413-7417) SCD hardcodat '461' — aviz catre clienti debitori DA — adauga_articol_factura pentru 28 intra pe ramura ntip IN (3,21,28,42,47) (:5053-5078), care cauta in COMENZI_ELEMENTE dupa comanda+pret, nu dupa id_pol — un articol fara politica poate gasi potrivire aici daca exista element de comanda corespunzator GOL SORA, mai grav decat ntip=4: ramura noua ar scrie SCD='4111' in loc de '461' pe un aviz catre client debitor — cont contabil gresit, atins efectiv.
toate celelalte (ELSE, :7418-7422: 21,22,23,24,25,27,30,41,42,47,...) SCD hardcodat '418' — aviz generic DA, pentru aceleasi motive (multe din aceste ntip — 3,21,42,47 — trec prin ramura "din comenzi" fara cerinta de id_pol; altele avansate direct din bucket implicit ELSE din adauga_articol_factura, :5187-5203, care nu cere id_pol deloc) Acelasi gol SCD hardcodat, aplicabil la orice document de tip aviz (nu doar 28/29).
= 4 (:7472,7520-7537) scrie_fact_aviz_custodie in loc de descarca_gestiune+discount NU — exclus structural la pasul 1.3 (adauga_articol_factura esueaza inainte) Neatins in scenariul normal (a); atins doar in scenariul defensiv (b) daca cineva forteaza id_pol si cont_venit simultan — vezi sectiunea 2.

Cel mai important rezultat al acestui tabel: golul cerut explicit (ntip=4) e cel mai putin grav dintre cele gasite, pentru ca e singurul exclus structural. Golurile reale, efectiv atinse, sunt hardcodarea SCD='4111' pe orice document de tip aviz (28,29 si bucket-ul ELSE) si omisiunea gardei ntip=46 pentru scrie_nota.

4. Text propus pentru plan (sectiunea deciziei 34)

**Ramura `ntip=4` (facturare din avize, `:7520-7537`) — exclusa structural, nu necesita cod
suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta randul sursa
din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu `V_ID_POL=NULL`
(exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci apelul cade cu
`ORA-01403` la adaugarea articolului, inainte ca linia sa ajunga la `contabilizeaza_articol`. Un
articol fara politica nu poate fi, structural, adaugat pe un document `ntip=4`; ramura noua
`cont_venit` nu are nimic de tratat aici.

**Gol descoperit, de acoperit inainte de implementare**: ramurile de **aviz** (`ntip IN (28,29)` →
`SCD='461'`, celelalte → `SCD='418'`, `:7413-7422`) **raman accesibile** cu `cont_venit` populat —
`adauga_articol_factura` nu cere `id_pol` pentru aceste `ntip` (foloseste calea "din comenzi" sau
calea implicita). Ramura noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'`/`'418'`
pentru avize), nu sa hardcodeze `'4111'` necondiționat — altfel orice aviz cu articol fara politica
primeste contul de clienti in loc de contul de aviz. Similar, ramura noua trebuie sa pastreze garda
`IF ntip <> nTipNotaPlata` inainte de a apela `scrie_nota` (azi sarita pentru `ntip=46`).

**Defensiv (opțional)**: daca se doreste o garda explicita in loc de a te baza pe imposibilitatea
structurala de mai sus, se poate adauga `IF detalii_articol.cont_venit IS NOT NULL AND
pack_facturare.ntip = 4 THEN RAISE_APPLICATION_ERROR(...)` la inceputul ramurii noi — util doar
daca cineva ar reusi vreodata sa populeze simultan `id_pol` si `cont_venit` pe o linie `ntip=4`
(azi imposibil pe caile VFP cunoscute), caz in care ramura noua ar rula altfel tacut
`descarca_gestiune` in loc de `scrie_fact_aviz_custodie`, riscand dubla descarcare de gestiune.

5. Ce nu s-a putut stabili in aceasta runda

  • Daca exista vreo cale VFP care sa populeze simultan poArt.id_pol si viitorul cont_venit (scenariul defensiv (b)) — cercetarea de fata arata doar ca schema/codul Oracle nu o impiedica; n-am gasit (si nici n-am cautat exhaustiv) un loc in VFP care ar genera aceasta combinatie azi. Probabil imposibil pe fluxurile curente (proiectarea de baza presupune cont_venit populat doar cand id_pol lipseste), dar nu demonstrat exhaustiv pe tot codul VFP.
  • Comportamentul exact oExecute/oPrelucrareEroare la o exceptie Oracle neprinsa (scenariul (a)) — presupus ca ajunge la utilizator ca mesaj Oracle brut prin mecanismul general de eroare al goExecutor, dar nu am urmarit codul oPrelucrareEroare() in aceasta runda pentru a confirma formatarea exacta.
  • Daca ntip IN (23,25,30,41) (transfer subunitati) si IN (42,47) (custodie) pot, in practica, sa primeasca un articol fara politica prin ramura "din comenzi" a adauga_articol_factura (:5053-5078) — argumentat structural posibil (nu cere id_pol), dar nu testat pe date, nu urmarit pana la capat daca COMENZI_ELEMENTE insusi ar putea contine un rand fara politica.

Handoff

Cercetare incheiata, toate cele 5 livrabile cerute de team-lead sunt in sectiunile 1-4 de mai sus. Niciun cod atins, nicio scriere Oracle, doar SELECT/Read/Grep. Context consumat moderat — nu a fost necesara predarea de context.