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

479 lines
35 KiB
Markdown

# Cercetare — proiectare S3: portarea logicii de antet in formularul unificat
Investigatie READ-ONLY pentru povestea **S3** din `docs\plan_13_unificare_formular_facturare.md:1428-1439`.
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. Constrangerile din briefing —
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` doar citite (perimetrul
#6), `pack_facturare` si `pack_facturare_comun`-ul intern neatinse, analiticele read-only — respectate.
## Verdict (5-10 randuri)
S3 e **mai mare decat inventarul din plan sugereaza pe numar de metode** (29 `do_cauta_*` reale, nu
30 — vezi punctul 1), dar **mult mai mare decat sugereaza pe complexitate reala**, pentru doua motive
descoperite aici, nu presupuse: (a) `Init`, `inainte_de_do_termin` si — partial — `do_cauta_fdoc` sunt
**omonime cu semantica total diferita pe toate cele patru formulare-sursa** (antet vs. articole vs.
alte-date), deci portarea "o singura data" cere de fapt patru fuziuni de metoda, nu una singura cu
ramificare; (b) bug-ul **#16 e real, reprodus pe cod pana la linia exacta**, si **nu e un bug de focus**
— e o consecinta a faptului ca bucla de reincercare din `factureaza`/`factureaza2`
(`ofacturare.prg:174-571`) trateaza **orice esec SQL** (nu doar cursul lipsa) ca pe un "DA, continui cu
alt document", redeschizand din senin formularul de antet de la zero. Unificarea **nu-l reproduce
automat** — poate chiar sa-l elimine ca efect secundar, daca antetul nu se mai reconstruieste de la
Init dupa un esec Oracle in pasul urmator. Obstacolul principal pentru implementare nu e portarea
codului de validare (majoritatea e `Do Case`/`amessagebox` mecanic), ci **reconcilierea a patru cicluri
de viata `Init`/`inainte_de_do_termin` independente intr-unul singur**, cu ordinea de dependente de la
punctul 5.
---
## 1. Inventarul metodelor de portat — numere corectate
Sursa: `vfp_symbols.ps1 -Class frm_date_factura` / `-Class frm_date_aviz`, confirmat pe fisierul real
`COMUN\clase\ofacturare.vc2` (nu `.bak`).
**`frm_date_factura`** (`ofacturare.vc2:8482-9869`) — **16** metode `do_cauta_*`, nu 17:
| Metoda | Linii | Ce face | Echivalent in `frm_facturare_articole2` |
|---|---|---|---|
| `do_cauta_altele` | `8940-8961` (21) | cautare pe campul cu eticheta dinamica ("Altele"/contract/comanda/etc.) | nu |
| `do_cauta_avize` | `8963-8999` (36) | cautare aviz-sursa (tip=4) | nu |
| `do_cauta_client` | `9001-9060` (59) | cautare client, populeaza sold/cod fiscal | nu |
| `do_cauta_comanda` | `9062-9090` (28) | cautare comanda-sursa | nu |
| `do_cauta_contract` | `9092-9153` (61) | cautare contract-sursa | nu |
| `do_cauta_factura` | `9155-9171` (16) | cautare factura-sursa (credit note, tip 7) | nu |
| `do_cauta_facturi` | `9173-9212` (39) | cautare multi-factura (retur, tip 8/9) | nu |
| `do_cauta_fdoc` | `9214-9225` (11) | cautare generica fel-document, `caut_fdoc()` — **identica byte-cu-byte cu varianta din `frm_date_aviz`**, vezi punctul 4 | nu |
| `do_cauta_gestiune_init` | `9227-9242` (15) | cautare gestiune sursa | nu |
| `do_cauta_locatii` | `9244-9253` (9) | cautare locatie (tip 45) | nu |
| `do_cauta_lucrare` | `9255-9279` (24) | cautare lucrare | nu |
| `do_cauta_responsabil` | `9281-9301` (20) | cautare responsabil | nu |
| `do_cauta_sectie` | `9303-9342` (39) | cautare sectie | nu |
| `do_cauta_valuta` | `9344-9359` (15) | cautare valuta, muta focus pe `clb_zi_curs` daca exista, altfel pe serie (`:9351-9356`, deja conditionat `Type(...)<>'U'`) | nu |
| `do_cauta_venchelt` | `9361-9391` (30) | cautare venit/cheltuiala | nu |
| `do_cauta_venit` | `9393-9394` (1) | stub/alias, o linie — de verificat ce mai apeleaza | nu |
| `do_schimba_tipdoc` | `9396-9438` (42) | vezi punctul 6 — schimba `poDate.nIdTipDoc` si reinitializeaza serie+numar | **nu** — apelat orfan din prototip, vezi punctul 3 |
| `do_verifica` | `9440-9453` (13) | verificare ANAF pe cod fiscal client | nu (dar exista pe `frm_facturi`, aceeasi semantica) |
| `inainte_de_do_termin` | `9455-9561` (106) | validare completa antet inainte de trecerea la articole | **omonim cu semantica diferita** pe toate 4 formulare, vezi mai jos |
| `Init` | `9563-9796` (234) | vezi punctul 5 | **omonim cu semantica diferita**, vezi mai jos |
| `Clb_dataact...LostFocus` | `9798-9813` (15) | sincronizeaza `zi_curs=dataact` daca `clb_zi_curs` exista | nu |
| `Clb_dataireg...LostFocus` | `9815-9831` (16) | idem, pe `dataireg` | nu |
| `Clb_nract...LostFocus` | `9833-9835` (2) | — | nu |
| `Clb_serie_act...LostFocus` | `9837-9844` (7) | — | nu |
| `Ct_clb_fdoc._combobox1.Init` | `9846-9855` (9) | populeaza combo-ul FACTURA/PROFORMA/BON FISCAL | nu |
| `Ct_clb_fdoc._combobox1.LostFocus` | `9857-9859` (2) | `thisform.do_schimba_tipdoc()` — vezi punctul 6 | nu (frm_facturare_articole2 are un apel similar, dar **orfan**, vezi punctul 3) |
| `Ed_tx_simplu1._EDBASE1.KeyPress` | `9861-9867` (6) | — | nu |
**`frm_date_aviz`** (`ofacturare.vc2:6566-7618`) — **13** metode `do_cauta_*`, numarul din plan e corect:
| Metoda | Linii | Observatie |
|---|---|---|
| `do_cauta_altele` | `6882-6894` (12) | mai scurta decat pe factura (21) — set de tipuri mai mic |
| `do_cauta_avize` | `6896-6930` (34) | apropiata ca marime de factura (36) |
| `do_cauta_client` | `6932-7009` (77) | **mai lunga** decat pe factura (59) — trateaza eticheta variabila "Retur de la"/"Gestiune sursa" pe transfer |
| `do_cauta_comanda` | `7011-7034` (23) | |
| `do_cauta_contract` | `7036-7091` (55) | |
| `do_cauta_fdoc` | `7093-7104` (11) | **identica cu factura**, vezi punctul 4 |
| `do_cauta_gestiune_dest` | `7106-7131` (25) | **doar pe aviz** — gestiune destinatie la transfer subunitati |
| `do_cauta_gestiune_init` | `7133-7144` (11) | mai scurta decat pe factura (15) |
| `do_cauta_lucrare` | `7146-7165` (19) | |
| `do_cauta_politica` | `7167-7182` (15) | **doar pe aviz** — `Ct_clb_politici_preturi` |
| `do_cauta_responsabil` | `7184-7204` (20) | aceeasi lungime ca factura |
| `do_cauta_sectie` | `7206-7240` (34) | |
| `do_cauta_venchelt` | `7242-7275` (33) | |
| `inainte_de_do_termin` | `7277-7352` (75) | **omonim**, vezi mai jos — mai scurta decat factura (106): fara validare data-scadenta, fara validare valuta, fara validare client (!) |
| `Init` | `7354-7600` (247) | **omonim**, nu citit integral aici (doar `9563-9650`+`9733-9796` echivalentul pe factura) |
| `Clb_dataact...LostFocus` | `7602-7605` | |
| `Clb_dataireg...LostFocus` | `7607-7612` | |
| `Clb_nract...LostFocus` | `7614-7616` | |
**`frm_date_aviz` nu are `do_schimba_tipdoc`** — confirmat, container-ul `Ct_clb_fdoc` de pe aviz e
o cautare simpla, fara combo si fara evenimentul `LostFocus` care declanseaza schimbarea de tip.
**Total real: 16 + 13 = 29 metode `do_cauta_*`**, plus `do_schimba_tipdoc` (doar factura), `do_verifica`
(doar factura), `inainte_de_do_termin` si `Init` (omonime pe ambele) — planul le numara global corect
ca "17+13 ... plus restul", dar cifra "17" pe factura e gresita cu o unitate: **16**.
---
## 2. Metodele omonime — capcana centrala a portarii
**Nivelul care conteaza cel mai mult, necunoscut inca in plan**: `Init` si `inainte_de_do_termin` sunt
**acelasi nume de metoda pe toate cele patru formulare-sursa implicate in unificare**
(`frm_date_factura`, `frm_date_aviz`, `frm_facturare_articole`/`frm_facturare_articole2`,
`frm_alte_date`), fiecare cu corp complet diferit (antet vs. compunere articole vs. date suplimentare).
O singura clasa unificata nu poate avea patru metode `Init`. **Portarea "o singura data" a S3 inseamna
concret: patru corpuri de `Init` si patru corpuri de `inainte_de_do_termin` trebuie topite intr-un
singur `Init` si un singur `inainte_de_do_termin` (sau redenumite si inlantuite explicit)** — asta e
efortul real, nu doar mutarea codului de validare camp-cu-camp.
**`inainte_de_do_termin`, factura vs. aviz — diferenta reala, cu citat** (comparate direct, ambele corpuri
citite integral, `ofacturare.vc2:9455-9561` si `:7277-7352`):
- **Factura valideaza `id_client` obligatoriu; aviz nu valideaza deloc acest camp**:
```
ofacturare.vc2:9510-9513 (doar pe factura)
Case Empty(poDate.id_client) Or Isnull(poDate.id_client)
amessagebox("Nu ati ales clientul!",48,"Atentie")
This.ct_clb_nume_client.SetFocus()
```
Motiv plauzibil (neconfirmat mai departe): avizele de transfer intre subunitati (tip 23/25/30/41) nu
au neaparat un "client" — au o gestiune destinatie, validata separat.
- **Factura valideaza scadenta si valuta; aviz nu are deloc aceste `Case`-uri** — `Empty(poDate.datascad)`,
`poDate.datascad<poDate.dataact`, `poDate.in_valuta=1 And Empty(zi_curs)`, `poDate.in_valuta=1 And
Empty(id_valuta)` (`:9471-9493`) — aviz nu are control de valuta pe formular (S1, randul "Valuta").
- **Aviz valideaza politica de preturi; factura nu**:
```
ofacturare.vc2:7334-7337 (doar pe aviz)
Case Empty(Nvl(poDate.id_pol,0)) And InList(poDate.tip,23,41)
amessagebox("Nu ati ales politica de preturi!",48,"Atentie")
```
- **Setul de tipuri si mesaje pentru "sursa neselectata" (`listaid`) e complet diferit**: factura
`!Inlist(poDate.tip,1,5,7,8,9,10,48,49)` cu mesaje contract/comanda/aviz/locatie (`:9528-9541`); aviz
`Inlist(poDate.tip,21,23,25,26,28,41,42,47)` cu mesaje comanda/gestiune-destinatie/gestiune-retur/contract
(`:7318-7333`) — seturile de `tip` nu se suprapun deloc (factura = tipuri <21 sau in {45,48,49,51,52};
aviz = tipuri >=21 in afara de 27/30). **Discriminatorul natural pentru ramificare exista deja**:
`poDate.nIdTipDoc` (5=FACTURA, 6=AVIZ — calculat de apelant la `ofacturare.prg:192-196`) sau simpla
apartenenta a lui `poDate.tip` la unul din cele doua seturi disjuncte, **nu** un flag nou.
- **Contul folosit la verificarea de facturi duplicate difera**: `facturi_duplicate('4111', 0, ...)`
(factura, `:9545`, cont clienti) vs. `facturi_duplicate('418', 0, ...)` (aviz, `:7341`, alt cont) —
un literal hardcodat care trebuie sa devina el insusi parte din ramificare, nu doar mesajul.
**Alte omonime cunoscute, cu semantica diferita, relevante pentru zona din jurul lui S3** (nu introduse
aici — reconfirmate, vezi si `docs\handoff_13_formular_unificat.md:192-194`):
- `do_calculeaza_discount` — **la nivel de document** pe `frm_facturare_articole` (`:13405-13424`) si
`frm_facturare_articole2` (`:17670-17686`, verificat aici: identic ca semantica, scrie
`Thisform.ndiscfactron`/`ndiscfactval`); **la nivel de linie** pe `frm_articol_factura` (`:1874-1976`,
scrie `poArticol.discount_unitar*`). Nu intra direct in S3 (nu e printre metodele antetului), dar
orice cod nou care apeleaza `do_calculeaza_discount` prin nume pe formularul unificat trebuie sa
stie care semantica o vrea.
- `do_verifica` — pe `frm_date_factura` (`:9440-9453`, verificare ANAF) si pe `frm_facturi`
(verificare ANAF, aceeasi semantica) — omonim inofensiv, aceeasi actiune.
- `do_cauta_gestiune_init` / `do_cauta_responsabil` / `do_cauta_sectie` / `do_cauta_venchelt` /
`do_cauta_altele` / `do_cauta_comanda` / `do_cauta_contract` / `do_cauta_avize` / `do_cauta_lucrare` —
**acelasi nume pe factura si aviz, lungimi diferite** (vezi tabelele de la punctul 1) — nu omonime
periculoase (aceeasi intentie: cautare pe camp), dar **nu identice** — portarea trebuie sa ia corpul
fiecareia separat, nu sa presupuna ca sunt duplicate de sters.
- **Singura pereche confirmata identica byte-cu-byte**: `do_cauta_fdoc` (vezi punctul 4) — aceasta
chiar poate fi portata o singura data, fara ramificare.
---
## 3. Ce e deja in prototip vs. ce lipseste
`frm_facturare_articole2` (`ofacturare.vc2:15741-19355`) are controalele de antet montate direct pe
formular (confirmat in `inventar_controale_formulare.md`), dar **zero logica**:
| Metoda/mecanism | Exista in `frm_facturare_articole2`? | Detaliu |
|---|---|---|
| Toate cele 29 `do_cauta_*` | **NU** | niciunul in lista de metode proprii a clasei (confirmat cu `vfp_symbols.ps1 -Class`) |
| `do_schimba_tipdoc` | **NU, dar e APELAT** — cod orfan | `clb_fdoc.cboFdoc.Valid` (`:19255-19257`) contine `thisform.do_schimba_tipdoc()`, dar clasa **nu are** aceasta metoda in lista proprie. Daca userul ar schimba azi combo-ul `clb_fdoc` pe acest formular mort, ar cadea cu eroare de metoda inexistenta. Confirma independent constatarea din plan ("controalele sunt acolo, logica nu") — de fapt e mai rau: e cod care ar crapa daca s-ar activa calea, nu doar cod lipsa |
| `inainte_de_do_termin` | **DA, dar cu alta semantica** | `:18809-18986` (178 linii) — valideaza ARTICOLE (stoc, discount, TVA), nu antetul; e omonimul de care vorbeste punctul 2, nu un candidat de reutilizare pentru validarea de antet |
| `Init` | **DA, dar cu alta semantica** | `:18988-19080` (92 linii, citit integral aici) — confirmat: doar grid/curs-label/total-mode/coloane in/afara valuta; **niciun** `poDate.xxx -> control.Value`. Nu populeaza antetul deloc |
| Serie/numar (`Clb_serie_act1`/`Clb_nract`) | Controale prezente, dar fara wiring de populare in `Init` | `Clb_serie_act1.TEXT_SIMPLU1.LostFocus` (`:19263-19270`) exista ca metoda proprie — nu verificat aici daca reproduce `genereazanumar` |
**Concluzie punctul 3**: prototipul e o **coaja vizuala**, nu un schelet de 50% functional. Ce trebuie
scris pentru S3 e efectiv tot codul de comportament — controalele economisesc timpul de aranjare in
`.scx`/`.vcx`, nu timpul de portare a logicii.
---
## 4. Ramificarea factura / aviz in interior
**Discriminatorul de ramificare recomandat: `poDate.nIdTipDoc`** (5=FACTURA, 6=AVIZ), deja calculat de
apelant inainte de `Createobject` (`ofacturare.prg:187-196`) si deja disponibil pe `poDate` in
momentul in care formularul unificat ar porni `Init`. Alternativ, acelasi rezultat se obtine testand
direct apartenenta lui `poDate.tip` la unul din cele doua seturi disjuncte folosite azi in
`inainte_de_do_termin` (vezi punctul 2) — cele doua seturi nu se suprapun niciodata, deci nu exista
ambiguitate. **Nu e nevoie de o proprietate noua** gen `lEsteAviz` — `nIdTipDoc` face deja treaba, si
e deja pe obiectul `poDate` care trece prin tot lantul de apeluri.
Ce trebuie sa ramifice concret, cu dovada:
1. **`inainte_de_do_termin`** — Cases specifice fiecarei parti (vezi citatele de la punctul 2), plus
constanta de cont (`'4111'` vs `'418'`) la `facturi_duplicate`.
2. **`Init`** — seturile `Do Case poDate.tip` care decid ce se elimina (`RemoveObject`) difera pe
fiecare parte (S1 documenteaza deja fiecare camp cu conditia lui de vizibilitate; portarea trebuie
sa pastreze fiecare conditie identic, nu sa le generalizeze pe ghicite).
3. **`do_schimba_tipdoc`** — **exista doar pe factura**; pe aviz nu exista mecanismul de schimbare a
tipului de document dupa deschiderea formularului (containerul `ct_clb_fdoc` de pe aviz e cautare
simpla, fara `_combobox1`). Ramificarea aici nu e "cod diferit pe aceeasi metoda" ci "metoda
prezenta doar pe o ramura" — formularul unificat trebuie sa decida daca aviz capata acest
comportament nou (schimbare tip dupa deschidere) sau ramane fara el, ca azi.
4. **`do_cauta_fdoc`** — **nu ramifica**, e identic (punctul 1) — poate fi portat o singura data, fara
`If nIdTipDoc=...`.
5. **`do_cauta_client`** — aviz e cu 18 linii mai lung (77 vs 59) pentru eticheta variabila
"Retur de la"/"Gestiune sursa" pe transfer (S1, randul "Client") — de citit integral la
implementare pentru ramificarea exacta, nu verificat linie-cu-linie aici.
---
## 5. `Init`-urile — ce face fiecare, in ce ordine, ce depinde de ce
**Secventa de azi, pe drumul normal (factura din lista de preturi, fara eroare Oracle):**
1. **Apelantul** (`ofacturare.prg:factureaza`, inainte de orice `Createobject`): construieste
`poDate = Createobject("oDateFactura", ...)` si `poGeneratorNumere = Createobject("oGeneratorNumere")`
(`:184-185`), seteaza `poDate.nIdTipDoc` (`:187-196`), cheama
`poDate.completeaza_setari_document(...)` daca e copiere (`:200-206`), apoi
`poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)`
(`:208-211`) — **toate acestea ruleaza inainte ca vreun formular sa existe**. Orice `Init` de mai jos
presupune `poDate`/`poGeneratorNumere` deja populate.
2. **`frm_date_factura.Init`** (`:9563-9796`, 234 linii, citit integral aici) — `DoDefault()`, apoi
masoara inaltimile containerelor de antet intr-un array (`laPozitii`), decide pe `Do Case
gnScadereStoc/poDate.tip` ce containere elimina (`RemoveObject`) si reface layout-ul pe verticala,
initializeaza serie+numar (`.clb_serie_act.do_initializeaza(poDate.rezultat_serii)`, `:9737`), si
**la final** seteaza focus: pe `ct_clb_valuta` daca documentul e in valuta si campul valutei apare
deasupra tipului si nu are inca valuta aleasa, **altfel pe `ct_clb_fdoc`** (`:9788-9793`, citat
integral la punctul 6). Depinde de: `poDate` (tip, in_valuta, ...), `poGeneratorNumere`
(`creeaza_cursor_serii` deja rulat de apelant), variabile globale de firma (`gnScadereStoc`).
3. **`frm_facturare_articole2.Init`** (`:18988-19080`, 92 linii, citit integral aici) — confirmat:
**nu atinge niciun camp de antet**. Configureaza doar gridul (`nNrInregVizibile`), eticheta
cursurilor valutare, modul total (`gnModTotFact`), si elimina coloanele RON sau valuta din grid
dupa `poDate.in_valuta`. Depinde de: `poDate.zi_curs`/`in_valuta`/`tip`, `crscursuri` (populat de
apelant intre pasii 2 si 3, nu de acest `Init`).
4. **`frm_alte_date.Init`** (`ferestre_cere_date.vc2:3105-3207`, 103 linii, citit integral aici) —
`DoDefault()`, citeste `poDate.dataora_exp`; **daca nu e proforma**: cauta ultimul delegat/masina
folosite pentru client (apel Oracle `cauta_date_ultima_factura[_tip]`, `:3119-3136`), populeaza
combo-ul de casa dintr-un cursor Oracle nou (`v_nom_casa`, `:3156-3173`), seteaza `opt_incasat`
implicit daca `poDate.incasat<>0`; **daca e proforma**: elimina complet grupul delegat/masina/
incasare (`RemoveObject` in cascada, `:3192-3201`) si redimensioneaza formularul la zona de text
aditional. **Risc gasit, neurmarit mai departe**: pe eroare la interogarea combo-ului de casa,
`Init` cheama `poGeneratorNumere.dezaloca_numar(5)` si `Return` (`:3159-3161`) — acelasi tipar de
"dezalocare numar pe eroare SQL neasteptata" ca la bug-ul #16 (punctul 6), dar intr-un `Init`, nu
intr-o bucla — **nu s-a verificat daca are aceeasi consecinta de recreare completa a formularului**;
semnalat, nu investigat suplimentar aici din motive de buget de context.
**Ordinea de dependente, explicit**: `poDate`+`poGeneratorNumere` (apelant) -> `frm_date_factura`/
`frm_date_aviz.Init` (foloseste serie/numar deja create) -> **Oracle: cursorul de articole**
(`ofacturare.prg:266-308`, aici poate pica bug #16) -> `frm_facturare_articole2.Init` (foloseste
`crscursuri` populat intre pasi, nu de propriul `Init`) -> `frm_alte_date.Init` (face **inca** un apel
Oracle nou, cu propriul risc de dezalocare). **Pentru formularul unificat, aceasta secventa de patru
`Init`-uri trebuie sa devina un singur `Init` care ruleaza fazat** (o singura data la deschidere) —
sau ramane o discutie deschisa daca vreo faza (Oracle-lookup-ul din `frm_alte_date.Init`) ramane
intarziata pana la deschiderea sectiunii pliate, ca sa nu incarce round-trip-uri Oracle inutile cand
utilizatorul nu ajunge niciodata la acea sectiune.
---
## 6. Bug-ul #16 — mecanismul exact, verificat pe cod
**Text original** (`COMUN\docs\todos.txt:45`): *"la revenire din formularul de curs valutar, focusul
revine inainte de numar document, cred ca pe TIP DOCUMENT, si la iesire din serie se regenereaza numar
act, ceea ce este periculos daca utilizatorul l-a schimbat".*
**Verdict: NU e un bug de focus. E o bucla de reincercare care trateaza orice esec Oracle ca pe un "DA,
mai fac un document" si reconstruieste formularul de antet de la zero — focusul si regenerarea
numarului sunt doar simptomele vizibile ale acestei reconstructii.**
Lantul complet, verificat linie cu linie:
1. Utilizatorul completeaza antetul, apasa Termina — `inainte_de_do_termin` trece, formularul de antet
se inchide (`pnButon=1`).
2. Apelantul (`factureaza`, `ofacturare.prg:266-308`) construieste cursorul de articole din Oracle
(`cursor_preturi`/etc., functie de `poDate.tip`). **Daca cererea esueaza** (`lnSucces<0`,
`:313`) — inclusiv, dar nu numai, cu eroarea Oracle `-20005` "Nu este setat cursul..." (curs
valutar lipsa pentru o valuta din listele de preturi ale utilizatorului, verificata neconditionat
in `pack_facturare.verifica_cursuri_valute`, documentat deja in `zi_curs_validare.md`):
```
ofacturare.prg:313-322
If lnSucces < 0
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
If goExecutor.nEroare = 20005
vizualizeaza_curs(poDate.zi_curs) && deschide "formularul de curs valutar" (frm_curs, modal)
ENDIF
poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc) && numarul alocat se elibereaza
Else
... [singurul loc unde se cere "Doriti sa continuati?" si se seteaza lnRaspuns]
Endif
```
`vizualizeaza_curs` (`oproceduri_curs.prg:8-42`) e confirmat: deschide `Createobject("frm_curs",
tdDataCurs).Show(1)` — chiar formularul din reclamatie.
3. **Punctul central, verificat prin numararea `If`/`Else`/`Endif` din fisier**: prompt-ul "Doriti sa
continuati cu operatii de acest fel?" care seteaza variabila de bucla `lnRaspuns` **exista doar in
ramura `Else` a lui `If lnSucces<0`** (`ofacturare.prg:555-564`, in interiorul aceluiasi bloc
`If/Else/Endif` care se inchide abia la `:568`). **Pe ramura de eroare (`lnSucces<0`), `lnRaspuns`
nu e niciodata atins.** Isi pastreaza valoarea din intrarea in aceasta iteratie a buclei — pe primul
document, `6` (initializat la `:174`, inainte de `Do While lnRaspuns = 6` de la `:175`).
4. Bucla externa (`Do While lnRaspuns = 6 ... Enddo`, `:175-571`) **reintra automat**, fara nicio
intrebare catre utilizator, pentru ca `lnRaspuns` e inca `6`. In aceasta noua iteratie:
`poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)`
ruleaza din nou (`:208-211`), apoi se creeaza **o instanta noua** de `frm_date_factura`/`frm_date_aviz`
(`Createobject`, `:230`) si se arata modal (`:235`) — `poDate` insusi **nu** e recreat (ramane
acelasi obiect, cu `nract`/`serie_act` deja setate de utilizator), dar **formularul da**, deci
`Init` ruleaza de la capat.
5. `frm_date_factura.Init` (`:9563-9796`) reface layout-ul si, **la final, seteaza focus necondiționat
pe `ct_clb_fdoc`** (tip document) in cazul normal:
```
ofacturare.vc2:9788-9793
DO CASE
CASE poDate.in_valuta = 1 and this.ct_clb_valuta.Top < this.ct_clb_fdoc.Top AND EMPTY(NVL(poDate.nume_valuta,''))
this.ct_clb_valuta.SetFocus()
OTHERWISE
this.ct_clb_fdoc.SetFocus()
ENDCASE
```
**Aceasta e "focusul care revine pe TIP DOCUMENT"** din reclamatie — nu un bug izolat de focus, ci
comportamentul normal de `Init` al unui formular nou, aparut unde utilizatorul nu se astepta la un
formular nou.
6. Cand focusul paraseste `ct_clb_fdoc` (Tab, click in alta parte — orice), se declanseaza
`Ct_clb_fdoc._combobox1.LostFocus -> thisform.do_schimba_tipdoc()` (`:9857-9859`). Prima linie a
acestei metode, **inainte de orice verificare "tipul chiar s-a schimbat?"**:
```
ofacturare.vc2:9417 (in do_schimba_tipdoc, INAINTE de guard-ul de la :9419-9421)
This.clb_serie_act._cbbase1.LostFocus()
```
— apeleaza **neconditionat** handler-ul de LostFocus al combo-ului de serie, indiferent daca tipul
documentului s-a schimbat sau nu.
7. Acel handler (`serii_numere.vc2:138-140`, clasa `clb_serie_act`) cheama `genereazanumar` (`:114-136`):
```
serii_numere.vc2:122-127
If poGeneratorNumere.verifica_serie(This.nid_tipdoc) Or &lcValoare. = 0
lcValoare = lcValoare+[=]+ALLTRIM(Str(poGeneratorNumere.aloca_numar(This.nid_tipdoc,Null),20,0))
&lcValoare
This.Parent.Refresh()
Endif
```
— **aloca un numar nou** si il scrie peste `poDate.nract` prin macro, **necondiționat de ce numar
avea utilizatorul inainte**. **Asta e "iesirea din serie regenereaza numarul actului"** din
reclamatie — confirmat exact, pana la linia care face scrierea.
**Verdict pe intrebarea planului ("se rezolva #16 in S3, sau se reproduce bug-ul?"): se poate rezolva
in S3, cu un cost mic, dar nu e o consecinta automata a unificarii — trebuie tratat explicit.**
Cauza reala nu e in `frm_date_factura`/`do_schimba_tipdoc`/`clb_serie_act` (cod care se comporta
"corect" fata de contractul lui local), ci in bucla de reincercare din apelant
(`ofacturare.prg:174-571`), care **nu distinge "utilizatorul a confirmat ca vrea alt document" de
"cererea Oracle a esuat"**. Doua directii posibile, ambele in afara perimetrului strict al portarii de
antet, dar declansate direct de ea:
- **(a) minimal**: in formularul unificat, dupa un esec Oracle la pasul de construire a cursorului de
articole (echivalentul liniei `:313`), **nu se distruge formularul de antet** — antetul e deja o
singura sectiune persistenta a aceluiasi formular, nu un obiect separat recreat de apelant; simpla
arhitectura unificata (un `Init` per sesiune de emitere, nu per formular) elimina pasul 4 de mai sus,
deci elimina intreg lantul 5-7. **Acesta e motivul pentru care unificarea are sansa reala sa rezolve
#16 ca efect secundar** — dar numai daca implementarea nu recreeaza formularul intreg la reincercare
dupa eroare Oracle (ceea ce ar reproduce bug-ul identic, doar mutat).
- **(b) daca (a) nu e suficient** (de ex. daca dupa fix la curs tot trebuie relansata interogarea de
articole): `lnRaspuns` (sau echivalentul lui in noua arhitectura) trebuie resetat explicit pe orice
cale care iese din ramura de eroare, ca sa nu mai fie confundat cu "utilizatorul a spus DA".
**Coordonarea cu #6 (decizia 30 si constrangerea din briefing) — verificat direct pe diff-uri, nu doar
pe proza handoff-ului**: `docs\diff_s4_valuta_dialog*.patch` (trei fisiere) ating exclusiv
`COMUN\clase\omodificari.vc2`, `COMUN\programe\ofacturare_editare.prg` si un fisier de test —
**niciunul nu atinge `ofacturare.vc2` (unde traiesc `frm_date_factura`, `do_schimba_tipdoc`,
`clb_serie_act`) sau `ofacturare.prg` (unde traieste bucla `factureaza`)**. Confirmat si de continutul
lui handoff intermediar (sters): intreaga lui cercetare e despre `frm_articol_factura`
(dialogul de adaugare articol pe linie, alt fisier/clasa) si `frm_modific2024.cmdAdaugaArticol.Click`
— un mecanism de valuta **pe linie de articol**, fara nicio legatura cu antetul sau cu bucla de
emitere. **#6 nu a atins deloc lantul lui #16. Bug-ul e in intregime valabil si neschimbat.**
---
## 7. Ordinea de lucru propusa pentru S3
Pasi care lasa suita functionala dupa fiecare (calea veche ramane in productie pe tot parcursul —
niciun pas nu sterge `frm_date_factura`/`frm_date_aviz`/`frm_alte_date` originale):
1. **Fuzioneaza `Init`-urile de antet** (`frm_date_factura` + `frm_date_aviz`) intr-o metoda unica pe
formularul unificat, ramificata pe `poDate.nIdTipDoc` (punctul 4). Verificare: formularul unificat
se deschide pe fiecare din cele ~15 tipuri principale de `poDate.tip` folosite azi in cele doua
`Init`-uri, si arata exact aceleasi campuri vizibile/eliminate ca varianta veche — comparatie
camp-cu-camp fata de tabelul din `docs\S1_inventar_campuri_formular_unificat.md`.
2. **Porteaza cele 29 `do_cauta_*`** (fara ramificare unde sunt identice — `do_cauta_fdoc` — cu
ramificare pe `nIdTipDoc` unde difera). Verificare: fiecare cautare deschide acelasi dialog, scrie
aceleasi proprietati pe `poDate`, muta focusul identic cu azi.
3. **Fuzioneaza `inainte_de_do_termin`**, cu ramificarea documentata la punctul 2. Verificare: aceleasi
mesaje de eroare, in acelasi ordine, pentru aceleasi campuri goale, pe fiecare tip.
4. **Porteaza `do_schimba_tipdoc`** — decide explicit daca ramane doar-factura sau se extinde si pe
aviz (punctul 4, pct. 3). **Aici se rezolva sau nu #16** (punctul 6) — de facut ca parte a acestui
pas, nu separat, pentru ca schimbarea de arhitectura care il rezolva (un singur `Init` persistent)
e chiar cea care se construieste la pasul 1.
5. **Porteaza `but_modifica`/blocarea antetului** (decizia 9, deja proiectata in plan sectiunea I) —
depinde de pasii 1-4 fiind stabili, pentru ca reutilizeaza aceleasi controale.
6. **Integreaza bucla de emitere** (`factureaza`/`factureaza2`) cu noul formular unificat, tratand
explicit esecul Oracle de la cursorul de articole (punctul 6, directia a/b).
**Criteriul de "gata", rescris verificabil.** Azi planul spune "un document se emite integral din
formularul unificat, pe tip 1, cu acelasi rezultat in `vanzari`/`act`/`rul` ca pe calea veche" — fara
sa spuna cum se compara. Propunere concreta:
- Se emite **acelasi document** (acelasi client, articole, cantitati, preturi) o data pe calea veche
(`frm_date_factura` -> `frm_facturare_articole` -> `frm_alte_date`) si o data pe formularul unificat,
pe date de test identice, in aceeasi zi contabila.
- Se compara, randuri-cu-randuri, rezultatul in `VANZARI` (toate coloanele, nu doar sumele), `ACT`,
`RUL`, `DOCUMENTE`, `JV2007` (nota contabila) — cel mai simplu cu doua interogari identice filtrate
pe cei doi `ID_FACT`/`ID_VANZARE` rezultati, exportate si diff-uite text-cu-text (unealta: acelasi
export SQL folosit deja pentru `PACK_FACTURARE` in `docs\ff_*.sql`, adaptat pe `SELECT * FROM VANZARI
WHERE ID_FACT=...`).
- Diferentele **asteptate** (marcate ca OK, nu ca esec): `ID_VANZARE`/`ID_FACT`/timestamp-uri de
creare — orice altceva trebuie sa fie identic.
- Se repeta pentru **cel putin un tip de factura in valuta** (S3 atinge direct campurile de valuta) si
**un tip de aviz** (ramificarea de la punctul 4), nu doar tip 1.
---
## 8. Ce nu se poate testa headless din S3
Capcana cunoscuta (`COMUN\docs\depanare_testare_vfp.md`, memorie de proiect): sub harness `-A -T`,
coloanele de grid nu se materializeaza (`ColumnCount=0`, `RecordSource` raman artefacte necitite) —
relevant direct pentru `frm_facturare_articole2` (grid-ul de compunere), dar S3 propriu-zis nu atinge
gridul.
Specific pentru S3 (antet):
- **Toate cele 29 `do_cauta_*`** deschid un dialog modal de cautare (`caut_ora.vcx`/similare) —
interactiunea reala de selectare dintr-o lista si `Show(1)` modal nu se poate simula headless; se
pot testa doar efectele **dupa** ce `poCauta`/rezultatul e construit manual (tiparul deja folosit in
`test_pret_cu_tva_dialog.prg`/`test_adauga_linie_valuta.prg` mentionat in
handoff intermediar (sters), punctul 7) — apel direct al metodei cu un obiect
simulat, fara `Show()`.
- **`Init`-ul complet** (redimensionare, `RemoveObject` in cascada, repozitionare containere) e
verificabil pe proprietati (`.Visible`, `.Height`, existenta obiectului dupa `Type(...)`) fara UI
vizibil, pentru ca `Createobject` ruleaza `Init` fara `Show()` — tiparul e deja validat in codebase.
- **Focusul si secventa reala de `LostFocus`** (exact ce a produs bug #16) **nu se poate reproduce
headless** — necesita fie `vfp_ui_harness.ps1` cu UI vizibil si input simulat pe masina, fie testare
manuala de Marius; simularea unui `SetFocus()`/`LostFocus()` apelat direct din cod ocoleste tocmai
secventa evenimentelor native care a cauzat bugul.
- **Eroarea Oracle -20005 si redeschiderea `vizualizeaza_curs`** — reproductibila headless doar daca
se poate forta controlat lipsa unui curs pentru o data de test (manipulare de date, nu de UI) — nu
s-a verificat aici daca exista deja o retetare de date de test pentru asta.
---
## Ce nu s-a putut stabili si de ce
- **Corpul complet al `frm_date_aviz.Init`** (`:7354-7600`, 247 linii) — citit doar partial in sesiuni
anterioare (pana la `~7533`, conform `inventar_controale_formulare.md:166-167`); nu re-citit integral
aici din motive de buget de context. Structura generala (Do Case pe tip, RemoveObject, focus final)
e foarte probabil simetrica cu `frm_date_factura.Init`, dar nu verificata linie-cu-linie.
- **`do_cauta_venit`** (`frm_date_factura`, `:9393-9394`, o singura linie) — nu s-a citit continutul;
posibil alias/stub mort, de verificat la implementare.
- **Riscul semnalat la punctul 5** (`frm_alte_date.Init:3159`, `dezaloca_numar` pe eroare la
interogarea combo-ului de casa) — **doar semnalat, neurmarit**: nu s-a verificat daca apelantul care
cheama `frm_alte_date` are aceeasi bucla "retry silentios" ca `factureaza`, sau daca eroarea aici
chiar opreste fluxul curat (`Return` explicit la `:3161`, spre deosebire de bug #16 unde nu exista
`Return` echivalent). Merita o cercetare separata, de marimea celei de la punctul 6, inainte de
implementare.
- **`Clb_serie_act1.TEXT_SIMPLU1.LostFocus`** din `frm_facturare_articole2` (`:19263-19270`) — nu
citit; nu s-a verificat daca reproduce corect `genereazanumar` sau e alt cod orfan ca cel de la
punctul 3.
- **Ramificarea exacta linie-cu-linie pentru restul celor 27 de perechi `do_cauta_*`** (dincolo de
`do_cauta_client` si `do_cauta_fdoc`, comparate direct aici) — s-au comparat doar lungimile (indiciu
de diferenta), nu continutul; de citit la implementare, nu presupus din lungime.
- **`frm_modifica_factura`** — clasa reala traieste in `ofacturare_comun.vc2` (perimetrul #6, doar
citire permisa), dar `vfp_symbols.ps1` a indexat-o din `.pre_s4butoane.bak.vc2` (linii duplicate,
nesigure) — nu s-au recitit liniile reale, pentru ca `frm_modifica_factura` **nu intra in portarea
S3** (e inlocuita de `but_modifica`, deja proiectat separat in plan, sectiunea I).
## Bug-uri semnalate, nereparate
1. **Cod orfan in `frm_facturare_articole2`**: `clb_fdoc.cboFdoc.Valid` (`ofacturare.vc2:19256`) cheama
`thisform.do_schimba_tipdoc()`, metoda **inexistenta** pe aceasta clasa — ar arunca eroare VFP daca
userul ar interactiona cu acest combo pe formularul mort. Fara consecinte azi (formularul nu e
accesibil in productie, cf. plan sectiunea A), dar de curatat sau completat cand prototipul devine
baza formularului unificat.
2. **Bug #16, confirmat si localizat complet** (punctul 6) — in `ofacturare.prg:555-564` (si simetric
in `factureaza2`, liniile ~1061 dupa numerotarea echivalenta, nu verificate linie-cu-linie aici):
`lnRaspuns` nu se reseteaza pe ramura de eroare Oracle a buclei de emitere, cauzand recrearea
completa si nesolicitata a formularului de antet. In afara perimetrului `ofacturare_comun.vc2`/
`ofacturare_editare.prg` (deci nu ciocneste cu #6), dar in `ofacturare.vc2`/`ofacturare.prg`, cod
comun suitei (afecteaza si ROACONT/ROAGEST/etc. daca folosesc acelasi `factureaza`/`factureaza2`
din `COMUN` — neverificat aici daca alte produse il apeleaza, dar fisierul e in `COMUN\programe`,
deci probabil da).
3. **Risc structural similar, neconfirmat**: `frm_alte_date.Init:3159` — acelasi tipar
"`dezaloca_numar` pe eroare SQL neasteptata", posibil fara aceeasi consecinta de reincercare
silentioasa, dar nu verificat (vezi "Ce nu s-a putut stabili").