# Propunere: ore/valoare manoperă + valoare piese (facturare și achiziție) în „Vizualizare comenzi" și „Facturi emise" Cercetare + plan. Data: 22.09.2026. Dovezile de plan/IO sunt luate pe schema `MARIUSM_AUTO` de pe `ROA_CENTRAL`. ## 0. Stare — 22.09.2026, implementat integral (pașii 1-4 + 6), în așteptare de review **Acesta este fișierul de implementat.** Planul e la §5, aprobat de Marius pe 22.09.2026. - **Pas 1 — baza de date: SCRIS ȘI RULAT pe `MARIUSM_AUTO` de pe `ROA_CENTRAL`** (aprobat de Marius). `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\09\ff_2026_09_22_01_AUTO.sql`, cu antet de comentarii care explică ce s-a modificat față de versiunile anterioare ale celor două view-uri. ASCII, CRLF, se încheie cu `UpdateVersiune` + `commit`. Dovadă: „View created" ×2, `VERSIUNE` conține `ff_2026_09_22_01_AUTO.sql`, toate cele 5 coloane noi există în `USER_TAB_COLUMNS`, zero obiecte `AUTO_*` invalide. **Nepublicat încă** la clienți (`publicare_scripturi.ps1` nu a fost rulat). - **Pas 2 — cursoarele VFP: FĂCUT.** `Programe/oproceduri_vizualizare.prg` — `vizualizare_comenzi` trecut pe `auto_istoric_comenzi` (+6 coloane), `vizualizare_facturi` +4 coloane. - **Pas 3 — gridurile: FĂCUT, cu write-back pe binar.** `Clase/oviz_devize.vc2` — `frm_viz_comenzi` `ColumnCount` 13→19, `frm_viz_facturi` `ColumnCount` 12→16, coloane noi **la coadă**. - **Pas 4 — totalurile de sub gridul de facturi: FĂCUT.** 4 containere `clb_tx_simplu` noi (`Clb_ore_manopera`, `Clb_val_manopera`, `Clb_val_mat_ach`, `Clb_val_mat_vz`) pe două rânduri sub cele existente, `frm_viz_facturi.Height` 497→560, iar `refreshdata` însumează cele 4 coloane cu `Nvl(...,0)` și garda `nrcrt=1`. - **Pas 5 (raportul `rap_facturi_clienti`) — NEFĂCUT**, opțional: `.frx` se editează doar în IDE-ul VFP. - **Pas 6 — testat pe bază: 7 PASS / 1 FAIL.** Raport: `docs/review/teste_coloane_manopera_piese_viz.md`. Rularea efectivă a ecranelor în VFP rămâne de făcut (necesită IDE). - **Necomis.** Diff-ul complet: `docs/review/diff_coloane_manopera_piese_viz.patch`. ### Ce au arătat testele (rezumat) - **Riscul principal e eliminat**: mutarea sursei ecranului de comenzi **nu schimbă setul de comenzi afișate** — `AUTO_NORMARE_COMENZI` și `AUTO_ISTORIC_COMENZI` întorc exact aceleași 375 de `ID_LUCRARE` (0 diferențe în ambele sensuri), iar cu filtrul `gcCondLuna` aplicat, exact aceleași 179 de rânduri. `INCH_VALIDARE` coincide cu `DEV_TIP_DEVIZ` și cu `AUTO_NORMARE_COMENZI`, 0 NULL. - Cele 4 coloane noi din `AUTO_FACTURI_EMISE` sunt **identice** cu omoloagele din `AUTO_ISTORIC_COMENZI` pe toate cele 131 de `ID_LUCRARE` comune. - Garda `id_lucrare > 0` este necesară și funcționează: `RUL` are 6 597 de rânduri cu `ID_LUCRARE = 0`, iar view-urile nu întorc niciunul. - **NULL-urile sunt frecvente** (§6.6 de mai jos) — orice sumă/afișare are nevoie de `Nvl`. - **§3.2 se confirmă pe producție, se infirmă pe dev.** Pe schema de test (`RUL` = 10 374 rânduri) optimizatorul dez-corelează subinterogările și face `TABLE ACCESS FULL` pe `RUL` — de aici „T8 FAIL". **Pe producția clientului AUTOMOTIVE (`RUL` = 571 508 rânduri) planul folosește `INDEX RANGE SCAN IDX_RUL_001`**, corelat, exact cum prezicea §3.2; la fel manopera, pe `IDX_ORDL_00001` + `IDX_OPER_01`. Deci T8 era un artefact al volumului mic. - ~~Dar apare o problemă mai mare: ecranul întoarce 32 790 de rânduri, +28 % timp~~ — **RETRAS 23.09.2026**, măsurătoare invalidă (lipsea `pack_sesiune.setluna/setan`). Real: **164 de rânduri, ~3.3 s, +0.4 s de la coloanele noi**. Vezi §7 și `docs/vfp_oracle_pack_sesiune.md`. - Cost I/O pe schema de test: 642 → 1 207 `consistent gets` (**1.88×**) pentru aceleași 179 de rânduri. ### Ce e de reținut pentru sesiunile următoare - **Capcană FoxBin2Prg**: blocurile `ADD OBJECT '..'` trebuie scrise în **ordine alfabetică** (case-insensitive) în `.vc2`; altfel `txt2vcx.ps1` cade la fidelity-check. Ordinea canonică se citește din `\verify\*.vc2` — fișierul acela e chiar editarea ta rescrisă canonic, deci se poate adopta ca sursă. - **Decizii deja luate, nu le relua**: §4 varianta 1; manopera în ambele forme (deviz + facturat); costul din `RUL.PRET`, nu din `VANZARI_DETALII.PRET_ACHIZITIE`. - **Interzis**: să atingi `AUTO_NORMARE_COMENZI` (§4); coloane noi la mijlocul gridurilor (§6.1); să publici scriptul la clienți fără cererea explicită a lui Marius; commit fără ca Marius să vadă diff-ul. - Conectare la Oracle de dev: vezi `COMUN\docs\conventie_mediu_oracle.md` §4. - `docs/handoff_comenzi_luna_curenta.md` și `docs/handoff_cost_materiale_manopera.md` sunt predări de subagenți, integral acoperite de acest document — de ignorat sau de șters. ## 1. Cererea clientului > „în vizualizare comenzi în luna curentă (la fel și pentru facturile în luna curentă), am nevoie > să apară și orele manoperă, valoare manoperă, valoare piese facturate și valoare piese achiziție" Îngrijorarea ta: comenzile deschise în luna curentă pot fi foarte vechi, deci calculul valorii și costului materialelor din `RUL` ar însemna o selecție pe toată baza. Constrângeri: Oracle Express (fără view-uri materializate), compatibil Oracle 10g. ## 2. Descoperirea care schimbă tot: funcționalitatea există deja Ecranul **„Istoric comenzi"** (meniu Comenzi, butonul de lângă „Vizualizare comenzi" — `Clase/ofundal_dev.vc2:685` → `vizualizare_ist_comenzi`) afișează **deja exact cele patru valori cerute**, plus încă două. Cursorul vine din view-ul Oracle `AUTO_ISTORIC_COMENZI` (`Programe/oproceduri_vizualizare.prg:443-476`), iar gridul din `frm_istoric_comenzi` (`Clase/oviz_devize.vc2:9115`) are coloanele: | Coloană grid | Coloană view | Ce e | |---|---|---| | `cOreManopera` | `ORE_MANOPERA` | ore manoperă (normate, din deviz) | | `cValManopera` | `VAL_MANOPERA` | valoare manoperă (din deviz) | | `cValManoperaFactura` | `VAL_MANOPERA_FACTURA` | manoperă efectiv facturată (din contabilitate, cont 704) | | `cValMaterialeVz` | `VAL_MATERIALE_VZ` | **valoare piese la preț de vânzare** | | `cValMaterialeAch` | `VAL_MATERIALE_ACH` | **valoare piese la preț de achiziție** | | `cValMaterialeFactura` | `VAL_MATERIALE_FACTURA` | materiale efectiv facturate (cont 707/419) | Diferența față de ce cere clientul este **doar filtrul**: „Istoric comenzi" pornește de la `lcFiltru = [2=2]` (`Clase/oviz_devize.vc2:10193`) cu filtre opționale pe interval de `datai`, în timp ce „Vizualizare comenzi" pornește de la `gcCondLuna` (`Clase/oviz_devize.vc2:15187`, definit în `Programe/ovariabile_globale.prg:16`). **Deci munca reală nu e „de calculat ceva nou", ci „de mutat 4-6 coloane deja existente și verificate în producție pe alte două ecrane".** ## 3. Analiza de cost pe bază de date ### 3.1 Costul de achiziție al pieselor NU se recalculează — e fixat la bonul de consum Pe fluxul auto, piesele **ies din gestiune la bonul de consum**, nu la facturare. În momentul emiterii bonului, pe rândul de ieșire din `RUL` se scriu deodată: - `RUL.PRET` = preț de achiziție - `RUL.PRETV` = preț de vânzare - `RUL.ADAOS` / `VALOARE_ADAOS` = adaosul - `RUL.CANTE` = cantitatea ieșită Exemplu real (`id_lucrare=14`): `pret=27.88`, `pretv=32.062`, `cante=1`. **Facturarea comenzii auto nu mai descarcă nimic** — doar citește din `RUL` ce s-a consumat deja și calculează valoarea de vânzare a materialelor, alături de manoperă. `frm_emitere_facturi.do_factureaza_final` (`Clase/oviz_devize.vc2:2358-2366`): ```sql select b.denumire, b.um, a.cante as cant, a.pretv, a.pret, a.dataact, a.id_rul, ... from (select id_articol, id_lucrare, pretv, ..., SUM(cante) as cante, ... from rul where sters = 0 and id_lucrare = group by id_lucrare, id_articol, pretv) a left join nom_articole b on a.id_articol = b.id_articol where a.cante <> 0 ``` Deci `PACK_FACTURARE.cursor_gestiuni_articol` (descărcarea FIFO din `STOC` la emiterea facturii) **nu e pe traseul comenzii auto** — acela e fluxul de facturare direct din stoc, al celorlalte aplicații ROA. Pentru ROAAUTO, sursa de adevăr pentru ambele prețuri este rândul de ieșire din `RUL`, scris la bonul de consum. Consecința pentru cerere: `val_materiale_ach = sum(pret*cante)` și `val_materiale_vz = sum(pretv*cante)` citesc exact valorile de pe bonurile de consum ale comenzii. **Nicio reconstrucție de cost la raportare** — nici FIFO, nici medie ponderată. Îngrijorarea legată de recalculul costului nu se confirmă. `RUL` nu conține niciodată manoperă: descărcarea sare articolele cu `NOM_ARTICOLE.IN_STOC = 0` (`SCRIPTURI_CLAR\2026\09\ff_2026_09_16_04_COMUN_PACK_FACTURARE.sql:8264-8270`): ```sql SELECT MAX(IN_STOC) INTO lnInStoc FROM NOM_ARTICOLE WHERE ID_ARTICOL = V_ID_ARTICOL; if lnInStoc = 0 then GOTO SFARSIT; end if; ``` Confirmat și pe date: ieșirile cu `id_lucrare` au doar conturi de stoc (371, 303, 345, 302x), niciodată 704. Deci suma nu amestecă manopera cu piesele. **De ce NU se folosește `VANZARI_DETALII.PRET_ACHIZITIE` ca sursă a costului.** Tocmai pentru că, pe fluxul auto, factura nu descarcă gestiunea, coloana aceea nu se populează. Pe schema de test e 0 pe aproape tot, inclusiv pe liniile de piese: | `NOM_ARTICOLE.IN_STOC` | linii | din care `PRET_ACHIZITIE = 0` | |---|---|---| | 0 (servicii/manoperă) | 2 526 | 2 475 (98 %) | | 1 (piese, gestionabile) | 8 889 | **8 561 (96 %)** | `RUL.PRET` rămâne singura sursă corectă — și e oricum cea folosită deja de `AUTO_ISTORIC_COMENZI`. ### 3.2 Subinterogarea pe `RUL` merge pe index, nu pe toată baza Formularea din `AUTO_ISTORIC_COMENZI`: ```sql (select sum(round(pret * cante, 2)) from rul where sters = 0 and id_lucrare = a.id_lucrare) as val_materiale_ach, (select sum(round(pretv * cante, 2)) from rul where sters = 0 and id_lucrare = a.id_lucrare) as val_materiale_vz ``` Există indexul **`IDX_RUL_001 (ID_LUCRARE, STERS)`**, creat încă din `D:\ROA\DATABASE\SCRIPTURI\2009\1\ff_2009_01_21_01_GESTIUNI.sql:63` și folosit explicit, cu hint, în `pack_auto.actualizeaza_deviz` (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:711`, acum șase luni) — deci e viu în producție, la toți clienții actualizați. `EXPLAIN PLAN` confirmă că Oracle împinge predicatul și accesează `RUL` prin: ``` VIEW PUSHED PREDICATE VW_SSQ_4 SORT GROUP BY TABLE ACCESS BY INDEX ROWID BATCHED RUL INDEX RANGE SCAN IDX_RUL_001 (access "ID_LUCRARE"=... AND "STERS"=0) ``` Costul scalează cu **numărul de rânduri afișate × rândurile de rulaj ale acelei comenzi**, nu cu mărimea istoricului. O comandă veche nu costă mai mult decât una nouă. ### 3.3 Ce chiar costă — și costă DEJA, azi, fără nicio modificare `AUTO_NORMARE_COMENZI` (sursa ecranului „Vizualizare comenzi") face deja `left join auto_vordl_facturi` → `auto_vordl_facturate` → **`MV_ORDL_SUME_ACT`**, care e un view normal (nu materializat, în ciuda numelui) ce agregă tabela `ACT` fără filtru de dată. La fel `AUTO_FACTURI_EMISE`, care are `join mv_ordl_sume_act`. Adică partea „scumpă" e deja plătită la fiecare deschidere a celor două ecrane. În plan, și aceasta primește predicat împins (`INDEX RANGE SCAN IDX_ORDL_AVANS` pe `ACT(ID_LUCRARE,STERS,ID_SET,SCD)`). ### 3.4 Măsurători (schema de test — cifre relative, nu absolute) `autotrace` pe `MARIUSM_AUTO`, toate comenzile din istoric (375 rânduri), respectiv toate facturile (154 rânduri): | Variantă | consistent gets | Δ | |---|---|---| | A — `AUTO_NORMARE_COMENZI`, lista de coloane de azi | 660 | — | | B — A + cele 4 coloane, formularea din `AUTO_ISTORIC_COMENZI` | 1 149 | +74 % | | C — ca B, dar cu `/*+ NO_UNNEST */` pe subinterogările de manoperă | 2 107 | +219 % (mai rău) | | D — ca B, dar manopera corelată direct pe `a.id_ordl` | 1 050 | +59 % | | E — `AUTO_FACTURI_EMISE`, lista de coloane de azi | 1 726 | — | | F — E + cele 4 coloane | 1 703 | ≈ 0 | Concluzii: - Pe **facturi** adăugarea coloanelor e practic gratuită (diferența e zgomot de măsurare): view-ul plătește deja agregarea peste `ACT`. - Pe **comenzi** creșterea e de ordinul a +60-75 % din I/O al ecranului, pe o interogare care azi costă sub o secundă. Absolut: aici baza de test e mică (`RUL` 10 320 rânduri, `ACT` 70 723, `DEV_OPER` 2 414). **Cifra trebuie reconfirmată la client** (punctul 7). - Partea de manoperă (`DEV_ORDL` ⋈ `DEV_OPER` pe `id_lucrare`) este singura care, în planul implicit, se „dez-corelează" într-un `HASH GROUP BY` peste tabelele întregi — deci singura care scalează cu istoricul. Varianta D o evită, dar schimbă semantica dacă o lucrare poate avea mai multe `ordl` (în baza de test raportul e 1:1 pe toate cele 375 de lucrări — de verificat la client). ### 3.5 Capcană de date `RUL.ID_LUCRARE` **nu e null niciodată** — mișcările care nu țin de o lucrare au `ID_LUCRARE = 0` (4 110 din 7 358 rânduri în baza de test, adică 56 %). La un client real acolo stau milioanele de mișcări de stoc obișnuite. O comandă auto are întotdeauna un `id_lucrare > 0` din `NOM_LUCRARI`, deci subinterogarea nu ar trebui să nimerească niciodată „găleata" 0 — dar merită o gardă explicită (`and a.id_lucrare > 0`), altfel un singur rând cu `id_lucrare = 0` scanează tot. ### 3.6 Unde chiar există problema de care te temeai (dar în alt loc) `Programe/oproceduri_listari.prg:1607-1625`, procedura `facturi_emise_pe_sectii`: ```sql left join (select sum(round(pret * cante, 2)) as valoarea, sum(round(pretv * cante, 2)) as valoarev, id_lucrare, id_sectie from rul where sters = 0 group by id_lucrare, id_sectie) e on a.id_lucrare = e.id_lucrare and a.id_sectie = e.id_sectie ``` Aici subselectul chiar grupează **tot** `RUL` (singurul filtru e `sters = 0`), iar join-ul pe perioadă se face abia după. Ăsta e „selecția pe toată baza" reală — dar în raportul de facturi emise pe secții, nu în ecranele din cerere. De reținut ca anti-pattern (formularea propusă aici, corelată pe `id_lucrare`, îl evită) și ca loc de reparat separat dacă raportul acela e lent la client. ## 4. Variante ### Varianta 0 — niciun cod: clientul folosește „Istoric comenzi" Zero efort, zero risc. Dezavantaj: filtrul e pe interval de `datai`, nu „comenzi deschise în luna curentă", și nu acoperă deloc ecranul de facturi. **Merită propusă clientului ca soluție imediată**, cât timp se lucrează la restul. ### Varianta 1 (APROBATĂ) — ecranul de comenzi trece pe `AUTO_ISTORIC_COMENZI` Ideea lui Marius, mai bună decât varianta inițială: nu se copiază subinterogările în `AUTO_NORMARE_COMENZI`, ci **`vizualizare_comenzi` își schimbă sursa** pe `AUTO_ISTORIC_COMENZI`, păstrând filtrul `gcCondLuna`. Comparația coloanelor celor două view-uri (rulată pe `MARIUSM_AUTO`) arată că `AUTO_ISTORIC_COMENZI` conține tot ce folosește azi `frm_viz_comenzi`, cu **o singură excepție**: `INCH_VALIDARE`, de care are nevoie `gcCondLuna`. Se adaugă din `dev_tip_deviz`, deja joinat acolo (`i.inch_validare`). De ce e mai bună: - **un singur view modificat, cu o singură coloană** — în loc de patru subinterogări în două view-uri; - `AUTO_NORMARE_COMENZI` rămâne **neatins**, iar el mai e folosit în **10 alte locuri** (`Programe/oproceduri_devize.prg:233` și `:1632`, `Programe/oproceduri_listari.prg:998`, `Programe/oproceduri_vizualizare.prg:387` și `:515`, `Clase/oviz_devize.vc2:1579`, `:2316`, `:4481`, `:11724`, `:18751`), inclusiv un `select *`. Varianta inițială le-ar fi pus pe toate să plătească subinterogările degeaba; - SQL-ul de calcul rămâne exact cel rulat deja în producție pe „Istoric comenzi" → risc semantic zero; - compatibil Oracle 10g, fără obiecte noi, fără indecși noi, fără MV. Ecranul de facturi rămâne pe `AUTO_FACTURI_EMISE` (altă formă — un rând per factură, cu `nrcrt`), deci acolo cele patru coloane se adaugă în view. ### Varianta 2 — optimizarea manoperei (corelare pe `id_ordl`) Subinterogările de manoperă scrise direct pe `DEV_OPER` prin `a.id_ordl` (index `IDX_OPER_01`), în loc de `DEV_ORDL ⋈ DEV_OPER` pe `id_lucrare`. Scapă de singurul acces care scalează cu istoricul. Marius a confirmat că **o lucrare are exact un `dev_ordl`**, deci e aplicabilă — dar rămâne o optimizare ulterioară, de făcut doar dacă măsurătorile la client o cer. Nu intră în livrarea curentă. ### Varianta 3 — tabelă de totaluri întreținută la validare/facturare Un `AUTO_COMENZI_TOTALURI(id_lucrare, ore_manopera, val_manopera, val_mat_ach, val_mat_vz)` scris de `PACK_DEVIZE`/`PACK_FACTURARE` la validare și la facturare. Citire O(1) per rând. **Nerecomandat acum**: cost mare (pachet + migrare + backfill + risc de desincronizare) pentru o problemă care, după măsurători, nu există încă. De ținut în sertar dacă varianta 1 se dovedește lentă la un client cu volum mare. ## 5. Plan de implementare — APROBAT de Marius, 22.09.2026 Ordinea contează: întâi baza, apoi VFP — altfel cursorul cere coloane inexistente. **Pas 1 — script de bază de date.** Versiunile curente ale celor trei view-uri sunt în `D:\ROA\DATABASE\SCRIPTURI_CLAR\` (istoricul de migrări, SVN, partajat între aplicațiile ROA — nu în `Scripturi_instalare\` din ROAAUTO, care e doar install-ul inițial): - `AUTO_ISTORIC_COMENZI` — `2025\06\ff_2025_06_30_01_AUTO.sql:37-95` - `AUTO_FACTURI_EMISE` — de localizat în același arbore Se scrie un script nou cu `CREATE OR REPLACE VIEW`, necalificat, fără prefix de schemă: - **`AUTO_ISTORIC_COMENZI`**: se adaugă o singură coloană, `i.inch_validare`, din `dev_tip_deviz` deja joinat. Restul view-ului rămâne neschimbat. - **`AUTO_FACTURI_EMISE`**: se adaugă `ORE_MANOPERA`, `VAL_MANOPERA`, `VAL_MATERIALE_ACH`, `VAL_MATERIALE_VZ`, copiate literal din `AUTO_ISTORIC_COMENZI`, cu garda `a.id_lucrare > 0`. (`MANOPERA` și `MATERIALE` — cele facturate — există deja acolo.) `AUTO_NORMARE_COMENZI` **nu se atinge.** Se rulează **doar la cererea ta explicită**, pe `MARIUSM_AUTO` de pe `ROA_CENTRAL` întâi. Se publică apoi prin fluxul obișnuit de scripturi de actualizare (vezi skill-ul `roa-oracle-migration`). **Pas 2 — cursoarele VFP** (`Programe/oproceduri_vizualizare.prg`): - `vizualizare_comenzi` (`:649`) — sursa se schimbă din `auto_normare_comenzi` în **`auto_istoric_comenzi`**; `pcschema` și `pcselect` se extind cu `ORE_MANOPERA`, `VAL_MANOPERA`, `VAL_MANOPERA_FACTURA`, `VAL_MATERIALE_VZ`, `VAL_MATERIALE_ACH`, `VAL_MATERIALE_FACTURA` (`N(20,4)`, ca în `vizualizare_ist_comenzi`). Filtrul rămâne `gcCondLuna`. - `vizualizare_facturi` (`:231`) — `pcschema`/`pcselect` extinse cu cele 4 coloane noi. **Pas 3 — gridurile** (`Clase\oviz_devize.vc2`, write-back prin `txt2vcx.ps1`): - `frm_viz_comenzi` (`:14503`, grid `grdcomenzi`) — coloane noi **la coadă**, copiate ca definiție (format, aliniere, `InputMask`) din coloanele omoloage ale lui `frm_istoric_comenzi` (`:9115`, `Column19`-`Column25`). - `frm_viz_facturi` (`:16762`, grid `grdfacturi`) — idem. `do_excel` iterează generic peste `grid.Columns` pe ambele formulare (`Clase/oviz_devize.vc2:15208` și `:17472`) — coloanele noi apar automat în export, fără cod. `do_listare` diferă între ecrane: - la comenzi (`:15381`) nu atinge gridul — nimic de făcut; - la facturi (`:17570`) exportă prin raportul `.frx` **`rap_facturi_clienti`** (`goExport.export2frx`). Dacă se vrea coloana și pe listarea tipărită, raportul se editează **doar în IDE-ul VFP** (fără write-back din text). **Pas 4 (opțional) — totaluri pe ecranul de facturi.** `frm_viz_facturi.refreshdata` (`Clase/oviz_devize.vc2:17610`) calculează deja sumele afișate sub grid pe `valctva`, `manopera`, `materiale`, cu `WHERE nrcrt=1` pentru deduplicarea liniilor multiple pe aceeași factură. Totalurile pentru coloanele noi se adaugă aici, cu aceeași gardă `nrcrt=1`, altfel se dublează. **Pas 5 (opțional) — listarea facturilor.** `do_listare` la facturi (`:17570`) exportă prin raportul `.frx` `rap_facturi_clienti`. Coloanele noi se adaugă acolo **doar în IDE-ul VFP** (fără write-back din text). La comenzi, `do_listare` (`:15381`) nu atinge gridul — nimic de făcut. **Pas 6 — testare.** Pe lângă cronometrarea pe lună curentă la un client cu volum mare: - valorile din ecranul de comenzi trebuie să coincidă cu cele din „Istoric comenzi" pentru aceleași comenzi (aceeași sursă, deci trebuie să fie identice); - `do_executa` și `do_listare` din `frm_viz_comenzi` fac `Scatter Name ocomanda` pe cursor și pasează obiectul la `vizualizeaza_detalii_comanda` / `listare_comanda_deviz`. Câmpurile folosite (`id_ordl`, `id_lucrare`, `proc_tvav`, `validat`) există în ambele view-uri, dar **schimbarea sursei cursorului trebuie verificată la rulare** — e singurul punct unde se poate rupe ceva. ## 6. Capcane de reținut la implementare 1. **GridExtras — coloanele noi se adaugă OBLIGATORIU la coadă.** Ambele formulare apelează `this.gridextra1.setup()` (`Clase/oviz_devize.vc2:15441` și `:17607`), deci preferințele de coloane sunt salvate per utilizator în `gridprefs.tmp`. `restoregridpreferences` (`COMUN/utile/GridExtras/gridextras.vc2:1051`) aplică perechile `ColumnOrder,Width` **pozițional**, pe index de coloană: `loColumn = toGridObject.Columns((lnCounter + 1)/2)`, cu `lnMax = Min(ColumnCount_nou * 2, Alen(preferinte_vechi))`. Consecințe: - coloană adăugată **la coadă** (`Column14`, `Column13`): preferințele vechi 1..N se aplică corect, iar coloana nouă ajunge vizual ultima (acceptabil — vezi soluția de repoziționare runtime din `COMUN\docs\capcana_grid_preferinte_utilizator.md`, aplicată pe `frm_modific2024`); - coloană **inserată la mijloc**: preferințele vechi se aplică peste coloane deplasate → lățimi și ordini greșite pentru toți utilizatorii existenți. **De evitat.** 2. **`ALTER TABLE` pe cursorul de la `goExecutor`** nu merge — coloanele noi trebuie să fie în `SELECT` de la început (`COMUN\docs\conventie_goexecutor_alter_table.md`). Aici e respectat by design, pentru că se modifică `pcselect`. 3. **Encoding CP1252** la editarea `.vc2` (`COMUN\docs\conventie_encoding_cp1252.md`); write-back doar prin `txt2vcx.ps1`, skill `roa-vfp-text-edit`. 4. `CursorFill` aduce **tot** setul de rezultate în cursor local (`COMUN\clase\decabaza.vc2:36`), deci costul e proporțional cu numărul de rânduri returnate — filtrul de lună chiar contează. 5. Filtrele sunt diferite pe cele două ecrane: facturi = strict `dataact` în luna curentă (`Clase/oviz_devize.vc2:17425`); comenzi = `gcCondLuna`, care include **și comenzi mai vechi nefacturate/nevalidate** (`Programe/ovariabile_globale.prg:16`). Asta e sursa reală a comenzilor „foarte vechi" — dar, cum s-a arătat la 3.2, vechimea nu scumpește subinterogările. 6. **Planul de execuție măsurat pe `MARIUSM_AUTO` nu spune nimic despre client — și greșește în ambele sensuri.** Plătită pe 22.09.2026, măsurând aceeași interogare pe dev și pe producția AUTOMOTIVE: - pe dev (`RUL` = 10 374 rânduri) optimizatorul **dez-corelează** subinterogările scalare (unnesting) într-un `GROUP BY` global și face `TABLE ACCESS FULL` pe `RUL` — concluzia ar fi fost „indexul nu se folosește, e periculos"; - pe producție (`RUL` = 571 508 rânduri) aceeași interogare păstrează subinterogările corelate și folosește `INDEX RANGE SCAN IDX_RUL_001` — exact invers. Costul subinterogărilor corelate se înmulțește cu numărul de rânduri afișate (~16 buffer gets/rând), deci și numărul acela trebuie luat de la client. **Regulă**: orice afirmație despre plan sau cost pentru o interogare cu subinterogări corelate se verifică pe un client real, nu pe `MARIUSM_AUTO`. Două lucruri trebuie luate de la client, nu de pe dev: **forma planului** (corelat vs dez-corelat) și **numărul de rânduri returnate**. 7. **`gcCondLuna` nu înseamnă „luna curentă".** Include și comenzile vechi nefacturate (`inch_validare=0`) sau nevalidate (`inch_validare=1`). La AUTOMOTIVE, luna 10/2025: 107 comenzi ale lunii + 88 vechi = **195 de rânduri** (164 după filtrul implicit „Active") — deci coada veche e mică aici, dar numărul trebuie măsurat, nu presupus. **Atenție**: se măsoară doar cu `pack_sesiune.setluna/setan` inițializate (`docs/vfp_oracle_pack_sesiune.md`); altfel `validat` iese 0 peste tot și numărul sare la 32 790. ## 7. Verificat pe producție (AUTOMOTIVE, 22-23.09.2026) — închis Măsurat prin tunel SSH, strict read-only. Detalii și planuri: `docs/review/teste_coloane_manopera_piese_viz.md`, secțiunea „Teste pe PRODUCTIE". > **Corectat 23.09.2026.** Măsurătoarea din 22.09 (32 790 rânduri / ~50 s) era **invalidă**: > rulată din `sqlplus` fără inițializarea lunii de lucru. Vezi > `docs/vfp_oracle_pack_sesiune.md`. Cifrele de mai jos sunt cele refăcute corect. 1. **Câte rânduri întoarce „Vizualizare comenzi" pe luna curentă și cât durează?** Cu luna de lucru 10/2025 inițializată (`pack_sesiune.setluna/setan`, exact ce face aplicația la login): **195 de rânduri** cu filtrul `gcCondLuna` gol, **164** cu filtrul implicit al formularului (`cbArhivat = "Active"` → `inchis_fortat = 0`, `Clase/oviz_devize.vc2:15630`). **~3.3 s prin tunel.** Ecranul nu e greu la acest client. 2. **Dimensiunile reale**: `ACT` 833 257, `RUL` 571 508, `DEV_OPER` 137 285, `DEV_ORDL` 38 609. `RUL` are 215 269 de rânduri cu `ID_LUCRARE = 0` (161 259 cu `STERS = 0`) — garda `id_lucrare > 0` nu e teoretică, chiar ține la distanță 161 de mii de rânduri. 3. **`IDX_RUL_001` există la client?** **Da**, `NORMAL NONUNIQUE (ID_LUCRARE, STERS)`, și **este folosit** de planul formei noi. Nu e nevoie de `CREATE INDEX`. 4. ~~Poate o lucrare să aibă mai multe `dev_ordl`?~~ **Închis**: exact unul. 5. ~~„Valoare manoperă" = deviz sau facturat?~~ **Închis**: se livrează amândouă. ### 7.6 Costul măsurat pe producție, pe filtrul real (refăcut 23.09.2026) Aceeași sesiune, luna de lucru 10/2025 inițializată, filtrul implicit al formularului (`inchis_fortat = 0`), 164 de rânduri, două rulări fiecare, scăzut overhead-ul de conectare: | Variantă | timp net | |---|---| | fără coloanele noi | 3.3 s | | cu cele 6 coloane noi | 3.7 s | | raport | **×1.13 (+0.4 s)** | Prin tunel SSH; în LAN-ul clientului, mai puțin. Costul scalează cu **numărul de rânduri afișate** (~16 buffer gets/rând), deci diferența rămâne mică atâta timp cât ecranul întoarce sute, nu zeci de mii de rânduri. ### 7.7 Ce facem cu asta — decis 23.09.2026 **Nu e nimic de decis.** Problema de performanță (cele 32 790 de rânduri / 50 s) nu există: era artefactul măsurătorii, nu al datelor. Pe filtrul real ecranul întoarce 164 de rânduri, iar coloanele noi adaugă ~0.4 s. Variantele 2 (strângerea filtrului) și 3 (tabelă de totaluri întreținută) **nu se mai justifică** — nu mai au ce să rezolve. Se merge cu implementarea existentă, la review pe diff. ## 8. Recomandare Varianta 1, aprobată de Marius pe 22.09.2026: ecranul de comenzi trece pe `AUTO_ISTORIC_COMENZI` (cu `INCH_VALIDARE` adăugat), ecranul de facturi primește cele patru coloane în `AUTO_FACTURI_EMISE`, cu garda `id_lucrare > 0`. `AUTO_NORMARE_COMENZI` rămâne neatins. Este cel mai mic diff posibil care rezolvă cererea, reutilizează SQL deja rulat în producție și nu adaugă niciun obiect nou în bază. Până la livrare, clientul poate folosi „Istoric comenzi" (varianta 0).