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

41 KiB

S4b — Bara de butoane si meniul de adaugare

Proiectare pe cod, READ-ONLY (fara editari, fara git_sync.ps1/txt2vcx.ps1, fara commit), pentru povestea S4b din docs\plan_13_unificare_formular_facturare.md:1890-1899 (textul decis in sectiunea J, :911-1101). Nu s-a atins COMUN\clase\ofacturare_comun.vc2 si COMUN\programe\ofacturare_editare.prg (perimetrul #6) — doar citite, cand au aparut in cautari.

Verdict (esenta, 10 randuri)

Cele trei bucati din decizia 13 sunt implementabile fara cod nou major, dar cu trei corectii fata de textul din plan, toate in favoarea utilizatorului: (1) golul de contract nu e doar but_urmator_tot1.Visible lipsa pe tip 2/6/26 — pe tip 52 formularul nu intra deloc in ramura lui, Do Case nu are niciun Case care sa-l prinda, deci pierde si titlul, si eliminarea coloanei cSerie, nu doar butonul "tot"; (2) tiparul RORIS (frm_tranzit, dialog bespoke) nu e cel mai ieftin de refolosit — suita are deja, folosit chiar in acest formular pentru retur (do_cauta_facturi), un mecanism generic de selectie multipla cu bifare, cauta_alfa(..., tnTipReturn=1), mai aproape de cerinta („dialog modal cu coloana de bifat si criterii de cautare") decat un formular nou; (3) „unitatea de selectie e rata" pe contract nu e valabil pentru toate contractele — cursorul crsarticole1 are doua ramuri disjuncte, OPT_FACTURARE=3 (articole reale) si OPT_FACTURARE IN (1,2) (rate de scadentar), iar un contract e mereu pe una singura; eticheta „Alege ratele de facturat…" e corecta doar pe ramura a doua. Echivalenta „tot" = „alege total" tine azi pe o singura rutina comuna, do_adauga_articol, dar do_adauga_tot nu parcurge crsarticole1 deloc — pe contract, „adauga tot" ar trebui sa fie scris, nu doar facut vizibil. Detaliile, cu fisier:linie, mai jos.


1. Inventarul butoanelor de azi

1.1 frm_facturare_articole (formularul de compunere in productie, ofacturare.vc2:10968-15739)

Grid sursa (comanda/lista preturi) = grd_articole (RecordSource=crsarticole, ofacturare.vc2:11570). Grid sursa-contract = grd_contracte (RecordSource=crsarticole1, :11891). Grid destinatie (linii factura) = grd_factura (RecordSource=crsfactura, :12263).

Control Clasa Caption/Picture L/T/W/H ToolTipText fisier:linie
But_modifica1 but_modifica (fara caption) modific_sus.bmp 773/61/30/27 "Modificare (CTRL+M)" :11192
But_sterge1 but_sterge (fara caption) sterg_sus.bmp 811/61/30/27 "Stergere (CTRL+D)" :11229
But_renunt1 but_renunt (fara caption) renunt_sus.bmp 792/1/30/27 "Renuntare (ESC)" :11202
But_reset1 but_reset (fara caption) reset_sus.bmp 303/295/30/27 "Reseteaza" :11213
But_urmator1 but_urmator, caction=do_urmator urmator1.bmp 343/348/30/27 "Verificare" :11237
But_urmator2 but_urmator, caction=do_urmator2 urmator1.bmp 343/143/30/27 "Verificare" :11247
But_urmator_tot1 but_urmator_tot, caction=do_adauga_tot urmator1_tot.bmp, fara Caption, fara ToolTipText (clasa insasi nu are niciuna din ele — cmd_butoane.vc2:414-425) 343/378/30/27, Visible=.F. implicit — :11257
But_retur but_retur, caction=do_retur retur1.bmp, Visible=.F. implicit 343/407/30/27 "Retur" :11221

Niciun buton "linie noua" (but_nou) — o linie se adauga alegand un rand din grd_articole / grd_contracte si apeland do_adauga_articol (dublu-click / Enter, mecanism de grid standard, nu citit exhaustiv aici), nu prin APPEND BLANK pe crsfactura. "Detalii linie" nu exista ca buton separat — But_modifica1 e azi echivalentul: deschide frm_articol_factura pe randul curent din crsfactura (do_modifica, :13746-13914), reface plafonul de cantitate din cursorul sursa (crsarticole sau crsarticole1, ales prin poArticol.opt_facturare, :13778) si reconstruieste proprietatile de discount/pret in valuta.

"Adauga tot" — But_urmator_tot1.Click -> do_adauga_tot (:13169-13198):

PROCEDURE do_adauga_tot
    If Used('crsarticole') And Reccount('crsarticole') > 0
        Select crsarticole
        Scan
            ...
            Thisform.do_adauga_articol(.T.)     && tlContract = omis => .F. implicit
            ...
        Endscan
    Else
        aMessageBox("Nu exista articole de adaugat!", 48, "Atentie")
    Endif
ENDPROC

Parcurge exclusiv crsarticole. Nu exista nicio ramura care sa parcurga crsarticole1 — deci chiar daca s-ar face vizibil pe tip 2/6/26/52, "adauga tot" nu ar aduce nimic pe un contract al carui grd_contracte/crsarticole1 are randuri (rate sau articole de contract), pentru ca butonul citeste doar cursorul celalalt. E un gol de cod, nu doar de vizibilitate — vezi sectiunea 5.

Conventia de clase de butoane (confirmat, inventar_controale_formulare.md): clasa de baza buton (_cmd_base.vc2:16) are Caption="" implicit — butoanele sunt doar-imagine 30x27px prin design. but_nou (cmd_butoane.vc2:214-227, caption=do_adauga, ToolTipText="Adaugare (CTRL+N)") si but_sterge (:354-366, caption=inainte_de_do_sterge, tot fara Caption propriu la nivel de clasa) au deja ToolTipText, dar niciodata Caption — "eticheta, nu iconita muta" ceruta de decizia 13 e o schimbare reala, nu o simpla refolosire a clasei: fie se seteaza Caption pe instanta (clasa buton are proprietatea, pur si simplu nefolosita azi), fie se creeaza o varianta de clasa cu Caption implicit.

1.2 frm_facturare_articole2 (prototip, ofacturare.vc2:15741-19355)

Arhitectura diferita: un singur grid grd_factura cu editare inline prin combo-uri in celule (cCodMat.cboCodmat, cDenumire.cCboDenumire) — nu exista grd_articole/crsarticole separat.

Control Clasa Caption/Picture L/T/W/H ToolTipText fisier:linie
But_nou1 but_nou (caption suprascris pe instanta, vezi mai jos) nou_sus.bmp 719/175/30/27 "Adaugare (CTRL+N)" :15936
But_sterge1 but_sterge sterg_sus.bmp 747/175/30/27 "Stergere (CTRL+D)" :15955
But_renunt1 but_renunt renunt_sus.bmp 716/1/30/27 "Renuntare (ESC)" :15944

Sunt deja unde trebuie: Top=175, gridul la Top=204 (:15936, :15955) — deasupra tabelului, exact pozitionarea ceruta de decizia 13.

But_nou1.Click -> do_adauga (:17118-17122, override propriu pe instanta, nu caction mostenit de la clasa but_nou):

PROCEDURE do_adauga
    Select crsFactura
    APPEND BLANK
    this.grd_factura.SetFocus()
ENDPROC

Adauga direct un rand gol si da focus in grid — nu cheama do_adauga_articol, nu verifica stoc, nu deschide niciun dialog. Completarea articolului se face dupa, prin editare inline in celule (cboCodmat/cCboDenumire) — acesta e tiparul care se leaga direct de S4 (cautare pe server, vezi sectiunea 6), nu de meniul de adaugare in masa.

Nu exista But_modifica/detalii linie in acest prototip — editarea se face inline, in celula.

1.3 Tabel comparativ

frm_facturare_articole (productie) frm_facturare_articole2 (prototip)
Pozitia butoanelor de linie lateral, intre cele doua griduri (Left=773+) deasupra gridului (Top=175, gridul la Top=204) — cerinta decizie 13
"Linie noua" nu exista — se alege dintr-un grid sursa But_nou1 -> APPEND BLANK + focus
"Sterge linie" But_sterge1, lateral But_sterge1, deasupra
"Detalii linie" But_modifica1 -> frm_articol_factura (dialog complet) nu exista — editare inline
Sursa de articole 2 griduri separate, crsarticole/crsarticole1 niciunul — combo pe cod/denumire in celula
Caption pe butoane fara, peste tot (doar iconite) fara, peste tot (doar iconite) — nici prototipul nu rezolva "eticheta"

Concluzie pentru implementare: pozitionarea se ia din prototip, comportamentul de adaugare in masa (do_adauga_tot/dialoage de alegere) se ia din formularul de productie (prototipul nu are deloc aceasta logica — n-a fost construit pentru documente cu sursa), iar eticheta pe buton nu exista azi in niciunul din cele doua, e de adaugat explicit in ambele cazuri.


2. xmenu() — contract si exemple

Definitie: COMUN\programe\proceduri_comune.prg:755-822 (identica, byte-cu-byte in structura, cu COMUN\programe\oproceduri_comune.prg:1550-1617 — a doua e cea incarcata efectiv, SET PROCEDURE ADDITIVE peste prima, dar comportamentul e acelasi).

Procedure XMENU
    Lparameters TCITEMS, TNBAR
    && TCITEMS = optiuni separate prin ";" ; "\-" = separator vizual (bara, nu optiune selectabila,
    &&           nu se trece prin ALLTRIM); "\<X" in fata unei litere = accelerator de tastatura
    && TNBAR    = optiunea initial selectata (default 1)
    ...
    RETURN IIF( LASTKEY()=27, 0, m.NSELECT )   && 0 la ESC sau la orice tastatura care nu alege
ENDPROC
  • Popup-ul apare la pozitia mouse-ului (Mrow()/Mcol()), cu DEFINE BAR cate unul per optiune.
  • Selectia se prinde in GETCHOICE (oproceduri_comune.prg:1660-1666): m.NSELECT = Bar().
  • Returul e 1-based (numarul barei alese), 0 daca s-a apasat ESC (Lastkey()=27) — verificat prin Empty(m.lnOptiune) in tot codul (0 e Empty pentru numeric).
  • Nu exista parametru de "titlu" separat — primul item e primul rand din popup, nu un header.

Trei exemple reale, gata de copiat:

  1. inainte_de_do_modifica (ofacturare_comun.vc2:4928-4935) — meniu static, doua optiuni, exact tiparul pe care planul il citeaza ca precedent pentru butonul unic:

    PROCEDURE inainte_de_do_modifica
        Local lnOptiune
        lnOptiune = xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')
        Do Case
            Case lnOptiune = 1
                This.do_modifica()
            Case lnOptiune = 2
                This.do_editare_factura()
        Endcase
    ENDPROC
    
  2. do_verifica (ofacturare_comun.vc2:4879-4886) — meniu static, trei optiuni, cu accelerator pe fiecare literal:

    lnOptiune = xmenu('Verifica codurile fiscale la fiecare \<data a facturilor;Verifica doar la \<inceputul lunii;Verifica doar la \<sfarsitul lunii')
    IF EMPTY(m.lnOptiune)
        RETURN
    ENDIF
    
  3. Meniul de borderouri (ofacturare_comun.vc2:5006-5029) — meniu construit dinamic, cu optiuni suplimentare adaugate conditionat si un separator vizual \-:

    lcMeniuPlus = ''
    IF !(EMPTY(m.lnOptiuniPlus) OR ... )
        FOR lnOptiunePlus = 1 TO m.lnOptiuniPlus
            lcTitlu = ALLTRIM(GETWORDNUM(m.lcListaMeniu, m.lnOptiunePlus, '|'))
            lcMeniuPlus = m.lcMeniuPlus + ';' + m.lcTitlu
        ENDFOR
    ENDIF
    lcMeniu = m.lcMeniu + IIF(!EMPTY(m.lcMeniuPlus), ';\-' + m.lcMeniuPlus, '')
    lnOptiune = xmenu(m.lcMeniu)
    If Empty(m.lnOptiune)
        Return
    Endif
    DO CASE
        CASE m.lnOptiune = 1
            ...
    

    Acesta e tiparul direct aplicabil la S4b: lcMeniu se construieste cu Do Case poDate.tip (vezi sectiunea 3), apoi un singur xmenu(lcMeniu) si un Do Case lnOptiune = N care ramifica pe pozitia in lista construita — nu pe numar fix, ca la exemplele 1-2, pentru ca lista variaza pe tip. Trebuie tinut sincron lcMeniu (textul optiunii) cu ordinea in care se evalueaza lnOptiune in Do Case, altfel un Case lnOptiune = 3 nimereste alta optiune decat cea afisata — riscul concret al unui meniu dinamic, absent la cele statice.

Ce NU ofera xmenu(): niciun mecanism de dezactivare a unei optiuni individuale (doar prezenta/ absenta din lista construita), nicio pictograma pe item, niciun submeniu. Pentru meniul din decizia 13 (4-5 optiuni pe sursa, variabile) e suficient — nu e nevoie de mai mult.


3. Conditiile de vizibilitate ale meniului, pe tip de document

Sursa: frm_facturare_articole.Init, Do Case poDate.eProforma / poDate.lCopiere / poDate.tip, ofacturare.vc2:15109-15248 (citit integral, nu esantion).

Ramura (Case) tip(uri) Titlu but_urmator_tot1.Visible but_retur.Visible fisier:linie
eProforma = 1 proforma (orice tip) "PROFORMA" .T. — :15110-15113
lCopiere copiere factura/aviz "COPIERE FACTURA/AVIZ ..." .T. — :15115-15120
Inlist(tip,1,5,7,10) lista de preturi (lei/valuta/credit note/fiscala valuta) — — .T. :15122-15127
Inlist(tip,2,6) contract (lei/valuta) "FACTURA PE CTR. ..." — (nesetat, ramane .F.) — :15129-15143
tip = 3 comanda "FACTURA LA COMANDA ..." .T. — :15144-15150
tip = 4 din avize "FACTURA DIN AVIZE" .T. — :15151-15167
Inlist(tip,21,28,42,47) aviz catre clienti din comanda "AVIZ DE EXPEDITIE DIN COMANDA" .T. — :15168-15177
Inlist(tip,22,29) aviz din lista de preturi "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" — — :15178-15186
tip = 23 transfer subunitati "TRANSFER INTRE SUBUNITATI" — — :15187-15195
tip = 41 retur transfer "RETUR TRANSFER" — — :15196-15205
tip = 25 transfer din comanda "TRANSFER INTRE SUBUNITATI PE BAZA DE COMANDA" .T. — :15206-15215
tip = 26 aviz din contract "AVIZ DE EXPEDITIE DIN CONTRACTUL ..." — (nesetat) — :15216-15234
Inlist(tip,8,9) factura retur lei/valuta — .T. — :15236-15241
tip = 24 aviz retur "RETUR AVIZ DE EXPEDITIE" .T. — :15242-15248
(niciun Case) tip = 52 nesetat — ramane titlul implicit al formularului nesetat — —

Corectie fata de plan (plan_13...md:924-926, :1882-1883): planul spune "tipurile de contract (2, 6, 26, 52) nu sunt in lista [de vizibilitate a but_urmator_tot1]". E adevarat pentru 2/6/26, dar incomplet pentru 52: poDate.tip = 52 nu apare in niciun Case al acestui Do Case — nu doar ca nu primeste but_urmator_tot1.Visible = .T., ci nu primeste nimic: nu i se seteaza titlul (lb_titlu_alb_b121.Caption ramane cel implicit al formularului, probabil gol sau invechit), nu i se scoate coloana cSerie din grid (ramane vizibila, desi contractul in valuta n-are serie de lot relevanta aici), nu i se schimba eticheta coloanei de cantitate. Pe cod, tip 52 se comporta azi ca un tip necunoscut care a ajuns totusi sa deschida formularul (posibil pentru ca fluxul Oracle il recunoaste — Inlist(tnTip,2,26,6,52) la cursor_contract, ofacturare.prg:283 — dar formularul de articole nu l-a "prins" niciodata in Do Case-ul lui local). Consecinta pentru S4b: reparatia corecta nu e "adauga 52 la linia lui 2/6" (ar ramane fara titlu/fara eliminare cSerie), ci adauga-l explicit ca al treilea membru al ramurii Inlist(poDate.tip, 2, 6) de la :15129, identic cu cum a fost tratat deja in frm_date_factura.Init (ofacturare.vc2:9530: Case Inlist(poDate.tip, 2, 6, 52), si inca o data la :9632, :9670) — acolo tip 52 e deja grupat corect cu 2/6, doar in acest formular de articole a fost omis.

Ce inseamna gruparea 2/6/26/52 pentru continutul meniului: toate patru sunt "contract" — 2/6 = factura pe contract (lei/valuta), 26 = aviz pe contract, 52 = factura fiscala in valuta pe contract (COMUN\docs\tipuri_documente_facturare.md:23,27,38,50). Optiunile din tabelul J raman aceleasi pentru toate patru (schimba doar cuvantul "Adauga"/"Alege" vs. eventual "Avizeaza" pe 26, daca se pastreaza conventia de limbaj factura/aviz existenta in restul formularului — lcTipDoc calculeaza deja acest cuvant in alte ramuri ale aceluiasi Do Case, e.g. :15175,15185,15194, dar nu e setat deloc pe ramura contract — inca o mica omisiune, lcTipDoc ramane la valoarea implicita "factura" si pe aviz de contract).

Meniul propus, per sursa (reluat din tabelul J, cu corectiile de mai sus):

Sursa (tip) Optiunile din xmenu()
lista de preturi (1,5,7,10) Cauta in lista de preturi… · Alege din nomenclator… · Retur de articole…
contract, OPT_FACTURARE=3 (2,6,26,52) Adauga tot din contract · Alege articolele din contract… · Cauta in lista de preturi… · Alege din nomenclator…
contract, OPT_FACTURARE IN (1,2) (2,6,26,52) Adauga toate ratele · Alege ratele de facturat… · Cauta in lista de preturi… · Alege din nomenclator…
comanda (3) Adauga tot din comanda · Alege din comanda… · Cauta in lista de preturi… · Alege din nomenclator…
avize (4) / avize din comanda (21,28,42,47) / transfer din comanda (25) Adauga tot din avize · Alege avizele… · Alege liniile din avize… · Cauta in lista de preturi… · Alege din nomenclator…
retur (8,9,24) Adauga tot din facturile alese · Alege liniile de returnat… · Cauta in lista de preturi… · Alege din nomenclator…

Diferenta fata de tabelul din plan e explicata in sectiunea 4 (contractul are doua meniuri posibile, nu unul) si in sectiunea 9 (retur nu are azi un pas separat "alege facturile" la nivelul acestui formular — vezi mai jos).


4. Dialogul de alegere selectiva

4.1 Nu porni de la frm_tranzit (RORIS) — porneste de la cauta_alfa

Raportul de referinta (COMUN\docs\cercetare\import_roris_roaacnpro.md) descrie corect arhitectura generala (buton conditionat -> metoda unica -> dialog cu bifare si criterii -> populare aditiva prin INSERT, fara scriere Oracle pana la salvare) — dar frm_tranzit insusi e un formular specific ROAACNPRO, care nu exista in ROAFACTURARE si n-ar trebui portat ca formular.

ROAFACTURARE are deja, in productie, in acelasi frm_facturare_articole care e subiectul acestei povesti, mecanismul cerut — cauta_alfa() (COMUN\programe\cauta_alfa.prg:16-260), functia de cautare generica folosita de zeci de do_cauta_* din toata suita. Are deja tot ce cere decizia 13:

  • coloana de bifat: cand tnTipReturn=1, cauta_alfa adauga singura o coloana ales peste cursorul de rezultate (cauta_alfa.prg:124: Select *, 0 As ales From &lcCursort ... Into Cursor &lcCursor) si titlul devine explicit "Alegeti ... (mouse-click pe numar sau apasati SPACE)" (oproceduri_facturare.prg:2104);
  • criterii de cautare: parametrul tcStringCriterii deschide cauta_alfa_form_plus, varianta cu bara de criterii (cauta_alfa.prg:22); fara el, cautarea alfa/browse standard tot filtreaza interactiv pe coloanele afisate;
  • populare aditiva, fara scriere in baza: returul (tnTipReturn=1) e un XML cu randurile bifate, convertit local prin Xmltocursor(...) (exemplu direct, ofacturare.vc2:9196) — nimic nu ajunge la Oracle in acest pas;
  • e deja folosit in acest exact formular, pentru un caz de "alege documentul sursa": vezi 4.2.

Recomandare de proiectare: dialoagele "Alege..." din meniul S4b se construiesc pe reteta cauta_alfa(..., tnTipReturn=1), cu tcselect/tcfiltru diferite pe sursa (comanda/contract/avize/ retur), nu pe un formular nou stilizat dupa frm_tranzit. Ramane de portat din RORIS doar ideea, nu codul: buton unic condiționat, metoda de intrare unica, populare aditiva — toate trei deja adevarate si pentru cauta_alfa.

4.2 Precedentul direct: retur multi-factura

frm_date_factura.do_cauta_facturi (ofacturare.vc2:9173-9212) foloseste deja exact acest tipar, pentru cazul "alege mai multe facturi sursa de retur":

lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .T.)
If !Empty(lcXMLFacturi) and gnButon = 1
    Xmltocursor(lcXMLFacturi, "crsFacturiTemp")
    ...
    poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")

si caut_facturi_multiple_client (oproceduri_facturare.prg:2091-2121) cheama direct cauta_alfa(..., lnTipReturn = Iif(tlFacturiMultiple, 1, 0)). Important pentru sectiunea 9: acest pas ruleaza azi la antet (frm_date_factura), inainte ca formularul de articole (si deci bara de butoane S4b) sa existe — poDate.listaid e fixat o singura data, cursorul crsarticole se incarca o singura data din el (cursor_retur_document, cu V_LISTAID). Optiunea "Adauga tot din facturile alese" din meniul S4b poate refolosi direct do_adauga_tot/crsarticole asa cum e azi (sursa e deja bounded). O optiune noua "Alege facturile de returnat…" la nivelul barei de butoane ar insemna insa a permite schimbarea setului de facturi sursa dupa ce formularul de articole s-a deschis — azi nu exista acest flux (odata setat, poDate.listaid nu se rescrie din articole). E o decizie de proiectare noua, nu o simpla mutare a codului existent (vezi 9.3).

4.3 Ce cursor-sursa pe fiecare tip, si ce inseamna "linie" acolo

Sursa Cursor(oare) populat(e) la intrarea in factureaza/factureaza2 Ce e o "linie" pentru dialogul de alegere fisier:linie
comanda (3, 21, 25, 28, 42, 47) crsarticole <- pack_facturare.cursor_comanda un articol de pe COMENZI_ELEMENTE, cu id_pol mostenit direct de pe linia comenzii ofacturare.prg:292-293
contract, OPT_FACTURARE=3 (2,6,26,52) crsarticole1 <- pack_facturare.cursor_contract, ramura CTR_ARTICOLE un articol al contractului, id_pol derivat prin CTR_ARTICOLE.ID_POL_ART -> CRM_POLITICI_PRET_ART ff_...COMUN_PACK_FACTURARE.sql:2752-2836
contract, OPT_FACTURARE IN (1,2) (2,6,26,52) crsarticole1 <- acelasi cursor, ramura CTR_SCADENTAR (UNION ALL) o rata de scadentar (ID_RATA, NR_RATA, DATA_RATA, DATA_SCADENTA, DEN_RATA); id_articol e NULL, cantitate e mereu 1, gestionabil=0 ff_...COMUN_PACK_FACTURARE.sql:2836-2924
contract, oricare din cele doua — cautare libera crsarticole <- acelasi apel, dar cu pack_facturare.cursor_preturi legat in interior lista de preturi normala a operatorului (nu e "libera de politica", vezi idpol_comanda_contract.md E.3) ff_...COMUN_PACK_FACTURARE.sql:2940-2948
avize (4) crsarticole <- pack_facturare.cursor_avize un rand de pe avizul sursa ofacturare.prg:294-295
retur (8, 9, 24) crsarticole <- pack_facturare.cursor_retur_document, filtrat pe V_LISTAID (facturile alese la antet, 4.2) un articol al facturii/facturilor deja alese; nicio coloana id_vanzare/id_vanzare_det in cursor — legatura cu factura sursa se pierde inainte sa ajunga in VFP (docs\cercetare\legatura_linie_retur.md:8-20) ff_...COMUN_PACK_FACTURARE.sql:3949-4062

Pe contract, crsarticole1 e populat printr-un singur UNION ALL, dar cele doua ramuri sunt disjuncte pe OPT_FACTURARE: pentru un contract cu OPT_FACTURARE=3, WHERE A.OPT_FACTURARE = 3 elimina ramura de rate; pentru OPT_FACTURARE IN (1,2), WHERE ... IN (1,2) elimina ramura de articole. Un contract nu produce niciodata ambele tipuri de rand in acelasi crsarticole1. Deci eticheta din meniu ("Adauga tot din contract" vs. "Adauga toate ratele") trebuie sa depinda de crsarticole1.opt_facturare (coloana exista in cursor, :2751,2893 — poate fi citita din primul rand dupa incarcare), nu poate fi un text static ca in tabelul din plan.

Daca crsarticole1 iese gol (contract fara articole/rate configurate), formularul de azi elimina complet grd_contracte/But_urmator2 si redistribuie inaltimea catre grd_articole (ofacturare.vc2:15294-15324, If !Used('crsarticole1') ... Thisform.RemoveObject('grd_contracte')) — documentul se comporta ca o factura "libera" din lista de preturi. Meniul S4b trebuie sa verifice aceeasi conditie inainte sa ofere "Adauga tot din contract"/"Alege ratele…": daca cursorul nu exista sau e gol, optiunea nu apare deloc (nu apare goala).

4.4 Populare aditiva si "nicio scriere pana la salvare"

Deja adevarat prin constructie, pentru toata familia do_adauga_articol: fiecare rand ales (fie prin do_adauga_tot, fie prin rezultatul unui cauta_alfa) trece prin Thisform.do_adauga_articol(...), care termina cu Insert Into crsfactura(...) (cursor local, .T. Buffering) — niciun apel Oracle in acest pas (Oracle e atins abia la salvarea documentului, finalizeaza_factura/scrie_factura2, in afara acestei povesti). Un dialog nou de alegere trebuie doar sa produca lista de randuri bifate (cursor XML din cauta_alfa) si sa cheme aceeasi rutina rand cu rand — vezi sectiunea 5, e punctul care garanteaza si echivalenta cu "tot".

4.5 Zero rezultate — mesaj, nu tacere

RORIS nu spune nimic la zero rezultate (import_roris_roaacnpro.md, punctul 5: butonul "pare ca nu face nimic"). cauta_alfa insasi nu are un mesaj dedicat de "zero rezultate" (e un browse generic — daca cursorul de rezultate e gol, grila apare goala, fara alt semnal, cf. structurii ei standard). Reteta pentru S4b: verificarea "zero rezultate" se face inainte de a deschide cauta_alfa (similar cu do_cauta_facturi, care verifica intai Empty(poDate.id_client) inainte de a cauta), printr-un SELECT COUNT(*) (sau verificare pe cursorul deja incarcat, Reccount(...)=0) urmat de AMESSAGEBOX care spune de ce: "Contractul nu are rate de facturat neincasate" / "Comanda nu are articole ramase de facturat" / etc., in loc sa lase dialogul sa se deschida gol. Exact tiparul deja folosit de do_adauga_tot insusi pe ramura fara date (:13195-13197, "Nu exista articole de adaugat!") — se extinde acelasi obicei la dialogul de alegere.


5. Echivalenta „tot" vs. „alege"

Garantia structurala exista, dar e incompleta azi. Toate cele trei cai de populare a crsfactura — do_adauga_tot (SCAN + apel), un viitor dialog de alegere (bifare + apel pe fiecare rand bifat), si adaugarea manuala rand cu rand — converg spre aceeasi rutina, Thisform.do_adauga_articol(tlImplicit, tlContract, tlRetur) (ofacturare.vc2:12813-...). Cata vreme toate trei cheama exact aceasta rutina, cu acelasi cursor sursa pozitionat pe randul corect, rezultatul per linie in crsfactura e identic prin constructie — nu exista o a doua cale de INSERT in crsfactura in afara acestei rutine (confirmat prin cautarea Insert Into crsfactura — singurul alt loc e ramura specifica tip=30/aviz din NIR, ofacturare.prg:357-376, care nu intra in perimetrul S4b).

Ce lipseste, concret, ca sa fie adevarat si pe contract: do_adauga_tot (:13169-13198) parcurge doar crsarticole. Pentru ca "Adauga tot din contract" / "Adauga toate ratele" sa functioneze, do_adauga_tot are nevoie de o ramura noua (sau un al doilea parametru tlContract, simetric cu do_adauga_articol) care sa faca SCAN peste crsarticole1 si sa cheme Thisform.do_adauga_articol(.T., .T.). Fara aceasta completare, "tot" pe contract fie nu exista, fie (daca cineva ar face butonul vizibil fara sa atinga metoda) ar aduce tacut liniile gresite din crsarticole (lista de preturi) in loc de crsarticole1 (articolele/ratele contractului) — o eroare mai grava decat lipsa vizibilitatii, pentru ca n-ar da nicio eroare, doar rezultat gresit.

Alegerea selectiva trebuie sa respecte aceeasi regula: dupa ce cauta_alfa intoarce XML-ul cu randurile bifate, bucla care le proceseaza trebuie sa pozitioneze cursorul sursa (crsarticole sau crsarticole1, dupa caz) pe id_c-ul corespunzator inainte de fiecare apel do_adauga_articol, nu sa reconstruiasca linia din campurile XML direct — altfel cele doua cai ("tot" vs. "alege") ar avea doua implementari diferite ale "ce inseamna sa adaug randul X", exact riscul pe care criteriul de gata din plan il exclude explicit ("rezultatul in crsfactura e identic pe cele doua cai cand selectia e totala").

Pe comanda/avize/retur, criteriul e deja adevarat prin constructie — do_adauga_tot foloseste azi crsarticole, care e cursorul lor unic; singurul de completat e contractul (cele doua cazuri de mai sus).


6. Interactiunea cu S4 (cautarea articolelor pe server)

Decizia din S4 (plan_13...md:1879-1888) e explicita: crsarticole nu se mai incarca in masa pentru facturarea libera (lista de preturi/nomenclator), inlocuita cu combosql pe cod/denumire care completeaza randul pe loc — dar "se pastreaza adauga tot pentru tipurile care au document sursa (comanda, aviz, contract) — acolo setul e marginit si incarcarea lui e legitima". Consecinta directa pentru meniul S4b:

  • Optiunile "Cauta in lista de preturi…" si "Alege din nomenclator…" (constante peste tot, cf. deciziilor 16/18/20) nu deschid un dialog de tip cauta_alfa — ele sunt fatada meniului pentru mecanismul S4: combosql pe o singura linie, populata pe loc, fara incarcare in masa. In termeni de UI, alegerea uneia din aceste doua optiuni echivaleaza cu ce face azi But_nou1.do_adauga din prototip (APPEND BLANK + focus pe celula de cod/denumire) — deci "linie noua" (sectiunea 1) si aceste doua optiuni de meniu ajung la acelasi gest UI, doar ca "linie noua" il porneste direct (fara meniu), iar cele doua optiuni de meniu il pornesc din context (dupa ce operatorul a vazut si restul optiunilor sursei). Nu sunt cai de cod diferite — sunt doua puncte de intrare in acelasi APPEND BLANK + editare inline.
  • Toate optiunile "Adauga tot din X…" / "Alege X…" (comanda, contract, avize, retur) opereaza pe cursoarele bounded (crsarticole/crsarticole1) pe care S4 le pastreaza neschimbate — pentru aceste tipuri, nimic din mecanismul de cautare pe server nu se aplica; secțiunile 3-5 de mai sus raman valabile ca atare.
  • Punct de atingere real intre cele doua povesti: campul id_pol pe randul adaugat prin combosql (S4, cautare din nomenclator liber) — subiectul blocajului FACT-024 documentat pe larg in plan (J-ter/J-quater) si in idpol_comanda_contract.md. S4b nu rezolva acest blocaj (e decizia de produs deschisa la J-quater, intre reteta in 4 pasi / nota pe antet / nota pe antet ca fallback) — doar il mosteneste: optiunea "Alege din nomenclator…" din meniul S4b va ajunge la aceeasi eroare FACT-024 la salvare, indiferent de cum arata butonul, pana cand acea decizie separata se ia. De mentionat explicit in criteriul de gata al S4b, ca sa nu se creada ca butonul rezolva problema.

7. Ordinea de executie in pasi

  1. Bara de linie (but_nou, but_sterge, "detalii linie"), mutata deasupra gridului, cu Caption explicit pe fiecare instanta (clasele but_nou/but_sterge au deja ToolTipText, nu au niciodata Caption — de setat pe instanta, nu de asteptat de la clasa). Verificabil: cele trei butoane sunt vizibile deasupra gridului de linii, fiecare cu text vizibil (nu doar iconita), pe orice tip de document deschis.
  2. Corectarea Do Case din Init (ofacturare.vc2:15109-15248): adauga 52 la ramura Inlist(poDate.tip, 2, 6) (linia 15129) astfel incat sa primeasca titlu, eliminare cSerie, si sa fie tratat identic cu 2/6 in tot restul metodei — corecteaza golul mai mare decat cel semnalat in plan (sectiunea 3 de mai sus). Adauga 26 la lista tipurilor care primesc but_urmator_tot1.Visible = .T. candva in pasul 3, nu aici (26 ramane azi cu titlu corect, doar fara butonul "tot"). Verificabil: un document nou de tip 52 arata acelasi titlu si acelasi grid (fara cSerie) ca un document de tip 2/6.
  3. Extinderea do_adauga_tot cu ramura pe crsarticole1 (parametru sau ramura separata dupa poDate.tip/crsarticole1.opt_facturare), plus vizibilitatea butonului/optiunii pe 2/6/26/52 — cei doi pasi impreuna, nu separat (vizibilitate fara continut ar aduce un buton mut; continut fara vizibilitate nu s-ar vedea niciodata). Verificabil: pe un contract cu OPT_FACTURARE=3 si articole configurate, "adauga tot" produce in crsfactura exact liniile din crsarticole1; pe un contract cu OPT_FACTURARE IN (1,2), produce cate o linie per rata neincasata.
  4. Butonul unic "Adauga articole" cu xmenu(), inlocuind But_urmator_tot1 (fara caption) si orice buton separat de alegere — construit dinamic dupa tabelul din sectiunea 3, cu eticheta pe ramura de contract aleasa in functie de crsarticole1.opt_facturare (sectiunea 4.3). Verificabil: pe fiecare sursa, meniul deschis contine exact optiunile din tabelul sectiunii 3, in ordinea specificata, iar alegerea ESC (xmenu() intoarce 0) nu declanseaza nimic.
  5. Dialogul de alegere selectiva, pe reteta cauta_alfa(..., tnTipReturn=1) (sectiunea 4), cu mesaj explicit la zero rezultate (4.5) si populare prin do_adauga_articol rand cu rand (5) — cate o instantiere per sursa (comanda/contract-articole/contract-rate/avize/retur-linii), cu tcselect/tcfiltru proprii. Verificabil: pe fiecare sursa, bifarea a N randuri din M disponibile adauga exact acele N randuri in crsfactura, cu aceleasi valori ca daca ar fi fost adaugate individual; bifarea tuturor produce acelasi rezultat ca "adauga tot" (criteriul de gata al plan-ului, rescris verificabil). Depinde de: pasul 3 (aceeasi rutina do_adauga_articol trebuie sa functioneze deja pe crsarticole1 inainte ca dialogul de alegere sa se poata baza pe ea).
  6. Reconcilierea cu selectia de facturi la antet (sectiunea 4.2, 9.3) — decizie separata, nu blocanta pentru pasii 1-5: daca "Alege facturile de returnat…" ramane doar la antet (cum e azi) sau devine posibila si din bara de butoane.

Gata cand (rescris): pe fiecare din cele sase surse (lista de preturi, contract-articole, contract-rate, comanda, avize, retur), meniul "Adauga articole" ofera exact optiunile tabelate in sectiunea 3; pentru fiecare sursa cu "tot"/"alege" ambele prezente, o selectie totala prin dialogul de alegere produce in crsfactura acelasi set de randuri (aceleasi valori, nu doar acelasi numar) ca "adauga tot"; zero rezultate in orice dialog de alegere produce un mesaj care spune sursa si motivul, nu un dialog gol; tip 52 se comporta identic cu tip 2/6 in titlu si structura de grid.


8. Ce NU se poate testa headless

  • Continutul si comportamentul meniului xmenu() — Activate Screen + Define Popup ... Bar e UI nativa Windows/VFP (shortcut popup la pozitia mouse-ului); nu exista API de citire a itemilor unui popup activ prin automatizare headless. Verificarea "meniul contine optiunile N" se poate face doar indirect, citind stringul lcMeniu construit inainte de apelul xmenu() (verificabil headless, ca text), nu popup-ul afisat efectiv.
  • Coloanele griduri (grd_articole, grd_contracte, grd_factura, si viitorul grid al dialogului de alegere) — capcana deja cunoscuta si confirmata pe acest proiect: sub -A -T (rulare headless) ColumnCount=0 si RecordSource raman artefacte, coloanele nu se materializeaza. Exista un harness UI vizibil (nu headless) care le citeste corect — orice verificare vizuala a meniului/dialogului de alegere trebuie sa treaca prin acel harness, nu prin rulare -A -T.
  • cauta_alfa/cauta_alfa_form_plus — dialog modal cu .Show(1), blocheaza thread-ul UI pana la interactiune; testarea automata ar necesita fie injectare de input (interzisa pe aceasta masina, partajata cu Marius — vezi memoria masina-partajata-fara-input-real), fie apelarea directa a functiilor Oracle/cursor din spatele dialogului, ocolind UI-ul (verifica datele, nu dialogul).
  • AMESSAGEBOX la zero rezultate — verificabil ca text al mesajului in cod (string literal), nu ca aparitie reala pe ecran, din acelasi motiv.
  • Pozitionarea vizuala "deasupra gridului" (Top/Height relative) — se poate verifica numeric (comparand Top-ul butoanelor cu Top-ul gridului in .vc2), dar nu si "arata bine"/aliniere vizuala — necesita captura de ecran prin harness-ul UI vizibil.

Ce se poate verifica headless, direct: continutul cursoarelor (crsarticole, crsarticole1, crsfactura) dupa apelul rutinelor de adaugare, apelate direct (nu prin click) cu parametri de test; stringul lcMeniu construit inainte de xmenu(); valorile Visible/Caption/ToolTipText setate pe obiectele din .vc2 (proprietati statice, nu comportament de runtime).


9. Riscuri, capcane de UX, si ce ramane de decis de Marius

9.1 Regulile de formulare/griduri (COMUN\docs\reguli_lucru.md, punctul 7)

Aplicabile direct aici (citite, respectate in proiectarea de mai sus):

  • GO pe Recno() — orice bucla de tip do_adauga_tot/dialog de alegere care itereaza cursorul sursa (crsarticole/crsarticole1) trebuie sa retina si sa refaca pozitia (Recno()) dupa fiecare apel do_adauga_articol, exact cum face deja do_adauga_tot azi (lnNrInregistrare = Recno() ... Go lnNrInregistrare, :13175,13185,13193) — pastrat si in ramura noua pe crsarticole1.
  • UX formulare/griduri — butoanele fara Caption sunt azi conforme cu restul suitei (toate butoanele suita sunt icon-only); a le adauga Caption e o schimbare vizibila la nivelul intregii bare, nu doar a celor trei butoane noi — de discutat daca But_renunt1/But_reset1 raman mute langa butoane cu text (inconsistenta vizuala posibila, nu doar tehnica).

9.2 Contractul are doua meniuri, nu unul (sectiunea 4.3)

Tabelul din plan (J) trateaza "contract" ca o singura sursa cu un singur set de optiuni. Pe cod, un contract e fie OPT_FACTURARE=3 (articole), fie 1/2 (rate) — niciodata ambele. Eticheta "Alege ratele de facturat…" din decizia explicita a lui Marius e corecta doar pe ramura a doua; pe prima, echivalentul e "Alege articolele din contract…". De decis: se pastreaza o singura eticheta generica ("Alege din contract…") care acopera ambele cazuri fara sa distinga in text, sau se diferentiaza explicit (mai clar pentru operator, dar mai mult cod de verificare a lui crsarticole1.opt_facturare inainte de a construi meniul)?

9.3 "Alege facturile de returnat…" — la antet sau la bara de butoane?

Azi (sectiunea 4.2), selectia facturilor sursa pentru retur se face o singura data, la deschiderea formularului de antet (frm_date_factura.do_cauta_facturi), inainte ca formularul de articole sa existe. Meniul S4b, asa cum e cerut de decizia 13, ar pune "Alege facturile de returnat…" in bara de butoane a formularului de articole — adica dupa ce poDate.listaid a fost deja fixat. Doua optiuni, de decis de Marius:

  1. Optiunea din meniul S4b nu schimba setul de facturi sursa — e doar un rascolitor spre inapoi, catre acelasi dialog de la antet, dar utilizatorul trebuie sa iasa din pasul de articole ca sa-l foloseasca (comportament posibil confuz: optiunea apare in meniul de articole, dar actioneaza pe alt ecran).
  2. Se adauga un mecanism nou: alegerea de facturi suplimentare din formularul de articole, care extinde poDate.listaid si reincarca aditiv crsarticole (cursor_retur_document apelat a doua oara, cu filtru pe facturile noi, INSERT INTO crsarticole in loc de SELECT INTO) — cere cod nou in VFP, dar nu in pack_facturare (procedura Oracle ramane aceeasi, doar apelata de mai multe ori).

"Alege liniile de returnat…" (per articol, din facturile deja alese) e diferita si nu are conflict cu fluxul de azi — e un dialog de alegere clasic peste crsarticole, ca pe comanda/avize.

9.4 id_pol pe rata de contract — NULL prin constructie, dar validat corect

Rata de scadentar (crsarticole1, ramura OPT_FACTURARE IN (1,2)) are id_pol NULL prin constructia SQL insasi (NULL AS ID_POL, :2840) — dar asta nu ajunge niciodata la FACT-024, pentru ca ratele nu trec prin contabilizeaza_articol, ci prin contabilizeaza_rata (functie separata in pack_facturare, idpol_comanda_contract.md §0c), care citeste nota din CONTRACTE.ID_NOTA. De verificat inainte de implementare: do_adauga_articol/do_scrie_articole scriu randul de rata in crsfactura cu ce anume in coloana id_pol (probabil NULL, mostenit din poArticol) — daca salvarea foloseste ramura corecta (contabilizeaza_rata, nu contabilizeaza_articol) pe baza altui semnal decat id_pol (probabil id_ctr/opt_facturare pe linie), nu pe baza lui id_pol fiind gol. Nu s-a verificat cine alege intre cele doua functii la salvare — e in afara perimetrului S4b (tine de scriere, nu de bara de butoane), dar merita un pointer explicit ca sa nu se presupuna gresit ca "rata fara id_pol" e acelasi caz cu "articol din nomenclator fara id_pol" (J-quater) — sunt cai de cod diferite, cu tratament diferit.

9.5 Ce ramane strict de decis de Marius

  1. Eticheta pe contract (9.2) — generica sau diferentiata pe OPT_FACTURARE.
  2. Fluxul "Alege facturile de returnat…" (9.3) — raman la antet sau se adauga incarcare aditiva.
  3. Daca But_renunt1/But_reset1 primesc si ele Caption, pentru consistenta vizuala cu bara nou- etichetata, sau raman icon-only (9.1).
  4. Ordinea si formularea exacta a optiunilor in xmenu() per sursa (tabelul din sectiunea 3 e o propunere, nu o decizie finala de text).

Handoff

Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara predarea. Toate cele noua sectiuni cerute de brief sunt completate mai sus, cu fisier:linie verificat direct pe fisierele text reale (nu .bak), plus SQL-ul din PACK_FACTURARE exportat curent (D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql). Nicio editare de cod, niciun git_sync.ps1/txt2vcx.ps1, niciun commit.