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

490 lines
34 KiB
Markdown

# S4c — Discountul pe linie, mutat din dialog in grid — proiectare implementabila
Cercetare + proiectare READ-ONLY pentru `docs\plan_13_unificare_formular_facturare.md`, `#### S4c`
(`:2156-2190`). Nu s-a modificat niciun fisier, nu s-a rulat `git_sync.ps1`/`txt2vcx.ps1`, nu s-a dat
commit, nu s-a scris nimic pe Oracle (numai `SELECT`, niciunul rulat de fapt — toata cercetarea a
fost pe cod VFP). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`.
**Surse de adevar deja stabilite, citate ca atare** (nu se re-verifica aici):
`docs\cercetare\discount_in_rapoarte_si_efactura.md` (verdictul principal — niciun raport/eFactura nu
tipareste discountul, dar amandoua citesc `valdiminuatftva`/`discountftva` din cursorul de tiparire),
`docs\cercetare\discount_verificare2.md` (structura reala a coloanelor din `grd_factura` si a
`VANZARI_DETALII`). **`docs\cercetare\discount_pe_articol.md` NU e citat ca sursa** — task-ul mi-a
semnalat ca are doua afirmatii gresite, corectate deja in `discount_verificare2.md` sectiunea finala
("Ce era gresit in afirmatiile de mai sus").
## Verdict (10 randuri)
S4c e implementabila cu cod nou moderat, nu cu redesign. Contractul de calcul exista deja, complet si
reciproc, in `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) si
`frm_articol_factura.do_calculeaza_totaluri` (`:2068-2179`, delegand la functia partajata
`calculeaza_totaluri()` din `oproceduri_facturare.prg:2258-2381`) — aceasta din urma **e deja scrisa
generic, pe orice obiect cu proprietatile potrivite**, deci se poate rula direct pe un rand din
`crsfactura` (via `Scatter`/`Gather Name`), fara sa mai treaca prin dialog. Tiparul de eveniment
pentru coloane editabile de grid **exista deja in acelasi fisier**, pe alt formular din aceeasi
familie de clase (`frm_avizare_lucrare.grd_articole.cCantitate/cPret.Text1.LostFocus`,
`:6549-6562`) — `LostFocus` care cheama `Thisform.do_calculeaza_totaluri()`, nu `InteractiveChange`.
**Capcana reala e alta decat pare din titlul poveste**: exista **doua cai de scriere independente**
catre destinatii diferite, si doar una e azi corect legata. `do_scrie_articole` trimite spre Oracle
(`pack_facturare.adauga_articol_factura`, `ofacturare.vc2:14081-14083`) discountul citit **direct
din `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`** — deci `VANZARI_DETALII.DISCOUNT_UNITAR`
**e mereu corect**, indiferent de bug, pentru ca aceste campuri sunt chiar campurile editate in grid.
Riscul e strict local, in sesiunea VFP curenta: `prelucreaza_factura` (apelata pentru tiparire/eFactura,
`ofacturare.prg:1887`) construieste cursorul de tiparire **din acelasi `crsfactura` deja in memorie**,
citind `valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` — campuri **agregate**
(pret-discount)*cantitate, care **nu se recalculeaza singure** cand se editeaza discountul brut. Deci
Oracle poate avea `DISCOUNT_UNITAR` corect si totusi factura tiparita/eFactura sa arate valoarea veche,
in aceeasi sesiune, pana la reincarcare. Solutia: coloanele noi de discount trebuie sa scrie, pe
`LostFocus`, atat campurile brute cat si campurile agregate — vezi sectiunea 4.
---
## 1. Lantul complet al campurilor de discount, cu `fisier:linie`
### 1.1 Structura cursorului local `crsfactura`
Definit in `creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1779-1782`):
```
1779 valdiscountctva N(20,max(gnPc,4)),valdiminuatftva N(20,max(gnPc,4)),valdiminuattva N(20,max(gnPc,4)),valdiminuatctva N(20,max(gnPc,4)),proc_Tvav N(20,4),cu_tva N(1), lot C(20) null, serie c(100) Null,;
1782 vvaldiscountctva N(20,max(gnPVal,4)),vvaldiminuatftva N(20,max(gnPVal,4)),vvaldiminuattva N(20,max(gnPVal,4)),vvaldiminuatctva N(20,max(gnPVal,4)),id_set_fact N(20) Null,explicatie M Null,;
```
(`discountftva`, `discountctva`, `vdiscountftva`, `vdiscountctva`, `valdiscountftva`, `valdiscountctva`,
`vvaldiscountftva`, `vvaldiscountctva` sunt definite pe liniile adiacente, aceeasi procedura —
nu recitate individual, tiparul `N(20,...)` e identic).
**Nomenclatura confirmata pe cod** (nu doar dedusa din nume) — opt campuri distincte, trei niveluri:
| Camp | Nivel | Moneda | Cu/fara TVA | Sens |
|---|---|---|---|---|
| `discountftva` | pe unitate | lei | fara TVA | discount unitar, sursa in lei |
| `discountctva` | pe unitate | lei | cu TVA | discount unitar, derivat |
| `vdiscountftva` | pe unitate | valuta | fara TVA | discount unitar, sursa in valuta |
| `vdiscountctva` | pe unitate | valuta | cu TVA | discount unitar, derivat |
| `valdiscountftva`/`valdiscountctva` | pe linie (x cantitate) | lei | ambele | discount agregat lei |
| `vvaldiscountftva`/`vvaldiscountctva` | pe linie (x cantitate) | valuta | ambele | discount agregat valuta |
| `valdiminuatftva`/`valdiminuatctva` | pe linie (x cantitate) | lei | ambele | **valoare neta** (pret-discount)*cant — cea tiparita/eFactura |
| `vvaldiminuatftva`/`vvaldiminuatctva` | pe linie (x cantitate) | valuta | ambele | valoare neta in valuta |
### 1.2 De la tastare la `crsfactura` — calea de azi (prin dialog)
1. Operator tasteaza in dialogul `frm_articol_factura` (`ofacturare.vc2:1108-2659`), pe unul din 3
perechi de campuri: `Clb_procent_discount.Text_simplu1` (`InteractiveChange`, `:2646`),
`Clb_discount_unitar.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2614/:2620`),
`Clb_discountctva.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2602/:2608`).
2. Toate cheama `Thisform.do_calculeaza_discount(valoare, tip)` — **`frm_articol_factura`**
(`:1874-1976`, contract detaliat in sectiunea 2) — scrie in `poArticol`:
`discount_unitar`, `discount_unitar_val`, `discount_unitar_ctva`, `discount_unitar_ctva_val`.
3. `do_calculeaza_discount` cheama la final `Thisform.do_calculeaza_totaluri()` (`:1974`,
`frm_articol_factura`, `:2068-2179`) care delega la **`calculeaza_totaluri(poArticol)`**
(`oproceduri_facturare.prg:2258-2381`, functie globala) — aceasta scrie in `poArticol`:
`valdiminuatftva`, `valdiminuattva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuattva`,
`vvaldiminuatctva`, plus `valdiscountftva/ctva`, `vvaldiscountftva/ctva`, `valftva/ctva/tva` etc.
4. La inchiderea dialogului, `do_adauga_articol` (`ofacturare.vc2:12813-13167`) scrie **tot obiectul
deja calculat** in `crsfactura`, in doua pasi:
- `Gather Name poArticol Fields Like ... valdiminuatftva, valdiminuattva, valdiminuatctva,
vvaldiminuatftva, vvaldiminuattva, vvaldiminuatctva ...` (`:12945-12950`) — campurile agregate,
**deja calculate de `calculeaza_totaluri`**, nu recalculate aici.
- `Replace ... discountftva With poArticol.discount_unitar, discountctva With
poArticol.discount_unitar_ctva, vdiscountftva With Nvl(poArticol.discount_unitar_val,0),
vdiscountctva With Nvl(poArticol.discount_unitar_ctva_val,0)` (`:12952-12957`) — campurile pe
unitate, separat, pentru ca nu sunt in lista `Gather` (doar variantele `val*` agregate sunt).
5. `do_modifica` (`:13746-13914`, editarea unei linii existente) urmeaza acelasi tipar —
`Gather`/`Replace` cu aceleasi campuri (`:13837-13881`), dupa ce redeschide dialogul pe rand.
6. **Cale suplimentara, deja existenta, cu bug documentat**: pe factura in valuta, coloana
`cVdiscountftva` (`ControlSource="vdiscountftva"`, `ofacturare.vc2:12340-12345`) e editabila
direct in grid, **fara niciun handler** — scrie `vdiscountftva` direct in `crsfactura` prin
binding-ul standard de grid, dar **nu recalculeaza** `vvaldiminuatftva`/`vvaldiminuatctva`
(confirmat cautat explicit, `discount_verificare2.md` punctul 3).
### 1.3 De la `crsfactura` la Oracle (`VANZARI_DETALII.DISCOUNT_UNITAR`)
`do_scrie_articole` (`ofacturare.vc2:13916-...`), la finalizarea facturii, trimite spre
`pack_facturare.adauga_articol_factura` un singur parametru de discount, ales **direct din campurile
pe unitate** ale randului curent din `crsfactura` (`:14081-14083`):
```
14081 IIF(poArt.cu_tva = 0,;
14082 Iif(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountftva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountftva,18,gnPVal))), ;
14083 IIF(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountctva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountctva,18,gnPVal))))
```
**Nu foloseste `valdiminuatftva`.** Deci Oracle primeste intotdeauna valoarea curenta din
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` — cele patru campuri pe care coloanele
noi de grid le-ar edita direct. `VANZARI_DETALII.DISCOUNT_UNITAR NUMBER(22,6)` e singura coloana
Oracle de discount (confirmat `discount_verificare2.md` punctul 4) — nu exista coloana de procent pe
Oracle.
### 1.4 Inapoi, pentru tiparire/eFactura — `crsfacttemp`
`prelucreaza_factura` (`ofacturare_comun.prg:1055-1059`, apelata din `ofacturare.prg:1887`) **NU
reincarca din Oracle** — primeste ca parametru **acelasi `crsfactura`** deja in memorie (cel scris la
pasii 1.2/1.3), si construieste cursorul de tiparire prin agregare SQL locala:
```
1182 Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,;
1186 Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,;
```
(`ofacturare_comun.prg:1182-1186`, cazul comun `discount_evidentiat=0`). eFactura foloseste **acelasi
cursor de iesire** (`xmlefactura.prg:231`, `LineExtensionAmount = valftva`, `PriceAmount = pretftva`,
`xmlefactura.prg:935-937`, `:1043-1046`).
**Consecinta directa**: `pretftva-discountftva` de la 1182 citeste `discountftva` (mereu proaspat,
pentru ca e campul editat direct), dar `Sum(valdiminuatftva)` de la 1186 citeste campul **agregat**,
care ramane vechi daca nu a fost recalculat explicit dupa editare. Rezultat: **randul tiparit poate
avea `pretftva` corect (net, recalculat corect din `discountftva`) dar `valftva` (valoarea liniei)
gresit** — o discrepanta pret x cantitate ≠ valoare, vizibila chiar pe hartie, nu doar o valoare
veche uniforma. Asta e mai grav decat "arata vechi" — arata **inconsistent**.
---
## 2. Contractul `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`)
**Semnatura**: `Lparameters tnValoare, tnTip` — `tnTip`: `1` = s-a modificat procentul, `2` =
discount in lei (fara TVA daca `preturi_cu_tva=0`, cu TVA altfel), `3` = discount in valuta.
**Ramura principala** — `If poArticol.preturi_cu_tva = 0` (pretul de referinta e fara TVA, cazul
uzual): sursa de adevar e `discount_unitar`(_val).
- `tnTip=1` (procent -> valoare): daca `tip_valuta=0`,
`discount_unitar = Round(pretftva * procent/100, gnPPretV)`, apoi **procentul se re-normalizeaza**
din valoarea rotunjita (`:1888-1889`) — nu se pastreaza procentul brut tastat, ci cel rezultat din
rotunjire. Daca `tip_valuta=1`: `discount_unitar_val` se calculeaza intai (`gnPVal`), apoi
`discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`.
- `tnTip=2` (lei -> procent): `discount_unitar = tnValoare` direct, procentul se deriva
(`Round(discount_unitar/pretftva*100, 2)`).
- `tnTip=3` (valuta -> procent + lei): `discount_unitar_val = tnValoare`, procentul deriva din
valuta, apoi `discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`.
- **Dupa `Do Case`, neconditionat**: `discount_unitar_ctva = discount_unitar + Round(discount_unitar
* (proc_tvav-1), gnPPretV)` (`:1913-1914`) — varianta cu TVA e **mereu derivata** din cea fara TVA
in aceasta ramura, niciodata sursa.
- Daca `tip_valuta=1`, simetric: `discount_unitar_ctva_val` derivat din `discount_unitar_val`.
**Ramura alternativa** — `Else` (`preturi_cu_tva=1`, pretul de referinta e cu TVA): rolurile se
inverseaza complet — `discount_unitar_ctva`(_val) e sursa (calculata direct din `tnValoare`/`pretctva`
in cele 3 cazuri, simetric cu ramura de mai sus), iar `discount_unitar`(_val) **fara TVA** e derivat
la final (`:1958`, `Round(discount_unitar_ctva / proc_tvav, gnPPretV)`).
**Rotunjiri**: `gnPPretV` pentru valorile unitare in lei, `gnPVal` pentru cele in valuta, `2` fix
pentru procent — niciodata `gnPc` (precizia de linie/document) in aceasta metoda.
**La final, neconditionat** (`:1972-1974`): `Thisform.clb_tva_discount.Refresh()`,
`Thisform.clb_pret_diminuat.Refresh()`, **`Thisform.do_calculeaza_totaluri()`** — deci orice apel
recalculeaza si campurile agregate de linie, nu doar cele pe unitate.
**In valuta, ce ramane needitat de aceasta metoda**: campurile agregate (`valdiminuatftva` etc.) NU
sunt scrise aici — `do_calculeaza_totaluri` (sectiunea urmatoare) le calculeaza separat din
`discount_unitar`/`cantitate`.
---
## 3. Starea de azi in grid — tabel
| Coloana | `ControlSource` | Formular | `ReadOnly` | Editabil azi | Recalculeaza la editare |
|---|---|---|---|---|---|
| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole` (productie), `:12311-12317` | `.T.` explicit | Nu | — |
| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole`, `:12340-12345` | nesetat (implicit `.F.`) | **Da, pe factura in valuta** | **Nu** — niciun `Valid`/`LostFocus`/`InteractiveChange` propriu, cautat explicit in `10968-15739` |
| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole2` (prototip, neinstantiat in productie) | `.F.` explicit, `:16657` | Da (prototip) | Nesigur — prototip, nu s-a cautat handler dedicat |
| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole2` | `.F.` explicit, `:16688` | Da (prototip) | idem |
| `Column14`/`procdisc` | `procdisc` | `frm_facturare_articole2` numai, `:16721` | nesetat | scaffold, fara scriere | **Nu are corespondent Oracle, nu are `Gather`/`Replace` nicaieri in fisier** (`discount_verificare2.md` sectiunea 5) |
**Excludere pe valuta** (`ofacturare.vc2:15269-15278`, in `frm_facturare_articole.Init`): cand
`poDate.in_valuta = 0` se elimina `cVpretFtva`, `cVdiscountftva`, `cVvaldiminuatftva`; cand
`in_valuta <> 0` se elimina `cPretFtva`, `cDiscountctva` (si simetricele lor). Deci azi, pe orice
factura, **doar una din cele doua coloane de discount e vizibila** — cea in lei, needitabila, sau cea
in valuta, editabila-dar-fara-recalcul. Nu exista azi nicio coloana de **procent** de discount in
`grd_factura` in productie (doar `discountctva`/`vdiscountftva`, valori, nu procent) — pentru procent,
azi operatorul trebuie sa deschida dialogul.
---
## 4. Proiectarea
### 4.1 Ce coloane se adauga/deschid
Nu se sterge nimic din `crsfactura` (modelul de date ramane neschimbat, conform cerintei). Se
lucreaza cu campurile deja existente:
- **Coloana procent discount** (noua in grid, in ambele monede) — `ControlSource` pe un camp
calculat, nu direct pe un camp Oracle (nu exista coloana Oracle de procent) — vezi 4.4 pentru
optiunea recomandata.
- **`cDiscountCTva`** (`discountctva`, lei): `Column5.ReadOnly` trece din `.T.` in `.F.` — devine
editabila, simetric cu ce azi doar prototipul `frm_facturare_articole2` face.
- **`cVdiscountftva`** (`vdiscountftva`, valuta): ramane editabila ca azi, dar castiga handler-ul
care azi lipseste.
Se pastreaza `RemoveObject` pe valuta (`:15269-15278`) neschimbat — excluderea reciproca deja
implementeaza cerinta "se pastreaza excluderea pe `in_valuta`".
### 4.2 Pe ce eveniment se cableaza calculul reciproc
**Recomandare: `Text1.LostFocus`, dupa tiparul deja folosit in acest fisier pentru coloane
editabile de grid** — `frm_avizare_lucrare.grd_articole.cCantitate.Text1.LostFocus` si
`.cPret.Text1.LostFocus` (`ofacturare.vc2:6549-6562`), ambele in aceeasi clasa de baza de grid
(`_grdrow`, `_grd_base.vc2:445`) folosita si de `grd_factura`. Motivele, nu teoretice ci din cod:
- **`InteractiveChange` fireste pe fiecare tasta** — ar recalcula la fiecare caracter tastat intr-un
numar cu zecimale (comportament vazut la coloana `procent` in dialog, `:2646`, dar acolo controlul
e un camp simplu de dialog, nu o celula de grid cu re-randare de coloane vecine la fiecare tasta —
in grid ar fi vizibil costisitor si ar zgaltai focusul).
`Valid`-urile din dialog (`:2602-2624`) folosesc explicit un guard `<> nSumaNatOld`/`nSumaValOld`
ca sa nu recalculeze cand valoarea nu s-a schimbat efectiv — semnaleaza ca autorii au evitat
deliberat recalculul pe fiecare tasta chiar si la `Valid`.
- **`LostFocus` e tiparul deja validat pentru grid-uri de articole in acest fisier**, pe doua coloane
numerice diferite (cantitate, pret), amandoua cu acelasi tip de nevoie (schimbarea unei valori
declanseaza recalculul liniei si al totalului documentului).
- Grid-ul VFP nu are `Valid` pe coloana insasi in mod uzual folosit aici — handlerele existente sunt
pe `<Coloana>.Text1.LostFocus`, deci noile handlere trebuie sa fie
`grd_factura.cDiscountCTva.Text1.LostFocus` si `grd_factura.cVdiscountftva.Text1.LostFocus`
(plus coloana noua de procent, daca implementata ca `Text1` editabil).
### 4.3 Rutina de recalcul — reutilizare, nu reimplementare
`calculeaza_totaluri()` (`oproceduri_facturare.prg:2258-2381`) **e deja generica**: primeste orice
obiect (`toArticol`) cu proprietatile `preturi_cu_tva`/`discount_unitar`/`pretftva`/`cantitate`/...
(le adauga singura, prin `AddProperty`, daca lipsesc — `:2264-2272`, mapand numele de camp
`crsfactura`-stil, `discountftva`/`vdiscountftva`, pe numele `poArticol`-stil,
`discount_unitar`/`discount_unitar_val`) si scrie inapoi campurile agregate. **Poate rula direct pe
un `Scatter Name` al randului curent din `crsfactura`**, fara sa deschida dialogul:
```
Select crsfactura
Scatter Name loArt Memo
loArt = calculeaza_totaluri(loArt)
Gather Name loArt Memo
```
Aceasta acopera pasul "recalculeaza campurile agregate din discountul unitar deja stabilit"
(`valdiminuatftva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuatctva`,
`valdiscountftva/ctva`, `vvaldiscountftva/ctva`), **exact campurile care azi raman vechi**
(sectiunea 1.4).
**Ce `calculeaza_totaluri()` NU face**: conversia reciproca procent<->valoare — aceea e logica din
`do_calculeaza_discount` (sectiunea 2), care azi scrie in `poArticol` si actualizeaza controale de
dialog (`Thisform.clb_*.Refresh()`) care nu exista in grid. **Recomandare**: se extrage o functie noua,
fara referinte la `Thisform.clb_*` (partea de calcul pur, liniile `:1878-1970` minus liniile de
`Refresh`), reutilizabila atat din dialog (daca dialogul de articol individual mai exista undeva —
nu la S4c, dialogul dispare) cat si din handler-ul de grid — parametrizata pe rand
(`toArticol`/`Scatter Name`) in loc de `poArticol` global. Aceasta functie noua intra la fel ca
`calculeaza_totaluri()`, in `oproceduri_facturare.prg`, ca sa fie apelabila din handler-ul de coloana
fara sa depinda de `poArticol`-ul dialogului disparut.
### 4.4 Coloana de procent — implementare recomandata
Nu exista coloana Oracle de procent (sectiunea 1.3) si nici coloana persistenta pe `crsfactura`
pentru asta (spre deosebire de `discountftva` etc, care sunt in `creeaza_facturacrs`). Doua optiuni:
1. **Adauga camp calculat, needitabil, alaturi de coloana editabila de valoare** — afiseaza procentul
derivat (`Round(discountftva/pretftva*100,2)`), needitabil direct — evita sa se mai adauge o
coloana Oracle noua si o cale de editare in plus (mai putin cod nou, mai putina suprafata de bug).
2. **Adauga coloana editabila de procent, needitabila-pe-model** — ca in `frm_articol_factura`
(`Clb_procent_discount`), scrie tot in `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`
prin acelasi calcul reciproc, dar camp de UI, nu de Oracle. Mai aproape de comportamentul de azi
din dialog (operatorul poate tasta fie procentul, fie valoarea), dar cere si al treilea handler de
`LostFocus` si inca un camp de lucru in cursorul local (`AddProperty` la `do_initializeaza_articol`,
dupa tiparul `id_jtva_coloana`/`valdiminuatftva`, `:13666-13681`).
Cerinta din plan ("cele doua campuri de discount ale lui — procent si valoare unitara — devin coloane
in grid") cere explicit **ambele campuri editabile** — deci optiunea 2 e cea care respecta litera
cerintei; optiunea 1 e o simplificare de discutat cu Marius (vezi sectiunea 10).
### 4.5 Unde se cheama exact recalculul, pas cu pas (handler propus)
Pentru coloana `cDiscountCTva` (`discountctva`, lei, `tip=2` in nomenclatura `do_calculeaza_discount`):
```
PROCEDURE grd_factura.cDiscountCTva.Text1.LostFocus
Select crsfactura
Scatter Name loArt Memo
* recalcul reciproc procent<->valoare, tip=2 -- functia noua din 4.3, nu do_calculeaza_discount
loArt = recalc_discount_linie(loArt, This.Value, 2) && scrie discountftva/discountctva(_val)
loArt = calculeaza_totaluri(loArt) && scrie valdiminuat*/vvaldiminuat*
Gather Name loArt Memo
Thisform.do_calculeaza_totaluri() && resumeaza totalurile documentului
ENDPROC
```
Simetric pentru `cVdiscountftva` (`tip=3`) si pentru coloana de procent, daca implementata editabil
(`tip=1`). Guard-ul `<> valoare veche` (ca la `Valid`-urile din dialog, `:2602-2624`) se pastreaza ca
sa nu se recalculeze la simplu tab-through fara modificare.
---
## 5. Interactiunea cu discountul din politica de pret
**Azi**: `do_initializeaza_articol` (`ofacturare.vc2:13618` si urm.) preia
`toArticol.discount_unitar = Nvl(toArticol.discount_unitar, 0)` (`:13630`) **din `crsarticole`**
(cursorul de stoc/oferta, populat inainte de deschiderea dialogului — sursa SQL exacta, in afara
`ofacturare.vc2`, ramasa necercetata si in raportul precedent). Daca politica de pret a populat deja
un discount, acela apare **preincarcat** in campurile dialogului (`Clb_discount_unitar` etc.),
operatorul il poate suprascrie tastand — suprascrierea intra prin acelasi `do_calculeaza_discount`
ca orice alta tastare, fara distinctie intre "valoare din politica" si "valoare tastata manual".
**Dupa S4c**: nu se schimba nimic in mecanismul de preincarcare — `do_adauga_articol` inca scrie
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` in `crsfactura` la adaugarea liniei
(sectiunea 1.2, pasul 4), inainte ca operatorul sa apuce sa editeze coloana de grid. Valoarea din
politica **ramane vizibila in celula**, exact ca azi in dialog, iar editarea manuala in grid o
suprascrie la fel — singura diferenta e ca suprascrierea se intampla acum pe `LostFocus` de celula,
nu pe `Valid` de camp de dialog. **Nu exista azi o distinctie de tip "flag discount din politica vs.
discount manual"** in `crsfactura` (nu am gasit un camp `discount_din_politica` sau similar) — deci
S4c nu pierde nicio informatie care exista deja, dar nici nu castiga vreo trasabilitate noua.
---
## 6. Refacerea totalurilor documentului
Totalurile documentului (`Thisform.nbazaron`, `ntotalron`, `ndiscron`, variantele `*val`) se
recalculeaza in `frm_facturare_articole.do_calculeaza_totaluri` (`ofacturare.vc2:13423-13520`) —
**re-sumeaza direct din `crsfactura`** (si `crsfacturaset` daca exista), citind exact campurile
agregate din sectiunea 1.1/1.4:
```
13456 Select Sum(Nvl(valdiminuatctva,0)) As Total,Sum(Nvl(valdiminuatftva,0)) As Baza,;
13457 Sum(Nvl(valdiminuattva,0)) As Tva,;
13458 Sum(Nvl(valdiscountftva,0)) As discount From crsfactura Into Cursor crstotalurifact
```
Simetric pentru valuta (`vvaldiminuat*`) mai jos in aceeasi procedura. **Consecinta directa pentru
proiectare**: daca handler-ul de coloana (sectiunea 4.5) actualizeaza corect `valdiminuatftva`/
`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva`/`valdiscountftva`/`vvaldiscountftva` pe randul
editat **inainte** de a chema `Thisform.do_calculeaza_totaluri()`, totalurile documentului se refac
automat, corect, din acelasi mecanism care azi refece totalurile la adaugare/stergere de linie
(`do_adauga_articol:13160`, `do_sterge:14676`) — **nu trebuie cod nou pentru pasul de resumare**, doar
apelul, la finalul handler-ului de coloana. `Thisform.do_calculeaza_totaluri` e deja legat prin
`Bindevent` de `actualizeaza_total_mod` (`:15265`) — orice apel al lui declanseaza si actualizarea de
ecran a etichetelor de total, fara cablaj suplimentar.
---
## 7. Pasi de implementare, ordonati, cu criteriu de "gata"
1. **Extrage functia de calcul reciproc** din `frm_articol_factura.do_calculeaza_discount`
(`:1874-1976`), fara liniile de `Thisform.clb_*`/`Thisform.nprocent`, ca functie noua in
`oproceduri_facturare.prg`, parametrizata pe obiect + valoare + tip (semnatura similara cu
`calculeaza_totaluri(toArticol)`).
*Gata cand*: pentru fiecare din cele 3 `tnTip` si ambele ramuri `preturi_cu_tva`, functia noua
produce exact aceleasi `discount_unitar`/`discount_unitar_ctva`/`_val` ca metoda originala, testat
pe acelasi set de intrari (procent, lei, valuta) — comparatie directa, nu doar citire de cod.
2. **Deschide `Column5`/`cDiscountCTva` la editare** (`ReadOnly = .F.`,
`ofacturare.vc2:12311-12317`), pastrand `RemoveObject` pe valuta neschimbat.
*Gata cand*: pe factura in lei, celula de discount unitar cu TVA e editabila din grid (nu doar
afisata).
3. **Adauga handler `Text1.LostFocus` pe `cDiscountCTva` si pe `cVdiscountftva`**, dupa modelul din
sectiunea 4.5: recalcul reciproc (pasul 1) + `calculeaza_totaluri()` (deja existent,
`oproceduri_facturare.prg:2258`) + `Gather` + `Thisform.do_calculeaza_totaluri()`.
*Gata cand*: editarea oricareia din cele doua coloane schimba, in acelasi moment, si
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` **si**
`valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` pe randul curent,
verificat prin `Browse`/inspectie cursor, nu doar pe ecran.
4. **Adauga coloana de procent** (decizie 4.4 de confirmat cu Marius — recomandare: optiunea 2,
editabila, ca sa respecte litera cerintei din plan), cu acelasi handler, `tip=1`.
*Gata cand*: tastarea unui procent produce aceeasi valoare de discount ca tastarea valorii
echivalente, pe ambele monede.
5. **Verifica totalurile documentului** dupa editare in grid — nu ar trebui sa fie nevoie de cod nou
(sectiunea 6), doar de apelul din pasul 3.
*Gata cand*: dupa editarea discountului pe o linie, `Thisform.nbazaron`/`ntotalron` (si variantele
`val`) se schimba imediat, fara sa fie nevoie de o alta actiune (adaugare/stergere de linie) care
sa le forteze.
6. **Verifica scrierea in Oracle** (`do_scrie_articole`, `:14081-14083`) — nu ar trebui sa fie nevoie
de nicio schimbare, pentru ca citeste deja campurile pe unitate direct (sectiunea 1.3).
*Gata cand*: `VANZARI_DETALII.DISCOUNT_UNITAR` dupa salvare = valoarea tastata in grid, pe o
factura testata cu discount editat exclusiv din grid (fara sa fi trecut prin dialog, care oricum
dispare la unificare).
7. **Verifica tiparirea si eFactura** — cea mai importanta proba, cea ceruta explicit de plan.
*Gata cand*: vezi sectiunea 8.
---
## 8. Cum se verifica — concret
**Pasii**, pe o factura de test (lei si separat valuta):
1. Adauga o linie in grid (fara discount).
2. Editeaza direct in grid coloana de discount (valoare, apoi pe alt rand procent) — verifica pe
ecran ca celelalte coloane afisate (`cPretFtva`/`cVpretftva`, `cValdiminuatctva`/`cVvaldiminuatftva`
daca ramase vizibile) se actualizeaza imediat.
3. **Interogheaza direct cursorul** (nu doar ecranul) — din Command Window/breakpoint, pe randul
editat: `? discountftva, discountctva, vdiscountftva, vdiscountctva, valdiminuatftva,
valdiminuatctva, vvaldiminuatftva, vvaldiminuatctva` — toate opt trebuie sa reflecte editarea, nu
doar cele patru "brute".
4. **Finalizeaza factura** (`do_scrie_articole`) si interogheaza Oracle (schema `MARIUSM_AUTO`,
conventia din `COMUN\docs\scripturi-migrare-db.md:36-40`, prin `goExecutor`, doar `SELECT`):
```sql
SELECT discount_unitar FROM vanzari_detalii WHERE id_vanzare = :id_factura AND id_articol = :id_articol;
```
trebuie sa fie egal cu valoarea tastata in grid.
5. **Retipareste factura** (nu doar priveste pe ecranul de compunere — deschide efectiv raportul,
`factura.fr2` sau echivalentul in lei/valuta) si verifica pe pagina tiparita ca `pretftva`/`valftva`
pe linia editata reflecta discountul nou (pretul unitar net si valoarea liniei trebuie sa fie
consistente intre ele: `valftva = pretftva * cantitate`, altfel exact bug-ul din sectiunea 1.4).
6. **Genereaza XML-ul de eFactura** pentru aceeasi factura si verifica in fisier
`cbc:LineExtensionAmount`/`cbc:PriceAmount` pe linia editata — trebuie sa corespunda cu pretul net
nou, nu cu cel dinaintea editarii.
7. Repeta 1-6 pe **factura in valuta**, ca sa acoperi ramura `vdiscountftva`/`vvaldiminuatftva`
(ramura azi cu bug-ul confirmat).
---
## 9. Ce nu se poate testa headless
- **Editarea propriu-zisa a celulei de grid** (tastare in `Text1` al unei coloane, tab/enter pentru
`LostFocus`) — headless-ul VFP (`-A -T`) nu materializeaza interactiunea de tastatura intr-un grid;
cf. `docs\cercetare\...grid-coloane-nu-se-materializeaza-headless` (memorie de proiect) — coloanele
de grid sunt artefacte needitabile sub `-A -T`; verificarea reala a comportamentului UI cere
harness-ul cu UI vizibil sau un test manual asistat.
- **Tiparirea efectiva a raportului** (`.frx`) — generarea unui PDF/preview real, nu doar constructia
cursorului `crsfacttemp` in memorie, cere motorul de raportare VFP, care nu ruleaza util headless
pentru verificare vizuala (se poate verifica campurile cursorului sursa, dar nu pagina tiparita).
- **Trimiterea efectiva catre ANAF** (SPV) — se poate genera si inspecta XML-ul local, dar validarea
reala (acceptare/respingere) cere mediul de test ANAF, in afara acestei sarcini.
- **Comportamentul focus/tab-order al noilor coloane** in grid (ordinea de tab intre celule, daca
`LostFocus` se declanseaza corect la navigare cu tastatura vs. mouse) — cere sesiune interactiva.
---
## 10. Riscuri si ce ramane de decis de Marius
- **Coloana de procent — editabila sau doar afisata** (sectiunea 4.4). Recomandare: editabila
(optiunea 2), pentru ca respecta litera deciziei din plan ("cele doua campuri... devin coloane"), dar
costa un handler si un camp de lucru in plus. Daca simplitatea conteaza mai mult decat paritatea cu
dialogul vechi, optiunea 1 (doar afisaj) reduce suprafata de cod fara sa piarda functionalitate reala
(operatorul tot poate obtine orice discount tastand valoarea).
- **Riscul de rotunjire in lant**: `do_calculeaza_discount` are cel putin 3 rotunjiri succesive pe
drumul procent->valoare->procent (sectiunea 2) — comportament deja existent, nu introdus de S4c, dar
mutarea in grid (editare mai frecventa, rand cu rand, fara sa mai treaca prin "confirmare" de
dialog) ar putea face vizibile discrepante mici de rotunjire care azi treceau neobservate. Nu e un
motiv sa se schimbe rotunjirile (ar strica alte fluxuri), doar un risc de UX de semnalat.
**Zero cazuri gasite in cod care sa demonstreze deja o problema** — semnalat preventiv, nu confirmat.
- **`Gather`/`Scatter` pe rand cu campuri MEMO**: `do_adauga_articol` foloseste `Gather ... MEMO`
(`:12945-12950`) — handler-ul nou de coloana (sectiunea 4.5) trebuie sa faca la fel
(`Scatter Name ... Memo` / `Gather Name ... Memo`), altfel campul `explicatie` (M) s-ar putea goli
la fiecare editare de discount — **de verificat explicit la implementare**, nu doar presupus.
Recomand un test dedicat: editeaza discountul pe o linie cu `explicatie` populata, verifica ca
`explicatie` ramane neschimbata dupa `LostFocus`.
- **Coloana `procdisc` din prototip** (`frm_facturare_articole2`, `:16721`) — scaffold mort, fara
scriere (sectiunea 3). Recomandare: nu se reutilizeaza ca atare pentru coloana noua de procent —
se porneste curat, cu functia noua din sectiunea 4.3, nu cu acest camp neconectat.
- **Formularul `frm_facturare_articole2`** ramane prototip separat, ne-instantiat in productie — S4c
nu are nevoie sa il atinga, dar daca exista intentia sa devina formularul unificat, coloanele lui
de discount (deja editabile, `:16657`/`:16688`) ar trebui auditate separat pentru acelasi bug de
recalcul (nu verificat aici — in afara perimetrului cerut).
---
## 11. Punct deschis din S1 — `_checkbox1` vs `chkDetaliat`
Nu apartine povestii S4c (nu am gasit nicio legatura cu discountul pe linie sau `grd_factura`), dar
am dat peste ambele controale pe cale laterala, in `frm_alte_date` (`ferestre_cere_date.vc2`), asa ca
inchid ieftin: **sunt doua controale distincte, nu o duplicare de nume**.
- `_checkbox1` e un control generic (mostenit din clasa de baza), folosit in `frm_alte_date.Init`
(`ferestre_cere_date.vc2:3188-3202`) doar ca **reper de layout** — pozitia lui determina inaltimea
ferestrei si offset-ul altor controale, fara logica de business proprie vizibila in acest formular.
- `chkDetaliat` e un checkbox cu nume propriu, legat de fluxul de incasare (`actualizeaza_tipincasare`,
`:2728-2853`) — vizibil doar cand `opt_incasat` indica un anumit tip de incasare (POS/detaliat),
ascuns/zero altfel; pe ramura `Else` a lui `Init` (`:3187-3205`, cand alt tip de context nu implica
incasare deloc) e **eliminat explicit** din formular (`Thisform.RemoveObject('chkDetaliat')`,
`:3201`), spre deosebire de `_checkbox1`, care ramane (e folosit chiar pe linia urmatoare pentru
calculul inaltimii finale, `:3202`).
**Concluzie**: nu e o inconsecventa de cod, sunt doua controale cu roluri diferite pe acelasi
formular — punctul se poate inchide ca "nu e bug", cu rezerva ca n-am cercetat *de ce* `chkDetaliat`
exista ca si `checkbox` separat de `opt_incasat` (ce reprezinta exact "detaliat") — nu era in
perimetrul S4c si nu am aprofundat.
---
## Necunoscute ramase (mostenite din rapoartele-sursa, nu re-investigate aici)
- Interogarea SQL exacta care populeaza `crsarticole` cu discountul din politica de pret
(`CRM_POLITICI_PRET_ART`) — cod in afara `ofacturare.vc2`, netrasat (mostenit din
`discount_verificare2.md`, sectiunea "Necunoscute ramase").
- Valorile implicite ale flag-urilor `gnEFACTURA_XML_DISC_PLISTA_LINIE`/`_ART` (mostenit din
`discount_in_rapoarte_si_efactura.md`).
- Comportamentul `crsfacturafinalaval` (varianta valuta a cursorului final de tiparire) — presupus
simetric cu varianta lei, nu reverificat linie cu linie separat pentru S4c.