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

29 KiB

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.

-- :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;
-- :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:

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):

-- 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.