# 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`, `COMUN\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: ```foxpro 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`): ```sql 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: ```foxpro * 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).