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

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

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

34 KiB

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):
    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.