Files
roafacturare/docs/cercetare/s4d_zi_curs_reactiv.md
Marius Mutu b5a7108f34 docs: cercetarile comune trec in COMUN, reziduul #6 dispare, progres.md la zi
Curatenie ceruta dupa inchiderea lui #6. Folderul cercetare\ NU se putea sterge
in bloc: plan_13 se sprijina pe el cu 85 de trimiteri, deci e baza de dovezi a
planului aflat in lucru. Impartirea:

- 14 cercetari trans-proiect trec in COMUN\docs\cercetare\ - valuta si curs,
  TVA/VANZARI, consumatorii VANZARI din toata suita, integrarile #10/#11/#12,
  watchdog VFP, proiectarea Oracle a lui S5, view-ul VVANZARI_ARTICOLE. Nu sunt
  ale ROAFACTURARE, iar #10/#11/#12 se reiau chiar din ele.
- 20 de rapoarte de executie ale lui #6, nereferite de nimic viu, sterse.
- 91 raman, neatinse.

Trimiterile catre handoff-urile si diff-urile intermediare deja desfiintate au
fost curatate peste tot (39 de fisiere): 50 catre handoff-uri, 37 catre diff-uri
aplicate, plus caile celor mutate in COMUN. Zero trimiteri rupte ramase.

progres.md preia rolul de predare: ce ramane din #6 (cele sase documente
parazite, cele doua esecuri reale din S8 pe factura din aviz), cifrele de test
citite din log, si cele doua capcane de mediu platite - .FXP vechi executat in
locul .prg-ului, si GETFONT() care atarna un formular instantiat fara goApp.

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

430 lines
28 KiB
Markdown

# 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 <valuta>!"*. 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).