docs: runda 6 - cercetari, propuneri si conventia mediului Oracle

Cercetarile si propunerile rundelor 4-6 pe editarea facturii emise. Starea
rundei 6 si ce s-a stabilit intra in progres.md.

Nou: docs/conventii_mediu_oracle.md - pe dev/test se lucreaza numai cu
ROA_CENTRAL si schemele CONTAFIN_ORACLE si MARIUSM_AUTO; schema ACN nu se
foloseste. Scripturile de pachet sunt necalificate, deci schema tinta o
decide sirul de conectare, nu fisierul.

roafacturare.pj2 regenerat de git_sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013gYCNE26G1G7UoGu2ju7aS
This commit is contained in:
2026-08-20 16:35:03 +03:00
parent b5a7108f34
commit ca3c5d7eea
26 changed files with 5316 additions and 103 deletions

View File

@@ -0,0 +1,411 @@
# Cercetare: totaluri goale pe factura editata din aviz (LISTA FACTURI, AVIZE SI PROFORME)
Simptom: dupa editarea din formularul unificat a unei facturi emise DIN AVIZ, randul facturii in
lista principala apare cu **Total fara TVA / Total TVA / Total cu TVA GOALE** (NULL, nu 0.00).
Randul avizului de dedesubt ramane corect. Regresie fata de comportamentul anterior.
Diagnostic STRICT — nu s-a modificat niciun fisier de cod, nu s-a facut write-back, nu s-a comis
nimic.
> **Depasit partial.** Acest raport se opreste la "cauza probabila, NEVERIFICAT pe date".
> Interogarea pe `MARIUSM_AUTO@ROA_CENTRAL` a inchis intre timp intrebarea: linia facturii are
> `ID_VALUTA = 2` (EURO) fara rand corespunzator in `VANZARI_CURSURI`, iar ramura de conversie
> valutara a procedurii produce NULL. Concluzia si reparatia sunt in
> `docs\propunere_runda5_articole_valuta_rate.md`, punctul 2. Ce ramane valabil aici: analiza
> lantului de scriere si infirmarea ipotezei cu `id_vanzare_set`/`id_vanzare_det`.
## 1. De unde vin coloanele Total fara TVA / Total TVA / Total cu TVA
Gridul `grid_facturi` din `frm_facturi` (`COMUN\clase\ofacturare_comun.vc2:1172` obiectul,
`:1203-1208` coloanele `cTotal_cu_tva`/`cTotal_fara_tva`/`cTotal_tva`) e legat pe cursorul
`crsfacturi` (`RecordSource = "crsFacturi"`, `COMUN\clase\ofacturare_comun.vc2:2238`), populat prin
`gencursor('poFacturi','crsfacturi', lcSelect, ...)`.
Cursorul e umplut din UNA din doua proceduri Oracle, **alese la RUNTIME de utilizator**, printr-un
prompt (`Clase\ofundal_facturare.vc2:944-953`, `Page4.Cw1.do_actiune`):
```
lnOptiune = xmenu('Vizualizare \<standard;Vizualizare \<experimentala (mai rapida)')
If m.lnOptiune = 1
Do vizualizare_facturi In oproceduri_facturare.prg && FACT_VFACTURI
Else
Do vizualizare_facturi2 In oproceduri_facturare.prg && FACT_VFACTURI2
Endif
```
Cele doua proceduri (`COMUN\programe\oproceduri_facturare.prg:314` si `:398`) selecteaza
`total_fara_tva, total_tva, total_cu_tva` din **doua view-uri Oracle diferite** cu semantica
diferita — asta e cheia problemei:
- **`FACT_VFACTURI`** ("standard"): coloanele sunt **calculate live**, printr-un `SUM(...)`
peste `vanzari_detalii` (si `vanzari_seturi` pentru liniile de tip set). Nu citeste niciodata
coloanele stocate din `VANZARI`.
Definitie: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_11_COMUN_FACT_VFACTURI.sql:192-194`
(`a.suma_fara_tva - a.disc_fara_tva_ron as total_fara_tva`, etc.), agregarea la
`:408-460` (subquery `vd`, `LEFT JOIN` intre `v.id_vanzare` si rezultatul agregat).
- **`FACT_VFACTURI2`** ("experimentala"): coloanele sunt citite **direct** din `VANZARI` (coloane
stocate), fara recalcul la interogare — comentariul din cod spune explicit de ce:
"*totalurile ... sunt calculate in vanzari, in loc sa fie luate din vanzari_detalii, pentru
rapiditate; totalurile sunt completate in vanzari la insert into vanzari*"
(`COMUN\programe\oproceduri_facturare.prg:397-399`).
Definitie view: `ff_2026_08_06_11_COMUN_FACT_VFACTURI.sql:682-684`
(`a.total_fara_tva, a.total_tva, a.total_cu_tva` din `vanzari a`).
**NEVERIFICAT**: nu stiu ce optiune a ales Marius la momentul screenshot-ului (`standard` sau
`experimentala`) — promptul e interactiv, nu am gasit un default hard-codat. Concluzia de mai jos
e valabila pentru amandoua variantele, din motive diferite (vezi §3).
## 2. Ce scrie salvarea din formularul unificat pe partea de factura
`ScrieArticoleFacturaEditate` (`COMUN\programe\ofacturare_editare.prg:473-571`), apelata cu
`tvanz.id_vanzare` (id-ul corect al FACTURII, verificat — `tvanz` vine din
`IncarcaVanzareNota`/`IncarcaVanzareDinNota`, care interogheaza `VANZARI` dupa
`cod/nract/serie_act/data_act` ale notei editate, deci nu e confuzie aviz/factura; apelurile reale
sunt in `COMUN\clase\comun.vc2:2492` si `COMUN\clase\ofacturare_comun.vc2:3829`, ambele in ramura
`"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))`):
1. **Marcheaza sters=1 TOATE liniile active** din `vanzari_detalii` pentru acel `id_vanzare`
(`:492-495`).
2. **Reinvie/actualizeaza** liniile pastrate, cele cu `id_vanzare_det > 0` din cursorul `tvd`
(`:498-518`) — UPDATE pe cheia `id_vanzare_det`, camp cu camp: `sters, cantitate, pret,
pret_cu_tva, pret_achizitie, proc_tvav, discount_unitar, id_articol, id_gestiune, id_valuta,
id_jtva_coloana, taxcode, cont, id_utils, dataoras`. **NU** atinge `id_vanzare_set` — coloana nu
apare in lista SET, deci valoarea existenta in DB ramane neschimbata la UPDATE.
3. **Insereaza** liniile noi (`id_vanzare_det = 0`, `:522-540`) — la fel, **fara** `id_vanzare_set`
in lista de coloane (linii noi ies cu `id_vanzare_set` implicit NULL in DB).
4. **Doar daca 1-3 au reusit complet** (`IF m.llSucces`, verificat dupa fiecare pas, `EXIT` la
primul esec Oracle — deci un esec partial NU ajunge la pasul urmator), cheama procedura Oracle
de recalcul totaluri (`:559-563`, vezi §3).
Pe o factura din aviz **fara** articole compuse ("seturi"), toate liniile din `tvd` au
`id_vanzare_set = 0/NULL` — confirmat de cercetarea anterioara `docs/livrare_s5.md:117-118`:
"*Liniile din seturi de articole (id_vanzare_set nenul) — neacoperibil pe datele actuale, nu din
omisiune: interogare pe MARIUSM_AUTO, zero documente cu id_vanzare_set nenul*". Deci pentru cazul
din screenshot (SSS 100037), branch-ul de "seturi" din view/procedura Oracle (vezi §3) probabil nu
se activeaza — **ramane insa NEVERIFICAT direct pe acest document**, n-am rulat SQL pe schema
reala.
**Nu am gasit nicio scriere directa pe antetul `VANZARI` in `ScrieArticoleFacturaEditate` in afara
apelului la procedura Oracle de la pasul 4** — totalurile de antet nu sunt niciodata calculate in
VFP, sunt lasate integral pe seama Oracle.
## 3. Procedura Oracle de recalcul — cea mai probabila cauza
`ScrieArticoleFacturaEditate` (`ofacturare_editare.prg:559-563`) cheama necondiționat:
```
lcSql = [begin pack_facturare.recalculeaza_totaluri_vanzari(] + Alltrim(Str(m.tnIdVanzare)) + [,] + ;
Iif(m.llDiscountNul,[NULL],Alltrim(Str(m.lnDiscount,18,4))) + [); end;]
llSucces = goExecutor.oExecuta(m.lcSql)
```
Aceasta procedura este **cod nou**, introdus in exact aceeasi runda de modificari ca bug-ul
raportat:
- Script-ul care o documenteaza in `SCRIPTURI_CLAR` e
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(comentariu la inceput: "*adauga procedura recalculeaza_totaluri_vanzari*").
- Apelul din VFP a fost adaugat in commit-ul `1c42ae0` din `COMUN`
("*#6 editare factura emisa: S5 - scrierea sumelor editate in Oracle*"), **10.08.2026**, adica o
zi dupa ce procedura Oracle a fost capturata in script (09.08.2026) — coerent cu "procedura noua
in DB, apoi codul VFP care o cheama".
Corpul procedurii (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024-16228`) face un
`SELECT ... INTO` cu agregare (SUM) peste vanzari_detalii/vanzari_seturi — **aceeasi structura**
ca formula din `FACT_VFACTURI` (§1): UNION ALL intre liniile normale
(`nvl(a.id_vanzare_set,0)=0 and a.id_vanzare=V_ID_VANZARE and a.sters=0`) si liniile "set"
(`vanzari_seturi` join `vanzari_detalii`), apoi `SUM(pack_facturare.calculeaza_total_fara_tva_fact(...))`
etc. Rezultatul e scris **necondiționat**, fara nicio garda `NVL`:
```
update vanzari
set discount = lnDiscountFactura,
...
total_fara_tva = lnTotalFaraTVA,
total_tva = lnTotalTVA,
total_cu_tva = lnTotalCuTVA,
...
where id_vanzare = V_ID_VANZARE;
```
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16211-16223`)
Contrast direct cu scriptul de backfill istoric din aceeasi runda
(`ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`), care recalculeaza aceleasi coloane dar explicit
**"fill-only"**: `v.total_fara_tva = NVL(v.total_fara_tva, src.c_ftva)`, cu comentariu propriu care
spune raspicat ca scrierea trebuie sa fie doar completare de goluri, "*niciodata peste o valoare
deja scrisa*". `recalculeaza_totaluri_vanzari` **nu are aceasta garda** — daca `SELECT INTO`
calculeaza `NULL` pe oricare coloana (SUM peste zero randuri, sau peste randuri toate NULL),
UPDATE-ul suprascrie o valoare corecta deja existenta cu NULL.
**Cand poate iesi NULL din SUM**: in Oracle, `SUM()` peste un set de randuri produs de un
`LEFT JOIN` fara nicio potrivire da NULL (nu 0), iar daca TOATE liniile relevante ale documentului
ies cu pret/discount NULL din subquery (de ex. `vanzari_seturi` fara rand pentru
`id_vanzare_set`-ul cerut, in branch-ul de "seturi"), rezultatul agregat pe acel document e NULL.
Pentru un document fara linii de tip "set" (cazul obisnuit, §2), acest branch nu ar trebui sa se
activeze — dar **nu am verificat pe date reale** daca liniile facturii SSS 100037 au ramas cu
`sters=0` dupa salvare sau daca vreo alta conditie (curs valutar lipsa in `vanzari_cursuri` pentru
`id_valuta`-ul liniei, de ex.) a produs NULL in agregat.
## 4. Regresia, concret — NEVERIFICAT complet, dar convergenta puternica
Nu exista fisiere `.bak.vc2` pentru `ofacturare_editare.prg` (bak-urile mentionate in cerere —
`omodificari.pre_cantitate.bak.vc2` etc. — sunt pentru `omodificari.vc2`, o clasa diferita; codul
de scriere efectiva a articolelor si totalurilor traieste in `ofacturare_editare.prg`, care nu are
un `.bak`). Istoricul git (`COMUN`) arata insa clar ca **intreg mecanismul de recalcul al
totalurilor pe factura editata e cod nou din 09-10.08.2026** (S5), introdus in acelasi pachet cu
extinderea UPDATE-ului mentionata in cerere (`id_articol, id_gestiune, id_valuta, proc_tvav,
taxcode, cont, pret_achizitie, discount_unitar, id_jtva_coloana`) — commit-urile ulterioare
(S4b etapa 2, 11.08.2026) nu ating aceasta zona.
**Ipoteza suspectata explicit de cerere** (UPDATE-ul extins scrie campuri NULL peste linii
existente si strica `id_vanzare_set`/`id_vanzare_det`) **NU se confirma din citirea codului**:
UPDATE-ul de la pasul 2 (§2) nu atinge deloc coloana `id_vanzare_set`, iar `id_vanzare_det` e
folosit doar in clauza `WHERE`, niciodata scris. Round-trip-ul cursorului `tvd` (incarcat din
`VVANZARI_ARTICOLE`, view 1:1 pe `vanzari_detalii` — vezi
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql`, fara nicio
agregare) e coerent camp cu camp.
**Cauza cea mai probabila, cu incredere medie-mare**: nu o eroare in codul VFP de scriere a
liniilor, ci procedura Oracle noua `pack_facturare.recalculeaza_totaluri_vanzari`
(§3) — cod introdus in aceeasi runda, apelat necondiționat dupa fiecare salvare de factura editata
din formularul unificat, care scrie fara garda NULL peste coloanele de total din `VANZARI`. Pe
"vizualizarea standard" (`FACT_VFACTURI`), acelasi tip de agregare NULL-propagabila se repeta live
la fiecare afisare a listei, deci simptomul ar aparea indiferent care view e activ.
## 5. Trigger/procedura declansata la INSERT — nu se aplica aici
Nu exista trigger Oracle care sa recalculeze totalurile la INSERT in `vanzari_detalii` — scrierea
e facuta explicit, o singura data, prin apelul manual la `recalculeaza_totaluri_vanzari` de la
finalul lui `ScrieArticoleFacturaEditate` (§3). Punctul 4 din cererea initiala nu se aplica.
## Reparatie propusa (fara aplicare)
Minimal, in `D:\ROA\DATABASE\SCRIPTURI_CLAR\...\PACK_FACTURARE.sql` /
`recalculeaza_totaluri_vanzari`: inlocuit UPDATE-ul necondiționat cu varianta "fill-safe" folosita
deja in scriptul de backfill — fie NVL pe fiecare coloana (`total_fara_tva = NVL(lnTotalFaraTVA,
total_fara_tva)` etc., pastreaza valoarea veche daca recalculul da NULL), fie un `RAISE`/log
explicit cand agregatul iese NULL, ca sa nu treaca neobservat. Inainte de asta, e nevoie de o
verificare directa pe schema reala (SQL pe `vanzari_detalii`/`vanzari_seturi` pentru
`id_vanzare`-ul facturii SSS 100037) ca sa se confirme ce ramura produce NULL — recomand asta ca
prim pas al oricarei remedieri, nu modificarea pe ghicite.
## Ce ramane NEVERIFICAT (runda 1)
- Ce optiune de vizualizare (standard/experimentala) a fost activa la momentul screenshot-ului.
- Continutul real al `vanzari_detalii`/`vanzari_seturi` pentru factura SSS 100037 dupa editare —
**verificat in runda 2** (mai jos), pe baza interogarilor SQL facute de team-lead + interogari
proprii read-only, cu `sqlplus`.
---
# Runda 2 — cauza confirmata prin SQL pe date reale
Team-lead a interogat direct Oracle (`MARIUSM_AUTO@ROA_CENTRAL`) si a stabilit cauza imediata:
linia `VANZARI_DETALII.id_vanzare_det = 1602` (singura linie activa a facturii `id_vanzare = 1054`,
SSS 100037) are `ID_VALUTA = 2` (EURO), desi documentul e `IN_VALUTA = 0` si linia-sursa din aviz
avea `ID_VALUTA = 3` (RON). `VANZARI_CURSURI` nu are niciun rand pentru `id_vanzare = 1054`, deci
`recalculeaza_totaluri_vanzari` calculeaza `pret_ron = NULL` pentru acea linie (branch-ul de
conversie valutara, fara curs) si `total_fara_tva/total_tva/total_cu_tva` ies `NULL`. Am confirmat
independent, cu SQL read-only propriu (`sqlplus`, vezi mai jos), amploarea si mecanismul.
## 1. Cine scrie `id_valuta` pe linia de articol — CONFIRMAT
Singura cale prin care `tvd.id_valuta` se schimba pe o linie **existenta** e nomenclatorul de
valuta din grid: `ArticoleNotaEditor.ModificaNomenclator`, ramura `nume_val`
(`COMUN\programe\ofacturare_editare.prg:1093-1094`):
```
CASE m.lcCamp == 'nume_val'
REPLACE nume_val WITH loCauta.nume_val, id_valuta WITH loCauta.id_valuta
```
`loCauta = caut_valuta()` — nomenclatorul standard de valute, deschis fara nicio conditie legata
de `tvanz.in_valuta`. Singura garda de la inceputul procedurii (`:1069`) e
`Nvl(tvd.id_vanzare_set,0) <> 0` (blocheaza doar liniile din seturi de articole) — **nu exista
nicio garda legata de tipul documentului**, deci utilizatorul poate alege orice valuta pe orice
linie normala, indiferent daca factura e in RON sau in valuta.
UPDATE-ul care duce `id_valuta` in `VANZARI_DETALII` e in
`ScrieArticoleFacturaEditate` (`COMUN\programe\ofacturare_editare.prg:512`):
```
[, id_valuta = ] + Iif(Isnull(id_valuta),[NULL],Alltrim(Str(id_valuta))) + ;
```
**Confirmat prin diff cu `COMUN\programe\ofacturare_editare.prg.pre_runda_butoane.bak:501-505`**:
inainte de runda curenta, UPDATE-ul pe liniile existente scria doar
`sters, cantitate, pret, pret_cu_tva, id_utils, dataoras` — **`id_valuta` nu era in lista**. Deci
inainte, o alegere de valuta pe o linie existenta (facuta prin `nume_val`) se pierdea silentios la
salvare (UI arata schimbarea, dar UPDATE-ul n-o scria) — inofensiv, dar si fara efect. Extinderea
UPDATE-ului (runda curenta, aceeasi runda care a adaugat si `recalculeaza_totaluri_vanzari`, vezi
runda 1 §3-4) e cea care face ca alegerea gresita de valuta sa ajunga acum in `VANZARI_DETALII` si
sa strice totalurile. **Simptomul e nou din exact acest motiv — confirmat pe cod, nu presupunere.**
## 2. Ce ar trebui sa fie corect pe un document `in_valuta = 0` — CONFIRMAT
`VANZARI_CURSURI` e scris o singura data, la EMITERE, de `pack_facturare.scrie_cursuri`
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:14538-14548`):
```sql
PROCEDURE scrie_cursuri(V_ID_VANZARE IN NUMBER) IS
BEGIN
INSERT INTO VANZARI_CURSURI (ID_VANZARE, ID_VALUTA, CURS, MULTIPLICATOR)
SELECT DISTINCT V_ID_VANZARE, ID_VALUTA, CURS, MULTIPLICATOR
FROM VANZARI_DETALII_TEMP
WHERE ID_VALUTA <> pack_facturare.nid_moneda_nationala;
END scrie_cursuri;
```
Sursa e tabela de STAGING folosita la emitere (`VANZARI_DETALII_TEMP`), nu `VANZARI_DETALII` live —
deci **nu exista niciun mecanism care sa adauge/actualizeze `VANZARI_CURSURI` cand se schimba
`id_valuta` pe o linie DUPA emitere**, din editarea unificata sau din oricare alt punct. Raspuns
direct la intrebare: **da, e conceptual posibil ca o linie sa aiba alta valuta decat RON pe un
document in lei** (mecanismul de facturare mixta exista, cu curs propriu per linie in
`VANZARI_CURSURI`) — dar **doar daca acea alegere s-a facut la emitere**, cand `scrie_cursuri`
inca ruleaza si scrie perechea `(id_vanzare, id_valuta) -> curs`. O schimbare **post-emitere**,
prin editarea unificata, nu are nicio cale sa creeze acel rand — deci orice `id_valuta` diferit de
RON ales dupa emitere e, prin constructie, orfan (fara curs), garantand `pret_ron = NULL` in
`recalculeaza_totaluri_vanzari`.
## 3. Toate caile prin care agregatele pot iesi NULL — enumerare + masurare pe date reale
Din citirea corpului `recalculeaza_totaluri_vanzari`
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024-16228`):
1. **Zero randuri active in `VANZARI_DETALII`** pentru `id_vanzare` (toate `sters=1`, sau
document fara nicio linie) — `SUM()`/`MAX()` peste zero randuri = NULL pentru toate coloanele.
2. **Linie cu `id_valuta` diferit de moneda nationala, fara rand in `VANZARI_CURSURI`** pentru acea
pereche `(id_vanzare, id_valuta)` — cazul masurat mai jos (Marius). `LEFT JOIN vc` nu gaseste
nimic, `vc.curs`/`vc.multiplicator` = NULL, `pret_ron` = NULL pentru acea linie. Daca **toate**
liniile documentului sunt afectate, `SUM()` total iese NULL; daca doar unele, `SUM()` ignora
randurile NULL si totalul iese **trunchiat, nu NULL** (lipseste contributia liniei respective,
fara sa fie vizibil ca eroare).
3. **Linie din "set de articole" (`id_vanzare_set <> 0`) fara rand corespunzator in
`VANZARI_SETURI`** — acelasi tipar ca #2, prin `LEFT JOIN vanzari_seturi b`. Neconfirmat pe date
reale (branch aproape neutilizat — vezi `docs/livrare_s5.md:117-118`, zero documente cu
`id_vanzare_set` nenul cunoscute anterior).
4. **`VANZARI.in_valuta` NULL** — `nvl(lnInValuta,-1) > -1` devine fals, exclude tot branch-ul de
"seturi"; nu produce NULL pe branch-ul normal, dar poate goli branch-ul 2 pe un document compus
doar din linii de set.
5. **Argument NULL in `pack_facturare.calculeaza_total_fara_tva_fact`/`_tva_fact`** (ex.
`proc_tvav` sau `cantitate` NULL pe o linie) — acelasi tipar trunchiat/total, dupa cate linii
sunt afectate.
**Masurat pe schema reala** (`MARIUSM_AUTO@ROA_CENTRAL`, SELECT-uri read-only, script-urile
folosite raman in `scratchpad`, nu s-a scris nimic in baza):
| Categorie (din cele 235 `VANZARI.total_cu_tva IS NULL`) | Numar documente |
|---|---|
| Total `VANZARI` cu `total_cu_tva IS NULL` | **235** |
| Fara nicio linie activa in `VANZARI_DETALII` (cauza #1, veche — carve-out cunoscut, vezi
`ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, "facturile fara nicio linie activa raman
neatinse, prin decizie de produs") | **120** |
| Linie cu `id_valuta` diferit de RON si fara rand in `VANZARI_CURSURI` (cauza #2 — **exact
bug-ul curent**) | **1** (chiar SSS 100037 / `id_vanzare=1054`, confirmat `IN_VALUTA=0`) |
| Neexplicat de #1 sau #2 (alta cauza, preexistenta, in afara scopului acestei cercetari) |
**114** — din acestea, 109 au `VANZARI.discount_evidentiat IS NULL` (posibil o cauza separata,
nelegata de `id_valuta`; neinvestigat in continuare, iese din scopul cererii) |
**Concluzie importanta**: bug-ul de `id_valuta` orfan e **izolat la 1 singur document in toata
baza**, in acest moment — consistent cu faptul ca UPDATE-ul extins care il face vizibil e cod de
cateva zile (runda curenta). Nu e nevoie de o reparare in masa; celelalte 234 de documente cu total
NULL au alta cauza (majoritar #1, cunoscuta si acceptata prin design) si nu trebuie atinse de
reparatia acestui bug.
## 4. Reparatie propusa, in trei straturi (fara aplicare)
### Client (VFP)
In `ArticoleNotaEditor.ModificaNomenclator`
(`COMUN\programe\ofacturare_editare.prg:1069`), extinde garda existenta ca sa blocheze
nomenclatorul de valuta pe liniile unui document care nu e in valuta:
```
IF !This.Editabil() OR Reccount('tvd') = 0 OR !This.AreNomenclator(m.lcCamp) OR Nvl(tvd.id_vanzare_set, 0) <> 0 ;
OR (m.lcCamp == 'nume_val' AND Nvl(tvanz.in_valuta,0) <> 1)
RETURN .F.
ENDIF
```
(presupune ca `tvanz` e vizibil din contextul clasei — de verificat la implementare; alternativ,
o proprietate `This.oForm.lDocInValuta` populata la incarcare). Efect: pe o factura in RON,
coloana `nume_val` devine needitabila, la fel ca liniile din seturi — elimina posibilitatea de a
introduce `id_valuta` orfan prin UI.
### DB — `recalculeaza_totaluri_vanzari`
Doua schimbari, ambele minime:
1. **Restrange conditia de conversie** la cazul in care documentul insusi e in valuta, nu cand
linia are o valuta diferita de RON pe un document RON (elimina exact vulnerabilitatea gasita):
```sql
-- inainte:
(case when (lnInValuta = 1 or vd.id_valuta <> pack_def.GetIdMonedaNationala())
then ROUND(vc.curs * vd.pret / vc.multiplicator, lnPreciziePretV)
else vd.pret end) as pret_ron
-- dupa:
(case when lnInValuta = 1
then ROUND(NVL(vc.curs,1) * vd.pret / NVL(vc.multiplicator,1), lnPreciziePretV)
else vd.pret end) as pret_ron
```
Motivatie: pe un document `in_valuta=0`, `vd.pret` e prin definitie deja in RON (asa scrie
`ScrieArticoleFacturaEditate` liniile, indiferent de `id_valuta` afisat) — conversia n-are ce sa
faca acolo, indiferent ce `id_valuta` a ajuns (corect sau nu) pe linie. Pastrez `NVL(vc.curs,1)`
doar ca ultima plasa de siguranta pe ramura `lnInValuta=1` (document CHIAR in valuta) — daca
acolo lipseste cursul e o eroare de date reala care merita semnalata, nu ascunsa; de discutat cu
Marius daca `NVL(...,1)` e acceptabil acolo sau daca ar trebui sa opreasca salvarea cu eroare in
loc sa scrie o suma gresita tacut. **Nu propun `NVL` necondiționat pe ramura de conversie
reala** — ar masca o factura in valuta cu curs lipsa, scriind un total fals dar nenul, mai greu
de observat decat un NULL.
2. **Plasa finala de siguranta la UPDATE**, dupa modelul "fill-safe" deja folosit in
`ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, ca sa nu se mai poata suprascrie tacut o valoare
corecta cu NULL, indiferent ce cauza noua ar aparea in viitor:
```sql
update vanzari
set total_fara_tva = NVL(lnTotalFaraTVA, total_fara_tva),
total_tva = NVL(lnTotalTVA, total_tva),
total_cu_tva = NVL(lnTotalCuTVA, total_cu_tva),
...
where id_vanzare = V_ID_VANZARE;
```
Cu un `dbms_output`/log undeva (tabela de erori aplicative, daca exista una) cand recalculul
iese NULL, ca sa nu treaca neobservat un caz nou.
### Date (script propus, NU RULAT)
```sql
-- 1. Corectie punctuala pentru SSS 100037 (id_vanzare=1054): id_valuta-ul liniei 1602 revine la
-- RON, aliniat cu documentul (in_valuta=0) si cu linia-sursa din aviz (id_vanzare_det=1599,
-- care avea deja id_valuta=3).
UPDATE VANZARI_DETALII
SET ID_VALUTA = pack_def.GetIdMonedaNationala()
WHERE ID_VANZARE_DET = 1602
AND ID_VANZARE = 1054;
COMMIT;
-- 2. Retrigger recalcul dupa fix (acelasi apel pe care il face si formularul la salvare)
BEGIN
pack_facturare.recalculeaza_totaluri_vanzari(1054);
END;
/
COMMIT;
```
Restrans STRICT la acest document — cele 234 de alte randuri cu total NULL au alta cauza (§3) si nu
intra in scopul acestei reparatii. Daca se doreste o baleiere completa dupa ce fix-ul de la client
e livrat (sa nu mai apara alte cazuri noi), propun mai intai o interogare de monitorizare
periodica (varianta de query C/F de mai sus), nu o corectie automata in masa.
## Ce ramane NEVERIFICAT (runda 2)
- Cauza celor 114 documente "neexplicate" (mult mai vechi, posibil legate de
`discount_evidentiat IS NULL`) — iese din scopul cererii, semnalez doar ca exista.
- Daca `tvanz` e efectiv vizibil ca alias in contextul `ArticoleNotaEditor.ModificaNomenclator` la
momentul apelului (necesar pentru fix-ul de client propus) — de verificat la implementare, nu am
urmarit domeniul complet de vizibilitate al claselor `.vc2`.