# 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=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").