Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git, nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de cercetare pe care se sprijina modificarile din cod. O stergere acolo era definitiva. Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele fisierelor de test la care se refereau. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
37 KiB
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:
LEFT JOINpeSTOCagregat (:2359-2378, ramura 1/2) — subquery cuGROUP BY ID_ARTICOLpeste 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.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.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):
- se adauga ca
CurrentControlal coloanei (Column1.CurrentControl = "cCboDenumire",:16621); ControlSourceleaga afisarea de campul din cursorul de linii (crsFactura.codmat,:16754);- evenimentul de reactie e
LostFocus, nuValidsauInteractiveChange— abia la parasirea celulei se scriu campurile in cursorul de linii, cu pazaIf this.Value <> thisform.cOldValue(setat inWhen,:19308-19310) ca sa nu se rescrie fara motiv; LostFocusde azi scrie doar campurile pe care le arevnom_articole:codmat,denumire,id_articol(:19293) — nimic despre pret, TVA, valuta. Aici se opreste prototipul azi; tot ce trebuie adaugat pentru S4 e sa inlocuiascacrsCodmat/crsDenumire(rezultatul dinvnom_articole) cu rezultatul cursorului Oracle filtrat de la punctul 4, care are toate coloanele, si sa extindaREPLACE-ul de la:19293/:19317cu 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 negestionabil |
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):
crsarticolenu se mai incarca la deschidere;combosqlcauta filtrat; "adauga tot" ramane ascuns (ca azi — n-are sens pe catalog intreg). - Comanda (3,21,25,28,42,47) si avize (4):
crsarticoleramane incarcat in masa, neschimbat;combosqlnu 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(jumatateacursor_preturi) trece pe cautare filtrata ca orice lista de preturi;crsarticole1(rate + articoleOPT_FACTURARE=3) ramane incarcat in masa, bounded de contract, neschimbat de S4 (randurile de rata n-auid_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:
- cursorul filtrat (
cursor_preturi/cursor_gestiunecu filtru pe articol) se cheama o singura data, la selectarea liniei incombosql(LostFocus) — pretul obtinut atunci se scrie incrsfacturasi nu se re-interogheaza la scriere, exact ca azi pe caleacrsarticolecompleta; - 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 inadauga_articol_factura(scriere), nu in cursorul de cautare (citire).
9. Ordinea de executie in pasi
- Pas 1 — Oracle,
cursor_preturifiltrat. Supraincarcare cuV_FILTRU_COD/V_FILTRU_DENopționale, aceleasi 5 ramuri, filtru adaugat inWHERE-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 deV_TIP) cu varianta actuala pe acelasi set de parametri — comparatie directa pe SQL, nu pe timp. - Pas 2 — Oracle,
cursor_gestiunefiltrat. Acelasi tipar, un singurSELECT, mai simplu. Gata cand: idem, comparatie randuri-cu-randuri cu filtru gol. - Pas 3 — Oracle,
cursor_contract, doar parteaV_CURSOR. Filtrul se propaga gratuit prin apelul catrecursor_preturide la:2940-2948; nicio schimbare suplimentara necesara daca Pasul 1 e facut corect. Gata cand:cursor_contractcu filtru gol produce acelasiV_CURSORca inainte de Pasul 1 (regresie, nu functie noua). - Pas 4 — VFP,
combosqlpecCodMat/cCboDenumire. Inlocuiestecsourcesql/SelectDatacu apelul la cursorul filtrat (punctul 5); extindeLostFocus(:19288-19330) sa scrie toate campurile din tabelul de la punctul 6, nu doarcodmat/denumire/id_articol. Gata cand: alegerea unei linii in grid, pe un document de tip lista-de-preturi, produce incrsfacturaaceleasi valori ca alegerea aceluiasi articol azi dincrsarticoleincarcat complet — comparatie pe date, punctul 10. - Pas 5 — VFP, oprirea incarcarii in masa pe tipurile vizate. In
ofacturare.prg:266-311, pentrutnTipdin ramurile de lista de preturi (1,5,7,10,22,29,23,41,45,48,49) si jumatateacursor_preturia contractului,goExecutor.oExecutenu se mai cheama la deschidere — grid-ul porneste gol,combosqlpopuleaza la cerere. Gata cand: deschiderea formularului pe aceste tipuri nu executa niciun apelcursor_preturi/cursor_gestiune/cursor_contract(verificabil in log-ulgoExecutor/WAIT WINDOW ... NOWAITdincombosql.selectdata, care ramane singurul loc care cheama Oracle pentru articole). - Pas 6 — verificare "adauga tot" neatins. Pe comanda/aviz/contract-rate,
do_adauga_tot,do_sterge,do_scrie_facturaraman 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_coloanalipsa dincursor_preturi(punctul 6) — de clarificat inainte de implementare daca e completat in alt pas (nevazut aici) sau ramaneNULLsi 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 —csourcesqlstatic din.vcxnu poate primi parametrii de sesiune direct; solutia propusa (suprascriere punctualarefreshdata) 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 (
crsarticolefiltrat +crsarticole1needitat) — 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 inDo Case) — de verificat separat daca tipurile 23/41 au alt mecanism de adaugare in masa, neacoperit de cautarea incrsarticole/crsarticole1directa; 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-20005la 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 explicitnCharCountBegin>= 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.