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
42 KiB
S3b — Proiectare: sectiunea pliata "alte date + analitice"
Livrabilul povestii S3b din docs\plan_13_unificare_formular_facturare.md:1838-1854 (deciziile 7,
8, 25, 26). Proiectare pe cod, fara nicio modificare — zero editari, zero
git_sync.ps1/txt2vcx.ps1, zero commit. COMUN\clase\ofacturare_comun.vc2 si
COMUN\programe\ofacturare_editare.prg (perimetrul #6) doar citite, neatinse.
Verdict (rezumat)
S3b nu e o simpla "mutare de controale" cum sugereaza textul din plan. Descoperirea centrala,
negasita in materialele existente: mecanismul de alocare a numarului de chitanta nu porneste doar
din clicul utilizatorului pe opt_incasat — porneste din orice atribuire programatica a
proprietatii .Value, pentru ca opt_incasat.ProgrammaticChange (ferestre_cere_date.vc2:3351-3352)
cheama exact acelasi actualizeaza_tipincasare() ca si .Click (:3250-3251). Init-ul de azi
(:3150) seteaza chiar el Thisform.opt_incasat.Value = 2 cand documentul soseste cu
poDate.incasat<>0 (caz real: copiere, confirmat la ofacturare_stoc.prg:535,
oproceduri_facturare.prg:1186, ofacturare_comun.vc2:4066,4260) — deci azi, deschiderea
dialogului aloca deja un numar de chitanta fara ca userul sa atinga nimic, de fiecare data cand
documentul soseste cu o incasare presetata. In formularul unificat, unde zona nu se mai deschide o
singura data modal ci se pliaza/depliaza repetat pe acelasi obiect poDate persistent, acelasi cod
rulat la fiecare depliere ar realoca/dealoca la fiecare toggle — exact opusul criteriului cerut de
decizia 25. Sectiunea 4 trateaza asta ca risc central, cu recomandare concreta.
A doua descoperire: niciun mecanism de pliere reutilizabil nu exista in prototipul
frm_facturare_articole2 (cautare completa, zero potriviri pe "plia/colaps/accordion/expand" in
ofacturare.vc2) — cel mai apropiat tipar din suita e frm_modific2024.afiseaza_rulaje
(omodificari.vc2:13169-13199, perimetrul #6, citit dar neatins), care refoloseste acelasi idiom
sus/jos deja validat pentru but_modifica/but_salveaza (decizia 9), dar aplicat unui show/hide,
nu unei blocari. Sectiunea 6 detaliaza.
A treia: pentru analiticele read-only (decizia 26), mecanismul existent ct_clb_cautare.do_dezactiveaza()
(caut_ora.vc2:800-806) ascunde doar lupa de cautare, nu blocheaza si textbox-ul de afisare —
cineva tot poate scrie liber in camp. Sectiunea 2 detaliaza gap-ul si completarea minima necesara.
1. Inventarul exact al celor patru grupuri din frm_alte_date
Sursa primara a controalelor: docs\cercetare\inventar_controale_formulare.md §4 si
docs\S1_inventar_campuri_formular_unificat.md (randurile "Pliat — alte date"), reconfirmate aici
direct pe COMUN\clase\ferestre_cere_date.vc2 (clasa frm_alte_date, 2219-3353; garda de validare
inainte_de_do_termin la 3044-3103, Init la 3105-3208).
1.1 Delegat / transport
| Control | Caption real | fisier:linie (ADD OBJECT) |
Vizibilitate | Ruta de scriere (din rute_scriere_antet.md / S1) |
|---|---|---|---|---|
Ct_clb_delegat |
"Delegat" | ferestre_cere_date.vc2:2553 (container ct_clb_cautare-like, caution=do_cauta_delegat) |
pliat, mereu editabil pe document nou; eliminat cu totul cand poDate.eProforma=1 (:3192) |
pe loc, modifica_date_factura (V_ID_DELEGAT), neconditionat — modifica_date_factura_parametri.md:17 |
Ct_clb_masina |
"Masina" | :2569 |
idem, eliminat pe proforma (:3193) |
V_ID_MASINA, neconditionat — :18 |
Ct_clb_agent |
"Agent" | :2537 |
idem, nu e eliminat pe proforma (nu apare in cascada RemoveObject :3192-3201) |
V_ID_AGENT, neconditionat — :19 |
Clb_dataora_exp |
"Data si ora expedierii" | :2443 |
idem, nu eliminat pe proforma; validat obligatoriu in inainte_de_do_termin (:3062-3067, Case Empty(Nvl(poDate.dataora_exp,{})) And poDate.eProforma = 0) |
V_DATAORA_EXP, neconditionat — :20 |
But_modifica1 |
(icon, caction implicit -> do_modifica) |
:2343 |
actiune, nu camp — deschide nom_parteneri_modifica pe delegatul curent (ferestre_cere_date.vc2:2925-2935) |
— |
Validarea de grup (inainte_de_do_termin, :3055-3060): delegatul e obligatoriu doar cand
gcNumeProgram = [ROAFACTURARE] si documentul nu e proforma sau bon fiscal
(!(poDate.eProforma = 1 OR poDate.eBonFiscal = 1)) — nuanta care trebuie pastrata identic in
formularul unificat, nu doar copiata "delegat obligatoriu".
1.2 Incasare (grupul cel mai mare, vezi si sectiunile 3-4)
| Control | Caption real / eticheta dinamica | fisier:linie |
Vizibil cand |
|---|---|---|---|
opt_incasat (radiogrup 4 optiuni) |
"Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | :2638-2685 (default Value=1) |
mereu, dar intreg containerul incasare dispare pe proforma (:3140-3143, Thisform.opt_incasat.Visible = .F., doar pe ramura non-proforma exista oricum) si pe gcNumeProgram=[ROAGEST] sau !Between(poDate.tip,1,4) |
Cb_casa |
"Casa" -> "Banca POS" pe POS (:2967 _lbbase1.Caption='Banca POS') |
:2362 |
ascuns doar la opt_incasat=1 |
Clb_serie_chit |
"Serie chitanta" | :2503 |
vizibil doar la Chitanta (Value=2) |
Clb_nrchit |
"Nr. chitanta" -> "Nr. bon" pe Bon fiscal/POS | :2480 |
ascuns doar la Value=1 |
Clb_incasat |
"Incasat" | :2461 |
ascuns doar la Value=1 |
cmdModificaBon |
icon, caction=do_modifica_bon |
:2526 |
vizibil doar la Bon fiscal (Value=3) |
chkPOS |
"POS" | :2409 |
vizibil la Bon fiscal (Value=3, bifabil) si la POS (Value=4, fortat .T. si ascuns — :2845 thisform.chkPOS.Value=1 apoi .Visible=.F.) |
chkDetaliat |
"Detaliat" | :2396 |
vizibil doar la Bon fiscal |
cboTipFactura |
combo, langa "Tip factura" | :2378 |
mereu (langa incasare), scrie poDate.tip_saft |
Masina de stari completa e la sectiunea 3-4. Eticheta "Casa"/"Banca POS" si vizibilitatea lui
chkPOS/chkDetaliat fac parte din aceeasi metoda unica (actualizeaza_tipincasare) — nu sunt
conditii separate de refacut, sunt randuri din acelasi Do Case.
1.3 Adresa de facturare
| Control | Caption | fisier:linie |
Vizibilitate |
|---|---|---|---|
clb_adresa_facturare |
"Adresa facturare" | :2422 |
pliat, mereu editabil; nu eliminat pe proforma (absent din cascada :3192-3201) |
do_cauta_adresa (metoda, nu control separat) |
— | :2911-2926 |
cauta pe poDate.id_client, seteaza poDate.adresa_facturare/poDate.id_facturare |
Ruta de scriere: V_ID_FACTURARE, neconditionat — modifica_date_factura_parametri.md:21.
1.4 Text aditional
| Control | Caption | fisier:linie |
Vizibilitate |
|---|---|---|---|
Ed_tx_simplu1 |
"Text aditional (max. 1000 caractere)" | :2585 |
pliat, mereu editabil; nu eliminat pe proforma — dimpotriva, pe proforma ramane singurul grup activ, formularul se redimensioneaza in jurul lui (:3184-3207) |
_checkbox1 ("Listare detaliata") |
"Listare detaliata" | :3002-3011 |
ADD OBJECT separat, ControlSource=poDate.nListareDetaliata — nu e in cascada celor patru grupuri din B, e propriul lui control, langa text aditional in layout, dar conceptual apartine grupului de raportare (I-bis) alaturi de tip_saft/efactura, nu grupului "text aditional" |
Ruta: V_TEXT_ADITIONAL, neconditionat, fara NVL — modifica_date_factura_parametri.md:23.
Observatie structurala pentru mockup: _checkbox1 (listare detaliata) e cablat separat de
chkDetaliat din grupul incasare (control diferit, aceeasi semantica probabila — ambiguitate deja
semnalata in S1, randul 57, neinchisa aici). Formularul unificat trebuie sa aleaga unul singur
dintre cele doua controale existente, nu sa le porteze pe amandoua.
2. Analiticele coborate din antet (decizia 8, nuantata de decizia 26)
Controale: Ct_clb_venchelt ("Venit / cheltuiala"), Ct_clb_sectie ("Sectie"), Ct_clb_responsabil
("Responsabil"), Ct_clb_lucrare ("Lucrare") — toate patru container ct_clb_cautare (subclasa
caut_ora.vcx), azi pe frm_date_factura/frm_date_aviz, nu pe frm_alte_date
(inventar_controale_formulare.md §3; S1 randurile 39-42). Live in sectiunea I a planului ca grup
"Pliat — analitice", distinct de cele patru grupuri ale lui frm_alte_date — dar aceeasi sectiune
pliata a formularului unificat, conform textului S3b ("peste ele coboara analiticele din antet").
2.1 Ce inseamna "afisare read-only" mecanic
Sursa: docs\cercetare\rute_scriere_antet.md §1 — verdict deja stabilit, DA pentru
ID_SECTIE/ID_RESPONSABIL/ID_LUCRARE printr-o cale noua (frm_facturi.do_editare_factura ->
frm_modific2024, perimetrul #6), neconfirmat pentru ID_VENCHELT. Pentru #13, indiferent de
raspunsul acelei rezerve, decizia 26 e explicita: zero editare din formularul unificat, oricare ar
fi ruta reala din Oracle.
Mecanismul de blocare vizuala, verificat pe cod, cu un gap real:
ct_clb_cautare.do_dezactiveaza() (caut_ora.vc2:800-806):
PROCEDURE do_dezactiveaza
This.lactiv = .F.
This.img_cautare.Visible = .F.
This.ctooltip = This.clb_tx_cautare.text_simplu1.ToolTipText
This.clb_tx_cautare.text_simplu1.ToolTipText = []
This.clb_tx_cautare.text_simplu1.Refresh()
ENDPROC
This.lactiv gateaza doar deschiderea dialogului de cautare — DblClick (:822-826),
KeyPress pe Enter/Tab/F7 (:829-836), img_cautare.Click (:840-843) — toate testeaza
This.Parent(.Parent).lactiv inainte sa cheme do_apeleaza(). Nu seteaza ReadOnly pe
clb_tx_cautare.text_simplu1 — textbox-ul bindat (ControlSource=This.cvar_afisata, setat in
Init, caut_ora.vc2:814-817) ramane direct tastabil. Pentru un camp cu adevarat read-only (decizia
26 cere explicit "nu se editeaza"), do_dezactiveaza() trebuie completat, nu doar apelat: se
adauga This.clb_tx_cautare.text_simplu1.ReadOnly = .T. (si eventual .TabStop = .F., ca sa nu
primeasca focus deloc) langa apelul existent — o linie noua, in tiparul deja recomandat de plan
(sectiunea I: "singura reteta completa e clb_tx_data.dezactiveaza()... se generalizeaza de la ea").
Consecinta pentru "un singur buton deschide tot" (decizia 9): do_dezactiveaza() se apeleaza o
singura data, la constructia sectiunii, si nu se mai cheama niciodata do_activeaza() pentru
aceste patru controale — nici cand but_modifica deblocheaza restul antetului. E o exceptie
deliberata de la "un singur control comanda toata protectia antetului" (plan I:843-853), simetrica cu
exceptia deja acceptata pentru grupurile B/C la decizia 25 (sectiunea 5 de mai jos).
2.2 Eticheta / tooltip
Nicio propunere de text nu exista inca in materialele citite — de scris la implementare. Recomandare
minima, in stilul deja folosit in codebase (ex. Ct_clb_gestiune_init.ToolTipText, un citat complet
la inventar_controale_formulare.md:93-95, deci precedent de lungime/ton acceptat): eticheta ramane
neschimbata ("Venit / cheltuiala"/"Sectie"/"Responsabil"/"Lucrare"), ToolTipText nou de tipul
"Se editeaza din Editare factura (articole, cantitati, preturi) — buton disponibil pe fiecare linie
a notei contabile", ca sa nu para camp stricat. Text exact — de decis de Marius, vezi sectiunea 9.
2.3 Nivel antet vs. nivel linie — de retinut la afisare
rute_scriere_antet.md §1.4: editarea reala prin #6 e pe linie de nota, nu pe antet — un
document poate ajunge cu sectii diferite pe linii diferite dupa editare (imposibil la emitere). Cand
formularul unificat afiseaza analiticele "cu valoarea de antet" (cum spune decizia 26), afiseaza de
fapt o singura valoare reprezentativa (probabil prima linie sau variabila de sesiune retinuta la
emitere), nu o agregare a liniilor — daca liniile diverg dupa o editare #6, afisarea din #13 devine
"aproximativa, nu autoritara". Nu e o contradictie de rezolvat aici (decizia 26 exista tocmai ca sa
evite ca #13 sa scrie peste diferentierea de linie), dar merita un rand explicit in criteriul de
"gata" (sectiunea 10): afisarea trebuie sa spuna clar ce valoare arata, nu sa pretinda ca e valoarea
unica a documentului daca liniile difera.
3. actualizeaza_tipincasare
Definita la COMUN\clase\ferestre_cere_date.vc2:2698-2856, metoda proprie a clasei frm_alte_date.
Comuta vizibilitatea intregului subgrup de incasare pe This.opt_incasat.Value (patru ramuri, tabel
complet la sectiunea 1.2 si in inventar_controale_formulare.md:130-135) si, in aceeasi metoda,
aloca/dezaloca numerele — cele doua responsabilitati (UI si alocare) sunt impletite in acelasi
Do Case, nu separate. Fiecare ramura incepe prin a dezaloca defensiv celelalte trei tipuri de
numar, apoi aloca pe cel curent daca e cazul:
opt_incasat.Value |
Dezaloca la intrare | Aloca | Alte efecte |
|---|---|---|---|
| 1 (Fara incasare) | 16, 3, 26 | — | poDate.incasat=0, poDate.nr_incasare=0 |
| 2 (Chitanta) | 3, 16, 26 | creeaza_cursor_serii(16) + clb_serie_chit.genereazaNumar() (:2783) — doar daca nRezultatSerii=0 (nu realoca daca seria era deja creata) |
poDate.incasat=poDate.totalctva |
| 3 (Bon fiscal) | 3, 16, 26 | do_aloca_nr_bon([CLICK]) (:2807) -> cod 3 |
idem, plus tip_saft poate deveni 751 daca gnEFactura_tip_saft_bf e setat |
| 4 (POS/Card) | 3, 16, 26 | do_aloca_nr_pos([CLICK]) (:2844) -> cod 26 |
idem, chkPOS.Value fortat 1 si ascuns |
Ce depinde de ea: opt_incasat.Click (:3250-3251) si opt_incasat.ProgrammaticChange
(:3351-3352) — vezi sectiunea 4, e disjunctia care conteaza. Nu are alti apelanti directi in
ferestre_cere_date.vc2 (cautare vfp_symbols.ps1 -Grep actualizeaza_tipincasare -CodeOnly
recomandata la implementare pentru confirmare exhaustiva pe tot proiectul — nu rulata aici din motive
de buget, dar apelantul extern deja cunoscut e citat mai jos, la 3.1).
3.1 Apel extern, cu context important
COMUN\clase\ofacturare.vc2:14919-14926 (frm_facturare_articole(2).inainte_de_do_termin, inainte
de ofrmdatesupl.Show(1)):
ofrmdatesupl = Createobject("frm_alte_date")
ofrmdatesupl.nnrbon = poDate.nract
If (poDate.eBonFiscal = 1)
With ofrmdatesupl
.opt_incasat.Value = 3 && INCASARE CU BON FISCAL
.actualizeaza_tipincasare() && apel EXPLICIT, dupa .Value=
.opt_incasat.option1.TabStop = .F.
...
Apelantul seteaza .Value=3 si cheama explicit actualizeaza_tipincasare() imediat dupa — ceea
ce, coroborat cu descoperirea de la sectiunea 4 (.Value= deja declanseaza ProgrammaticChange ->
aceeasi metoda), inseamna ca metoda ruleaza de doua ori la acest apel. Nu produce o eroare vizibila
(fiecare ramura e idempotenta: dezaloca ce era alocat, realoca acelasi tip), dar confirma ca autorul
codului nu s-a bazat exclusiv pe evenimentul ProgrammaticChange — semn ca mecanismul lui exact
(cand se declanseaza, cand nu) n-a fost niciodata documentat explicit, doar "acoperit din ambele
parti". Portarea "ca atare" trebuie sa pastreze acest apel dublu identic, nu sa-l "curete" ca
redundant — eliminarea unuia dintre cele doua declansatoare ar putea rupe o presupunere neverificata
in alta parte a codului.
3.2 "Se muta ca atare" — ce inseamna concret
Corpul metodei (:2698-2856) nu are nicio dependenta de fereastra-container (Thisform.* peste
tot, nu This.Parent.*) — se poate muta byte-cu-byte pe formularul unificat, cu conditia ca
toate cele noua controale referite (opt_incasat, cb_casa, clb_serie_chit, clb_nrchit,
clb_incasat, _shape3, lb_simplu1, cmdModificaBon, chkPOS, chkDetaliat, cboTipFactura)
sa existe cu exact aceleasi nume pe noul formular, in acelasi container logic (Thisform, nu un
subpanou cu alt scope) — altfel referintele nekvalificate Thisform.xxx pica silentios pe obiect
inexistent. Aceasta e o constrangere de implementare directa: sectiunea pliata trebuie sa fie
container-transparenta pentru aceasta metoda (fie pusa direct pe Thisform, fie metoda insasi
adaptata sa foloseasca This.Parent-ul corect) — nu poate fi mutata intr-un obiect-container separat
fara o trecere explicita de Thisform.xxx -> This.Parent.xxx pe toate liniile.
4. Alocarea si dezalocarea numerelor — evenimentele exacte
4.1 Evenimente de alocare (azi)
opt_incasat.Click(:3250-3251) — interactiune reala a utilizatorului pe radiogrup.opt_incasat.ProgrammaticChange(:3351-3352) — descoperire centrala a acestui raport, negasita in materialul de plan sau in cele patru rapoarte de referinta: in VFP, orice atribuire.Value = nfacuta din cod (nu de utilizator) declanseazaProgrammaticChange, nuClick. Clasa are ambele metode cablate pe acelasi apel (actualizeaza_tipincasare()), deci orice loc din cod care scrieopt_incasat.Value = ...aloca/dealoca numere, indiferent de intentie.- Chiar
Init-ul clasei foloseste acest canal::3150,Thisform.opt_incasat.Value = 2— ruleaza doar dacapoDate.incasat <> 0la momentul deschiderii. Verificat pe cod (nu presupus) capoDate.incasatpoate fi nenul inainte cafrm_alte_datesa se deschida, prin cel putin patru cai reale:ofacturare_stoc.prg:535,oproceduri_facturare.prg:1186,ofacturare_comun.vc2:4066,ofacturare_comun.vc2:4260(toate patru scriupoDate.incasat = incasat, parametru primit de la apelant — flux de copiere/reluare a unui document cu incasare deja stabilita). Concluzie verificata: azi, deschiderea dialoguluifrm_alte_datepe un astfel de document aloca deja un numar de chitanta la Init, inainte ca userul sa apese orice. Nu e o ipoteza — e mecanismul descris la sectiunea 3, declansat de linia 3150. do_modifica_bon/do_modifica_pos(:3001-3022) — dezaloca explicit + realoca, la apasarea butonului "Modifica bon"/echivalent POS.do_aloca_nr_bon/do_aloca_nr_pos(:2864-2907) — chemate din interiorul luiactualizeaza_tipincasare(ramurile 3 si 4), nu independent.
4.2 Evenimente de dezalocare (azi)
- In interiorul
actualizeaza_tipincasare— fiecare ramura dezaloca defensiv celelalte trei tipuri inainte de a (re)aloca pe cel ales (tabelul de la sectiunea 3). Efect: comutarea intre optiuni, in cadrul aceleiasi sesiuni de dialog, e curata — nu ramane niciun numar orfan din optiunea anterioara. inainte_de_do_renunt(:3023-3043) — la anulare (buton Renunt / ESC pe dialogul modal):Gap real, preexistent, verificat pe cod: acest bloc dezaloca 16 (chitanta) si 3 (bon fiscal), dar nu 26 (POS) — nici direct, nici in blocul urmator (inainte_de_do_renunt (ferestre_cere_date.vc2:3025-3030) If Type('poGeneratorNumere') = 'O' poGeneratorNumere.dezaloca_numar(16) && chitanta poGeneratorNumere.dezaloca_numar(3) && bon fiscal ...:3033-3035, care dezaloca doar 16 din nou, conditionat). Daca utilizatorul alege POS/Card si apoi anuleaza dialogul, numarul POS alocat ramane alocat, nedezalocat. Nu e introdus de S3b — exista deja in codul de azi — dar merita semnalat explicit ca risc mostenit (sectiunea 9), pentru ca S3b il "muta ca atare" (plan, textul povestii), deci il si multiplica pe orice cale noua prin care sectiunea pliata s-ar putea "renunta" fara sa fie un dialog modal cu propriul buton Renunt.Initpe eroare Oracle (:3159) —poGeneratorNumere.dezaloca_numar(5)(factura) daca interogareav_nom_casaesueaza, cuReturnimediat dupa (linie explicita, spre deosebire de bug-ul #16 dins3_portare_antet.md§6, unde nu existaReturnechivalent). Semnalat, neurmarit mai departe aici — acelasi risc de arhitectura ca #16, dar pe alt fisier.
4.3 Riscul central pentru formularul unificat, si recomandarea
Problema mecanica exacta: in fluxul de azi, frm_alte_date e un dialog modal, instantiat o
singura data per emitere (ofacturare.vc2:14921), asa ca Init-ul lui (si declansarea eventuala de
la linia 3150) ruleaza o singura data. In formularul unificat, grupul de incasare devine o
sub-sectiune a unui panou pliabil, pe acelasi obiect de formular persistent — daca implementarea
naiva re-populeaza opt_incasat.Value din poDate de fiecare data cand sectiunea se depliaza (ex.
"la fiecare Show/Visible=.T. al panoului, resincronizeaza controalele cu starea curenta a lui
poDate"), fiecare depliere ar re-declansa ProgrammaticChange -> actualizeaza_tipincasare() ->
dezaloca tot + realoca pe optiunea curenta — incalcand direct criteriul din S3b ("deschiderea si
inchiderea formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar").
Recomandare concreta: populeaza opt_incasat.Value din poDate o singura data, la
constructia sectiunii de antet a documentului (echivalentul lui Init de azi, care ruleaza o data per
sesiune de emitere/editare) — nu la fiecare toggle de pliere/depliere. Plierea/deplierea trebuie sa
fie strict un toggle de Visible/Height pe containerul vizual, fara nicio atingere a
proprietatii .Value a lui opt_incasat sau a oricarui alt control cu ProgrammaticChange cablat pe
alocare. Aceasta e exact genul de regula care trebuie verificata explicit la implementare (criteriu de
"gata" concret la sectiunea 10), nu presupusa.
5. Grupul de incasare pe document deja emis (decizia 25)
Ce spune decizia: orice camp schimbat din grupul C (incasare) pe un document deja emis
"marcheaza documentul pentru regenerare" — dar regenerarea nu exista in etapa I, deci planul tine
campurile blocate, cu explicatie la hover, pana la etapa II (plan :192-201, :1847-1848).
rute_scriere_antet.md §3 confirma independent ca nu exista nicio cale directa de scriere pentru
grupul C dupa emitere (verdict "NU" pentru toate controalele, "PARTIAL neconfirmat" doar pentru o
cale indirecta prin editarea notei — vezi §3.2 acolo, ramane deschisa).
5.1 Mecanic: pe ce proprietate, pe ce conditie
Decizia 9 stabileste ca but_modifica deblocheaza tot antetul, fara exceptie de camp — dar
decizia 25 taie o exceptie explicita peste asta pentru grupurile B si C. Nu exista inca (verificat,
rute_scriere_antet.md §2-3) un mecanism de blocare per-grup separat de but_modifica; trebuie
construit nou, dar dupa un tipar deja folosit in codebase pentru exact intrebarea "acest document are
deja un numar, sau e nou": chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
(ofacturare_comun.vc2:5736-5750, citat deja in plan I:839 si S1 randul 60). Decizia 9 abandoneaza
acest test ca poarta pe cele patru bife de identitate (serie/numar/data/scadenta) — dar testul insusi
ramane exact instrumentul potrivit pentru o alta intrebare: "documentul e nou sau deja emis?", care e
tocmai conditia ceruta de decizia 25 pentru grupurile B/C.
Recomandare mecanica: la construirea antetului, se calculeaza o singura data un flag (ex.
lDocumentExistent = !EMPTY(NVL(poRec.numar_act,0)) sau echivalentul lui pe obiectul poDate/poRec
folosit de formularul unificat — de confirmat la implementare care obiect poarta numar_act in noua
arhitectura, pentru ca poRec era specific lui frm_modifica_factura, perimetrul #6). Controalele
grupului C (opt_incasat si tot ce atarna de el din tabelul sectiunii 1.2) primesc Enabled = .F.
neconditionat de starea lui but_modifica cand lDocumentExistent = .T., in etapa I. Cand
but_modifica deblocheaza restul antetului, acest grup ramane dezactivat — e o exceptie fixa, nu
una care se recalculeaza la fiecare click pe but_modifica.
Grupul B (tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi) are
aceeasi tratare — dar acele controale traiesc azi in frm_date_factura/frm_date_aviz
(sectiunea I a planului, nu sectiunea B de aici), deci blocarea lor nu e in perimetrul S3b propriu-zis;
se semnaleaza aici doar pentru consecventa mecanismului (acelasi flag, acelasi tipar de Enabled=.F.
peste orice stare a lui but_modifica).
5.2 La emitere, se comporta ca azi
Pentru un document nou (nu inca emis), lDocumentExistent = .F., grupul C e activ normal — se
comporta identic cu frm_alte_date de azi, cu masina de stari intreaga din sectiunile 3-4. Nimic nu
se schimba pe calea de emitere initiala; blocarea se aplica doar cand formularul unificat deschide
un document deja scris in VANZARI (calea de editare, nu de creare).
5.3 Tooltip / explicatie la hover
Ca la sectiunea 2.2, textul exact nu exista inca — de propus la implementare, in tonul deja folosit in
codebase. Continut minim necesar: sa spuna ca modificarea incasarii pe un document emis nu e inca
posibila din acest formular ("disponibil dupa ce regenerarea documentului e implementata" sau
echivalent), nu doar "camp dezactivat" fara explicatie — plan :198-200 cere explicit "cu explicatie
la hover".
6. Mecanismul de pliere — nu exista, se construieste nou dupa un tipar existent
Cautare directa in prototip: vfp_symbols.ps1 -Grep (si grep text simplu) pe "plia|colaps|
collapse|expand|acordeon|accordion" in COMUN\clase\ofacturare.vc2 (unde traieste
frm_facturare_articole2, :15741-19355) — zero potriviri. Prototipul are controalele de antet
montate direct pe formular (confirmat deja de s3_portare_antet.md §3 si de
inventar_controale_formulare.md §2), dar niciun mecanism de show/hide de sectiune — nu exista
nici macar un buton candidat.
Cel mai apropiat tipar real din suita: frm_modific2024.afiseaza_rulaje
(COMUN\clase\omodificari.vc2:13169-13199, perimetrul #6, doar citit aici). Cod complet:
PROCEDURE afiseaza_rulaje
* COLAPSEAZA SAU MARESTE PAGEFRAME-UL RULAJE
lcPicture = this.but_afiseaza_rulaje.Picture
...
llAfiseazaRulaje = 'sus'$m.lcPicture
IF m.llAfiseazaRulaje
lcPicture2 = STRTRAN(m.lcPicture,'sus','jos') ...
ELSE
lcPicture2 = STRTRAN(m.lcPicture,'jos','sus') ...
ENDIF
this.but_afiseaza_rulaje.Picture = m.lcPicture2
...
this.pgfArticole.Visible = m.llAfiseazaRulaje
this.but_copiazaR.Visible = m.llAfiseazaRulaje
this.but_modificaR.Visible = m.llAfiseazaRulaje
this.but_stergeR.Visible = m.llAfiseazaRulaje
thisform.resize_grid1()
ENDPROC
Trei observatii directe:
- Foloseste exact acelasi idiom sus/jos deja validat si documentat pentru
but_modifica/but_salveaza(decizia 9,docs\cercetare\buton_comutator_picture.md, citat in plan:63-75): citeste starea curenta din numele fisieruluiPicture(contine "sus" sau "jos"), calculeaza perechea opusa prinSTRTRAN, rescriePicture+cPictureDown+cPictureUpimpreuna — aceeasi precautie semnalata in plan ("numai.Picturenu ajunge: hover-ul standard dinbuton.MouseEnter/MouseLeaverescriePicturedincPictureUp/cPictureDown"). - Nu e o clasa reutilizabila, e cod ad-hoc per caz — toggle direct pe
.Visibleal unei liste fixe de controale, plus un apel manual de reflow (resize_grid1(), metoda proprie a formularului, nu un mecanism generic). Nu exista in nicio biblioteca de clase dinCOMUN(_baza.vcx, etc., cautare separata, zero potriviri pentru o clasa "panou pliabil"/"expander"/"accordion") un container gata facut de pliere. S3b trebuie sa scrie cod nou, dupa acest tipar, nu sa instantieze o clasa existenta. - E in perimetrul #6, doar citire — poate fi copiat ca idee/tipar la implementare, dar
omodificari.vc2insusi nu se atinge cat timp #6 e in lucru (decizia 30).
6.1 Ce trebuie sa scrie S3b, concret
- Un buton comutator per sectiune pliata (sau unul singur pentru toata zona "alte date +
analitice", de decis la implementare — plan I nu specifica granularitatea), cu imaginile
sus/jos deja existente in
COMUN\grafice(aceleasi perechi folosite pentrubut_afiseaza_rulaje, de identificat exact numele fisierelor la implementare — nu verificate aici, doar tiparul de mecanism). - Toggle de
.Visible/.Heightpe grupul de controale al sectiunii (delegat/transport, incasare, adresa, text aditional, analitice), plus un apel de reflow echivalent luiresize_grid1()— pentru ca restul formularului (butoane, alte sectiuni de mai jos) sa nu ramana cu goluri sau suprapuneri cand sectiunea se pliaza/depliaza. VFP nu reflow-eaza automat un layout absolut-pozitionat (Left/Topfixe pe fiecare control) — de asta si tiparul dinfrm_date_factura.Init(citat ins3_portare_antet.md§5, "masoara inaltimile containerelor de antet intr-un arraylaPozitii") cat siafiseaza_rulajefac manual acest calcul; nu exista un layout manager automat de reutilizat. - Populare o singura data a starii initiale (sectiunea 4.3) — separat de toggle-ul de vizibilitate,
ca sa nu retrigger-eze
ProgrammaticChange. - Intrebare deschisa, semnalata deja in
s3_portare_antet.md§5 (nu redusa aici): daca lookup-urile Oracle ale luifrm_alte_date.Init(delegat/masina ultima factura, cursorulv_nom_casa) raman eager (rulate la construirea formularului, ca azi) sau devin lazy (amanate pana la prima depliere a sectiunii, ca sa nu incarce round-trip-uri Oracle inutile cand userul nu ajunge niciodata acolo). Ambele optiuni sunt compatibile cu criteriul "deschiderea/inchiderea nu aloca numere" doar daca populate keeping in mind sectiunea 4.3 (populare o singura data, indiferent de varianta aleasa) — de decis de Marius, vezi sectiunea 9.
7. Ordinea de executie
Pasi care lasa suita functionala dupa fiecare — calea veche (frm_alte_date ca dialog separat) nu
se sterge in etapa I, cf. textul povestii ("frm_alte_date nu se sterge cat timp calea veche mai e
in uz").
- Construieste sectiunea vizuala pliata (patru grupuri din sectiunea 1 + analiticele din
sectiunea 2), fara nicio logica de alocare/validare inca — doar controale + toggle de
Visible/Height (sectiunea 6). Criteriu: fiecare control existent in
frm_alte_date/antetul de analitice apare o data, cu aceeasi eticheta, in sectiunea corecta a formularului unificat — verificare camp-cu-camp fata de tabelele din sectiunile 1-2 de aici. - Porteaza
actualizeaza_tipincasareca atare (sectiunea 3), cu toate cele noua referinteThisform.xxxrezolvate corect in noul container (sectiunea 3.2). Criteriu: alegerea fiecarei optiuni dinopt_incasatproduce exact aceleasiVisible/etichete pe controalele dependente ca pefrm_alte_datede azi, verificabil headless (sectiunea 8). - Rezolva explicit riscul de la sectiunea 4.3 — populare unica a starii, toggle de pliere fara
atingere de
.Value. Criteriu, verificabil headless: pe un document de test cupoDate.incasat<>0la construirea formularului, se depliaza si se plie aza sectiunea de trei ori consecutiv fara a atinge niciun control din grup —poGeneratorNumere(cursorul lui de numere alocate) arata acelasi numar de chitanta la finalul celor trei toggle-uri ca la inceput, nu unul nou de fiecare data. - Porteaza delegat/transport si adresa de facturare (
do_cauta_delegat/do_cauta_masina/do_cauta_agent/do_cauta_adresa/do_modifica), cu validarea dininainte_de_do_termin(sectiunea 1.1). Criteriu: fiecare cautare deschide acelasi dialog si scrie aceleasi proprietati pepoDateca azi (comparatie directa, cod identic mutat). - Adauga analiticele read-only (sectiunea 2), inclusiv completarea
do_dezactiveaza()cuReadOnly=.T.explicit. Criteriu: tastarea directa in oricare din cele patru campuri nu modifica valoarea afisata (verificare pe proprietate, headless). - Implementeaza blocarea grupului C pe document deja emis (decizia 25, sectiunea 5), cu flag-ul
de "document existent" si exceptia peste
but_modifica. Criteriu: pe un document nou,but_modifica(cand exista, dupa S3/decizia 9) nu afecteaza grupul C (mereu activ); pe un document deja emis, grupul C ramaneEnabled=.F.inainte si dupa apasarea luibut_modifica. - Verificare de regresie end-to-end: emite acelasi document (numerar, chitanta, bon fiscal, POS —
toate patru, nu doar unul) o data pe calea veche (
frm_alte_datedialog) si o data pe formularul unificat, comparapoDate-ul rezultat inainte de scriere in Oracle (aceleasi campuri de incasare, acelasi numar alocat) — tipar deja folosit ins3_portare_antet.md§7 pentru S3, reutilizat aici pe grupul mai mic al lui S3b.
Depinde de S3 (planul o spune explicit) — pasii 4 si 6 presupun ca but_modifica/blocarea antetului
(proiectata in S3, sectiunea I a planului) exista deja ca mecanism, nu doar ca design pe hartie.
8. Ce nu se poate testa headless
Precedent direct: s3_portare_antet.md §8, si memoria de proiect
(grid-coloane-nu-se-materializeaza-headless — coloanele de grid nu se materializeaza sub -A -T,
exista harness UI vizibil separat care le citeste corect).
Se POATE testa headless (contrar primei intuitii, pentru ca e logica pe proprietati, nu pictura pe ecran):
- Toata masina de stari
actualizeaza_tipincasare—Createobject()fara.Show()tot ruleazaInitsi tot cabl eaza evenimentele;thisform.opt_incasat.Value = ndeclanseazaProgrammaticChangeidentic cu UI vizibil sau nu. Verificarile de alocare/dezalocare de la pasul 3 al sectiunii 7 sunt testabile 100% headless, pe proprietatile luipoGeneratorNumere. - Vizibilitatea controalelor dupa toggle (
.Visible,.Height, existenta obiectului viaType(...)) — acelasi motiv, confirmat deja des3_portare_antet.md§8 pentru mecanismul similar dinInit-urile de antet.
NU se poate testa headless:
- Aspectul vizual real dupa pliere/depliere (suprapuneri, goluri, pozitionarea corecta a controalelor de sub sectiune dupa reflow-ul manual, sectiunea 6.1) — layout absolut-pozitionat, fara reflow automat; verificarea ceruta e "arata bine pe ecran", nu o proprietate booleana — necesita harness UI vizibil (memoria de proiect citata mai sus) sau verificare manuala de Marius.
- Cele patru dialoguri modale (
do_cauta_delegat/do_cauta_masina/do_cauta_agent/do_cauta_adresa) — interactiunea reala de alegere dintr-o lista nu se simuleaza headless; se poate testa doar efectul dupa ce rezultatul cautarii e construit manual (tiparul deja folosit in proiect, citat ins3_portare_antet.md§8, pct. 1). - Lookup-urile Oracle din
Init(delegat/masina ultima factura,v_nom_casa) — necesita conexiune Oracle reala (harnessul suporta asta prinMARIUSM_AUTO, dar tot nu e "headless" in sensul de "fara dependinte externe") si date de test care sa existe pentru clientul folosit. - Butonul
cmdModificaBon/do_modifica_bon— deschideviz_config_serii_complet WITH 3(oserii_numere.prg), alt formular modal, aceeasi limitare ca dialogurile de cautare.
9. Riscuri si capcane
- Riscul central (sectiunea 4.3): re-populare a starii
opt_incasatla fiecare toggle de pliere ar re-declansa alocare/dezalocare, incalcand direct criteriul cerut de decizia 25/S3b. Tratat explicit ca pas 3 in ordinea de executie — nu de lasat "se rezolva de la sine prin arhitectura", pentru ca mecanismul de declansare (ProgrammaticChange) e usor de lovit accidental de orice cod ulterior care "resincronizeaza UI-ul cu modelul" intr-un loc gresit. - Gap preexistent, nu introdus de S3b, dar mostenit prin "muta ca atare":
inainte_de_do_renunt(:3025-3030) nu dezaloca numarul POS (26) la anulare — doar chitanta (16) si bon fiscal (3). Daca formularul unificat introduce vreo cale noua de "renunta la document" care nu mapeaza exact pe acest cod, riscul se poate agrava (numar POS ramas alocat orfan, de fiecare data cand utilizatorul incepe un document cu POS si renunta). Recomandare: fie se corecteaza in trecere (nu e in perimetrul strict al portarii "ca atare", dar e o linie), fie se semnaleaza explicit ca bug cunoscut de preluat separat. _checkbox1/"Listare detaliata" vs.chkDetaliat(sectiunea 1.4) — doua controale cu semantica probabil identica, ambiguitate deja semnalata in S1 (randul 57), neinchisa aici. Formularul unificat trebuie sa aleaga unul singur; alegerea gresita (portarea amandurora ca doua campuri distincte) ar introduce un camp duplicat fara sens pentru utilizator.do_dezactiveaza()insuficient pentru read-only real (sectiunea 2.1) — daca implementarea apeleaza doar metoda existenta fara completarea deReadOnly, decizia 26 nu e respectata mecanic: campul arata blocat (fara lupa) dar tot accepta taste.- Referinte
Thisform.xxxnekvalificate inactualizeaza_tipincasare(sectiunea 3.2) — orice decizie de a pune sectiunea pliata intr-un container/obiect separat (nu direct peThisform) rupe tacit aceste referinte, cu eroare de "obiect inexistent" la runtime, nu la compilare. - Dependinta pe S3:
but_modificasi testul "document existent" (sectiunea 5.1) nu exista inca nicaieri in cod — proiectate doar pe hartie in plan (sectiunea I). Pasii 4 si 6 din sectiunea 7 nu pot fi implementati complet inainte ca S3 sa livreze acel mecanism, doar proiectati. - Bug-ul de
sectie(omodificari.vc2:13941, semnalat deja inrute_scriere_antet.md§1.3 si in plan:761-764) — nu afecteaza direct S3b (analiticele sunt read-only aici, deci S3b nu scrie niciodataid_sectie), dar afecteaza increderea in ce se afiseaza: daca bug-ul e real, coloanaid_sectiedin Oracle poate sa nu reflecte eticheta aratata dupa o editare din #6. In afara perimetrului S3b de reparat (e in #6), dar relevant pentru cine citeste afisarea din #13 dupa o editare de la #6.
10. Criteriul de "gata", rescris verificabil
Textul din plan (:1849-1852) spune: "un document cu incasare prin bon fiscal emis din formularul
unificat produce aceleasi randuri si acelasi numar de bon ca pe calea veche; deschiderea si inchiderea
formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar; iar pe un document
emis analiticele se vad dar nu se pot edita." Precizat mai jos, pe fiecare bucata:
- Paritate de emitere, pe toate patru tipurile de incasare, nu doar bon fiscal: se emite acelasi
document de test (client, articole, cantitati, preturi identice) o data pe calea veche
(
frm_date_factura/frm_date_aviz->frm_facturare_articole(2)->frm_alte_date) si o data pe formularul unificat, pentru fiecare din Fara incasare / Chitanta / Bon fiscal / POS-Card. Se comparalcListaIncasare(stringul asamblat inainte descrie_factura2, sectiunea 3.1 alrute_scriere_antet.md) si numarul alocat (poDate.nr_incasare) — trebuie sa fie identice pe fiecare din cele patru cazuri, nu doar pe bon fiscal. - Non-alocare la toggle de pliere, testabila headless (sectiunea 7, pasul 3, deja formulat ca
assert concret pe
poGeneratorNumere) — pe doua scenarii de start, nu unul: document nou (poDate.incasat=0la construire) si document venit din copiere cu incasare presetata (poDate.incasat<>0, sectiunea 4.1 punctul 3) — al doilea caz e cel care azi deja aloca la deschidere, exact scenariul pe care criteriul trebuie sa-l acopere explicit. - Blocarea grupului C, pe ambele stari ale antetului: pe un document deja emis, campurile din
grupul de incasare raman
Enabled=.F.atat inainte cat si dupa apasarea luibut_modifica— nu doar "la deschidere" (ce ar lasa loc unei regresii dacabut_modificale-ar debloca din greseala odata cu restul). - Analiticele, pe ambele stari: pe un document emis, cele patru campuri (venit/cheltuiala, sectie,
responsabil, lucrare) afiseaza valoarea curenta din sursa lor (sectiunea 2.3 — de precizat exact
care sursa la implementare) si resping orice tentativa de tastare directa sau de deschidere a
dialogului de cautare — verificat pe proprietatea
ReadOnly/lactiv, nu doar vizual. - Delegat/transport/adresa/text aditional — paritate camp-cu-camp cu
frm_alte_datede azi, pe cel putin un document proforma (grup delegat/incasare eliminat, sectiunea 1.4) si unul non-proforma (grup complet), ca sa acopere ambele ramuri aleInit-ului de azi (:3109-3207).
Depinde de: S3 (but_modifica, mecanismul de blocare a antetului). Nu depinde de: etapa II (regenerarea) — grupurile B/C raman blocate prin design in etapa I, nu prin absenta temporara a unei functionalitati care ar trebui testata aici.
Ce ramane de decis de Marius
- Textul exact al tooltip-urilor pentru analiticele read-only (sectiunea 2.2) si pentru grupul de incasare blocat pe document emis (sectiunea 5.3) — doar continutul minim necesar e propus aici, nu formularea finala.
- Granularitatea butonului/butoanelor de pliere: un singur comutator pentru toata zona "alte date
- analitice", sau unul separat per subgrup (delegat/transport, incasare, adresa, text, analitice)?
Plan sectiunea I nu specifica, iar tiparul gasit (
afiseaza_rulaje) e per-sectiune unica, nu per-formular-intreg — precedentul nu decide singur granularitatea (sectiunea 6.1).
- analitice", sau unul separat per subgrup (delegat/transport, incasare, adresa, text, analitice)?
Plan sectiunea I nu specifica, iar tiparul gasit (
- Eager vs. lazy pentru lookup-urile Oracle din
Init(delegat/masina ultima factura, casa) — semnalat si ins3_portare_antet.md§5 ca discutie deschisa, reconfirmat aici pentru acelasi cod (sectiunea 6.1, ultimul punct). - Corectarea gap-ului de dezalocare POS la anulare (sectiunea 9, punctul 2) — se repara in trecere ca parte a S3b, sau se lasa exact ca azi si se semnaleaza separat ca bug de preluat ulterior?
_checkbox1vs.chkDetaliat(sectiunea 1.4, sectiunea 9 punctul 3) — care dintre cele doua controale de "listare detaliata" devine campul unic in formularul unificat; ambiguitatea ramane deschisa din S1 si nu s-a inchis aici.
Handoff
Cercetare incheiata, nu intrerupta la mijloc — toate cele zece sectiuni cerute in briefing sunt
complete, cu citate fisier:linie verificate direct pe fisierele reale (nu .bak). Niciun cod
atins, nicio interogare Oracle rulata (nu a fost nevoie — tot ce trebuia era in codul VFP deja citit
sau in cele patru rapoarte de referinta). Fisierul e complet la aceasta versiune; nu e nevoie de o
sesiune de continuare pentru S3b ca atare. Urmatorul pas natural (nu al acestei sarcini) ar fi S3
insusi (blocarea antetului, but_modifica) — S3b depinde de el pentru pasii 4 si 6 din sectiunea 7,
dar proiectarea de aici nu asteapta acel livrabil, doar implementarea o va astepta.