Files
roafacturare/docs/cercetare/s10_pret_contract_reemitere.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:17:17 +03:00

469 lines
29 KiB
Markdown

# 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: <vechi> -> <nou>. 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.