sync SVN r18196

This commit is contained in:
2026-09-22 19:43:55 +03:00
parent 5b94492806
commit c047ff339a
3 changed files with 599 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# Handoff: cercetare "ecran comenzi in luna curenta" (read-only)
Sarcina: identificare ecran ROAAUTO pentru lista de comenzi (work orders) din luna curenta,
la cererea team-lead. Cercetare READ-ONLY — nu s-a modificat niciun fisier din proiect.
Toate constatarile au fost deja transmise integral catre `team-lead` prin doua mesaje
SendMessage (cu citate exacte de cod si file:line). Acest fisier e doar predarea pe disc,
conform REGULA ZERO — pentru cazul in care mesajele s-ar pierde.
## Stare: COMPLET pentru intrebarile puse pana acum (2 runde)
Nu exista lucru neterminat, nu exista fisiere editate, nu exista tranzactii deschise,
nu exista date de test consumate. Subagentul nu a scris/modificat cod.
## Inventar constatari (file:line)
### Runda 1 — identificarea ecranului
- Punct de intrare: tile dashboard "Comenzi în luna curentă"
- `Clase/ofundal_dev.vc2:266` — caption `Label_item1.Caption`
- `Clase/ofundal_dev.vc2:681-682` — `DO vizualizare_comenzi IN oproceduri_vizualizare.prg`
- Procedura care construieste cursorul: `Programe/oproceduri_vizualizare.prg:649-678`
(`Procedure vizualizare_comenzi`), sursa = view Oracle `auto_normare_comenzi`,
cursor `crscomenzi`, porneste cu `pcfiltru=[1=2]` (grid gol la deschidere),
deschide formularul `frm_viz_comenzi`.
- Filtrul real de "luna curenta" = `gcCondLuna`, aplicat in formular la `do_cauta`
(`Clase/oviz_devize.vc2:15148`, clasa `frm_viz_comenzi` incepe la `:14503`).
- `gcCondLuna` definit in `Programe/ovariabile_globale.prg:14-18` — NU e "creat luna asta",
ci "luna de lucru curenta (gnLuna/gnAn) SAU comenzi mai vechi inca deschise/nefacturate/
nevalidate".
- Coloane grid (13) + Oracle view sursa + lipsa camp manopera/piese calculat — detaliate in
mesajul 1 catre team-lead.
### Runda 2 — detalii cerute explicit (puncte 1-4)
1. Grid `frm_viz_comenzi`: `Clase/oviz_devize.vc2:14745` (ADD OBJECT 'grdcomenzi',
ColumnCount=13, RecordSource="crscomenzi"), coloane 1-13 la liniile 14760-14818,
headere la 14827-14962. Coloana noua → extinde `ColumnCount` + bloc `ColumnN` acolo,
plus camp adaugat in `pcschema`/`pcselect` din `oproceduri_vizualizare.prg:656-666`.
2. Preferinte grid per utilizator: mecanism `GridExtras`
(`COMUN/utile/GridExtras/gridextras.vc2:337-1434`, clasa `gridextra`), instantiat ca
`Gridextra1` (`Clase/oviz_devize.vc2:15065`), pornit din `Init` (`:15438-15441`) prin
`this.gridextra1.setup()`. `restoregridpreferences` (`gridextras.vc2:1051`) si
`savegridpreferences` (`:1130`) salveaza perechi `ColumnOrder,Width` **pozitional pe
index de coloana** (nu pe nume/ControlSource) in `%APPDATA%\...\gridprefs.tmp`, cheie
`SYS(1272, grid)`. Confirma capcana din `COMUN/docs/capcana_grid_preferinte_utilizator.md`:
coloana noua trebuie adaugata la coada (ultimul index), nu in mijloc.
3. `gencursor()`: `COMUN/programe/gencursor.prg:1` — creeaza `NEWOBJECT('deca_baza')` +
`.ADDOBJECT('ca_baza1','ca_baza')`. Clasa `ca_baza` (`COMUN/clase/decabaza.vc2:7`) e
bazata pe `cursoradapter` (via `_cabase`/`_ca_base.vcx` — FetchSize nu am gasit sa fie
suprascris explicit, ramane implicit din `_ca_base.vcx`, neverificat). `afisare()`
(`decabaza.vc2:36-74`) taie SQL-ul de la `WHERE` incolo si il reconstruieste cu
`cfiltru` curent — aici se inlocuieste `1=2` cu filtrul real.
4. Ecran cu coloanele `AUTO_ISTORIC_COMENZI` (VAL_MANOPERA_FACTURA etc.): DA, exista deja —
`frm_istoric_comenzi` (`Clase/oviz_devize.vc2:9115-10506`), grid cu `ColumnCount=25`,
coloanele 19-25 (`:9579-9622`) afiseaza exact `valctva_factura, val_manopera_factura,
val_materiale_factura, val_materiale_ach, val_materiale_vz, ore_manopera, val_manopera`.
Filtrul de luna NU e `gcCondLuna` — `do_cauta` (`:10190`) porneste cu `lcFiltru=[2=2]`
(fara restrictie de luna), filtrare doar optionala prin checkbox-uri UI.
## Ce s-a stabilit deja (sa nu se reia)
- Ecranul-tinta pt "comenzi in luna curenta" = `frm_viz_comenzi` (nu `frm_istoric_comenzi`,
nu `vizualizare_manopera`).
- Query-ul de baza si mecanismul de filtrare (gencursor + ca_baza.afisare) sunt clarificate,
nu mai cauta SQL-ul de la zero.
- Capcana pozitionala GridExtras e confirmata in cod, nu doar in doc.
## Interzis / neverificat
- Nu am citit `_ca_base.vcx` — FetchSize exact al CursorAdapter-ului ramane neconfirmat daca
devine relevant (ex. pentru performanta pe volum mare).
- Nu am cautat DDL-ul view-urilor Oracle (`auto_normare_comenzi`, `auto_istoric_comenzi`) —
nu exista in acest repo VFP; ar necesita acces Oracle (`roa-oracle-production-access`).
## Comanda de rulare teste
N/A — sarcina a fost strict cercetare/cautare in cod, fara modificari, fara teste rulate.

View File

@@ -0,0 +1,156 @@
# Handoff: cercetare "cost materiale / rulaj + manoperă" (read-only)
Sarcina (subagent `lane-cost`): găsește și documentează cum se determină azi (a) costul de
achiziție al unei piese ieșite pe o comandă/factură și (b) valoarea + orele de manoperă,
pentru cererea clientului de a afișa aceste valori în listele de comenzi/facturi. Cercetare
READ-ONLY — **niciun fișier din proiect nu a fost modificat.**
## Stare: COMPLET
Toate constatările au fost transmise integral către `team-lead` prin două mesaje SendMessage
(cu citate exacte de cod și file:line). Acest fișier e doar predarea pe disc, conform REGULA
ZERO — pentru cazul în care mesajele s-ar pierde. Nu există lucru neterminat, fișiere editate,
tranzacții deschise sau date de test consumate.
**Important**: în timp ce scriam handoff-ul am găsit că exista deja pe disc
[`docs/propuneri_coloane_manopera_piese_viz.md`](propuneri_coloane_manopera_piese_viz.md) —
un document mult mai complet (scris cu acces direct la Oracle: `EXPLAIN PLAN`, `autotrace`,
măsurători reale pe schema `MARIUSM_AUTO`/`ROA_CENTRAL`), care **acoperă și depășește** tot ce
am găsit eu static din cod. Acela e documentul de referință pentru implementare, nu acesta.
Constatările mele (fără acces DB, doar citire de cod) coincid cu ale lui la toate punctele
verificabile static.
## Inventar constatări (file:line)
### 1. Tabelul RUL (rulaj) — coloane relevante
Schema cursorului `viz_rulaje`: `COMUN/programe/oproceduri_rulaje.prg:15-25`.
`PRET n(16,4)` = preț achiziție/cost per rând de mișcare, `PRETV n(16,4)` = preț vânzare,
`CANT`/`CANTE` = cantitate, `COD` = document sursă, `ID_LUCRARE`, `ID_GESTIUNE`, `STERS`.
### 2. Cost achiziție — DEJA STOCAT pe linia de vânzare/factură (nu recalculat la citire)
- Schema cursor linii deviz: `Programe/oproceduri_devize.prg:882` — `pret_achizitie N(20,4)`.
- Populat din lotul de stoc consumat: `COMUN/clase/ofacturare.vc2:4233,13131,13511` —
`poArticol.pret_achizitie = Pret`.
- Scris efectiv prin `pack_facturare.adauga_articol_factura_deviz`
(`Programe/oproceduri_devize.prg:1218-1244`).
- Persistă și la editare facturi: `COMUN/programe/ofacturare_editare.prg:225,547,652,775,807-817`;
schema tabel `vanzari_det`: `COMUN/programe/oproceduri_facturare.prg:378-491,1114-1133`.
- Rapoarte care citesc costul direct de pe linie, fără recalcul:
`COMUN/programe/oproceduri_rapoarte_fact.prg:579-590` (view `fact_vfacturi_detalii`).
### 3. Algoritm de determinare a costului la ieșire — FIFO pe LOT, din tabela STOC
Sursă (în afara repo-ului git ROAAUTO, în `D:\ROA\DATABASE\SCRIPTURI_CLAR\` — istoric SVN al
schemei Oracle COMUN, actualizat curent, ultimul fișier relevant la 2026-09-16):
`PACK_FACTURARE.cursor_gestiuni_articol`
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\09\ff_2026_09_16_04_COMUN_PACK_FACTURARE.sql:4513-4597`).
Selectează loturi din **`STOC`** (nu din `RUL`), filtrate pe `LUNA`/`AN`/`ID_ARTICOL`/
`ID_GESTIUNE`, cantitate rămasă = `CANTS + CANT - CANTE > 0`, ordonate **`ORDER BY DATAIN`**
(FIFO pe data intrării lotului). `PRET` = costul lotului ales, scris o singură dată pe linia
de vânzare (punctul 2). Rapoartele nu re-rulează FIFO, doar re-însumează ce s-a scris deja.
### 4. Manoperă — ore și valoare
DDL `DEV_OPER`: `Scripturi_instalare/tabele.sql:674-693` — `TIMPN NUMBER(8,3)` (ore),
`PRET NUMBER(17,4)` (tarif orar), `ID_ORDL` (FK spre `DEV_ORDL`, fără index explicit pe
`DEV_OPER.ID_ORDL` în DDL). `DEV_ORDL` are index pe `ID_LUCRARE` (`IDX_ORDL_00001`,
`Scripturi_instalare/tabele.sql:636`).
Valoare manoperă = `TIMPN * PRET`, agregată în raport:
`Programe/oproceduri_listari.prg:1608-1621` (`facturi_emise_pe_sectii`).
### 5. Raportul cerut de client EXISTĂ DEJA — view Oracle `AUTO_ISTORIC_COMENZI`
Versiune curentă (ultima modificare găsită, 2025-06-30, nimic mai nou):
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2025\06\ff_2025_06_30_01_AUTO.sql:37-95`.
```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,
(select sum(b1.timpn) from dev_ordl a1 left join dev_oper b1 on a1.id_ordl=b1.id_ordl where a1.id_lucrare=a.id_lucrare and a1.sters=0 and b1.sters=0) as ore_manopera,
(select sum(round(b1.timpn*b1.pret,2)) from dev_ordl a1 left join dev_oper b1 on a1.id_ordl=b1.id_ordl where a1.id_lucrare=a.id_lucrare and a1.sters=0 and b1.sters=0) as val_manopera,
```
Subselecte **corelate pe `id_lucrare`** (nu scan fără filtru), susținute de indexul
`IDX_RUL_001 on RUL(ID_LUCRARE, STERS)` (creat `D:\ROA\DATABASE\SCRIPTURI\2009\1\ff_2009_01_21_01_GESTIUNI.sql:63`,
încă folosit activ cu hint în producție — ultima apariție
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:711`).
Folosit deja în VFP: `Programe/oproceduri_vizualizare.prg:441-457` (`vizualizare_ist_comenzi`),
afișat în grid `Clase/oviz_devize.vc2:9585-9607` (`ControlSource`: `val_materiale_ach`,
`val_materiale_vz`, `val_materiale_factura`, `val_manopera_factura`, `ore_manopera`).
### 6. Raportul care CHIAR scanează tot RUL fără scopare pe lucrare (risc real, dar altă bucată de cod)
`Programe/oproceduri_listari.prg:1607-1625` (`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
```
`GROUP BY` peste tot `RUL` (fără filtru pe `id_lucrare` în where), join-ul pe perioadă se face
abia după — spre deosebire de `AUTO_ISTORIC_COMENZI` (punctul 5), care e corelat per lucrare.
Acesta e adevăratul candidat de "selecție pe toată baza de date", dacă vreodată devine relevant
de optimizat.
## Ce s-a stabilit deja (să nu se reia)
- Costul de achiziție e stocat pe linia de vânzare/factură (`pret_achizitie`), nu recalculat FIFO
la fiecare citire — FIFO rulează o singură dată, la facturare, pe tabela `STOC`.
- View-ul `AUTO_ISTORIC_COMENZI` deja livrează exact coloanele cerute de client și e deja folosit
în ecranul „Istoric comenzi"; documentul de propuneri recomandă copierea acelorași subinterogări
în `AUTO_NORMARE_COMENZI`/`AUTO_FACTURI_EMISE` pentru ecranele „Vizualizare comenzi"/"Facturi".
- Sursa de adevăr pentru DDL/PL-SQL Oracle (istoric complet de migrări, inclusiv versiuni curente
ale pachetelor și view-urilor) NU e în `Scripturi_instalare\` din ROAAUTO, ci în
`D:\ROA\DATABASE\SCRIPTURI\` (brut, cronologic) și `D:\ROA\DATABASE\SCRIPTURI_CLAR\` (reformatat) —
repo SVN separat, partajat între toate aplicațiile ROA.
## Interzis / nu s-a făcut
Nicio modificare de cod, niciun script Oracle rulat, niciun write-back `.vc2`/`.sc2`. Cercetare
pur read-only, conform mandatului primit.
## Runda 2 — întrebări punctuale de la team-lead (trimise, nu doar aici)
Sursă: `PACK_FACTURARE.descarca_gestiune`, în
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\09\ff_2026_09_16_04_COMUN_PACK_FACTURARE.sql` (versiune
curentă, 2026-09-16).
1. **`RUL.PRET` la ieșire — SIGUR**: nu se recalculează în `descarca_gestiune`. Procedura primește
`V_PRET_ACHIZITIE IN NUMBER` ca parametru, selectează din `STOC` exact lotul cu acel preț
(`AND A.PRET = V_PRET_ACHIZITIE`, linia 8766 și analog 8659/8899/10642), apoi
`V_PRET := tab_stoc(i).PRET` (linia 8975) și `V_PRET as PRET` în `INSERT INTO RUL_TEMP`
(linia 9456). FIFO-ul (alegerea lotului) se face separat, mai devreme, în
`cursor_gestiuni_articol` (`ORDER BY DATAIN`, raportat runda 1).
**Incert**: n-am găsit niciun `INSERT INTO RUL` literal (fără `_TEMP`) în acest fișier — nu am
urmărit cum `RUL_TEMP` ajunge fizic în `RUL`. Nu schimbă răspunsul (valoarea `PRET` e fixată la
`V_PRET` oricum), dar rămâne un gol de investigat dacă cineva vrea firul complet.
2. **RUL pe `id_lucrare` = doar piese, niciodată manoperă — SIGUR**: gardă la intrarea în
`descarca_gestiune` (liniile 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;
```
Articolele cu `IN_STOC=0` (servicii/manoperă) nu ajung niciodată să scrie în `RUL_TEMP`/`RUL`.
Replicat pe VFP prin `gestionabil = Iif(Nvl(in_stoc,0)<>0,1,0)`
(`COMUN/programe/ofacturare_editare.prg:242`).
3. **`CANTE` vs `CANT` — SIGUR**: `CANTE`=ieșire, `CANT`=intrare, din formula soldului
`CANTS+CANT-CANTE` (folosită consecvent, ex. `cursor_gestiuni_articol`). La retur (ramura
`WHEN pack_facturare.ntip IN (8,9)` din `descarca_gestiune`, ~linia 8355) nu se folosesc
cantități negative — ieșirea originală (`a.cante`) devine `cant` (intrare) pe rândul nou:
`SELECT 1 as tip, ..., a.cante as cant, 0 as cante, ... FROM RUL A ... WHERE A.CANTE<>0`.
4. **`DEV_OPER.TIMPN` = ore — SIGUR pe bază de uz**, nicio conversie de unitate întâlnită:
`Programe/oproceduri_listari.prg:1620` (`sum(...timpn) as ore`), `:2005` (`sum(timpn) as ore`),
caption `Clase/odevize.vc2:6310` (`"Total ore"`). Niciun `COMMENT ON COLUMN` găsit în DDL —
eticheta vine din convenția de cod, nu dintr-un comentariu de schemă.
5. **`VANZARI_DETALII.PRET_ACHIZITIE` — mecanism SIGUR, procentul de 95% zero INCERT**: scris de
același flux (`descarca_gestiune`/finalizare factură), doar pentru linii gestionabile
(`IN_STOC<>0`), cu `V_PRET` din `STOC` (condiție de MERGE, linia 12741:
`AND A.PRET_ACHIZITIE = V_PRET ...`). Pentru linii de manoperă (`IN_STOC=0`) rămâne 0 **prin
design** (codul nici nu rulează pentru ele — punctul 2). Nu am verificat empiric (fără acces
Oracle) dacă cele ~95% zero din schema de test chiar corespund liniilor de manoperă sau ascund
și piese needescărcate din alt motiv — am recomandat team-lead-ului un count pe
`IN_STOC × (PRET_ACHIZITIE=0)` pentru confirmare.
**Concluzie rundă 2**: `pret_achizitie` de pe `VANZARI_DETALII` e de încredere ca sursă a costului
pe factură pentru liniile de piese; un 0 pe linie de manoperă e corect, nu semnal de problemă.
**CORECTARE ulterioară (din `docs/propuneri_coloane_manopera_piese_viz.md`, actualizat de altcineva
cu acces Oracle, după ce am trimis răspunsul de mai sus)**: pe schema de test, `PRET_ACHIZITIE = 0`
nu doar pe liniile de manoperă (`IN_STOC=0`: 2475/2526 = 98%), ci **și pe 96% din liniile de piese
gestionabile** (`IN_STOC=1`: 8561/8889 = 96%). Deci concluzia mea "e de încredere pentru piese" nu
se confirmă empiric — mecanismul de scriere există (descris mai sus), dar pe date reale coloana e
goală aproape peste tot, inclusiv acolo unde ar trebui completată. Motivul exact nu e stabilit
(posibil: câmpul se completează doar pe un anumit flux de facturare, sau pe majoritatea documentelor
din baza de test descărcarea de gestiune nu s-a mai executat din alt motiv). **Nu folosiți
`VANZARI_DETALII.PRET_ACHIZITIE` ca sursă de cost fără verificare suplimentară — `RUL.PRET` (via
`AUTO_ISTORIC_COMENZI`) rămâne sursa confirmată empiric.**

View File

@@ -0,0 +1,371 @@
# 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 — predare către o sesiune nouă
**Acesta este fișierul de implementat.** Planul e la §5, aprobat de Marius pe 22.09.2026.
- **Nimic implementat.** Zero fișiere de cod atinse, zero scripturi rulate pe Oracle, zero commit-uri.
`git status` are doar fișiere noi în `docs/`.
- **Nimic într-o stare periculoasă**: fără editări de binar fără write-back, fără tranzacții deschise,
fără procese rămase vii, fără date de test consumate.
- **Primul pas într-o sesiune nouă**: `git_sync.ps1` (obligatoriu la început de sesiune), apoi §5 pas 1.
- **Decizii deja luate, nu le relua**: §4 varianta 1 (ecranul de comenzi trece pe
`AUTO_ISTORIC_COMENZI`, nu se modifică `AUTO_NORMARE_COMENZI`); manopera se livrează în ambele
forme (deviz + facturat); costul se ia din `RUL.PRET`, nu din `VANZARI_DETALII.PRET_ACHIZITIE`.
- **Interzis**: să atingi `AUTO_NORMARE_COMENZI` (§4); să inserezi coloane noi la mijlocul gridurilor
(§6.1); să rulezi scriptul de bază fără cererea explicită a lui Marius (§5 pas 1); să dai commit
fără ca Marius să vadă întâi diff-ul complet.
- **Rămâne de aflat de la client** (§7.1-7.3): rânduri + timp actual pe luna curentă la baza cea mai
mare, dimensiunile `RUL`/`ACT`/`DEV_OPER`, existența `IDX_RUL_001`. Nu blochează pașii 1-3.
- 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 = <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.
## 7. De verificat înainte de implementare
1. **La clientul cu cea mai mare bază**: câte rânduri întoarce azi „Vizualizare comenzi" pe luna
curentă, și cât durează deschiderea acum. Fără numărul ăsta, procentele de la 3.4 nu spun nimic
despre experiența reală.
2. Dimensiunile reale ale `RUL`, `ACT`, `DEV_OPER` la acel client.
3. Există `IDX_RUL_001 (ID_LUCRARE, STERS)` și la client? Aproape sigur da (creat în 2009, folosit
cu hint în `pack_auto` în 2026), dar merită confirmat cu o interogare pe `USER_INDEXES` la
clientul respectiv. Dacă lipsește, ăsta e **singurul** caz în care apare cu adevărat scanarea pe
toată baza de care te temeai — și se rezolvă cu un `CREATE INDEX`, nu cu rescrierea logicii.
4. ~~Poate o lucrare să aibă mai multe `dev_ordl`?~~ **Închis**: Marius a confirmat — o
`NOM_LUCRARI` are exact un `DEV_ORDL`. Deschide varianta 2 ca optimizare ulterioară.
5. ~~„Valoare manoperă" = deviz sau facturat?~~ **Închis**: se livrează amândouă, ca în „Istoric
comenzi" — `VAL_MANOPERA` (din `DEV_OPER`, `timpn*pret`) și `VAL_MANOPERA_FACTURA` (cont 704 din
`ACT`), respectiv `VAL_MATERIALE_VZ` și `VAL_MATERIALE_FACTURA`. La comenzile încă nefacturate
există doar prima; la cele facturate, diferența dintre ele e chiar semnalul util.
## 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).