Files
roafacturare/docs/cercetare/s3b_alte_date_analitice.md
2026-09-09 22:19:22 +03:00

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)

  1. opt_incasat.Click (:3250-3251) — interactiune reala a utilizatorului pe radiogrup.
  2. 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 = n facuta din cod (nu de utilizator) declanseaza ProgrammaticChange, nu Click. Clasa are ambele metode cablate pe acelasi apel (actualizeaza_tipincasare()), deci orice loc din cod care scrie opt_incasat.Value = ... aloca/dealoca numere, indiferent de intentie.
  3. Chiar Init-ul clasei foloseste acest canal: :3150, Thisform.opt_incasat.Value = 2 — ruleaza doar daca poDate.incasat <> 0 la momentul deschiderii. Verificat pe cod (nu presupus) ca poDate.incasat poate fi nenul inainte ca frm_alte_date sa 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 scriu poDate.incasat = incasat, parametru primit de la apelant — flux de copiere/reluare a unui document cu incasare deja stabilita). Concluzie verificata: azi, deschiderea dialogului frm_alte_date pe 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.
  4. do_modifica_bon/do_modifica_pos (:3001-3022) — dezaloca explicit + realoca, la apasarea butonului "Modifica bon"/echivalent POS.
  5. do_aloca_nr_bon/do_aloca_nr_pos (:2864-2907) — chemate din interiorul lui actualizeaza_tipincasare (ramurile 3 si 4), nu independent.

4.2 Evenimente de dezalocare (azi)

  1. 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.
  2. inainte_de_do_renunt (:3023-3043) — la anulare (buton Renunt / ESC pe dialogul modal):
    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
        ...
    
    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 (: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.
  3. Init pe eroare Oracle (:3159) — poGeneratorNumere.dezaloca_numar(5) (factura) daca interogarea v_nom_casa esueaza, cu Return imediat dupa (linie explicita, spre deosebire de bug-ul #16 din s3_portare_antet.md §6, unde nu exista Return echivalent). 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:

  1. 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 fisierului Picture (contine "sus" sau "jos"), calculeaza perechea opusa prin STRTRAN, rescrie Picture + cPictureDown + cPictureUp impreuna — aceeasi precautie semnalata in plan ("numai .Picture nu ajunge: hover-ul standard din buton.MouseEnter/MouseLeave rescrie Picture din cPictureUp/cPictureDown").
  2. Nu e o clasa reutilizabila, e cod ad-hoc per caz — toggle direct pe .Visible al 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 din COMUN (_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.
  3. E in perimetrul #6, doar citire — poate fi copiat ca idee/tipar la implementare, dar omodificari.vc2 insusi 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 pentru but_afiseaza_rulaje, de identificat exact numele fisierelor la implementare — nu verificate aici, doar tiparul de mecanism).
  • Toggle de .Visible/.Height pe grupul de controale al sectiunii (delegat/transport, incasare, adresa, text aditional, analitice), plus un apel de reflow echivalent lui resize_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/ Top fixe pe fiecare control) — de asta si tiparul din frm_date_factura.Init (citat in s3_portare_antet.md §5, "masoara inaltimile containerelor de antet intr-un array laPozitii") cat si afiseaza_rulaje fac 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 lui frm_alte_date.Init (delegat/masina ultima factura, cursorul v_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").

  1. 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.
  2. Porteaza actualizeaza_tipincasare ca atare (sectiunea 3), cu toate cele noua referinte Thisform.xxx rezolvate corect in noul container (sectiunea 3.2). Criteriu: alegerea fiecarei optiuni din opt_incasat produce exact aceleasi Visible/etichete pe controalele dependente ca pe frm_alte_date de azi, verificabil headless (sectiunea 8).
  3. 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 cu poDate.incasat<>0 la 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.
  4. Porteaza delegat/transport si adresa de facturare (do_cauta_delegat/do_cauta_masina/ do_cauta_agent/do_cauta_adresa/do_modifica), cu validarea din inainte_de_do_termin (sectiunea 1.1). Criteriu: fiecare cautare deschide acelasi dialog si scrie aceleasi proprietati pe poDate ca azi (comparatie directa, cod identic mutat).
  5. Adauga analiticele read-only (sectiunea 2), inclusiv completarea do_dezactiveaza() cu ReadOnly=.T. explicit. Criteriu: tastarea directa in oricare din cele patru campuri nu modifica valoarea afisata (verificare pe proprietate, headless).
  6. 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 ramane Enabled=.F. inainte si dupa apasarea lui but_modifica.
  7. 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_date dialog) si o data pe formularul unificat, compara poDate-ul rezultat inainte de scriere in Oracle (aceleasi campuri de incasare, acelasi numar alocat) — tipar deja folosit in s3_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 ruleaza Init si tot cabl eaza evenimentele; thisform.opt_incasat.Value = n declanseaza ProgrammaticChange identic cu UI vizibil sau nu. Verificarile de alocare/dezalocare de la pasul 3 al sectiunii 7 sunt testabile 100% headless, pe proprietatile lui poGeneratorNumere.
  • Vizibilitatea controalelor dupa toggle (.Visible, .Height, existenta obiectului via Type(...)) — acelasi motiv, confirmat deja de s3_portare_antet.md §8 pentru mecanismul similar din Init-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 in s3_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 prin MARIUSM_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 — deschide viz_config_serii_complet WITH 3 (oserii_numere.prg), alt formular modal, aceeasi limitare ca dialogurile de cautare.

9. Riscuri si capcane

  1. Riscul central (sectiunea 4.3): re-populare a starii opt_incasat la 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.
  2. 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.
  3. _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.
  4. do_dezactiveaza() insuficient pentru read-only real (sectiunea 2.1) — daca implementarea apeleaza doar metoda existenta fara completarea de ReadOnly, decizia 26 nu e respectata mecanic: campul arata blocat (fara lupa) dar tot accepta taste.
  5. Referinte Thisform.xxx nekvalificate in actualizeaza_tipincasare (sectiunea 3.2) — orice decizie de a pune sectiunea pliata intr-un container/obiect separat (nu direct pe Thisform) rupe tacit aceste referinte, cu eroare de "obiect inexistent" la runtime, nu la compilare.
  6. Dependinta pe S3: but_modifica si 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.
  7. Bug-ul de sectie (omodificari.vc2:13941, semnalat deja in rute_scriere_antet.md §1.3 si in plan :761-764) — nu afecteaza direct S3b (analiticele sunt read-only aici, deci S3b nu scrie niciodata id_sectie), dar afecteaza increderea in ce se afiseaza: daca bug-ul e real, coloana id_sectie din 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:

  1. 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 compara lcListaIncasare (stringul asamblat inainte de scrie_factura2, sectiunea 3.1 al rute_scriere_antet.md) si numarul alocat (poDate.nr_incasare) — trebuie sa fie identice pe fiecare din cele patru cazuri, nu doar pe bon fiscal.
  2. 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=0 la 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.
  3. 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 lui but_modifica — nu doar "la deschidere" (ce ar lasa loc unei regresii daca but_modifica le-ar debloca din greseala odata cu restul).
  4. 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.
  5. Delegat/transport/adresa/text aditional — paritate camp-cu-camp cu frm_alte_date de 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 ale Init-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

  1. 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.
  2. 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).
  3. Eager vs. lazy pentru lookup-urile Oracle din Init (delegat/masina ultima factura, casa) — semnalat si in s3_portare_antet.md §5 ca discutie deschisa, reconfirmat aici pentru acelasi cod (sectiunea 6.1, ultimul punct).
  4. 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?
  5. _checkbox1 vs. 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.