22 KiB
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 statusare doar fișiere noi îndocs/. - 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 dinRUL.PRET, nu dinVANZARI_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țaIDX_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șidocs/handoff_cost_materiale_manopera.mdsunt 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țieRUL.PRETV= preț de vânzareRUL.ADAOS/VALOARE_ADAOS= adaosulRUL.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):
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):
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:
(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ă (
RUL10 320 rânduri,ACT70 723,DEV_OPER2 414). Cifra trebuie reconfirmată la client (punctul 7). - Partea de manoperă (
DEV_ORDL⋈DEV_OPERpeid_lucrare) este singura care, în planul implicit, se „dez-corelează" într-unHASH GROUP BYpeste tabelele întregi — deci singura care scalează cu istoricul. Varianta D o evită, dar schimbă semantica dacă o lucrare poate avea mai multeordl(î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:
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_COMENZIră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 unselect *. 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-95AUTO_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, dindev_tip_devizdeja 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 dinAUTO_ISTORIC_COMENZI, cu gardaa.id_lucrare > 0. (MANOPERAșiMATERIALE— 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ă dinauto_normare_comenziînauto_istoric_comenzi;pcschemașipcselectse extind cuORE_MANOPERA,VAL_MANOPERA,VAL_MANOPERA_FACTURA,VAL_MATERIALE_VZ,VAL_MATERIALE_ACH,VAL_MATERIALE_FACTURA(N(20,4), ca învizualizare_ist_comenzi). Filtrul rămânegcCondLuna.vizualizare_facturi(:231) —pcschema/pcselectextinse cu cele 4 coloane noi.
Pas 3 — gridurile (Clase\oviz_devize.vc2, write-back prin txt2vcx.ps1):
frm_viz_comenzi(:14503, gridgrdcomenzi) — coloane noi la coadă, copiate ca definiție (format, aliniere,InputMask) din coloanele omoloage ale luifrm_istoric_comenzi(:9115,Column19-Column25).frm_viz_facturi(:16762, gridgrdfacturi) — 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.frxrap_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șido_listaredinfrm_viz_comenzifacScatter Name ocomandape cursor și pasează obiectul lavizualizeaza_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
- 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 îngridprefs.tmp.restoregridpreferences(COMUN/utile/GridExtras/gridextras.vc2:1051) aplică perechileColumnOrder,Widthpozițional, pe index de coloană:loColumn = toGridObject.Columns((lnCounter + 1)/2), culnMax = 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 dinCOMUN\docs\capcana_grid_preferinte_utilizator.md, aplicată pefrm_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.
- coloană adăugată la coadă (
ALTER TABLEpe cursorul de lagoExecutornu merge — coloanele noi trebuie să fie înSELECTde la început (COMUN\docs\conventie_goexecutor_alter_table.md). Aici e respectat by design, pentru că se modificăpcselect.- Encoding CP1252 la editarea
.vc2(COMUN\docs\conventie_encoding_cp1252.md); write-back doar printxt2vcx.ps1, skillroa-vfp-text-edit. CursorFilladuce 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ă.- 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
- 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ă.
- Dimensiunile reale ale
RUL,ACT,DEV_OPERla acel client. - Există
IDX_RUL_001 (ID_LUCRARE, STERS)și la client? Aproape sigur da (creat în 2009, folosit cu hint înpack_autoîn 2026), dar merită confirmat cu o interogare peUSER_INDEXESla 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 unCREATE INDEX, nu cu rescrierea logicii. Poate o lucrare să aibă mai multeÎnchis: Marius a confirmat — odev_ordl?NOM_LUCRARIare exact unDEV_ORDL. Deschide varianta 2 ca optimizare ulterioară.„Valoare manoperă" = deviz sau facturat?Închis: se livrează amândouă, ca în „Istoric comenzi" —VAL_MANOPERA(dinDEV_OPER,timpn*pret) șiVAL_MANOPERA_FACTURA(cont 704 dinACT), respectivVAL_MATERIALE_VZșiVAL_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).