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:
411
docs/cercetare/rec_r5_totaluri_factura_din_aviz.md
Normal file
411
docs/cercetare/rec_r5_totaluri_factura_din_aviz.md
Normal 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`.
|
||||
|
||||
Reference in New Issue
Block a user