Files
roafacturare/docs/cercetare/s4d_zi_curs_reactiv.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

28 KiB

Proiectare S4d — Data cursului valutar, numai cand are sens (reactiv)

Poveste: docs\plan_13_unificare_formular_facturare.md, #### S4d (decizia 15, proiectata in sectiunea M). Cercetare preliminara deja facuta si citata ca sursa de adevar: docs\cercetare\zi_curs_validare.md, docs\cercetare\valuta_si_curs.md, docs\cercetare\rec_dec42_proiectare.md/rec_d42_efactura.md (decizia 42), docs\cercetare\s3_portare_antet.md (bug #16), docs\cercetare\s4_cautare_articole_server.md/ s4_puncte_deschise.md (decizia 42, mecanismul ei exact). Read-only: nicio editare de cod, niciun git_sync.ps1/txt2vcx.ps1, nicio scriere pe Oracle.


Verdict (rezumat)

Regula reactiva se poate implementa fara sa strice nimic din ce e deja demonstrat sigur: implicitul poDate.zi_curs ramane neconditionat (nu se schimba), iar cheia reactivitatii e un camp deja prezent in cursorul de grid — crsfactura.tip_valuta — verificabil cu exact acelasi tipar SQL folosit deja in cod (ofacturare.prg:1656, :1730: Select Distinct ... From crsfactura Where tip_valuta = 1). Formularul-prototip frm_facturare_articole2 are deja, azi, un Clb_zi_curs propriu, editabil, legat la poDate.zi_curs (ofacturare.vc2:16285-16305) — nu trebuie inventat un control nou, ci reevaluata vizibilitatea celui care exista deja. Mesajul Oracle -20005 contine deja data si numele valutei lipsa (STRINGAGG peste toate valutele fara curs) — decizia 42 nu schimba continutul mesajului, ii schimba domeniul: il restrange de la "toate valutele din listele de preturi ale utilizatorului" la "valuta articolului cautat", dar doar pe varianta filtrata a cautarii (S4 punctul 1); pe caile cu document sursa (comanda/aviz/contract) incarcarea in masa ramane neconditionata. Plan sectiunea M (:2635-2638) cere explicit ca la aceasta eroare campul sa revina vizibil — asta intra ca pas de implementare, nu ca optiune. Bug-ul #16 nu e in perimetrul S4d — e deja cercetat si legat de S2 (bucla de reincercare din ofacturare.prg), cu verdict separat in s3_portare_antet.md; S4d doar confirma ca noua arhitectura (antet persistent, nu recreat) ii inlatura mecanismul, daca S2/S3 nu recreeaza formularul la eroare.


1. Inventarul controalelor de valuta si curs (fisier:linie)

Patru definitii Clb_zi_curs in tot ofacturare.vc2 (control compus clb_tx_data, camp text legat la poDate.zi_curs), confirmate exhaustiv (grep "ADD OBJECT 'Clb_zi_curs'"):

Formular Definitie Eliminat azi? Validare la Termina Sincronizare cu data
frm_date_aviz ofacturare.vc2:6727-6740 Niciodata Nu exista (fara inainte_de_do_termin care sa o ceara) Clb_dataact...LostFocus (:7603-7604), Clb_dataireg...LostFocus (:7610-7611) — Thisform.clb_zi_curs.Refresh(), neconditionat
frm_date_aviz_lucrare :7787-7801 Niciodata Neconditionata: :8076 Case Empty(Nvl(poDate.zi_curs,{})) -> :8078 This.clb_zi_curs.SetFocus(), plReturn=.F. :8186-8187, :8193-8194, neconditionat
frm_date_factura :8701-8714 Doar tip 8/9 (retur), Init :9717-9722 (RemoveObject) Conditionata: :9484 Case poDate.in_valuta=1 And Empty(...) And Type('thisform.clb_zi_curs.visible')<>'U' -> :9488 SetFocus :9805-9808, :9824-9827, ambele cu garda Type(...)<>'U'
frm_facturare_articole2 (prototip) :16285-16305, camp text legat la poDate.zi_curs (TEXT_SIMPLU1.ControlSource) Niciodata — Init (:18988-19080) nu contine niciun RemoveObject('clb_zi_curs') Nu exista (formularul de articole nu are inainte_de_do_termin propriu de tip antet) Clb_dataact.TEXT_SIMPLU1.LostFocus (:19219-19222) — acelasi tipar cu garda Type(...)<>'U'

Alte controale de valuta/curs, relevante ca sa nu fie confundate cu clb_zi_curs:

  • Ct_clb_valuta (selector de valuta document) — eliminat pe frm_date_factura cand poDate.in_valuta=0 (:9725-9728), neconditionat de tip. Confirma ca "ascunde cand nu are sens" e deja un tipar folosit in aceeasi metoda, la doar 3 linii distanta de blocul retur — dovada directa ca autorul stia sa faca exact acest lucru, doar nu l-a aplicat si campului de curs.
  • lb_cursuri + grd_cursuri (eticheta si grid read-only "Curs valutar (data)") — frm_facturare_articole.Init (:15092-15100) si frm_facturare_articole2.Init (:19000-19012):
    ofacturare.vc2:19000-19012
    If !Used('crscursuri') Or Reccount('crscursuri') = 0
        Thisform.RemoveObject('lb_cursuri')
        Thisform.RemoveObject('grd_cursuri')
    Else
        If !Empty(Nvl(poDate.zi_curs, {}))
            Thisform.lb_cursuri.Caption = "Curs valutar " + Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")
        ...
    
    Acesta e cel mai apropiat precedent de "arata/ascunde in functie de continut" din codul existent — dar evalueaza crscursuri (cursurile incarcate pentru toate valutele din listele de preturi ale utilizatorului), o singura data, la Init, inainte ca userul sa fi adaugat vreo linie pe grid. Nu e reactiv la adaugare/stergere de articole — e evaluat o singura data pe un cursor diferit de crsfactura. Nu se poate copia ca atare pentru S4d; poate fi reutilizat doar ca idiom (verificare Reccount(...) > 0 care decide RemoveObject/reafisare), mutat pe alt cursor si alt eveniment (punctul 2).
  • poArticol.tip_valuta — proprietate a politicii de pret a articolului (nu a documentului), populata la do_initializeaza_articol/cautare, citita masiv in calculul de pret/discount (ofacturare.vc2:1857-2288, frm_articol_factura). Cand articolul e adaugat pe grid, valoarea ajunge in coloana crsfactura.tip_valuta (vezi punctul 2) — acesta e semnalul pe care se construieste conditia reactiva, nu poArticol (obiect temporar, mort dupa Release).

Cine citeste poDate.zi_curs dupa formular (relevant ca sa nu se rupa nimic la ascundere) — deja documentat exhaustiv in zi_curs_validare.md §4 si valuta_si_curs.md §4: cursoarele de articole (cursor_preturi/cursor_articole_k/cursor_gestiune/cursor_lucrare, ofacturare.prg:266-308), eticheta lb_cursuri, si validarile de mai sus. Nimic din acest tabel se schimba prin S4d.


2. Conditia reactiva, exact

Camp folosit: crsfactura.tip_valuta (N(1)), definit la creearea cursorului de grid, creeaza_facturacrs (COMUN\programe\ofacturare_comun.prg:1776), populat la fiecare linie adaugata prin do_adauga_articol (coloana e in lista de Insert/Scatter, ofacturare.vc2:12950, :17258 pentru frm_facturare_articole2) direct din poArticol.tip_valuta.

Verificare, cu exact acelasi tipar deja folosit in productie (nu inventat):

ofacturare.prg:1656, :1730  [listeaza_ofacturare]
Select Distinct nume_val, Curs, multiplicator From crsfactura Where tip_valuta = 1

Pentru S4d, verificarea de vizibilitate se reduce la:

lVizibil = !Inlist(poDate.tip, 8, 9) And ;
           (poDate.in_valuta = 1 Or (Used('crsfactura') And Reccount('crsfactura', 1) > 0) )

unde Reccount('crsfactura', 1) inseamna "exista macar un rand cu tip_valuta=1" — in practica se scrie ca Calculate Cnt() To lnLinii For tip_valuta=1 sau Select Count(*) From crsfactura Where tip_valuta=1 Into Array laCnt, ca sa nu depinda de pozitia recordului curent din grid.

Cand se reevalueaza — pe evenimentul deja existent de recalcul, nu pe un timer nou. Atat do_adauga_articol (:17124-17393) cat si do_sterge (:18619-18704) se termina cu apelul Thisform.do_calculeaza_totaluri() (:17360, :18687) — acesta e deja punctul unic prin care formularul "stie" ca s-a schimbat compozitia liniilor (totaluri, discount pe articole etc). E locul natural unde se adauga si reevaluarea clb_zi_curs: dupa fiecare adaugare de linie si dupa fiecare stergere, do_calculeaza_totaluri (sau un apel adaugat imediat dupa el in cele doua metode) reface lVizibil de mai sus si seteaza Thisform.clb_zi_curs.Visible = lVizibil.

Modificarea unei linii existente (schimbare cantitate/pret pe o linie deja adaugata) nu schimba tip_valuta — acesta e o proprietate a politicii de pret, fixata la adaugare, nu editabila pe grid. Deci nu exista eveniment suplimentar de "editare linie" care sa afecteze conditia — doar adaugare si stergere pot muta numarul de linii cu tip_valuta=1 de la 0 la >0 sau invers. (Verificat: nu exista do_modifica separat pe frm_facturare_articole2 in indexul de simboluri — editarea unei linii merge prin acelasi do_adauga_articol/dialog, care oricum recheama do_calculeaza_totaluri.)

De ce nu poDate.in_valuta-style (o singura evaluare la Init): documentul poate incepe fara nicio linie in valuta si poate primi una la mijlocul sesiunii de facturare — exact cazul pe care unificarea il face posibil de rezolvat (azi antetul se inchide inainte sa existe crsfactura). Evaluarea trebuie sa fie pe eveniment, nu pe Init.


3. Trecerea inapoi — ultimul articol in valuta e sters

Recomandare: ascunde din nou (simetric), dar NU goli poDate.zi_curs.

Argumente pe tiparul deja folosit, nu pe teorie:

  • lb_cursuri/grd_cursuri (punctul 1) folosesc deja idiomul "vizibil doar cand Reccount(...) > 0" — o conditie booleana simpla, reevaluabila oricand fara efecte laterale, pentru ca RemoveObject/ re-adaugare (sau Visible=) nu ating proprietatea de date din spate (poDate.zi_curs). Simetria (ascunde la 0, arata la >0) e deja tratata ca stare normala pentru acel control, nu ca o exceptie de construit.
  • poDate.zi_curs nu se goleste niciodata in tot codul existent (confirmat exhaustiv in zi_curs_validare.md §5-6) — nici la RemoveObject (tip 8/9), nici la ascunderea lb_cursuri. Deci ascunderea campului de curs cand ultima linie in valuta dispare nu pierde valoarea: daca userul adauga din nou o linie in valuta in aceeasi sesiune, campul reapare cu aceeasi data pe care o avea inainte de ascundere (fie cea introdusa manual, fie implicitul de la Init), nu resetata la azi.
  • Alternativa ("ramane vizibil o data aratat") ar introduce un tip nou de stare per-sesiune (lFostVizibilCandva) fara niciun precedent in cod si fara beneficiu clar — userul care sterge singura linie in valuta de pe o factura in lei nu mai are, de fapt, niciun motiv sa vada/editeze cursul; a lasa campul vizibil ar fi exact inconsistenta pe care decizia 15 vrea sa o elimine.

Exceptie de la simetrie, ceruta de plan (sectiunea M, :2635-2638): cand vine eroarea Oracle -20005 cu campul ascuns, campul trebuie adus inapoi vizibil, indiferent de Reccount-ul curent — vezi punctul 5. Acesta e singurul caz in care regula reactiva simetrica se suspenda explicit.


4. Interactiunea cu in_valuta la nivel de document

poDate.in_valuta e stabilit o singura data, in Init, exclusiv din tnTip/tnIdSet (ofacturare_comun.prg:248-250, confirmat exhaustiv in valuta_si_curs.md §1) — nu exista cale de cod care sa-l schimbe dupa Init, iar controlul de alegere a valutei (Ct_clb_valuta) e chiar eliminat din formular cand in_valuta=0 (ofacturare.vc2:9725-9728), deci operatorul nu are de unde sa-l aleaga manual. Prin urmare intrebarea "ce se intampla daca operatorul schimba valuta documentului dupa ce a adaugat linii" nu se poate pune in arhitectura actuala — nu exista control care sa permita acea schimbare. Documentul e in valuta sau nu inca de la alegerea tipului (tnTip), inainte ca vreo linie sa existe.

Pentru S4d, consecinta e simpla: poDate.in_valuta=1 e o conditie statica pe toata durata sesiunii de facturare — cand e adevarata, clb_zi_curs ramane vizibil necontenit (ca azi), indiferent de ce se intampla pe grid; conditia reactiva descrisa la punctul 2 conteaza doar pentru ramura in_valuta=0. Nu exista tranzitie in_valuta 0->1 sau 1->0 de tratat.


5. Mesajul de eroare -20005

5.1 Unde se ridica azi

pack_facturare.verifica_cursuri_valute (Oracle, D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16247-16274):

16268    IF V_NUME_VALUTE IS NOT NULL THEN
16269      RAISE_APPLICATION_ERROR(-20005,
16270                              'Nu este setat cursul din data de ' ||
16271                              to_char(V_DATA_CURS, 'DD/MM/YYYY') ||
16272                              ' pentru ' || V_NUME_VALUTE || '!');
16273    END IF;

V_NUME_VALUTE e un STRINGAGG (:16252-16266) peste toate valutele din FACT_VPRETURI_UTILIZATOR ale utilizatorului curent care nu au curs valabil la V_DATA_CURS — exclude doar moneda nationala. Mesajul contine deja data si numele valutei/valutelor — nu e un cod generic. Apelata neconditionat din cursor_preturi (:2153), si delegat din cursor_contract (jumatatea crsarticole, s4_cautare_articole_server.md:83-85).

5.2 Traseul pana la operator, azi

ofacturare.prg:313-317  (identic ofacturare.prg:828-832, factureaza2)
If lnSucces < 0
    AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
    If goExecutor.nEroare = 20005
        vizualizeaza_curs(poDate.zi_curs)
    ENDIF

goExecutor.oPrelucrareEroare() (COMUN\programe\oproceduri_comune.prg:626-639) extrage textul brut dintre ORA-20xxx: si urmatorul ORA din mesajul Oracle — pentru erori in intervalul 20000-20999 (cazul de aici), operatorul vede exact textul PL/SQL de mai sus, netrunchiat, nemodificat. Deci raspunsul la "mesajul spune care valuta si ce zi lipsesc" e: da, deja o face, azi, inainte de orice modificare S4d.

5.3 Ce schimba decizia 42

Decizia 42 (plan :512-516) nu schimba textul mesajului — schimba domeniul de valute verificate, si doar pe varianta filtrata a cautarii (S4 punctul 1, cursor_preturi chemat per-articol-cautat, nu la incarcarea in masa). Azi (fara decizia 42), pe orice apel al lui cursor_preturi, V_NUME_VALUTE poate include valute complet nelegate de articolul pe care userul tocmai il cauta — de exemplu userul cauta un articol in RON, dar mesajul ii spune ca lipseste cursul pentru EUR, pentru ca EUR apare undeva in politicile lui de pret. Dupa decizia 42, pe cautarea filtrata, verificarea se restrange la valuta randului adus — deci mesajul (acelasi format text) va numi, natural, doar valuta relevanta pentru cautarea curenta, nu un agregat strain.

Rezerva importanta, de citit impreuna cu S4d: decizia 42 se aplica explicit "pe varianta filtrata a cursoarelor" — adica pe calea noua de cautare per-articol introdusa de S4 punctul 1, folosita azi doar pe ramura de lista de preturi (s4_cautare_articole_server.md:68-69: "S4 se aplica curat doar pe ramurile de lista de preturi"). Pe comanda / aviz / contract, incarcarea in masa a articolelor (bookkeeping obligatoriu, crsarticole citit si scris de do_adauga_tot/do_sterge/do_scrie_factura) ramane neschimbata, deci verifica_cursuri_valute tot ruleaza neconditionat (toate valutele din politicile utilizatorului), la incarcarea initiala a grilei — nu doar pe articolul cautat. Pe aceste tipuri, mesajul poate inca numi o valuta care n-are legatura cu documentul curent, indiferent de decizia 42 — asta ramane o diferenta reala fata de "campul e ascuns pentru ca documentul nu are nevoie de curs", si intra la riscuri (punctul 10).

5.4 Ce se schimba prin S4d — nu textul, ci vizibilitatea campului dupa eroare

Cerinta explicita din plan, sectiunea M (:2635-2638):

Ascunderea datei de curs poate lasa un document fara curs corectabil [...] utilizatorul nu mai are unde sa corecteze data daca i-am ascuns campul. Regula din M trebuie sa aduca inapoi campul in exact acest caz.

Implementare propusa, la punctul de interceptare deja existent:

* ofacturare.prg:313-317 (si simetric la :828-832)
If lnSucces < 0
    AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
    If goExecutor.nEroare = 20005
        If Type('thisform.clb_zi_curs.visible')<>'U' And !thisform.clb_zi_curs.Visible
            thisform.clb_zi_curs.Visible = .T.     && aduce campul inapoi, indiferent de conditia reactiva
        Endif
        vizualizeaza_curs(poDate.zi_curs)
    ENDIF

Nota: thisform aici nu e formularul de antet (deja inchis la acest punct in arhitectura veche) — e motivul pentru care acest fix e legat de rezultatul S3/S2 asupra bug-ului #16 (punctul 6): daca antetul unificat ramane deschis/persistent (nu recreat), thisform.clb_zi_curs exista si poate fi readus vizibil direct; daca arhitectura tot recreaza un formular nou dupa eroare, readucerea vizibila trebuie facuta in Init-ul noii instante, verificand acelasi semnal (goExecutor.nEroare=20005 din iteratia anterioara, sau un flag explicit propagat).

Textul mesajului propus de pastrat neschimbat (deja corect): "Nu este setat cursul din data de DD/MM/YYYY pentru !". Nu se propune inlocuirea lui — se propune doar sa nu mai fie surprinzator faptul ca operatorul nu are unde sa corecteze, prin readucerea campului.


6. Verificarea daca #16 dispare

Ce e #16: COMUN\docs\todos.txt:45 — "la revenire din formularul de curs valutar, focusul revine [...] pe TIP DOCUMENT, si la iesire din serie se regenereaza numar act". Citat si in plan :1926-1927, :2665-2668.

Deja cercetat, cu verdict, in docs\cercetare\s3_portare_antet.md §6 (runda 9), inainte de aceasta sesiune — S4d nu redeschide cercetarea, o confirma si o leaga de domeniul propriu (eroarea -20005, singurul declansator relevant pentru S4d):

  • #16 nu e un bug de focus. E o bucla de reincercare (ofacturare.prg:174-571, Do While lnRaspuns=6) care trateaza orice esec Oracle la incarcarea cursorului de articole ca pe un "DA, utilizatorul vrea alt document" — lnRaspuns nu se reseteaza pe ramura de eroare (ofacturare.prg:555-564, in Else, niciodata atinsa cand lnSucces<0), deci bucla externa reintra automat si recreeaza formularul de antet de la zero (Createobject, :230). Init-ul noii instante seteaza focus neconditionat pe ct_clb_fdoc (:9788-9793) — asta e "focusul care revine pe tip document" — si LostFocus-ul care urmeaza cheama neconditionat clb_serie_act, care aloca un numar nou (serii_numere.vc2:122-127) — asta e "regenerarea numarului".
  • Cauza reala e in ofacturare.prg (bucla de emitere, cod comun suitei), nu in formularul de antet. s3_portare_antet.md:337-350 e explicit: portarea antetului (S3) NU rezolva automat #16 — arhitectura unificata (un singur Init persistent, antetul nu mai e un obiect separat distrus/recreat de apelant) are sansa reala sa-l elimine ca efect secundar, dar numai daca implementarea nu recreaza formularul intreg la reincercare dupa o eroare Oracle — ceea ce reproduce bugul identic, doar mutat. Verdictul e legat explicit de S2, nu de S3/S4d: bucla traieste in procedura de emitere, nu in formular.

Raspunsul specific S4d, cerut de plan ("Verifica in acelasi timp daca #16 dispare — vezi M"): da, S4d confirma exact scenariul care declanseaza #16 — eroarea -20005 de la verifica_cursuri_valute e unul dintre cazurile lnSucces<0 care intra pe ramura problematica a buclei (vizualizeaza_curs(poDate.zi_curs) la :315, chiar linia care deschide frm_curs, exact recuperarea citata in reclamatia originala). Deci daca S2/S3 rezolva #16 (antet persistent, fara recreare), S4d beneficiaza direct — dupa -20005, userul revine in acelasi antet unificat (nu unul nou), campul clb_zi_curs readus vizibil (punctul 5.4) ramane exact acolo unde a fost adus inapoi, fara sarituri de focus si fara renumerotare. Daca S2/S3 NU rezolva #16 (recreare inca prezenta), S4d nu il agraveaza si nu il repara — comportamentul ramane identic cu azi pe acest punct, dar readucerea campului vizibil (5.4) trebuie facuta in Init-ul noii instante, nu presupusa mostenita.

Concluzie: #16 nu e in perimetrul de implementare al S4d (e S2, per plan :1944), dar S4d depinde de rezultatul lui pentru UX-ul complet al recuperarii de eroare (5.4) — de marcat explicit ca dependenta la implementare, nu de reinvestigat.


7. Pasi de implementare, ordonati

Fiecare pas lasa suita functionala; calea veche (frm_date_factura/frm_facturare_articole2 cum sunt azi) nu se sterge in etapa I.

  1. Adauga functia de evaluare a conditiei reactive pe formularul unificat (metoda noua, de ex. do_actualizeaza_vizibilitate_curs), care calculeaza lVizibil conform formulei din punctul 2 si seteaza Visible pe controlul clb_zi_curs mostenit din prototipul frm_facturare_articole2 (ofacturare.vc2:16285-16305) — nu un control nou, cel existent, ale carui evenimente de sincronizare (Clb_dataact...LostFocus, :19219-19222) raman neschimbate (deja garda pe Type(...)<>'U', deci tolereaza si eliminare, nu doar Visible=.F.). Gata cand: metoda exista, se poate apela manual (fara UI), si intoarce .T./.F. corect pe cele patru combinatii din punctul 8 (tip retur / nu, in_valuta 0/1, cu/fara linie tip_valuta=1).
  2. Cheama metoda din pasul 1 la sfarsitul do_adauga_articol si do_sterge, imediat dupa (sau in) Thisform.do_calculeaza_totaluri() (:17360, :18687 in prototip — liniile echivalente in formularul unificat, dupa portarea din S3/S4). Gata cand: adaugarea unei linii cu tip_valuta=1 pe o factura in lei fara alte linii de acest fel face campul vizibil imediat, fara Refresh manual; stergerea ultimei asemenea linii il ascunde din nou (simetric, punctul 3), fara sa goleasca poDate.zi_curs.
  3. Verifica initializarea la deschiderea formularului (cazul in_valuta=1 sau document reincarcat cu linii deja existente, ex. la editare factura emisa) — Init-ul unificat trebuie sa apeleze aceeasi metoda o data, dupa ce crsfactura e populat, nu doar sa se bazeze pe evenimentele de adaugare/ stergere (care nu ruleaza la incarcarea initiala a unui document existent). Gata cand: deschiderea unei facturi existente (in lei, cu linii in valuta deja salvate) arata campul corect de la primul Show(), fara sa fie nevoie de o adaugare/stergere care sa-l declanseze.
  4. Elimina campul neconditionat pe retur (tip 8/9), ca azi — verifica ca formula din pasul 1 include deja !Inlist(poDate.tip,8,9) inaintea oricarei alte conditii, ca sa nu-l readuca vizibil din greseala printr-o linie in valuta pe un retur (desi returul nu foloseste zi_curs, vezi zi_curs_validare.md §4c/§6 — comportamentul trebuie sa ramana identic azi, nu doar "fara efect"). Gata cand: pe orice tip 8/9, campul e absent indiferent de continutul grilei.
  5. Readu campul vizibil la eroarea -20005 (punctul 5.4) — modifica ramura goExecutor.nEroare=20005 din bucla de emitere (ofacturare.prg:313-317/:828-832, sau echivalentul ei in noua arhitectura integrata cu formularul unificat, cf. S3 pasul 6) ca sa forteze Visible=.T. pe clb_zi_curs inainte de a deschide frm_curs. Gata cand: pe o factura in lei cu campul ascuns, o eroare -20005 simulata (curs lipsa de test) face campul vizibil imediat dupa mesaj, inainte sau odata cu deschiderea frm_curs.
  6. Coordoneaza cu decizia 42 (S4 punctul 1): cand acea poveste implementeaza restrangerea verifica_cursuri_valute la valuta articolului cautat, verifica ca textul afisat prin oPrelucrareEroare() (punctul 5.1-5.2, neschimbat) tot numeste corect valuta si data — nu necesita modificare pe partea VFP, doar confirmare ca noul parametru Oracle e transmis corect din calea de cautare filtrata. Gata cand: pe cautarea filtrata (dupa decizia 42), mesajul -20005 numeste doar valuta cautata, nu un agregat.
  7. Regresie pe formularul vechi: confirma ca niciuna din modificarile 1-6 nu atinge frm_date_factura/frm_facturare_articole (calea veche, ramasa in productie) — toate modificarile se fac pe clasele formularului unificat / prototip, nu pe cele originale. Gata cand: svn diff/git diff arata modificari doar in clasele noii cai.

8. Cum se verifica

Pe probele din plan (:2243-2246), plus cazurile suplimentare care rezulta din punctele 2-6:

  1. Factura in lei, fara articole in valuta — clb_zi_curs nu apare la deschidere; documentul se emite corect (acelasi rezultat VANZARI/ACT/RUL ca azi, criteriul din S3 §7).
  2. Aceeasi factura, dupa adaugarea unui articol cu pret in valuta — campul apare imediat, cu data implicita deja completata (data documentului, de la Init/Reset, nemodificata).
  3. Stergerea articolului din pasul 2 (singurul cu tip_valuta=1) — campul dispare din nou; valoarea din poDate.zi_curs ramane cea introdusa/implicita (verificabil citind proprietatea direct, nu doar vizual).
  4. Readaugarea unui alt articol in valuta, in aceeasi sesiune — campul reapare cu aceeasi data ca la pasul 2, nu resetata.
  5. Factura in valuta (in_valuta=1) — campul apare mereu, indiferent de continutul grilei, exact ca azi (fara regresie pe cazul deja functional).
  6. Retur (tip 8/9) — campul absent indiferent de linii; document salvat corect (cf. precedent deja existent).
  7. -20005 cu campul ascuns (factura in lei, curs de test lipsa pentru o valuta din politicile utilizatorului) — mesajul numeste valuta si data lipsa (deja azi); campul devine vizibil dupa eroare.
  8. Editare factura emisa (cf. #6, formular deja in lucru separat) — deschiderea unei facturi in lei cu linii in valuta deja salvate arata campul corect de la primul Show() (pasul 3 de implementare).
  9. Dupa decizia 42 — cautarea filtrata a unui articol cu curs lipsa arata mesaj cu o singura valuta (a articolului cautat), nu un agregat.

9. Ce nu se poate testa headless

  • Focusul si secventa reala SetFocus/LostFocus/Visible= pe controale UI reale — capcana deja cunoscuta (grid-coloane-nu-se-materializeaza-headless, memorie de proiect): sub harness -A -T, proprietatile de grid/vizibilitate nu se materializeaza identic cu UI vizibil. Verificarea vizibilitatii reactive (punctele 2-3) trebuie facuta fie prin verificare directa a proprietatii .Visible dupa apelul metodei (fara Show()), fie cu vfp_ui_harness.ps1, UI vizibil.
  • Eroarea Oracle -20005 reprodusa real — necesita fie date de test cu un curs lipsa garantat pe o fereastra de date controlata (manipulare de date, nu de UI), fie mock pe goExecutor care simuleaza nEroare=20005 fara conexiune reala — niciuna verificata ca exista deja in suita de teste.
  • Deschiderea modala a frm_curs (vizualizeaza_curs) — Show(1) modal, aceeasi limitare generala a testarii headless pe formulare modale.
  • Bug #16 propriu-zis (secventa reala de focus dupa recreare de formular) — deja marcat netestabil headless in s3_portare_antet.md §8; S4d nu adauga o cale noua de testare, doar depinde de acelasi rezultat.

10. Riscuri si de decis de Marius

  1. Incarcarea in masa pe comanda/aviz/contract ramane neconditionata dupa decizia 42 (punctul 5.3). Pe aceste tipuri, -20005 poate inca numi o valuta nelegata de documentul curent, chiar daca S4d ascunde corect campul pe baza continutului grilei. Recomandare: de discutat daca extinderea restrangerii decizia-42-style merita si pe incarcarea in masa (poveste separata, posibil in S4 sau intr-o continuare a deciziei 42), sau se accepta ca diferenta cunoscuta, documentata aici.
  2. Cine seteaza Visible=.T. la -20005 cand antetul ar fi fost recreat (daca #16 nu se rezolva complet in S2/S3 pana la implementarea S4d) — punctul 5.4/6 presupune thisform = acelasi formular persistent; daca arhitectura finala tot recreaza o instanta, logica trebuie mutata in Init, cu un semnal explicit propagat (ex. proprietate poDate.lCursLipsa sau echivalent) ca sa stie noua instanta sa arate campul. Recomandare: de tratat ca parte a pasului 6 din s3_portare_antet.md (integrarea buclei de emitere), nu izolat in S4d.
  3. frm_date_aviz/frm_date_aviz_lucrare (tipurile 27, 30) nu intra in perimetrul imediat — daca formularul unificat le preia mai tarziu, validarea neconditionata de la :8076 trebuie aliniata la aceeasi regula reactiva (azi ramane necondiționata, per zi_curs_validare.md §6). Recomandare: nu se atinge acum; se trateaza cand/daca acele tipuri intra in unificare.
  4. Simetria "ascunde la 0 linii" (punctul 3) e o recomandare, nu o certitudine ceruta explicit de plan — planul cere doar "apare cand...", nu specifica explicit comportamentul la disparitia ultimei linii. Recomandare: simetria propusa aici (argumentata pe tiparul lb_cursuri), dar de confirmat cu Marius inainte de implementare, pentru ca schimba usor experienta (campul "clipeste" la adaugare/stergere repetata a aceleiasi linii).