# S10 (runda 3) — Proiectare: pretul re-derivat din contract la reemiterea #13 Agent de proiectare (research), read-only. Nicio editare de cod, nicio scriere Oracle in afara de `SELECT`. Sursa SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii — **de data asta liniile citate coincid exact cu cele din raportul vechi `s10_pret_rederivat.md`, fara offset**). Date verificate pe `MARIUSM_AUTO@ROA_CENTRAL`, doar `SELECT`, 11.08.2026 — baza de dezvoltare, esantion mic (vezi sectiunea 2), nu productie. ## 0. Rezumat **Intrebare adaugata de team-lead, raspuns scurt: NU.** `cursor_retur_document` (procedura folosita la INCARCAREA liniilor unui document existent, apelata din VFP cu `V_COPIERE=1`) **citeste pretul ca atare din `VANZARI_DETALII.PRET`, nu il re-deriva** — nu exista niciun `JOIN` catre `CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART` in toata procedura (`:3949-4062`). Presupunerea din S8b se confirma pe cod. Detaliu complet, cu dovezi, in sectiunea 1bis (nou adaugata, tratata ca prioritara fata de restul raportului, conform cererii). **Faptul portant (scrierea, nu citirea) se reconfirma integral** (`:5146-5185`) si se extinde cu doua descoperiri noi: 1. **TVA-ul si identitatea valutei sunt re-derivate din exact acelasi `JOIN`/`SELECT` ca pretul**, pe aceeasi ramura de contract — nu doar pretul e in pericol la reemitere, ci pretul + cota de TVA + valuta articolului, impreuna, tot-sau-nimic (sectiunea 3). 2. **Nu exista nicio a doua re-derivare mai departe in lant** — `PRET` trece neschimbat de la `VANZARI_DETALII_TEMP` in `VANZARI_DETALII` (`scrie_in_vanzari`, `:13705-13757`, coloana `PRET` copiata direct, fara `DECODE`/recalcul) si `contabilizeaza_articol` nu scrie niciodata pe `VANZARI_DETALII.PRET` (scrie doar `ACT_TEMP`, nota contabila). Singurul loc de re-derivare e `adauga_articol_factura:5146-5185` (sectiunea 1) — **la scriere, niciodata la citire** (sectiunea 1bis). Pe date reale (esantion mic, baza de dezvoltare): **3 din 11 linii deja facturate pe contract au azi un pret de contract diferit de pretul efectiv facturat** — divergenta nu e ipotetica, e deja prezenta (sectiunea 2). **Recomandare** (detaliata in sectiunea 4): varianta **(c) avertizare + confirmare explicita**, implementabila integral in VFP cu `goExecutor` (fara sa ating `pack_facturare`, conform deciziei 27-bis), cu optiunea de a trece la **(b) blocare stricta** daca Marius prefera zero schimbari tacite vreodata. Varianta **(d) ocolire a ramurii** e posibila mecanic dar produce o regresie reala (pierderea legaturii `VANZARI_DETALII.ID_CTR`) — **nu o recomand**. --- ## 1. Reverificarea faptului portant Cod citit direct din sursa (`:5039-5220`), nu preluat din raportul vechi. ```sql -- :5039-5050 — V_OPT_FACTURARE se calculeaza intern, din V_ID_CTR primit de la VFP 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; ``` ```sql -- :5146-5185 — ramura de contract, in interiorul CASE-ului principal (:5052-5220) WHEN V_OPT_FACTURARE = 3 THEN BEGIN SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR), B.PROC_TVAV, B.ID_VALUTA, A.PRET_CU_TVA, C.IN_STOC INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC FROM CTR_ARTICOLE A LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL; EXCEPTION WHEN NO_DATA_FOUND THEN ... V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP; V_IN_STOC := V_IN_STOC_TEMP; END; ``` **Confirmat, cuvant cu cuvant fata de runda 9**: cu `OPT_FACTURARE = 3` (sau `NULL`, implicit 3), daca `CTR_ARTICOLE.PRET_UNITAR <> 0`, acea valoare **inlocuieste** `V_PRET_TEMP` (pretul trimis de VFP), necondiționat de faptul ca operatorul a atins sau nu linia. Procedura **nu are nicio notiune de reemitere** — parametrii sunt toti `IN` (`:4989-5059`, verificat), niciun canal care sa spuna "nu recalcula". `NO_DATA_FOUND` (politica/articol lipsa) e singurul caz care respecta pretul trimis. ### Nu exista alt loc care suprascrie pretul - **`scrie_in_vanzari`** (`:13488+`), care muta liniile din `VANZARI_DETALII_TEMP` in `VANZARI_DETALII` (tabela finala) la finalizarea documentului: `PRET` e in lista de coloane copiate **neschimbat** (`:13705-13757`, `SELECT ... PRET ... FROM VANZARI_DETALII_TEMP` → `INSERT INTO VANZARI_DETALII (..., PRET, ...)`), fara `DECODE`/recalcul. Cautare exhaustiva pe fisier: **zero** `INSERT INTO VANZARI_DETALII` (tabela finala, nu `_TEMP`) in afara de acest punct. - **`contabilizeaza_articol`** (`:7173-7547`, reconfirmat structural fata de runda 9) citeste `detalii_articol.pret` (randul deja scris in `VANZARI_DETALII_TEMP`) doar ca sa calculeze suma notei contabile — nu scrie niciodata inapoi pe `VANZARI_DETALII.PRET`. - **`modificare_politica_stoc`** (`:2122-2135`) face un `UPDATE CRM_POLITICI_PRET_ART SET PRET=0, ...` — e singurul alt `SET PRET` gasit in tot fisierul, dar reseteaza **politica** la schimbarea monedei stocului, complet in afara lantului de facturare a unei linii; nu atinge vreo linie de document. **Concluzie sectiune**: raspunsul din runda 9 se confirma **integral**, fara nicio corectie, si se inchide explicit intrebarea "mai exista si alte locuri" — nu mai exista. --- ## 1bis. Citirea la incarcare: `cursor_retur_document` re-deriva pretul? **Adaugat la cererea team-lead-ului — critic pentru S8b, tratat ca prioritar.** ### Verdict: NU. Pretul se citeste ca atare din `VANZARI_DETALII.PRET`, fara nicio re-derivare. Corp complet, `PACK_FACTURARE:3949-4062`: ```sql PROCEDURE cursor_retur_document(V_IN_VALUTA IN NUMBER, V_LISTAID IN VARCHAR2, V_COPIERE IN NUMBER, V_PROFORMA IN NUMBER, V_ID_UTIL IN NUMBER, V_CURSOR OUT cursor_facturare) IS ... BEGIN pack_facturare.initializeaza_facturare(V_ID_UTIL); OPEN V_CURSOR FOR WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR))) SELECT ROWNUM AS ID_C, A.ID_ARTICOL, A.LOT, A.SERIE, A.ID_POL, A.ID_VALUTA, ... DISCOUNT_UNITAR (derivat din A.DISCOUNT_UNITAR) ..., B.CODMAT, B.CODBARE, B.DENUMIRE, NVL(B.UM,'') AS UM, ... GESTIONABIL ..., A.CANTITATE, A.PROC_TVAV, A.ID_JTVA_COLOANA, A.PRET_CU_TVA AS PRETURI_CU_TVA, A.CURS, A.MULTIPLICATOR, (CASE WHEN NVL(C.MONEDA_NATIONALA,1)<>1 THEN ROUND(A.CURS * ROUND(A.PRET, pack_sesiune.nzecimale_pretvval) / NVL(A.MULTIPLICATOR,1), pack_sesiune.nzecimale_pretv) ELSE ROUND(A.PRET, pack_sesiune.nzecimale_pretv) END) + A.DIFERENTA AS PRET, ... FROM (SELECT A1.ID_ARTICOL, A1.LOT, A1.SERIE, A1.ID_POL, A1.DISCOUNT_UNITAR, A1.DIFERENTA, A1.CANTITATE, A1.PROC_TVAV, A1.ID_JTVA_COLOANA, NVL2(A1.ID_GESTIUNE,1,0) AS GESTIONABIL, A1.PRET_CU_TVA, A1.PRET, A1.ID_VALUTA, NVL(A2.CURS,0) AS CURS, NVL(A2.MULTIPLICATOR,1) AS MULTIPLICATOR, A1.EXPLICATIE, A1.ID_GESTIUNE, A1.PRET_ACHIZITIE, A1.PRETD, A1.ID_JTVA_COLOANA_EX FROM VANZARI_DETALII A1 LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE AND A1.ID_VALUTA = A2.ID_VALUTA WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)) A LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL LEFT JOIN NOM_VALUTE C ON A.ID_VALUTA = C.ID_VALUTA ORDER BY B.DENUMIRE; END cursor_retur_document; ``` **Analiza surselor, coloana cu coloana:** - **`A.PRET`** (`:4009-4015`, alias final `PRET`) vine din subquery-ul `A` care e **`VANZARI_DETALII A1`** direct (`A1.PRET`, `:4041`) — **nicio referinta la `CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART`/`CRM_POLITICI_PRETURI` in toata procedura** (cautare exhaustiva pe corpul de la `:3949-4062`: zero hit-uri pentru oricare din cele trei tabele). Singurele `JOIN`-uri sunt: - **`VANZARI_CURSURI A2`** (`:4051-4053`), pe `ID_VANZARE`+`ID_VALUTA` — aduce `CURS`/ `MULTIPLICATOR` **stocate pe documentul insusi** (cursul valutar de la momentul cand a fost scris, nu un curs "de azi" recalculat — tabela `VANZARI_CURSURI`, nu `CURS`). - **`NOM_ARTICOLE B`** (`:4056-4057`) — doar campuri descriptive (`CODMAT`, `CODBARE`, `DENUMIRE`, `UM`), acelasi tipar confirmat deja in `rec_pret_lazy.md` A2 pentru alte cursoare din pachet — niciodata sursa de pret. - **`NOM_VALUTE C`** (`:4058-4059`) — doar `MONEDA_NATIONALA`/`NUME_VAL`, folosit in `CASE` ca sa decida **cum se formateaza** afisarea (lei vs. valuta), nu ca sa aduca un pret alternativ. - Expresia finala pe `PRET` e o **transformare de afisare** a lui `A.PRET` (rotunjire + conversie in valuta folosind `A.CURS`/`A.MULTIPLICATOR` **stocate pe document**) plus `A.DIFERENTA` (coloana proprie pe `VANZARI_DETALII`, un ajustaj deja persistat pe linie, nu o recalculare live) — nu o re-derivare dintr-o sursa externa. - **Acelasi tipar pe `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`** — toate citite direct din `A1.*` (`VANZARI_DETALII`), fara vreun `JOIN` catre politica/contract. **Confirmare suplimentara pe partea VFP — `cursor_retur_document` cu `V_COPIERE=1` e chiar calea folosita la reincarcarea unui document existent, cu prioritate fata de ramura de contract**: ``` COMUN\programe\ofacturare.prg:266-283 Do Case Case m.llCopiere lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}] Case Inlist(tnTip, 48, 49) ... Case tnTip = 45 ... Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi ... Case Inlist(tnTip, 2, 26, 6, 52) && contract lcSqlCursor = [{call ]+gcS+[.pack_facturare.cursor_contract(...)}] ``` `Do Case` in VFP executa **doar prima ramura adevarata**. Cand `llCopiere` e activ (incarcarea unui document existent — exact scenariul "editare prin regenerare" descris de team-lead), procedura apelata e **intotdeauna** `cursor_retur_document`, **indiferent de `tnTip`** — ramura de contract (`cursor_contract`, `:283+`) nu se mai ajunge deloc, chiar daca documentul e de tip 2/6/26/52. `cursor_retur` (`:3934-3947`) e doar un wrapper subtire peste `cursor_retur_document` cu `V_COPIERE=0`/`V_PROFORMA=0`, pentru returul propriu-zis — nu schimba concluzia. ### Consecinta pentru S8b **Presupunerea "re-derivarea e o proprietate a scrierii, nu a citirii" se confirma pe cod, fara rezerve.** Deschiderea formularului de editare (etapa II, incarcarea liniilor existente) **nu** declanseaza nicio re-derivare de pret/TVA/valuta — documentul apare "neschimbat" la simpla deschidere, exact cum a presupus S8b. Mecanismul de detectare a schimbarii (compara starea incarcata cu starea la confirmare) **poate functiona ca premisa** — riscul de suprascriere tacita descris in restul acestui raport (sectiunile 1-6) apare **doar la re-scriere** (regenerare efectiva prin `adauga_articol_factura`), nu la incarcare. Cele doua momente (citire la deschidere, scriere la regenerare) sunt guvernate de **cursoare Oracle complet diferite** (`cursor_retur_document` vs. `adauga_articol_factura`), fara cod comun — o modificare facuta doar pe unul din ele (de exemplu o garda adaugata la scriere, variantele (b)/(c) din sectiunea 4) **nu afecteaza** comportamentul celuilalt. --- ## 2. Tipurile afectate si frecventa in date **Tipurile de document**: `pack_facturare.ntip IN (2, 6, 26, 52)` (`:5039`), confirmat identic cu tabelul din `rec_editare_factura.md:176` (contract = tip 2/6/26/52, ramura de scriere `scrie_factura2`, la fel ca lista de preturi — nu are RPC separat). **Frecventa in date** (schema `MARIUSM_AUTO`, doar `SELECT`, 11.08.2026): ```sql -- VANZARI_DETALII cu ID_CTR populat, active: 74 -- din care, articolul are un rand corespunzator in CTR_ARTICOLE (id_ctr+id_articol): 11 -- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI diferit de VANZARI_DETALII.PRET: 3 -- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI egal cu VANZARI_DETALII.PRET: 8 -- din acelea, CTR_ARTICOLE.PRET_UNITAR = 0 (nu suprascrie): 0 ``` **Interpretare, cu grija la marimea esantionului**: baza e de dezvoltare, cu doar 27 randuri totale in `CTR_ARTICOLE` — nu extrapolez procentul (27%) la volumul de productie. Dar **3 cazuri reale, nu zero**, e dovada suficienta ca fenomenul **chiar se intampla**, nu doar teoretic: daca oricare din cele 3 facturi ar fi reemisa azi prin regenerare (#13 etapa II), suma ei s-ar schimba tacit, fara nicio actiune a operatorului legata de pret. Simetric, cele 8 cazuri "egal" arata ca in majoritatea situatiilor curente reemiterea ar fi azi inofensiva — ceea ce face problema mai insidioasa, nu mai putin reala: cand se produce, nimeni nu se astepta, pentru ca de obicei nu se intampla nimic vizibil. **Nu exista coloana de audit pe `CTR_ARTICOLE`** (confirmat din `user_tab_columns`: `ID_CTR_ART, ID_CTR, ID_POL_ART, PRET_UNITAR, CANT, COEF_DISCOUNT, VAL_DISCOUNT, ID_VALUTA, EXPLICATIE, UM, ID_ARTICOL, PRET_CU_TVA, ID_LOCATIA, PROC_TVAV, VALOARE` — nicio `DATA_MODIF`/`ID_UTIL_MODIF`) — nu exista cale de a masura *cat de des* se schimba `PRET_UNITAR` in timp, doar *cate cazuri divergente exista azi*. Raspunsul la "cat de des" ramane nedeterminabil din date; ce se poate spune e doar ca divergenta **exista deja**, azi, pe un esantion mic. --- ## 3. Descoperire noua: TVA si valuta sunt re-derivate din **aceeasi** interogare ca pretul Din citatul de la sectiunea 1: `SELECT DECODE(A.PRET_UNITAR, ...), B.PROC_TVAV, B.ID_VALUTA, ...` — **un singur `SELECT`**, trei valori. Asta inseamna ca varianta de raspuns nu poate trata pretul izolat: daca politica de pret a contractului (`CRM_POLITICI_PRET_ART`, aceeasi `B`) si-a schimbat si cota de TVA sau valuta intre timp, reemiterea le suprascrie **pe amandoua**, prin acelasi mecanism, in aceeasi conditie (`NO_DATA_FOUND` → respecta ce a trimis VFP; altfel → suprascrie). Comparativ cu discountul si cursul (reconfirmare fata de runda 9, sectiunile 6-7 ale raportului vechi, verificate din nou pe sursa curenta): | Camp | Tratament pe ramura de contract | Risc la reemitere | |---|---|---| | **Pret** (`V_PRET`) | `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` — suprascris daca `PRET_UNITAR<>0` | **Da — faptul portant** | | **TVA** (`V_PROC_TVAV`) | `B.PROC_TVAV`, din acelasi `SELECT`, fara fallback conditionat separat | **Da — aceeasi conditie ca pretul** | | **Valuta articol** (`V_ID_VALUTA`) | `B.ID_VALUTA`, idem | **Da — aceeasi conditie** | | **Discount** (`V_DISCOUNT_UNITAR`) | Parametru trimis de VFP, scris direct in `INSERT` (`:5266`), nicio ramura din `CASE` il citeste | **Nu — mereu respectat, indiferent de ramura** | | **Curs** (`V_CURS`) | `DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste doar 0 cu 1 | **Nu direct — dar vezi mai jos** | **Consecinta combinata pret+valuta+curs**: daca `V_ID_VALUTA` re-derivat difera de valuta pentru care VFP a calculat `V_CURS` (de ex. politica a trecut de la EUR la USD intre emitere si reemitere), documentul reemis scrie **noua valuta cu vechiul curs trimis de VFP** — nicio validare incrucisata intre cele doua (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). Aceeasi observatie ca in runda 9, dar acum cu dovada ca sursa comuna (pret+TVA+valuta) inseamna ca **orice gardă construita pentru pret trebuie sa verifice si TVA si valuta in acelasi pas**, nu doar pretul — altfel o factura reemisa poate trece garda de pret nemodificata si totusi sa iasa cu alta cota de TVA sau alta valuta. **Discountul nu ridica aceeasi problema** — e mereu respectat ca atare, indiferent de ramura, deci nu are nevoie de nicio garda la reemitere. --- ## 4. Variantele de raspuns Toate patru presupun ca infrastructura de "sterge + reemite" din #13 etapa II trimite, pentru liniile de pe un document-contract, acelasi `V_ID_CTR` care era pe linia originala (asta e premisa explicita a sarcinii: "acelasi drum ca la emitere" — daca `V_ID_CTR` nu s-ar retrimite, linia nici n-ar mai fi "de pe contract"). ### (a) Se accepta re-derivarea — nicio garda **Ce se face**: nimic — regenerarea apeleaza `adauga_articol_factura` exact ca la emitere, cu acelasi `V_ID_CTR`. Comportamentul existent (sectiunea 1) se aplica neschimbat. **Ce se strica**: criteriul din plan ("reemitere identica produce exact aceleasi sume" — vezi sectiunea 6) **esueaza silentios** pe orice linie de contract al carei pret/TVA/valuta a fost modificat intre timp. Dovedit sa se intample azi, pe date reale (sectiunea 2, 3 din 11). Utilizatorul care corecteaza o cantitate pe o factura veche poate vedea, fara sa i se spuna, totalul facturii schimbat pentru un articol la care nici nu s-a uitat. **Cod atins**: zero. **Regresie pe emiterea normala**: zero (comportamentul de azi ramane identic). ### (b) Se blocheaza reemiterea cand pretul curent difera **Ce se face**: inainte de a porni stergerea+regenerarea, VFP ruleaza un `SELECT` (prin `goExecutor`, exact tiparul deja folosit in `ofacturare.prg` pentru alte verificari punctuale — `goExecutor.oSelecteaza2Value(...)`, `:2628`, `:2661`) care compara, pentru fiecare linie a documentului cu `ID_CTR` populat, pretul/TVA/valuta stocate pe linie (`VANZARI_DETALII.PRET`/`PROC_TVAV`/`ID_VALUTA`) fata de ce ar recalcula azi ramura de contract (`CTR_ARTICOLE.PRET_UNITAR`/`CRM_POLITICI_PRET_ART.PROC_TVAV`/`.ID_VALUTA`, acelasi `JOIN` ca in sectiunea 1, dar rulat direct din VFP, fara sa ating `pack_facturare`). Daca gaseste macar o divergenta, **blocheaza regenerarea** cu mesaj ("Pretul de pe contract s-a schimbat pentru articolul X: -> . Actualizati contractul sau anulati regenerarea.") si nu porneste deloc stergerea. **Ce se strica**: orice regenerare pe un document cu **cel putin o linie** de contract cu pret schimbat e blocata **integral**, chiar daca utilizatorul voia sa corecteze cu totul altceva (ruta, o cantitate pe alta linie). Nu exista cale de a "accepta noul pret si continua" fara o a doua interactiune — varianta (c) rezolva exact asta. **Cod atins**: doar VFP (o interogare noua, read-only, plus un dialog de blocare), inaintea apelului la infrastructura de stergere+regenerare din #13. **Zero atingere `pack_facturare`** (decizia 27-bis respectata). **Regresie pe emiterea normala**: zero — garda ruleaza doar pe calea de regenerare, nu la emiterea unui document nou. ### (c) Se avertizeaza si se cere confirmare, cu diferenta afisata **Ce se face**: aceeasi interogare de detectie ca la (b), dar in loc sa blocheze, afiseaza un dialog cu lista liniilor divergente si delta (pret vechi vs. nou, si TVA/valuta daca difera — vezi sectiunea 3, toate trei trebuie aratate, nu doar pretul) si cere confirmare explicita ("Continuati cu noile preturi de pe contract?" Da/Nu). La "Nu", regenerarea se opreste ca la (b). La "Da", regenerarea continua normal si ramura de contract face exact ce face azi (suprascrie). **Ce se strica**: nu opreste suprascrierea insasi — doar o face **vizibila si asumata** inainte sa se produca, exact criteriul cerut de plan ("un rezultat explicit decis, nu accidental"). Nu ofera o cale de a **pastra** vechiul pret in timp ce se accepta restul modificarii — pentru asta ar trebui combinata cu (d), cu costul ei (vezi mai jos). Risc de "warning fatigue": daca divergenta e frecventa in productie (nu se poate masura azi, sectiunea 2), utilizatorii pot invata sa dea click pe "Da" fara sa citeasca. **Cod atins**: identic cu (b) plus un dialog cu doua butoane in loc de unul singur. **Regresie pe emiterea normala**: zero. ### (d) Se ocoleste ramura — reemiterea trimite pretul documentului pe o cale care nu trece prin `DECODE` **Posibil mecanic, cu un cost real.** Ramura de contract se selecteaza din doua conditii, ambele in afara controlului direct al apelantului per-linie: 1. `pack_facturare.ntip IN (2,6,26,52)` — variabila **de sesiune** (`:1882`, `pack_facturare.ntip := V_TIP`), setata o singura data la initializarea documentului (tipul documentului insusi), nu per linie. A o schimba ar insemna sa reclasifici tot documentul (afecteaza si alte ramuri `CASE` care depind de `ntip` — `:1905`, `:5053`, `:6056-6124`, `:14820` — cu efecte necunoscute si necontrolate) — **contrazice premisa "acelasi drum ca la emitere"** data explicit in sarcina. Nu recomand aceasta sub-varianta. 2. `V_ID_CTR` — parametru **per linie**, trimis explicit de VFP la fiecare apel `adauga_articol_factura`. Daca VFP trimite `V_ID_CTR = NULL` pentru o linie la reemitere, blocul de la `:5039-5050` gaseste `NO_DATA_FOUND` (cautarea `WHERE ID_CTR = NULL` nu potriveste nimic) → `V_OPT_FACTURARE := 4` → ramura `WHEN V_OPT_FACTURARE = 3` nu se mai potriveste → cade pe `ELSE` (`:5187-5203`) → `V_PRET := V_PRET_TEMP` (pretul documentului, respectat ca atare, la fel TVA prin `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA`). **Costul**: `V_ID_CTR` e si coloana scrisa in `VANZARI_DETALII_TEMP`/`VANZARI_DETALII.ID_CTR` (`:5280`, copiata neschimbat mai departe la `:13730`/`:13755`). Trimitand `NULL` la reemitere, linia **pierde definitiv legatura cu contractul** in tabela finala — orice raport care grupeaza/filtreaza pe `VANZARI_DETALII.ID_CTR` (regasire vanzari pe contract, situatii contractuale) nu va mai gasi acea linie dupa reemitere, desi articolul a fost, de fapt, livrat pe acel contract. E o regresie permanenta si greu de observat (nu da eroare, doar dispare tacit dintr-un raport), pe un camp folosit azi activ (`rec_pret_lazy.md`/`idpol_comanda_contract.md` confirma ca legatura cu contractul e cheie de raportare in tot lantul `PACK_FACTURARE`). In plus, o data pierduta legatura, **o a doua reemitere ulterioara nu ar mai putea re-deriva pretul din contract nici daca s-ar dori** — comutarea e ireversibila per linie, nu un flag comutabil. **Nu recomand varianta (d)** — costul (pierderea trasabilitatii contract-linie, permanenta) e mai mare decat problema pe care o rezolva, si nici macar nu rezolva complet criteriul "reemitere identica" (rezolva doar pretul/TVA/valuta acelei linii, cu pretul unei regresii pe alt camp). --- ## 5. Discount, TVA, curs valutar — verificare fata de sectiunile 6-7 din raportul vechi Deja tratat integral in sectiunea 3 de mai sus (descoperirea principala a acestei runde). Rezumat scurt pentru trasabilitate fata de cererea explicita: - **Discountul** (sectiunea 6 raport vechi): reconfirmat — niciodata re-derivat, indiferent de ramura. **Nu ridica problema de reemitere.** - **TVA-ul** (sectiunea 6 raport vechi): reconfirmat ca "mereu recalculat, din surse diferite pe ramura" — dar cu precizarea noua ca pe ramura de contract, sursa **coincide exact** cu sursa pretului (acelasi `SELECT`, aceeasi conditie `NO_DATA_FOUND`). **Ridica aceeasi problema ca pretul, prin acelasi mecanism** — orice varianta (b)/(c) trebuie sa acopere si TVA, nu doar pret. - **Cursul valutar** (sectiunea 7 raport vechi): reconfirmat — `V_CURS` mereu respectat ca atare (`DECODE(V_CURS,0,1,V_CURS)`), nu se re-deriva direct. **Dar** identitatea valutei (`V_ID_VALUTA`) se re-deriva pe aceeasi ramura de contract, din acelasi `SELECT` — daca politica a schimbat valuta intre timp, reemiterea scrie noua valuta cu vechiul curs trimis de VFP, fara nicio validare incrucisata (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). **Ridica aceeasi problema, prin acelasi mecanism, si trebuie acoperita de aceeasi garda.** --- ## 6. Cazul "reemitere identica" Criteriul din plan (S12) cere ca o reemitere fara nicio modificare intentionata sa produca **exact** aceleasi sume. Verificat pe fiecare ramura a `CASE`-ului de la `:5052-5220` (nu doar ramura de contract): | Ramura (`ntip`) | Garantat identic la reemitere? | De ce | |---|---|---| | `ELSE` (lista de preturi, fara contract) | **Da, cu o rezerva minora** | `V_PRET := V_PRET_TEMP` — passthrough direct (`:5200`). TVA vine din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (`:5188-5198`) — identic **doar daca** acea coloana de TVA nu a fost ea insasi editata intre timp (posibil, dar alt tip de risc, nu cel cerut de aceasta sarcina). | | `45` (restaurant) | **Da, cu aceeasi rezerva** | `V_PRET := V_PRET_TEMP` setat **inainte** de orice `SELECT` (`:5109`) — respectat necondiționat. TVA vine tot din `JTVA_COLOANE` (`:5114-5139`), aceeasi rezerva ca mai sus. | | `3,21,28,42,47` (comenzi) | **Nu garantat, dar esueaza tare, nu tacit** | `SELECT ... WHERE A.PRET = V_PRET_TEMP` (`:5077`) — daca pretul din comanda-sursa nu mai coincide exact cu ce trimite VFP, **`NO_DATA_FOUND` netratat** → eroare Oracle bruta, regenerarea pica integral, nu produce o suma gresita silentios. Diferit structural de ramura de contract. | | `4` (aviz) | **Nu garantat, silentios** | `SELECT DISTINCT A.PRET ... FROM VANZARI_DETALII` (`:5082-5103`), **fara filtru pe pret** — re-citeste pretul curent al liniei din avizul-sursa necondiționat. Daca avizul insusi a fost modificat intre emiterea initiala si reemitere, sau daca exista mai multe randuri candidate (`DISTINCT` pe o potrivire ambigua), reemiterea poate lua alt pret, tacit — **acelasi tipar de risc ca la contract**, dar pe alt document-sursa. | | `V_OPT_FACTURARE=3` (contract) | **Nu garantat, silentios** | Sectiunea 1 — faptul portant. | **Observatie in afara perimetrului cerut, dar direct relevanta**: daca #13 etapa II ajunge sa regenereze si facturi emise initial din **aviz** (`ntip=4`), nu doar din contract, **acelasi tip de risc silentios exista si acolo**, prin alt mecanism (re-citire necondiționata din `VANZARI_DETALII` a avizului-sursa, fara filtru pe pret). Raportul vechi (`s10_pret_rederivat.md`, sectiunea 4) semnalase deja asta ca "neverificat, in afara bugetului" — de data asta o marchez explicit ca **decizie deschisa**: daca reemiterea din #13 se aplica si documentelor provenite din aviz, aceeasi discutie (a/b/c/d) trebuie purtata si pentru acea ramura, cu propriul cost (sursa de comparat e alt document, nu `CTR_ARTICOLE`). Nu am extins cercetarea de date pe aceasta ramura (in afara cererii explicite a sarcinii, care a cerut "cazul confirmat", adica cel de contract). **Concluzie sectiune**: criteriul "reemitere identica" e garantat azi doar pe ramurile `ELSE`/restaurant (fara contract, fara comanda, fara aviz). Pe comenzi, o divergenta produce eroare vizibila (rau, dar nu tacit). Pe contract **si** pe aviz, o divergenta produce o suma gresita fara nicio semnalare — acestea sunt cele doua ramuri care au nevoie de o decizie explicita de tipul (a)-(d) inainte ca #13 etapa II sa poata pretinde ca satisface criteriul S12. --- ## 7. Ce ramane de decis de Marius 1. **(b) blocare stricta vs. (c) avertizare+confirmare** — (c) satisface literal criteriul "decis explicit, nu accidental" cerut de plan si nu intrerupe fluxul quasi-niciodata (doar cand chiar exista divergenta); (b) e mai sigur (niciodata nu se schimba nimic fara interventie manuala pe contract) dar mai frustrant daca divergentele sunt frecvente in productie — lucru nemasurabil azi (sectiunea 2). **Recomand (c)** ca implicit, cu mesajul aratand explicit delta de pret/TVA/valuta (sectiunea 3) — nu doar pretul. 2. **Garda trebuie sa acopere pret + TVA + valuta impreuna**, nu doar pretul — confirmat la sectiunea 3 ca vin din acelasi `SELECT`. O implementare care verifica doar pretul ar lasa trecerea tacuta a unei schimbari de TVA sau valuta. 3. **Varianta (d) (ocolire) — nu o recomand**, din cauza pierderii permanente a legaturii `VANZARI_DETALII.ID_CTR`. Daca Marius considera totusi ca merita costul (de ex. daca trasabilitatea contract-linie pe documentele reemise nu conteaza in practica), e o decizie de produs explicita, nu tehnica — semnalez aici, nu decid. 4. **Ramura de aviz (`ntip=4`) are acelasi tip de risc silentios** (sectiunea 6) — de decis daca intra in perimetrul #13 etapa II acum sau se trateaza separat, cu propriul studiu de fezabilitate pentru garda (sursa de comparat difera fata de contract). 5. **Frecventa reala in productie a divergentei `CTR_ARTICOLE.PRET_UNITAR`** ramane nemasurabila din baza de dezvoltare (27 randuri total) — daca decizia (b) vs (c) depinde de cat de des s-ar declansa garda, ar trebui repetata interogarea din sectiunea 2 pe o baza cu volum de productie inainte de implementare. --- ## STARE / CE RAMANE **Livrabil complet** — toate cele 6 puncte cerute in brief sunt acoperite (sectiunile 1-6), plus recomandare argumentata (sectiunea 4, cu tabel comparativ) si lista de decizii deschise (sectiunea 7). Nimic ramas in lucru; nicio verificare intrerupta. Singurele limitari explicite: - esantionul de date (27 randuri `CTR_ARTICOLE`, baza de dezvoltare) nu se extrapoleaza la productie (sectiunea 2); - ramura de aviz (`ntip=4`) e semnalata ca avand acelasi tip de risc, dar nu a primit acelasi nivel de cercetare (nu era in perimetrul cerut) — vezi sectiunea 6, ultimul paragraf inainte de concluzie.