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_clientobligatoriu; aviz nu valideaza deloc acest camp: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.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() - 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); avizInlist(poDate.tip,21,23,25,26,28,41,42,47)cu mesaje comanda/gestiune-destinatie/gestiune-retur/contract (:7318-7333) — seturile detipnu 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 laofacturare.prg:192-196) sau simpla apartenenta a luipoDate.tipla 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 pefrm_facturare_articole(:13405-13424) sifrm_facturare_articole2(:17670-17686, verificat aici: identic ca semantica, scrieThisform.ndiscfactron/ndiscfactval); la nivel de linie pefrm_articol_factura(:1874-1976, scriepoArticol.discount_unitar*). Nu intra direct in S3 (nu e printre metodele antetului), dar orice cod nou care apeleazado_calculeaza_discountprin nume pe formularul unificat trebuie sa stie care semantica o vrea.do_verifica— pefrm_date_factura(:9440-9453, verificare ANAF) si pefrm_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:
inainte_de_do_termin— Cases specifice fiecarei parti (vezi citatele de la punctul 2), plus constanta de cont ('4111'vs'418') lafacturi_duplicate.Init— seturileDo Case poDate.tipcare 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).do_schimba_tipdoc— exista doar pe factura; pe aviz nu exista mecanismul de schimbare a tipului de document dupa deschiderea formularului (containerulct_clb_fdocde 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.do_cauta_fdoc— nu ramifica, e identic (punctul 1) — poate fi portat o singura data, faraIf nIdTipDoc=....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):
- Apelantul (
ofacturare.prg:factureaza, inainte de oriceCreateobject): construiestepoDate = Createobject("oDateFactura", ...)sipoGeneratorNumere = Createobject("oGeneratorNumere")(:184-185), seteazapoDate.nIdTipDoc(:187-196), cheamapoDate.completeaza_setari_document(...)daca e copiere (:200-206), apoipoGeneratorNumere.ResetNumere()sipoDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)(:208-211) — toate acestea ruleaza inainte ca vreun formular sa existe. OriceInitde mai jos presupunepoDate/poGeneratorNumeredeja populate. frm_date_factura.Init(:9563-9796, 234 linii, citit integral aici) —DoDefault(), apoi masoara inaltimile containerelor de antet intr-un array (laPozitii), decide peDo Case gnScadereStoc/poDate.tipce 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: pect_clb_valutadaca documentul e in valuta si campul valutei apare deasupra tipului si nu are inca valuta aleasa, altfel pect_clb_fdoc(:9788-9793, citat integral la punctul 6). Depinde de:poDate(tip, in_valuta, ...),poGeneratorNumere(creeaza_cursor_seriideja rulat de apelant), variabile globale de firma (gnScadereStoc).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 dupapoDate.in_valuta. Depinde de:poDate.zi_curs/in_valuta/tip,crscursuri(populat de apelant intre pasii 2 si 3, nu de acestInit).frm_alte_date.Init(ferestre_cere_date.vc2:3105-3207, 103 linii, citit integral aici) —DoDefault(), citestepoDate.dataora_exp; daca nu e proforma: cauta ultimul delegat/masina folosite pentru client (apel Oraclecauta_date_ultima_factura[_tip],:3119-3136), populeaza combo-ul de casa dintr-un cursor Oracle nou (v_nom_casa,:3156-3173), seteazaopt_incasatimplicit dacapoDate.incasat<>0; daca e proforma: elimina complet grupul delegat/masina/ incasare (RemoveObjectin cascada,:3192-3201) si redimensioneaza formularul la zona de text aditional. Risc gasit, neurmarit mai departe: pe eroare la interogarea combo-ului de casa,InitcheamapoGeneratorNumere.dezaloca_numar(5)siReturn(:3159-3161) — acelasi tipar de "dezalocare numar pe eroare SQL neasteptata" ca la bug-ul #16 (punctul 6), dar intr-unInit, 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:
- Utilizatorul completeaza antetul, apasa Termina —
inainte_de_do_termintrece, formularul de antet se inchide (pnButon=1). - Apelantul (
factureaza,ofacturare.prg:266-308) construieste cursorul de articole din Oracle (cursor_preturi/etc., functie depoDate.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 inpack_facturare.verifica_cursuri_valute, documentat deja inzi_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] Endifvizualizeaza_curs(oproceduri_curs.prg:8-42) e confirmat: deschideCreateobject("frm_curs", tdDataCurs).Show(1)— chiar formularul din reclamatie. - Punctul central, verificat prin numararea
If/Else/Endifdin fisier: prompt-ul "Doriti sa continuati cu operatii de acest fel?" care seteaza variabila de buclalnRaspunsexista doar in ramuraElsea luiIf lnSucces<0(ofacturare.prg:555-564, in interiorul aceluiasi blocIf/Else/Endifcare se inchide abia la:568). Pe ramura de eroare (lnSucces<0),lnRaspunsnu e niciodata atins. Isi pastreaza valoarea din intrarea in aceasta iteratie a buclei — pe primul document,6(initializat la:174, inainte deDo While lnRaspuns = 6de la:175). - Bucla externa (
Do While lnRaspuns = 6 ... Enddo,:175-571) reintra automat, fara nicio intrebare catre utilizator, pentru calnRaspunse inca6. In aceasta noua iteratie:poGeneratorNumere.ResetNumere()sipoDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)ruleaza din nou (:208-211), apoi se creeaza o instanta noua defrm_date_factura/frm_date_aviz(Createobject,:230) si se arata modal (:235) —poDateinsusi nu e recreat (ramane acelasi obiect, cunract/serie_actdeja setate de utilizator), dar formularul da, deciInitruleaza de la capat. frm_date_factura.Init(:9563-9796) reface layout-ul si, la final, seteaza focus necondiționat pect_clb_fdoc(tip document) in cazul normal:Aceasta e "focusul care revine pe TIP DOCUMENT" din reclamatie — nu un bug izolat de focus, ci comportamentul normal deofacturare.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() ENDCASEInital unui formular nou, aparut unde utilizatorul nu se astepta la un formular nou.- Cand focusul paraseste
ct_clb_fdoc(Tab, click in alta parte — orice), se declanseazaCt_clb_fdoc._combobox1.LostFocus -> thisform.do_schimba_tipdoc()(:9857-9859). Prima linie a acestei metode, inainte de orice verificare "tipul chiar s-a schimbat?":— apeleaza neconditionat handler-ul de LostFocus al combo-ului de serie, indiferent daca tipul documentului s-a schimbat sau nu.ofacturare.vc2:9417 (in do_schimba_tipdoc, INAINTE de guard-ul de la :9419-9421) This.clb_serie_act._cbbase1.LostFocus() - Acel handler (
serii_numere.vc2:138-140, clasaclb_serie_act) cheamagenereazanumar(:114-136):— aloca un numar nou si il scrie pesteserii_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() EndifpoDate.nractprin 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 (unInitper 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):
- Fuzioneaza
Init-urile de antet (frm_date_factura+frm_date_aviz) intr-o metoda unica pe formularul unificat, ramificata pepoDate.nIdTipDoc(punctul 4). Verificare: formularul unificat se deschide pe fiecare din cele ~15 tipuri principale depoDate.tipfolosite azi in cele douaInit-uri, si arata exact aceleasi campuri vizibile/eliminate ca varianta veche — comparatie camp-cu-camp fata de tabelul dindocs\S1_inventar_campuri_formular_unificat.md. - Porteaza cele 29
do_cauta_*(fara ramificare unde sunt identice —do_cauta_fdoc— cu ramificare penIdTipDocunde difera). Verificare: fiecare cautare deschide acelasi dialog, scrie aceleasi proprietati pepoDate, muta focusul identic cu azi. - 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. - 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 singurInitpersistent) e chiar cea care se construieste la pasul 1. - 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. - 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 doiID_FACT/ID_VANZARErezultati, exportate si diff-uite text-cu-text (unealta: acelasi export SQL folosit deja pentruPACK_FACTURAREindocs\ff_*.sql, adaptat peSELECT * 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 siShow(1)modal nu se poate simula headless; se pot testa doar efectele dupa cepoCauta/rezultatul e construit manual (tiparul deja folosit intest_pret_cu_tva_dialog.prg/test_adauga_linie_valuta.prgmentionat in handoff intermediar (sters), punctul 7) — apel direct al metodei cu un obiect simulat, faraShow(). Init-ul complet (redimensionare,RemoveObjectin cascada, repozitionare containere) e verificabil pe proprietati (.Visible,.Height, existenta obiectului dupaType(...)) fara UI vizibil, pentru caCreateobjectruleazaInitfaraShow()— tiparul e deja validat in codebase.- Focusul si secventa reala de
LostFocus(exact ce a produs bug #16) nu se poate reproduce headless — necesita fievfp_ui_harness.ps1cu UI vizibil si input simulat pe masina, fie testare manuala de Marius; simularea unuiSetFocus()/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, conforminventar_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 cufrm_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_numarpe eroare la interogarea combo-ului de casa) — doar semnalat, neurmarit: nu s-a verificat daca apelantul care cheamafrm_alte_dateare aceeasi bucla "retry silentios" cafactureaza, sau daca eroarea aici chiar opreste fluxul curat (Returnexplicit la:3161, spre deosebire de bug #16 unde nu existaReturnechivalent). Merita o cercetare separata, de marimea celei de la punctul 6, inainte de implementare. Clb_serie_act1.TEXT_SIMPLU1.LostFocusdinfrm_facturare_articole2(:19263-19270) — nu citit; nu s-a verificat daca reproduce corectgenereazanumarsau e alt cod orfan ca cel de la punctul 3.- Ramificarea exacta linie-cu-linie pentru restul celor 27 de perechi
do_cauta_*(dincolo dedo_cauta_clientsido_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 inofacturare_comun.vc2(perimetrul #6, doar citire permisa), darvfp_symbols.ps1a indexat-o din.pre_s4butoane.bak.vc2(linii duplicate, nesigure) — nu s-au recitit liniile reale, pentru cafrm_modifica_facturanu intra in portarea S3 (e inlocuita debut_modifica, deja proiectat separat in plan, sectiunea I).
Bug-uri semnalate, nereparate
- Cod orfan in
frm_facturare_articole2:clb_fdoc.cboFdoc.Valid(ofacturare.vc2:19256) cheamathisform.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. - Bug #16, confirmat si localizat complet (punctul 6) — in
ofacturare.prg:555-564(si simetric infactureaza2, liniile ~1061 dupa numerotarea echivalenta, nu verificate linie-cu-linie aici):lnRaspunsnu se reseteaza pe ramura de eroare Oracle a buclei de emitere, cauzand recrearea completa si nesolicitata a formularului de antet. In afara perimetruluiofacturare_comun.vc2/ofacturare_editare.prg(deci nu ciocneste cu #6), dar inofacturare.vc2/ofacturare.prg, cod comun suitei (afecteaza si ROACONT/ROAGEST/etc. daca folosesc acelasifactureaza/factureaza2dinCOMUN— neverificat aici daca alte produse il apeleaza, dar fisierul e inCOMUN\programe, deci probabil da). - Risc structural similar, neconfirmat:
frm_alte_date.Init:3159— acelasi tipar "dezaloca_numarpe eroare SQL neasteptata", posibil fara aceeasi consecinta de reincercare silentioasa, dar nu verificat (vezi "Ce nu s-a putut stabili").