#13: formular de facturare unificat - etapa curenta

Squash al branch-ului de lucru plan13-s2.

Cod: clasa noua ofacturare_util (geometrie si utilitare de formular), inrolata in
roafacturare.prg; meniul "Facturare (nou)" plat, cu reparatia literalului peste
limita VFP de 255 de caractere care impiedica compilarea metodei.

Livrare: changelog 2.11.19, marcajul de versiune DB pentru scripturile finale din
SCRIPTURI_CLAR\2026\09, inventarul livrarii in docs/livrare_13.md si registrul de
erori deschise. Documentatia de lucru a etapei (cercetare, rapoarte, handoff-uri,
snapshot-uri de scripturi si backup-uri de rollback) a fost scoasa din arbore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkyjGyrV2S7932om4kfSiK
This commit is contained in:
2026-09-09 21:30:51 +03:00
parent 87063b06a8
commit 104c24ec20
80 changed files with 5585 additions and 27852 deletions

View File

@@ -1,342 +0,0 @@
# S10 — Pretul re-derivat in `adauga_articol_factura`, ramura cu ramura
## Nota pe sursa folosita
`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` **nu exista** (nici in `docs\`, nici urme in
istoricul git — `docs\` e integral netracked). Fisierul cu **acelasi nume** exista insa la
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si a fost folosit
ca sursa. Continutul `adauga_articol_factura` coincide structural cu ce descrie planul (aceeasi
ordine de ramuri, aceleasi coduri de eroare `FACT-012/013/018`, acelasi `DECODE(A.PRET_UNITAR, 0,
V_PRET_TEMP, A.PRET_UNITAR)`), dar liniile sunt deplasate cu **+17** fata de citatele din plan
(corp `4989-5284` aici vs. `4972-5268` in plan; ramura de contract `5146-5185` aici vs. `:5129-5168`
in plan) — probabil diferente de comentarii de antet intre cele doua exporturi. Toate citatele de mai
jos sunt pe fisierul din `SCRIPTURI_CLAR`, verificat direct.
## Verdict (raspuns la intrebarea 4 — reemiterea)
**Da, exista o ramura care suprascrie tacut pretul la orice apel, inclusiv la o eventuala
reemitere: liniile provenite dintr-un contract cu `OPT_FACTURARE = 3` (sau `NULL`, care se
implicit-eaza tot la 3).** Procedura nu are nicio notiune de "reemitere" — la fiecare apel cu acelasi
`V_ID_CTR`, daca articolul are in `CTR_ARTICOLE.PRET_UNITAR` o valoare diferita de 0, acea valoare
**inlocuieste** pretul trimis de VFP (`V_PRET_TEMP`), necontidionat de faptul ca linia a fost sau nu
atinsa de utilizator pe ecran. Riscul se materializeaza doar daca pretul din contract s-a schimbat
intre emiterea initiala si reemitere — daca n-a scazut, rezultatul e identic si nimeni nu observa
diferenta pana cand se schimba. Pe toate celelalte ramuri (implicita, restaurant), pretul trimis de
VFP e respectat ca atare. **Nu exista niciun parametru sau flag care sa opreasca re-derivarea** —
raspunsul la intrebarea 5 e "nu exista".
## 1. Semnatura completa (corp, nu spec)
`PACK_FACTURARE:4989-5015` (spec identica la `:537-563`, verificat doar ca prezenta, nu citat):
```
PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
V_ID_ARTICOL IN NUMBER,
V_SERIE IN VARCHAR2,
V_EXPLICATIE IN VARCHAR2,
V_ID_POL IN NUMBER,
V_ID_GESTIUNE IN NUMBER,
V_PRET_ACHIZITIE_TEMP IN NUMBER,
V_PRETD IN NUMBER,
V_ID_VALUTAD IN NUMBER,
V_PRET_TEMP IN NUMBER,
V_ID_VALUTA_TEMP IN NUMBER,
V_PRETURI_CU_TVA_TEMP IN NUMBER,
V_IN_STOC_TEMP IN NUMBER,
V_CANTITATE IN NUMBER,
V_DISCOUNT_UNITAR IN NUMBER,
V_CONT IN VARCHAR2,
V_CURS IN NUMBER,
V_MULTIPLICATOR IN NUMBER,
V_ID_JTVA_COLOANA IN NUMBER,
V_ID_PART_REZ IN NUMBER,
V_ID_LUCRARE_REZ IN NUMBER,
V_PRETV_ORIG IN NUMBER,
V_ID_VANZARE_SET IN NUMBER,
V_ID_CTR IN NUMBER,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL,
V_LOT IN VARCHAR2 DEFAULT NULL) IS
```
Toti parametrii sunt **IN**, niciunul OUT/IN OUT — nu exista un canal de intoarcere a pretului
recalculat catre VFP la momentul apelului (linia se citeste ulterior, la re-interogarea grilei).
**`V_OPT_FACTURARE` nu e parametru.** E o variabila locala (`:5029`), calculata **in interiorul
procedurii** din `CONTRACTE.OPT_FACTURARE`, folosind parametrul `V_ID_CTR` primit de la VFP
(`:5039-5050`):
```sql
IF pack_facturare.ntip IN (2, 6, 26, 52) THEN
BEGIN
SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;
EXCEPTION
WHEN NO_DATA_FOUND THEN
V_OPT_FACTURARE := 4;
END;
END IF;
```
VFP nu trimite si nu poate influenta `V_OPT_FACTURARE` altfel decat prin `V_ID_CTR` (`poArt.id_ctr`,
trimis `NULL` cand linia nu vine de pe contract). Pentru orice `pack_facturare.ntip` in afara lui
`(2, 6, 26, 52)`, `V_OPT_FACTURARE` **ramane neinitializat** (blocul IF nu ruleaza deloc) — echivalent
cu "nu conteaza", ramura de contract nu se poate nimeri.
## 2. Ce inseamna `V_OPT_FACTURARE` si cum se coreleaza cu VFP
Din `PACK_FACTURARE` (alte proceduri, aceeasi coloana `CONTRACTE.OPT_FACTURARE`) si din
`COMUN\clase\ofacturare.vc2` / `COMUN\programe\oproceduri_facturare.prg` (proprietatea locala
`poArt.opt_facturare`, populata la citirea contractului, in oglinda cu coloana Oracle):
| Valoare | Tip facturare | Dovada |
|---|---|---|
| `0` (sau lipsa contract) | Articol normal, nelegat de contract | `ofacturare.vc2:13055` `If poArticol.opt_facturare = 0` |
| `1`, `2` | **Rata** (scadentar contract, `CTR_SCADENTAR`) — linia nu trece prin `adauga_articol_factura`, ci prin `adauga_rata_factura` (`ofacturare.vc2:14041-14050`) | `ofacturare.vc2:14041` `If Inlist(poArt.opt_facturare,1,2)`; `PACK_FACTURARE:2898,2915` (`CTR_SCADENTAR`, doar pt. `OPT_FACTURARE IN (1,2)`) |
| `3` | **Articol de pe contract**, pret din `CTR_ARTICOLE` | `PACK_FACTURARE:2699,2820` (`CTR_ARTICOLE` doar pt. `OPT_FACTURARE = 3`); `ofacturare.vc2:14750` (`poDate.tip = 2 And poArticol.opt_facturare = 3`) |
| `4` | Fallback intern cand contractul nu (mai) exista (`NO_DATA_FOUND`) | `PACK_FACTURARE:5047-5048` |
VFP nu trimite `V_OPT_FACTURARE` ca parametru al `adauga_articol_factura` — coreleaza doar indirect,
prin faptul ca proprietatea locala `poArt.opt_facturare` (citita din acelasi `CONTRACTE.OPT_FACTURARE`
cand grila s-a populat) decide **ce RPC se apeleaza** (`adauga_rata_factura` pentru 1/2,
`adauga_articol_factura` pentru orice altceva, inclusiv 3), nu ce ramura Oracle se executa in interior
— aceea o recalculeaza serverul singur, din `V_ID_CTR`.
## 3. Ramura cu ramura pe `V_OPT_FACTURARE` (si pe `pack_facturare.ntip`, care are prioritate)
`CASE` la `PACK_FACTURARE:5052-5220`. Ramurile 1-3 sunt selectate dupa `pack_facturare.ntip`, nu
dupa `V_OPT_FACTURARE` — ramura 4 (contract) se testeaza **doar daca niciuna din primele trei nu s-a
potrivit**.
| Selector | Tip facturare | Pretul | Sursa re-derivarii | Citat |
|---|---|---|---|---|
| `ntip IN (3,21,28,42,47)` | Facturare/aviz din **comenzi** | **Re-derivat, dar constrans sa se potriveasca cu ce a trimis VFP** — `SELECT A.PRET ... WHERE A.PRET = V_PRET_TEMP` | `COMENZI_ELEMENTE` + `CRM_POLITICI_PRETURI`/`PRET_ART`, filtrat pe `A.PRET = V_PRET_TEMP`; daca nu exista rand cu exact acel pret, `NO_DATA_FOUND` **nu e tratat** — procedura pica cu eroare Oracle netratata | `:5053-5078` |
| `ntip = 4` | Facturare din **avize** | **Re-derivat necondiționat, din documentul sursa** — `SELECT DISTINCT A.PRET ... INTO V_PRET` fara filtru pe pretul trimis | `VANZARI_DETALII` (linia din avizul sursa), filtrata pe articol/politica/gestiune/discount/cont, dar **nu** pe pret | `:5080-5103` |
| `ntip = 45` | Facturare **restaurant** | **Respectat** — `V_PRET := V_PRET_TEMP` setat **inainte** de SELECT (`:5109`); SELECT-ul recalculeaza doar `V_PROC_TVAV` si `V_PRET_ACHIZITIE` (cost, nu pret de vanzare) | n/a pentru pret; TVA din `JTVA_COLOANE`, cost din `CRM_POLITICI_PRET_ART.PRETFTVA` | `:5104-5145` |
| `V_OPT_FACTURARE = 3` (deci `ntip IN (2,6,26,52)` **si** contract cu `OPT_FACTURARE=3`/`NULL`) | Facturare/aviz **de pe contract** | **Re-derivat daca contractul are pret setat** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)`; daca `PRET_UNITAR = 0` sau nu exista rand `CTR_ARTICOLE`/politica potrivita (`NO_DATA_FOUND`), cade pe `V_PRET_TEMP` | `CTR_ARTICOLE.PRET_UNITAR` (prin `CRM_POLITICI_PRET_ART`, `ID_POL_ART`) | `:5146-5185` (branch), `:5181-5184` (fallback `V_PRET_TEMP` in exceptie) |
| `ELSE` (implicit — orice altceva, inclusiv linii normale fara contract) | Standard | **Respectat** — `V_PRET := V_PRET_TEMP` | n/a; TVA din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` trimis de VFP | `:5187-5203` |
**Observatie de prioritate:** daca o linie e simultan "din comanda" (`ntip IN (3,21,28,42,47)`) si
are si `V_ID_CTR` completat, ramura de comenzi castiga — `V_OPT_FACTURARE` nici nu se calculeaza
(blocul de la `:5039` ruleaza doar pentru `ntip IN (2,6,26,52)`). Ramurile nu se pot suprapune in
productie curenta, dar asta inseamna ca **orice extindere viitoare a lui `ntip`** (de ex. daca #13
introduce un tip nou de document care refoloseste `adauga_articol_factura` pentru reemitere) trebuie
verificata explicit fata de acest `CASE` — un `ntip` nou care nu intra in niciuna din listele
`(3,21,28,42,47)` / `4` / `45` cade automat fie pe ramura de contract (daca are `V_ID_CTR`), fie pe
`ELSE`.
## 4. Reemiterea — detaliu (vezi si verdictul de mai sus)
Procedura **nu primeste si nu poate primi** vreun semnal de tip "acesta e un apel de reemitere, nu
recalcula". Comportamentul e identic la emiterea initiala si la orice apel ulterior cu aceiasi
parametri. Riscul descris in plan (sectiunea H) se confirma integral pentru ramura de contract
(`V_OPT_FACTURARE = 3`): daca `CTR_ARTICOLE.PRET_UNITAR` s-a modificat intre emiterea initiala si
reemitere, factura reemisa va lua **pretul curent din contract**, nu pretul confirmat pe ecran de
utilizator inainte de reemitere — chiar daca utilizatorul n-a atins linia.
Pentru ramurile "avize" (`ntip = 4`) si "comenzi" (`ntip IN (3,21,28,42,47)`), acelasi risc structural
exista (pretul se re-citeste din documentul sursa la fiecare apel), dar **nu sunt cazul confirmat de
#13** — planul S10 spune explicit "se verifica pe factura din contract, care e cazul confirmat" — nu
s-a cerut si nu s-a facut inventar suplimentar pentru daca fluxul de reemitere din #13 (etapa II,
stergere+regenerare cu aceleasi linii) ar trece vreodata prin aceste doua ramuri. **De retinut daca
#13 extinde reemiterea si la facturi/avize provenite din comanda sau din alt aviz** — pe ambele,
re-derivarea e chiar mai stricta decat pe contract (ramura de comenzi cade cu eroare netratata daca
pretul nu se potriveste exact; ramura de avize suprascrie necondiționat).
## 5. Mecanism de "nu re-deriva"
**Nu exista.** Niciun parametru boolean, nicio valoare santinela, nicio ramura in `CASE`
(`:5052-5220`) care sa respecte pretul primit *pentru ca i s-a cerut explicit*. Ramurile "implicita"
si "restaurant" respecta pretul doar pentru ca structural nu au de unde re-deriva altceva (nu exista
document sursa de recitit), nu pentru ca ar exista o comutare intentionata. Orice solutie de tip
"pretul vine din formular, nu se re-deriva" (cum sugereaza planul, sectiunea H) **ar trebui
implementata in VFP inainte de apel** (de exemplu, nu retrimite `V_ID_CTR` la reemitere daca vrei sa
eviti ramura de contract) — nu exista un parametru in pachet pe care VFP l-ar putea seta ca sa
dezactiveze re-derivarea, iar pachetul **nu se modifica** (decizia 27-bis, respectata — aceasta e o
constatare, nu o propunere).
## 6. Discountul si TVA-ul
- **Discountul (`V_DISCOUNT_UNITAR`) nu e niciodata re-derivat.** Parametrul intra direct in
`INSERT INTO VANZARI_DETALII_TEMP (..., DISCOUNT_UNITAR, ...) VALUES (..., V_DISCOUNT_UNITAR, ...)`
(`:5236`, `:5266`) — nicio ramura din `CASE` il citeste sau il modifica. Tratament **diferit** de
pret: discountul e mereu respectat, indiferent de ramura.
- **TVA-ul (`V_PROC_TVAV`) e mereu recalculat server-side, pe toate ramurile — dar din surse
diferite, nu dintr-un parametru brut.** Nu exista parametru `V_PROC_TVAV_TEMP` in semnatura; VFP
trimite doar `V_ID_JTVA_COLOANA` (un identificator de coloana de cota, nu cota insasi). Pe ramura
implicita si pe cea de contract-fara-potrivire, cota se cauta in `JTVA_COLOANE` dupa acel ID
(`:5188-5198`, `:5169-5179`); pe ramurile comenzi/avize/contract-cu-potrivire/restaurant, cota vine
din documentul sursa sau din politica de pret (`:5058`, `:5083`, `:5150`, `:5114`). Deci TVA-ul are
un tratament **mai strict** decat pretul: niciodata "pass-through" direct, intotdeauna o cautare,
doar sursa cautarii difera pe ramura.
## 7. Cursul valutar si pretul in valuta
- **Cursul (`V_CURS`) e mereu respectat ca valoare, pe toate ramurile.** Singura procesare e
`DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste 0 cu 1, altfel scrie exact ce a
trimis VFP. Procedura **nu interogheaza niciodata tabela `CURS`** pentru un curs "de azi" — nu are
de unde sa recalculeze cursul chiar daca ar vrea.
- **Identitatea valutei (`V_ID_VALUTA`, variabila locala, nu parametrul `V_ID_VALUTAD`) urmeaza
acelasi tipar ca pretul, ramura cu ramura:** re-derivata din sursa pe comenzi (`C.ID_VALUTA`,
`:5059`) si avize (`A.ID_VALUTA`, `:5084`), re-derivata din contract pe ramura `V_OPT_FACTURARE=3`
(`B.ID_VALUTA`, `:5151`, cu fallback `V_ID_VALUTA_TEMP` in exceptie, `:5182`), respectata pe
implicita si restaurant (`V_ID_VALUTA := V_ID_VALUTA_TEMP`, `:5110`, `:5201`).
- **Consecinta pentru reemiterea unui document in valuta pe contract:** daca politica de pret a
contractului (`CRM_POLITICI_PRET_ART`, prin `CTR_ARTICOLE.ID_POL_ART`) a fost schimbata intre timp
la o alta valuta, reemiterea ar scrie articolul in **noua** valuta a politicii, dar cu **cursul**
trimis de VFP (posibil cursul valutei vechi, daca VFP nu a fost actualizat sa retrimita cursul
corect pentru noua valuta) — o sursa suplimentara de neconcordanta, nesemnalata explicit in plan.
Nu s-a gasit nicio validare in procedura care sa verifice ca `V_CURS` corespunde valutei
re-derivate `V_ID_VALUTA`.
## Completare: nota contabila a politicii
Sursa: aceeasi, `contabilizeaza_articol` la `PACK_FACTURARE:7173-7547` (in fisierul din
`SCRIPTURI_CLAR`; corpul relevant pentru articol simplu — nu compus — e la `:7391-7544`), plus
`scrie_nota` la `:12329-12561`.
### 1. Cum ajunge de la `id_pol` la nota contabila — ia toate randurile setului, fara distributie
`cursor_articol` (`:7218-7271`):
```sql
FROM CRM_POLITICI_PRET_ART A
LEFT JOIN CRM_POLITICI_PRETURI B ON A.ID_POL = B.ID_POL
LEFT JOIN CRM_NOTE_VANZARI C ON B.ID_NOTA = C.ID_NOTA
LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET
WHERE A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol
```
E un `JOIN` simplu pe `ID_SET`, **fara `ROWNUM`, fara agregare, fara filtru suplimentar pe
`NOTE_CONTABILE`**. Daca setul are `N` randuri, cursorul intoarce `N` randuri pentru aceeasi
combinatie `id_pol`/`id_articol`. Bucla care il consuma (`:7393-7542`,
`OPEN cursor_articol; FETCH ...; WHILE cursor_articol%FOUND LOOP ... FETCH ...; END LOOP;`) executa
**intregul corp — `scrie_nota`, `descarca_gestiune`, `scrie_discount` — o data pentru fiecare rand
din set**, cu **aceeasi cantitate si acelasi pret intreg de fiecare data**, nu impartite intre
randuri. Nu exista nicio coloana `ORDINE` in tot pachetul (cautare `ORDINE` in fisier: zero
rezultate) si nicio logica de distributie procentuala intre randurile unui set.
**Consecinta directa pentru reteta aprobata (J-quater):** daca politica tehnica pentru contul de
venit ar ajunge sa foloseasca o nota al carei `ID_SET` are mai mult de un rand in
`NOTE_CONTABILE` (cazul general — pana la 30 de randuri pe unele seturi din baza, per verificarea ta
pe date vii), `contabilizeaza_articol` ar scrie **de N ori** aceeasi suma in contabilitate si ar
apela `descarca_gestiune` de N ori pentru aceeasi linie de vanzare — dublare de venit si dublare de
descarcare de gestiune, nu doar zgomot. Pasul 2 al retetei ("cauta o politica a carei nota are deja
`SCC`-ul calculat") **trebuie sa garanteze un singur rand `NOTE_CONTABILE` per `ID_SET` ales**, nu
doar un rand cu `SCC`-ul potrivit — verificarea facuta de tine pe cele 7 note active (exact un rand
per set) e conditia care face reteta sigura azi, dar nu e impusa de cod, e o coincidenta a datelor
curente.
### 2. `NOTE_CONTABILE.PTVA` — nu e citit deloc de `contabilizeaza_articol`
Lista de coloane a `cursor_articol` (`:7230-7260`) selecteaza explicit
`ID_NOTA, PRETURI_CU_TVA, ID_VENCHELT, ID_SECTIE, ID_SET, EXPLICATIE, SCD, ASCD, SCC, ASCC, CU_TVA,
IN_VALUTA` din lantul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI ->
NOTE_CONTABILE` — **`D.PTVA` nu apare in acest `SELECT`**. Coloana exista pe tabel (confirmat de
datele tale vii — `21`, `5`, `0`), dar `contabilizeaza_articol` n-o citeste niciodata.
Cota de TVA folosita efectiv in scriere e alta — vine din apelul catre `scrie_nota` (`:7444-7467`),
unde parametrul `V_PTVA` primeste `detalii_articol.proc_tvav * 100 - 100` (`:7464`), adica **cota
calculata deja in `adauga_articol_factura`** (din `JTVA_COLOANE` sau din documentul sursa, vezi
sectiunea 3 de mai sus), nu din nota. Deci **raspunsul direct la intrebarea ta: daca articolul are
21% dar nota politicii are `PTVA=5`, nu se intampla nimic — `PTVA` de pe nota e complet ignorat,
cota folosita e cea a articolului/documentului, nu a notei.** Coloana `NOTE_CONTABILE.PTVA` e moarta
din perspectiva acestei proceduri (posibil folosita in alt modul, neverificat aici).
### 3. `CU_TVA` si `IN_VALUTA` de pe nota
Ambele se citesc din `NOTE_CONTABILE` (`crs_rand_articol.cu_tva`, `crs_rand_articol.in_valuta`,
`:7253`, `:7254-7260`) si se transmit mai departe la `scrie_nota` ca parametri (`V_CU_TVA`,
`V_IN_VALUTA`), unde controleaza mecanic, nu semantic tot ce e legat de pret:
- **`V_CU_TVA`** (`scrie_nota:12416-12422`): daca `= 0`, suma acumulata in `V_INCASAT_CALCUL` e
`V_SUMA_FARA_TVA`; daca `= 1`, e `V_SUMA_CU_TVA`. Separat, la `:12537-12558`, **doar cand
`V_CU_TVA = 1` se scrie si o linie separata de TVA** (`pack_facturare.scrie_tva(...)`, cont
`4427`/etc.). Deci `CU_TVA` decide daca acest rand din notă genereaza o inregistrare contabila
separata pentru TVA sau nu — nu suprascrie si nu recalculeaza cota, doar comuta daca linia de TVA
se scrie.
- **`V_IN_VALUTA`** (`scrie_nota:12391-12404`, `:12492-12507`): daca `= 1`, se calculeaza si
`V_SUMA_VAL`/`V_SUMA_FARA_TVA_VAL`/etc. (sumele in valuta straina) si randul `ACT_TEMP` scrie
`ID_VALUTA = V_ID_VALUTA` (valuta reala a articolului) si `CURS = V_CURS`; daca `= 0`, randul
scrie `ID_VALUTA = pack_facturare.nid_moneda_nationala` si `CURS = 0`, indiferent de valuta reala
a articolului.
**Raspuns la "ce se intampla daca `IN_VALUTA=1` pe nota dar documentul e in lei":** nu apare nicio
eroare si nicio validare incrucisata intre `IN_VALUTA` de pe nota si valuta reala a documentului —
procedura calculeaza pur si simplu `V_SUMA_VAL` folosind `V_ID_VALUTA` si `V_CURS` primite din
`detalii_articol` (care, pe un document in lei, sunt deja moneda nationala si curs implicit 1).
Rezultatul practic: `ACT_TEMP.SUMA_VAL` se populeaza redundant (cu aceeasi valoare ca `SUMA`,
scalata la curs 1) in loc sa ramana `0`, iar `ID_VALUTA` de pe randul contabil ramane oricum
moneda nationala (pentru ca asta e `detalii_articol.id_valuta` pe un document in lei) — o
inconsistenta cosmetica in `ACT_TEMP` (rand cu `SUMA_VAL` populat desi `CURS` real e 1), nu o
eroare de suma. Relevant pentru politicile `SERVICII VALUTA` / `COMISION INTERMEDIERE`
(`SCC=704`, candidate la pasul 2 al retetei): daca oricare document in lei ar ajunge sa foloseasca
una din ele, ar produce astfel de randuri cosmetic-inconsistente, fara sa strice suma facturata.
### 4. `ASCD`/`ASCC` si `ID_PARTD`/`ID_PARTC`
- **`ASCD`/`ASCC` nu sunt obligatorii — au fallback automat cand sunt nule**, exact pe ramura care
se aplica pe facturi (`:7409-7428`):
```
V_ASCD := NVL(crs_rand_articol.ascd,
PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD));
...
V_ASCC := NVL(crs_rand_articol.ascc,
PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCC));
```
Cand nota nu are analitic explicit (cazul majoritatii notelor reale, per verificarea ta), se
deriva unul din grupul de utilizatori al celui care factureaza, functie de contul `SCD`/`SCC`. Nu
s-a verificat in aceasta sesiune ce intoarce `GetAnaliticByGrupUtilizatori` cand nici grupul de
utilizatori nu are un analitic configurat (posibil `NULL` mai departe, netratat explicit aici).
- **`ID_PARTD`/`ID_PARTC` nu sunt citite de la nota deloc.** `cursor_articol` nu le selecteaza (nu
exista `D.ID_PARTD`/`D.ID_PARTC` nicaieri in fisier — verificat). Cele doua coloane de pe
`ACT_TEMP` se calculeaza in `scrie_nota`/`scrie_tva` direct din **codul contului** (`V_SCD`,
`V_SCC`) si din variabile de sesiune ale pachetului (`pack_facturare.nid_part`,
`nid_part_rez`, `nid_partc`), nu din nota: `ID_PARTD` e un `CASE` pe prefixul lui `V_SCD`
(`41%`/`46%`/`45%`/`357`) la `:12510-12515`; `ID_PARTC` e un `DECODE` pe `V_SCC`
(`419`/`4111`/`357`) la `:12523-12530`. Daca `NOTE_CONTABILE` are coloane `ID_PARTD`/`ID_PARTC`,
ele sunt irelevante pentru `contabilizeaza_articol` — partenerul contabil vine intotdeauna din
contextul documentului (`pack_facturare.nid_part`), nu din configurarea notei.
### 5. Politica fara `ID_NOTA` (`HOTEL TAXE`, `HOTEL CAZARE`)
**Nu exista un cod `FACT-0xx` dedicat pentru acest caz — spre deosebire de articolul care lipseste
din politica (`FACT-024`, `:7278-7302`), aici procedura nu detecteaza si nu semnaleaza explicit
situatia.** Lantul de `LEFT JOIN` din `cursor_articol` (`:7261-7271`) e construit sa supravietuiasca
oricarui inel lipsa: `A` (politica-articol, gasita — altfel s-ar fi luat deja `FACT-024` mai
devreme) se pastreaza chiar daca `B.ID_NOTA` e `NULL` — `C` si `D` devin pur si simplu toate
`NULL` pe acel rand, cursorul tot intoarce **un rand** (`cursor_articol%FOUND = TRUE`), nu zero.
In bucla (`:7396-7541`), asta inseamna:
- `crs_rand_articol.scd`, `.ascd`, `.scc`, `.ascc`, `.cu_tva`, `.in_valuta`, `.explicatie` — toate
`NULL`.
- Ramura de calcul `V_ASCD := NVL(NULL, GetAnaliticByGrupUtilizatori(nid_util, NULL))` — analitic
calculat pe un cont `NULL`, deci probabil tot `NULL` (netestat aici ce intoarce functia pe
argument `NULL`).
- `scrie_nota` e apelata cu `V_SCD = NULL`, `V_SCC = NULL` — insereaza in `ACT_TEMP` un rand cu
conturile de debit/credit **nule**.
Daca `ACT_TEMP.SCD`/`ACT_TEMP.SCC` au constrangere `NOT NULL` pe schema, `INSERT`-ul ar pica cu o
eroare Oracle generica (`ORA-01400`), nu cu un mesaj `FACT-0xx` prietenos ca la celelalte cazuri
tratate explicit — **neverificat in aceasta sesiune** (DDL-ul `ACT_TEMP` nu e in exportul
`PACK_FACTURARE`). In orice caz, **comportamentul e cel putin la fel de riscant ca lipsa unui
articol din politica**, dar fara plasa de siguranta explicita din cod — de tratat cu atentie daca
reteta aprobata ajunge sa produca vreodata o politica fara `id_nota` (nu pare sa fie cazul retetei
J-quater, care cere explicit gasirea unei note existente, dar `HOTEL TAXE`/`HOTEL CAZARE` arata ca
starea "politica fara nota" chiar exista azi in baza vie, pe alte fluxuri).
## Ce nu s-a putut stabili si de ce
- **Comportamentul exact al viitorului flux de reemitere din #13** (etapa II) fata de aceasta
procedura — planul S10 cere verificarea "pe factura din contract", nu inventarul complet pentru
comenzi/avize; nu s-a extins cercetarea acolo (in afara observatiei de la punctul 4, care ramane o
constatare structurala, nu un verdict testat pe fluxul de reemitere, care **inca nu exista in
cod** — decizia 30, nimic implementat).
- **Ce se intampla la eroarea netratata din ramura de comenzi** (`NO_DATA_FOUND` cand
`A.PRET = V_PRET_TEMP` nu gaseste rand, `:5057-5078`) — daca propaga ca eroare Oracle bruta pana la
utilizator sau daca `goExecutor` o intercepteaza generic; n-a fost verificat (nu era in perimetrul
celor 7 intrebari, dar e un risc adiacent gasit din citirea codului).
- **Cate contracte reale au azi `CTR_ARTICOLE.PRET_UNITAR` diferit de pretul ultimei facturi emise**
— verificare pe date vii, nefacuta (cercetare read-only, fara acces la interogari live in aceasta
sesiune).
- **Discrepanta de numerotare fata de `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** (fisierul
cerut in prompt nu exista pe disc) — semnalata la inceputul raportului, nu blocheaza verdictul
pentru ca fisierul gasit in `SCRIPTURI_CLAR` are acelasi nume/data si continut structural identic.