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_polsi viitorulcont_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 presupunecont_venitpopulat doar candid_pollipseste), dar nu demonstrat exhaustiv pe tot codul VFP. - Comportamentul exact
oExecute/oPrelucrareEroarela o exceptie Oracle neprinsa (scenariul (a)) — presupus ca ajunge la utilizator ca mesaj Oracle brut prin mecanismul general de eroare algoExecutor, dar nu am urmarit coduloPrelucrareEroare()in aceasta runda pentru a confirma formatarea exacta. - Daca
ntip IN (23,25,30,41)(transfer subunitati) siIN (42,47)(custodie) pot, in practica, sa primeasca un articol fara politica prin ramura "din comenzi" aadauga_articol_factura(:5053-5078) — argumentat structural posibil (nu cereid_pol), dar nu testat pe date, nu urmarit pana la capat dacaCOMENZI_ELEMENTEinsusi 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.