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