Files
roafacturare/docs/cercetare/s4_cautare_articole_server.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
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
2026-08-11 22:17:17 +03:00

483 lines
37 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.