sync SVN r18077

This commit is contained in:
2026-09-09 22:19:22 +03:00
parent 104c24ec20
commit a7a7c098e3
68 changed files with 23574 additions and 42 deletions

View File

@@ -0,0 +1,231 @@
# 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.