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

37 KiB
Raw Blame History

Cercetare — proiectare S4: cautarea articolelor pe server, in linie

Investigatie READ-ONLY pentru povestea S4 din docs\plan_13_unificare_formular_facturare.md:1865-1874, plus sectiunea ### C. Incarcarea articolelor — de ce dureaza si ce inlocuieste (:589-614). Fara editari de cod, fara git_sync.ps1/txt2vcx.ps1, fara commit. COMUN\clase\ofacturare_comun.vc2 si COMUN\programe\ofacturare_editare.prg (perimetrul #6) doar citite, nu atinse. Pe Oracle doar SELECT, prin exportul PACK_FACTURARE de pe disc — nu s-a rulat nimic pe server.

Verdict (rezumat)

Mecanismul de inlocuire propus in plan exista deja, dar e un prototip la jumatate: grd_factura.cCodMat.cboCodmat / cCboDenumire (ofacturare.vc2:16751-16797, wiring la :19288-19334) cauta pe server prin combosql si scriu codmat/denumire/id_articol in crsfactura — dar nu scriu nimic altceva, pentru ca sursa lor (vnom_articole) nu are pret, TVA, valuta, id_pol, gestionabil. Asta confirma exact ce zice planul la punctul C: partea Oracle de facut e o varianta filtrata pe articol a celor cinci cursoare de facturare, nu o cautare noua.

Descoperirea care schimba proiectarea fata de textul din plan: crsarticole nu e doar sursa de populare a gridului — e un registru al cantitatii ramase de facturat, citit si scris de do_adauga_tot, do_sterge si do_scrie_factura pentru toate tipurile cu document sursa (comanda, aviz, contract-lista-de-preturi). Stergerea unei linii reface cantitatea in crsarticole (:14640-14669), iar la scriere se face Calculate Sum(cantitate) To lnCantitateRamasa peste crsarticole ca sa se decida daca se inchide automat comanda/avizul (:14303-14311, :14334-14338). Asta inseamna ca incarcarea in masa nu poate disparea pentru aceste tipuri, indiferent de S4 — nu doar pentru ca planul a decis sa pastreze "adauga tot", ci pentru ca bookkeeping-ul de cantitate ramasa e cablat direct pe cursorul incarcat. S4 se aplica deci curat doar pe ramurile de lista de preturi (cursor_preturi, plus jumatate din cursor_contract si cursor_gestiune) — vezi punctul 7.

A doua descoperire: calea de scriere a pretului la nivel de linie (adauga_articol_factura, verificata separat in S10, docs\cercetare\s10_pret_rederivat.md) primeste deja pretul ca parametru in loc sa-l re-deriveze — exact precedentul pe care trebuie sa-l urmeze si varianta filtrata: cauta pretul o singura data, la alegerea liniei, si il transmite mai departe neschimbat.

1. Inventarul celor cinci cursoare Oracle

Sursa: D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (corpul pachetului; liniile de mai jos sunt pe corp, :2138+, nu pe spec :335+ — offset +17 fata de citatele vechi din plan, confirmat si in S10).

1.1 cursor_preturi — lista de preturi, ramura principala (:2138-2644)

PROCEDURE cursor_preturi(V_DATA_CURS IN DATE, V_TIP IN NUMBER, V_ID_VALUTA IN NUMBER,
                          V_ID_GESTIUNE_INIT IN NUMBER, V_LUNA IN NUMBER, V_AN IN NUMBER,
                          V_ID_UTIL IN NUMBER, V_ID_SUCURSALA IN NUMBER,
                          V_CURSOR OUT cursor_facturare)

Fara filtru pe articol — semnatura n-are niciun parametru de cod/denumire. Ramuri pe V_TIP (CASE, :2159-2643):

Ramura Cand Sursa randurilor Observatie
V_TIP = 45 restaurant utilizatori_rol_intern -> politici_grupuri -> crm_politici_preturi -> crm_politici_pret_art -> nom_articole, plus curs/nom_valute fara filtru de stoc, cantitate fixa la 1 (:2193)
V_TIP IN (1,2) factura in lei acelasi lant de politici, plus LEFT JOIN pe STOC agregat pe gestiunile utilizatorului WHERE final filtreaza pe stoc > 0 sau RF_FACTURARE_FARA_STOC (:2387-2388)
V_TIP IN (5,6,10,52) factura in valuta FACT_VPRETURI_UTILIZATOR (view precalculat, nu politici brute) + STOC + CURS WHERE A.ID_UTIL = V_ID_UTIL AND ((A.ID_VALUTA = V_ID_VALUTA AND A.IN_VALUTA=1) OR A.ID_POL = politica_stoc) (:2461-2462)
V_TIP = 7 credit note FACT_VPRETURI_UTILIZATOR, filtrat suplimentar NVL(A.nota_discount,0)=1 (:2538) doar articole de discount
ELSE (aviz, tip implicit) orice alt tip FACT_VPRETURI_UTILIZATOR, fara filtrul de valuta din ramura 5/6/10/52 ramura cea mai generala

Coloane comune returnate (uniforme pe toate ramurile, ceea ce conteaza pentru mapare — punctul 6): id_c, id_articol, lot, serie, id_pol, id_valuta, nume_lista_preturi, discount_unitar, discount_unitar_val, codmat, codbare, denumire, um, gestionabil, cantitate, proc_tvav, preturi_cu_tva, curs, multiplicator, pret, pret_val, tip_valuta, nume_val (+ modificabil, id_gestiune, cont doar pe ramura restaurant).

Apeleaza intai initializeaza_facturare, completare_politica_stoc si verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL) (:2149-2153) — validare neconditionata a cursurilor pentru toate valutele din listele de preturi ale utilizatorului, indiferent de articolul cautat. Relevant pentru S4d (-20005).

1.2 cursor_contract — factura/aviz pe contract (:2646-2950)

PROCEDURE cursor_contract(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_LISTAID, V_ID_GESTIUNE_INIT,
                           V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA,
                           V_ID_AGENT OUT, V_NUME_AGENT OUT,
                           V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare)

Doua cursoare de iesire. V_CURSOR2 (:2718-2938) e specific contractului: UNION ALL intre (a) articole cu OPT_FACTURARE = 3 din CTR_ARTICOLE si (b) rate din CTR_SCADENTAR pentru OPT_FACTURARE IN (1,2), cu id_articol = NULL pe randurile de rata — filtrate pe V_LISTAID (lista de id_ctr) prin charn2collection. La final (:2940-2948) cheama cursor_preturi cu aceiasi parametri si scrie rezultatul in V_CURSOR — deci jumatate din cursor_contract e literalmente cursor_preturi.

In VFP, cele doua cursoare de iesire ajung (observat pe cod, nu documentat explicit in goExecutor) in crsarticole (=V_CURSOR, lista de preturi) si crsarticole1 (=V_CURSOR2, liniile contract + rate) — vezi ofacturare.vc2:13778 (lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole], [crsarticole1])) si Destroy (:12804-12811) care inchide ambele.

Randurile de rata nu au id_articol — nu pot fi gasite printr-o cautare cod/denumire; raman legate de gridul crsarticole1 existent (grd_contracte), bounded de numarul de rate ale contractului, nu de catalog. Vezi punctul 7.

Valideaza cursurile valutare doar pentru valutele prezente in CTR_SCADENTAR/CTR_ARTICOLE ale contractelor din V_LISTAID (:2679-2716) — spre deosebire de cursor_preturi, care valideaza tot ce are utilizatorul in politici. Filtru deja ingust pe sursa, dar tot pe tot contractul, nu pe articol.

1.3 cursor_comanda — factura/aviz pe comanda (:2952-3171)

PROCEDURE cursor_comanda(V_DATA_CURS, V_TIP, V_LISTAID, V_ID_UTIL, V_CURSOR OUT cursor_facturare)

V_LISTAID e un singur id_comanda (TO_NUMBER(V_LISTAID), :2963 — nu o lista, desi parametrul se numeste la fel ca la contract/avize). Doua ramuri identice ca forma (V_TIP <= 20 = factura, altfel aviz, :2994-3170), ambele pe COMENZI_ELEMENTE filtrat WHERE A.ID_COMANDA = V_ID_COMANDA (:3078, :3166) — deja filtrat pe un singur document sursa, nu pe tot catalogul. LEFT JOIN cu suma cantitatilor deja facturate din VANZARI_DETALII pe acelasi ID_COMANDA (:3060-3068) calculeaza cantitate ca ramas de facturat, nu cantitatea comandata bruta — exact sursa pentru bookkeeping-ul de "adauga tot" / stergere de la punctul urmator.

1.4 cursor_avize — factura din avize (:3703-3874)

PROCEDURE cursor_avize(V_LISTAID, V_ID_UTIL, V_DISCOUNT OUT NUMBER, V_CURSOR OUT cursor_facturare)

V_LISTAID = lista de id_vanzare (avize sursa), separate prin virgula. O singura interogare, fara CASE pe tip — agrega VANZARI_DETALII pe cheie compusa (articol, pol, lot, serie, discount, TVA, gestiune, cont, pret...) si scade ce a fost deja facturat din VANZARI_CANTITATI (WHERE A.CANTITATE <> NVL(B.CANTITATE, 0), :3871) — acelasi tipar "ramas de facturat" ca la comanda. Nu are ramuri pe V_TIP — planul o citeaza ca exemplu de "aceleasi ramuri pe tip", dar real e cea mai simpla dintre cele cinci: un singur SELECT.

1.5 cursor_gestiune — transfer intre subunitati pe lista de preturi (:4158-4310+)

PROCEDURE cursor_gestiune(V_DATA_CURS, V_ID_POL, V_ID_GESTIUNE, V_LUNA, V_AN,
                           V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR OUT cursor_facturare)

Foloseste V_ID_POL fix (nu politica derivata din utilizator ca la cursor_preturi), pe STOC filtrat ID_GESTIUNE = V_ID_GESTIUNE JOIN CRM_POLITICI_PRET_ART WHERE ID_POL = V_ID_POL (:4266-4280) — deja restrans la o singura gestiune si o singura politica, dar tot fara filtru pe articol; GROUP BY pe articol agrega stocul.

2. De unde sunt executate; cine consuma crsarticole

Executia: un singur loc, ofacturare.prg:266-311 (factureaza), rutare pe tnTip (punct de plecare confirmat, Do Case la :266-308) -> goExecutor.oExecute(lcSqlCursor, [crsarticole]) (:310-311). Acelasi tipar apare a doua oara in factureaza2 (ofacturare.prg:824+, ramura frm_facturare_articole2, prototipul).

Consumatorii crsarticole in perimetrul S4 (frm_facturare_articole, ofacturare.vc2:11xxx-15300; lista completa via vfp_symbols -Grep 'crsarticole\b' -CodeOnly):

Metoda Linii Ce face cu crsarticole Ramane dupa S4?
Destroy 12804-12811 inchide cursorul da, neschimbat
do_adauga_articol 12813-13167 citeste un rand ales (Scatter Name poArticol), il scrie in crsfactura inlocuit de randul intors de cautarea pe server, pe ramurile de lista de preturi — vezi punctul 7
do_adauga_tot 13169-13198 Scan peste tot cursorul, cheama do_adauga_articol pe fiecare rand pastrat neschimbat, dar numai pe tipurile cu document sursa
do_cauta 13566-13593 filtru client-side (Set Filter) peste cursorul deja incarcat, dupa text tastat in txtArticole/txtCodmat si politica aleasa dispare pe ramurile de lista de preturi — inlocuit de combosql
do_scrie_factura 14301-14338 Calculate Sum(cantitate) To lnCantitateRamasa peste crsarticole, decide daca se inchide comanda/avizul sursa (pnParametruAditional) pastrat neschimbat — vezi verdictul
do_sterge 14608-14669 la stergerea unei linii din crsfactura, reface cantitatea in crsarticole/crsarticole1 (Replace cantitate With cantitate + poArticol.cantitate) pastrat neschimbat pe tipurile cu bookkeeping; pe lista de preturi nu exista azi replace-back real (cantitatea nu scade la adaugare pe acele tipuri — stocul se verifica separat, prin cursor_gestiuni_articol*)
do_modifica 13778 alege crsarticole sau crsarticole1 dupa opt_facturare pentru verificare stoc pastrat, tine de gestiunea aleasa la editarea unei linii deja adaugate
cb_politici_preturi.InteractiveChange 15647-15658 cheama do_cauta() (filtru client-side) dispare — pe varianta noua, schimbarea politicii devine parametru al cautarii pe server (id_pol in WHERE), nu filtru local
KeyPress (navigare grid) 15463-15486 navigare in cursor la taste sageata dispare pe ramurile fara grid de sus

frm_facturare_articole2 (prototipul, :17xxx-18xxx) are exact aceeasi structura pe do_adauga_articol / do_adauga_tot / do_scrie_factura / do_sterge — nu difera in aceasta privinta.

Concluzie pentru cat se poate scoate: do_cauta si filtrul din cb_politici_preturi dispar integral (inlocuite de cautarea pe server). do_adauga_articol isi schimba sursa randului (de la Scan in cursorul local la randul ales de combosql), dar restul lui (verificare stoc, alegere gestiune via cursor_gestiuni_articol*, scriere in crsfactura) nu se schimba — vezi punctul 6. do_adauga_tot, do_sterge (partea de bookkeeping) si do_scrie_factura (lnCantitateRamasa) nu pot fi scoase cat timp crsarticole ramane sursa lor de adevar pentru "cat a mai ramas" — motiv suplimentar, nu doar UX, pentru care "adauga tot" ramane cablat pe incarcare in masa acolo unde exista document sursa.

3. Cat costa azi incarcarea, pe forma interogarii (nu pe timpi masurati — decizia 31)

cursor_preturi (ramurile 1/2 si generala) nu are niciun filtru pe articol — semnatura n-are parametru de cod/denumire (:2138-2146). Rezultatul e intreg catalogul accesibil utilizatorului: utilizatori_rol_intern -> politici_grupuri -> crm_politici_preturi (toate politicile active la data cursului) -> crm_politici_pret_art (toate liniile de pret ale acelor politici) -> nom_articole. Numarul de randuri creste cu produsul dintre "cate politici de pret vede utilizatorul" si "cate articole are fiecare politica" — nu cu numarul de articole cautate, care e de regula 1.

Trei surse structurale de cost, vizibile direct in forma interogarii, nu masurate:

  1. LEFT JOIN pe STOC agregat (:2359-2378, ramura 1/2) — subquery cu GROUP BY ID_ARTICOL peste tot stocul lunii curente, pe toate gestiunile la care utilizatorul are drept, recalculat la fiecare deschidere de formular, indiferent daca utilizatorul cauta un singur articol.
  2. FACT_VPRETURI_UTILIZATOR (ramurile 5/6/10/52 si 7 si aviz) e un view, nu un tabel — planul nu detaliaza corpul lui aici (ar fi o cercetare separata), dar fiind sursa pentru "toate preturile vizibile utilizatorului", are aceeasi forma: cost proportional cu marimea catalogului, nu cu cautarea.
  3. verifica_cursuri_valute (:2153, chemata necondiționat la fiecare apel) valideaza cursul pentru toate valutele din politicile utilizatorului, nu doar valuta articolului cautat — cost fix per deschidere, independent de ce se cauta.

Concluzia structurala: interogarea de azi calculeaza raspunsul pentru "orice articol ar putea alege utilizatorul", cand formularul are nevoie doar de "articolul pe care tocmai l-a tastat". Filtrarea pe id_articol/codmat/denumire reduce fiecare din cele trei surse la un numar de randuri marginit de cate politici de pret contin acel articol (de regula 1, rar cateva), nu de marimea catalogului.

4. Proiectarea variantei filtrate, cursor cu cursor

Toate cinci sunt proceduri PL/SQL in PACK_FACTURARE, apelate direct din VFP prin {call pack.proc(...)} + goExecutor.oExecute, fara view intermediar si fara strat ORM — deci minimul de schimbare e acelasi tipar: o procedura noua (sau o supraincarcare cu parametru suplimentar) in acelasi pachet, apelata din acelasi loc (ofacturare.vc2, metoda nou-introdusa pe combosql, nu din ofacturare.prg, care ramane neschimbat — el tot incarca varianta "in masa" acolo unde ramane necesara).

Alegere de proiectare, nu fapt verificat: supraincarcare (aceeasi denumire, parametru nou opțional V_FILTRU_COD IN VARCHAR2 DEFAULT NULL / V_FILTRU_DEN IN VARCHAR2 DEFAULT NULL) e mai sigura decat o procedura noua, pentru ca garanteaza aceleasi ramuri de CASE, acelasi JOIN, aceeasi logica de rotunjire — orice divergenta viitoare intre "cursor complet" si "cursor filtrat" ar fi un bug de sincronizare greu de prins. Cu supraincarcare, filtrul se adauga o singura data, in WHERE-ul final al fiecarei ramuri, nu in logica de business.

Cursor Ce se adauga Unde (linia WHERE/CASE care primeste filtrul)
cursor_preturi AND (V_FILTRU_COD IS NULL OR C.CODMAT LIKE V_FILTRU_COD) AND (V_FILTRU_DEN IS NULL OR UPPER(C.DENUMIRE) LIKE UPPER(V_FILTRU_DEN)) — pe alias-ul articolului, C pe patru din cinci ramuri, A pe ramurile cu FACT_VPRETURI_UTILIZATOR dupa WHERE existent, pe fiecare din cele 5 ramuri (:2264, :2387-2388, :2464-2465, :2540-2541, :2640-2641) — 5 locuri, nu unul
cursor_contract acelasi filtru pe V_CURSOR (delegat catre cursor_preturi, gratuit); pe V_CURSOR2 filtrul se adauga in UNION ALL-ul de la :2752 (partea cu id_articol), nu pe partea de rate (id_articol IS NULL — nu se pot filtra pe cod, raman needitate de filtru) :2825 (join articol) + propagare in WHERE-ul de la :2819
cursor_comanda filtru pe C.CODMAT/C.DENUMIRE in WHERE A.ID_COMANDA = V_ID_COMANDA (:3078, :3166) — dar nu are sens sa se filtreze: cf. punctul 7, comanda ramane pe calea "adauga tot" de proiectat doar daca decizia de la punctul 7 se schimba
cursor_avize idem — nu are sens, acelasi motiv idem
cursor_gestiune filtru pe C.CODMAT/C.DENUMIRE in interogarea de la :4234-4237, inainte de GROUP BY :4266-4293

Parametrii care raman identici pe varianta filtrata: V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT, V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA — tot ce vine din poDate / sesiune la deschiderea formularului, nu se recalculeaza per cautare. Doar filtrul de text e nou, plus (pentru cursor_preturi in noua utilizare) eventual V_ID_POL daca utilizatorul a ales explicit o lista de preturi in combo-ul cb_politici_preturi — azi acel combo doar filtreaza local (ofacturare.vc2:15647-15658, comentat inlocuit cu do_cauta()); pe varianta noua devine parametru real al interogarii.

Ce nu se poate verifica din cod, ramane de decis de Marius: daca filtrul pe codmat foloseste LIKE "incepe cu" (ca combosql de azi, nCharCountBegin/cautare incrementala) sau match exact la alegerea din lista — combosql de azi face amandoua (tastare = "incepe cu" pe server, alegere din lista = valoare exacta), deci varianta cea mai apropiata de comportamentul actual e sa pastreze acelasi tipar: interogarea filtrata se cheama la fiecare tastare (ca azi, RefreshData), iar valoarea finala aleasa vine din randul deja adus, nu dintr-o interogare separata "exact match".

5. combosql — contract si exemple reale

Clasa: combosql AS combobox, COMUN\clase\_cb_base.vc2:519-843. Nu e specifica facturarii — traieste in biblioteca comuna a suitei, dar cautarea nu a gasit nicio alta utilizare, nici in ROAFACTURARE, nici in COMUNROA/ROAGEST (grep -rn "AS combosql WITH" — zero potriviri in afara ofacturare.vc2:16751/16785/16861, cele trei coloane ale gridului prototip). Singurul exemplu real de folosire e chiar prototipul din plan — nu exista alt loc in suita de copiat.

Proprietati relevante (_memberdata, :560-575):

Proprietate Rol
csourcesql SELECT fara WHERE/ORDER BY — baza interogarii
csourcewhere conditia WHERE fixa (ex. inactiv = 0)
csourceorder ORDER BY, implicit pfieldactiv
pcursorname numele cursorului cu rezultatele
pfieldactiv campul dupa care se cauta si se afiseaza
psecondfield al doilea camp de cautare (optional)
ncharcountbegin cate caractere minim inainte sa porneasca interogarea pe server
llimittolist daca valoarea trebuie sa existe in lista

Mecanismul (refreshdata, :796-830): construieste SELECT ... FROM (cSourceSql) WHERE (cSourceWhere) AND (pFieldActiv LIKE ?pcValue [OR psecondfield LIKE ?pcValue]) ORDER BY ..., cu ?pcValue = cSearchString + '%' (incepe cu) sau '%'+cSearchString+'%' (contine, la Ctrl+Enter) si il executa prin goExecutor.oExecuta (selectdata, :832-840) — acelasi executor folosit peste tot in suita pentru apeluri Oracle, deci niciun mecanism nou de transport, doar un SQL nou de trimis. KeyPress (:610-776) gestioneaza incremental tastarea: la fiecare caracter tastat re-executa RefreshData(1) daca lungimea depaseste nCharCountBegin.

Contractul de legare la o coloana de grid, dedus din prototip (ofacturare.vc2:16751-16762 + :19288-19306):

  1. se adauga ca CurrentControl al coloanei (Column1.CurrentControl = "cCboDenumire", :16621);
  2. ControlSource leaga afisarea de campul din cursorul de linii (crsFactura.codmat, :16754);
  3. evenimentul de reactie e LostFocus, nu Valid sau InteractiveChange — abia la parasirea celulei se scriu campurile in cursorul de linii, cu paza If this.Value <> thisform.cOldValue (setat in When, :19308-19310) ca sa nu se rescrie fara motiv;
  4. LostFocus de azi scrie doar campurile pe care le are vnom_articole: codmat, denumire, id_articol (:19293) — nimic despre pret, TVA, valuta. Aici se opreste prototipul azi; tot ce trebuie adaugat pentru S4 e sa inlocuiasca crsCodmat/crsDenumire (rezultatul din vnom_articole) cu rezultatul cursorului Oracle filtrat de la punctul 4, care are toate coloanele, si sa extinda REPLACE-ul de la :19293/:19317 cu restul campurilor din tabelul de la punctul 6.

Ce nu ofera azi combosql si trebuie adaugat, nu doar copiat: csourcesql e un SELECT static definit in .vcx la design-time, nu poate primi parametrii dinamici ai documentului curent (poDate.zi_curs, poDate.tip, poDate.id_valuta...) direct in proprietate. Pentru cursorul Oracle cu 8 parametri, populate din poDate/sesiune, csourcesql/csourcewhere trebuie construite dinamic in Init sau la schimbarea documentului (nu sunt string-uri fixe ca azi), sau — alternativa mai simpla — refreshdata/selectdata se suprascriu punctual pe instanta din grid, ca sa apeleze {call pack_facturare.cursor_preturi_filtrat(...)} in loc de SELECT ... FROM (cSourceSql). A doua varianta pastreaza restul mecanismului (KeyPress, incremental, LostFocus) neschimbat si e schimbarea minima — de confirmat cu Marius, nu o certitudine de cod.

6. Maparea camp-cu-camp

Sursa cea mai fiabila pentru "ce completeaza azi crsarticole in linie" nu e cursorul Oracle brut, ci Gather Name poArticol Fields Like ... din do_adauga_articol (ofacturare.vc2:12945-12957, identic pe frm_facturare_articole2 la :17226+) — acolo se vede exact ce trece din randul scanat in crsfactura.

Camp in crsfactura Vine din crsarticole (coloana cursorului Oracle) Pe varianta filtrata
id_articol, codmat, codbare, denumire, um direct din cursor identic — filtrul e chiar pe aceste coloane
pret_achizitie nu din crsarticole — vine din poArtLista/crsartselectate (rezultatul lui cursor_gestiuni_articol*, ales dupa crsarticole), nu din cursorul de lista de preturi neschimbat — pasul e deja separat azi, ramane separat
id_pol direct (crsarticole.id_pol) identic
Cont direct (crsarticole.cont, doar ramura restaurant o are explicit; pe restul vine '371' implicit sau din cursor_gestiuni_articol*) identic pe ramurile care il au; de verificat pe ramurile 1/2/5/6/7/10/52/aviz — cursorul nu are CONT explicit in SELECT (:2268-2329 etc.), deci provine din do_alege_stoc, nu din crsarticole
id_gestiune direct doar pe ramura restaurant (A.ID_GESTIUNE, :2220); pe rest vine din alegerea de gestiune (cursor_gestiuni_articol*) identic — pasul de alegere gestiune ramane neschimbat
Proc_Tvav, pretftva, Pretctva, discountftva(discount_unitar), discountctva direct din cursor, plus do_initializeaza_articol (:13618-13659) care deriva pretftva/pretctva/tva reciproc dupa preturi_cu_tva identic — logica de derivare e in VFP, nu se schimba
id_valuta, tip_valuta, nume_val, Curs, multiplicator direct din cursor identic
gestionabil direct din cursor identic (inclusiv regula de proforma, ofacturare.prg:333-336, care suprascrie gestionabil=0 — ramane in ofacturare.prg, neschimbata)
id_jtva_coloana direct doar pe ramurile care il au explicit in SELECT (aviz/contract/retur); pe cursor_preturi (1/2/5/6/7/10/52) nu apare in lista de coloane returnate (:2264-2329) de verificat cu Marius — daca lipseste azi, Gather ... id_jtva_coloana scrie NULL/valoare implicita; filtrarea nu schimba nimic aici, dar merita clarificat inainte de implementare, nu presupus
pretv_orig, pretd, id_valuta_d nu apar in cursor_preturi; vin din poArtLista/crsartselectate (alegerea de gestiune) neschimbat
taxcode, explicatie, id_lucrare_rez, id_part_rez, id_ctr, opt_facturare nu din crsarticole — din poArticol/context (do_initializeaza_articol, do_alege_stoc) neschimbat
pret_cu_tva (flag preturi_cu_tva) direct din cursor (A.PRETURI_CU_TVA / D.PRETURI_CU_TVA) identic

Concluzie pentru punctul 6 al cerintei: nicio coloana din cele cerute explicit (pret, cota TVA, valuta, id_pol, gestionabil, pret_cu_tva) nu ridica probleme — toate vin direct din cursorul Oracle si filtrarea pe articol nu le atinge. Singurul semnal de atentie e id_jtva_coloana, absent din cursor_preturi insusi (posibil completat in alt pas, neverificat aici — iese din perimetrul "cele cinci cursoare", ar cere citirea intregului do_adauga_articol/calculeaza_totaluri).

7. „Adauga tot" — ce ramane si de ce (nu doar decizia din plan)

Planul spune "se pastreaza adauga tot pentru tipurile cu document sursa (comanda, aviz, contract) — acolo setul e marginit". Cercetarea (punctul 2) arata un motiv suplimentar, mai tare decat marimea setului: bookkeeping-ul de cantitate ramasa (do_sterge, do_scrie_factura) citeste si scrie direct in crsarticole, deci acel cursor trebuie sa existe incarcat in memorie cat timp formularul e deschis, indiferent cum alege utilizatorul liniile.

Vizibilitatea de azi a butonului but_urmator_tot1 ("adauga tot"), confirmata pe cod (ofacturare.vc2:15108-15248, Do Case poDate.tip):

Tip Vizibil "adauga tot" azi Sursa cursorului
proforma (eProforma=1) da (:15113) cursor_preturi sau alt cursor dupa tip, marcat neges­tionabil
copiere (lCopiere) da (:15120) cursor_preturi (varianta copiere, ofacturare.prg:464-473)
1, 5, 7, 10 — lista de preturi nu cursor_preturi
2, 6 — contract nu cursor_contract
3 — comanda da (:15150) cursor_comanda
4 — avize da (:15166) cursor_avize
21, 28, 42, 47 — aviz din comanda da (:15177) cursor_comanda
22, 29 — aviz din lista de preturi nu cursor_preturi
23, 41 — transfer subunitati nu menționat explicit vizibil cursor_gestiune
25 — transfer din comanda da (:15215) cursor_comanda
26 — aviz din contract nu cursor_contract
8, 9 — retur da (:15240) cursor_retur (nu e in cele 5, iese din perimetru)
24 — aviz retur da (:15245) cursor_retur

Coincide exact cu impartirea utila pentru S4: butonul e vizibil azi pe tipurile unde do_adauga_tot are sens pentru ca setul e mic si bookkeeping-ul de ramas il cere (comanda/aviz-din-comanda/retur), si e ascuns pe tipurile de lista de preturi (1/5/7/10, 22/29) si pe contract-lista-de-preturi (2/6/26) — exact ramurile pe care cautarea filtrata inlocuieste incarcarea in masa. Contractul insa nu are azi "adauga tot" vizibil deloc (nici pe partea de rate, nici pe partea de articole) — planul semnaleaza asta separat, la S4b, ca gol de acoperit (tipurile 2, 6, 26, 52 lipsesc din conditiile de vizibilitate), nu ca ceva de pastrat.

Concret, ce se schimba per tip:

  • Lista de preturi (1,5,7,10,22,29) si transfer pe lista (23,41,45,48,49): crsarticole nu se mai incarca la deschidere; combosql cauta filtrat; "adauga tot" ramane ascuns (ca azi — n-are sens pe catalog intreg).
  • Comanda (3,21,25,28,42,47) si avize (4): crsarticole ramane incarcat in masa, neschimbat; combosql nu se activeaza pe aceste tipuri (sau, daca se activeaza pentru UX, cauta in cursorul deja incarcat, nu pe server — cautare locala, nu Oracle) pentru ca bookkeeping-ul de cantitate ramasa il cere oricum incarcat.
  • Contract (2,6,26,52): dublu — crsarticole (jumatatea cursor_preturi) trece pe cautare filtrata ca orice lista de preturi; crsarticole1 (rate + articole OPT_FACTURARE=3) ramane incarcat in masa, bounded de contract, neschimbat de S4 (randurile de rata n-au id_articol, nu pot fi cautate).
  • Retur (8,9,24): in afara celor cinci cursoare cerute (cursor_retur/cursor_retur_document), proiectat separat la S4f — nesemnalat aici ca problema, doar exclus din perimetru.

8. Interactiunea cu S4b si S10

Cu S4b (bara de butoane / meniul de adaugare): S4b proiecteaza meniul xmenu() "Adauga articole" cu optiunile pe sursa (tot / alege), inclusiv acoperirea golului de pe contract (tipurile 2/6/26/52). S4 ii da continutul concret pentru ramura "cauta in linie": pe tipurile de lista de preturi (unde S4b nu are nevoie de optiunea "tot" azi), linia de grid cu combosql este metoda de adaugare — nu exista un dialog separat de ales. Pe tipurile cu document sursa, S4 nu schimba nimic din ce proiecteaza S4b: meniul ramane cablat pe do_adauga_tot/alegere din crsarticole existent. Punctul de atingere real: daca S4b decide sa afiseze si pe contract un "adauga tot" (gol semnalat la punctul 7), acel "tot" trebuie sa opereze pe crsarticole1 (rate+articole), nu pe crsarticole (lista de preturi) — cele doua cursoare raman distincte si dupa unificare.

Cu S10 (pretul care nu trebuie re-derivat, docs\cercetare\s10_pret_rederivat.md): verificarea S10 arata ca adauga_articol_factura primeste pretul ca parametru (V_PRET_ACHIZITIE_TEMP si restul) si nu-l recalculeaza, cu o singura exceptie tacuta: liniile de contract cu OPT_FACTURARE = 3, unde CTR_ARTICOLE.PRET_UNITAR suprascrie pretul trimis, necondiționat de sursa. Pentru S4, asta inseamna doua lucruri concrete:

  1. cursorul filtrat (cursor_preturi/cursor_gestiune cu filtru pe articol) se cheama o singura data, la selectarea liniei in combosql (LostFocus) — pretul obtinut atunci se scrie in crsfactura si nu se re-interogheaza la scriere, exact ca azi pe calea crsarticole completa;
  2. pe contract, indiferent daca linia a fost aleasa prin crsarticole1 (needitat de S4) sau, ipotetic, printr-o cautare filtrata viitoare, riscul de suprascriere tacuta descris in S10 exista deja si S4 nu-l introduce si nu-l rezolva — il mosteneste neschimbat, pentru ca punctul de suprascriere e in adauga_articol_factura (scriere), nu in cursorul de cautare (citire).

9. Ordinea de executie in pasi

  1. Pas 1 — Oracle, cursor_preturi filtrat. Supraincarcare cu V_FILTRU_COD/V_FILTRU_DEN opționale, aceleasi 5 ramuri, filtru adaugat in WHERE-ul fiecareia (punctul 4). Gata cand: apelat cu filtru gol, cursorul e identic randuri-cu-randuri (aceleasi coloane, aceleasi valori, pe fiecare din cele 5 ramuri de V_TIP) cu varianta actuala pe acelasi set de parametri — comparatie directa pe SQL, nu pe timp.
  2. Pas 2 — Oracle, cursor_gestiune filtrat. Acelasi tipar, un singur SELECT, mai simplu. Gata cand: idem, comparatie randuri-cu-randuri cu filtru gol.
  3. Pas 3 — Oracle, cursor_contract, doar partea V_CURSOR. Filtrul se propaga gratuit prin apelul catre cursor_preturi de la :2940-2948; nicio schimbare suplimentara necesara daca Pasul 1 e facut corect. Gata cand: cursor_contract cu filtru gol produce acelasi V_CURSOR ca inainte de Pasul 1 (regresie, nu functie noua).
  4. Pas 4 — VFP, combosql pe cCodMat/cCboDenumire. Inlocuieste csourcesql/SelectData cu apelul la cursorul filtrat (punctul 5); extinde LostFocus (:19288-19330) sa scrie toate campurile din tabelul de la punctul 6, nu doar codmat/denumire/id_articol. Gata cand: alegerea unei linii in grid, pe un document de tip lista-de-preturi, produce in crsfactura aceleasi valori ca alegerea aceluiasi articol azi din crsarticole incarcat complet — comparatie pe date, punctul 10.
  5. Pas 5 — VFP, oprirea incarcarii in masa pe tipurile vizate. In ofacturare.prg:266-311, pentru tnTip din ramurile de lista de preturi (1,5,7,10,22,29,23,41,45,48,49) si jumatatea cursor_preturi a contractului, goExecutor.oExecute nu se mai cheama la deschidere — grid-ul porneste gol, combosql populeaza la cerere. Gata cand: deschiderea formularului pe aceste tipuri nu executa niciun apel cursor_preturi/cursor_gestiune/cursor_contract (verificabil in log-ul goExecutor/WAIT WINDOW ... NOWAIT din combosql.selectdata, care ramane singurul loc care cheama Oracle pentru articole).
  6. Pas 6 — verificare "adauga tot" neatins. Pe comanda/aviz/contract-rate, do_adauga_tot, do_sterge, do_scrie_factura raman neschimbate (Pasii 1-5 nu le ating codul). Gata cand: regresie manuala pe un document din comanda si unul din aviz — comportament identic cu azi.

Depinde de: S3 (planul o marcheaza asa; portarea antetului trebuie sa existe inainte, ca sa aiba poDate populat corect la deschiderea gridului).

10. Cum se verifica „aceleasi valori ca azi"

Testabil pe date (nu doar pe cod), propunere concreta: pentru fiecare tip reprezentativ (1 sau 5 pentru lista de preturi, 2 pentru contract, 41 pentru transfer-gestiune), se alege un articol prezent in cursorul complet de azi si se compara randul din crsarticole (deschidere veche, neschimbata) cu randul intors de cursorul filtrat pe acelasi articol, aceiasi parametri (zi_curs, id_valuta, id_gestiune_init, utilizator) — comparatie coloana cu coloana, in Oracle direct (doua SELECT-uri, MINUS intre ele pe coloanele comune) sau in VFP prin doua cursoare deschise simultan. Se repeta pe un articol cu politica de discount, unul in valuta si unul fara stoc (RF_FACTURARE_FARA_STOC), ca sa acopere ramurile de CASE din punctul 1.

Ce nu se poate testa headless — capcana cunoscuta (grid-coloane-nu-se-materializeaza-headless, memorie de proiect): sub -A -T, ColumnCount/RecordSource ale gridului sunt artefacte, nu reflecta ce vede operatorul. Deci partea VFP (alegerea din combosql, LostFocus-ul care scrie in crsfactura) nu se poate verifica prin harnessul headless obisnuit — cere fie harnessul UI vizibil existent, fie verificare directa pe cursorul crsfactura dupa un apel programatic la metoda LostFocus (posibil, pentru ca metoda insasi nu depinde de randare, doar de This.Value — de confirmat la implementare). Partea Oracle (comparatia de cursoare de mai sus) e testabila headless, prin SELECT, fara nicio dependenta de VFP.

11. Riscuri si ce ramane de decis de Marius

  • id_jtva_coloana lipsa din cursor_preturi (punctul 6) — de clarificat inainte de implementare daca e completat in alt pas (nevazut aici) sau ramane NULL si azi; filtrarea nu schimba comportamentul, dar nota trebuie inchisa ca sa nu para un bug nou introdus de S4.
  • Mecanismul de legare combosql -> cursor Oracle parametrizat dinamic (punctul 5, ultimul paragraf) e o alegere de implementare, nu un fapt de cod — csourcesql static din .vcx nu poate primi parametrii de sesiune direct; solutia propusa (suprascriere punctuala refreshdata) e rezonabila, dar nu e singura posibila si nu exista alt exemplu in suita de comparat.
  • Contractul ramane cu doua surse de date pe acelasi grid conceptual (crsarticole filtrat + crsarticole1 needitat) — de confirmat ca UX-ul (doua zone: cautare + grid rate) e acceptabil, nu doar tehnic corect.
  • cursor_gestiune (transfer subunitati) nu are azi buton "adauga tot" vizibil (tabelul din punctul 7 nu-l gaseste in Do Case) — de verificat separat daca tipurile 23/41 au alt mecanism de adaugare in masa, neacoperit de cautarea in crsarticole/crsarticole1 directa; nu s-a citit codul suplimentar necesar pentru raspuns cert aici.
  • Validarea de curs valutar (verifica_cursuri_valute, punctul 1.1) ramane neconditionata chiar si pe varianta filtrata, daca se pastreaza in procedura supraincarcata — poate produce -20005 la prima cautare a unui articol, inainte ca utilizatorul sa fi vazut vreun rand, pe o valuta pe care n-o foloseste azi doar pentru ca incarcarea in masa "o gasea" oricum. De discutat cu Marius daca validarea trebuie restransa la valuta articolului cautat sau ramane globala (comportament identic cu azi, doar declansat mai des — la fiecare cautare, nu o data la deschidere).
  • Numarul exact de apeluri Oracle pe sesiune de cautare (un apel per tastare, cu debounce prin nCharCountBegin) nu a fost masurat si nu trebuie masurat aici (decizia 31) — dar merita setat explicit nCharCountBegin >= 2-3 la implementare, ca sa nu se piarda avantajul filtrarii intr-un numar mare de interogari de o litera.

Handoff

Cercetare incheiata in aceasta sesiune, fara nevoie de predare — toate cele 11 puncte cerute sunt acoperite mai sus, cu citate fisier:linie verificate pe fisierul real (nu .bak). Niciun fisier de cod atins, niciun git_sync.ps1/txt2vcx.ps1 rulat, nicio scriere pe Oracle.