Files
roafacturare/docs/cercetare/s3_portare_antet.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

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

35 KiB

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 docs\handoff_decizia35_valuta_dialog.md: 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 docs\handoff_decizia35_valuta_dialog.md, 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").