# Propunere S4b — actiunea explicita de sincronizare RUL <-> articole factura Document de **proiectare**, nu implementare. Niciun `.vc2`/`.prg` nu a fost modificat. Cerinta sursa: `docs\plan_06_editare_factura.md:208-231`. Context: handoff intermediar (sters) (sectiunea „S4b — ce e facut si ce lipseste"), handoff intermediar (sters). ## Corectie fata de predarea anterioara — nu e nevoie de cursor de instantaneu `docs\cercetare\rec_s5_cale_scriere_vfp.md` 2.3 propunea un cursor `tvd_initial`, luat imediat dupa `IncarcaArticoleFactura`, ca solutie la blocajul „`tvd` nu pastreaza valorile de dinainte de editare". Verificat pe cod si pe plan: **acea propunere rezolva o alta problema** — istoricul editarilor utilizatorului in aceeasi sesiune (cat a schimbat el insusi o cantitate) — nu ce cere literal S4b. Planul spune explicit: *„preturile si cantitatile din **rulaje** si cele din **articolele facturii** se pot desincroniza la editare"* (`plan_06_editare_factura.md:209-210`) — deci comparatia ceruta e intre `trul` (rulaje) si `tvd` (articole), **doua reprezentari independente ale aceluiasi document**, editabile separat in acelasi formular (PAGE1 vs PAGE3). Nu e nevoie de niciun instantaneu: „vechi" = valoarea curenta din cursorul-**tinta** (ce s-ar salva daca utilizatorul apasa Salveaza acum, fara sincronizare), „nou" = valoarea calculata din cursorul- **sursa**. Ambele cursoare sunt deja rezidente si populate cand utilizatorul ajunge pe PAGE3 — comparatia se face live, nu din istoric. Confirmare suplimentara ca `ACT`/`tact` nu poate fi parte a acestei comparatii (desi indicatorul existent `ActualizeazaVerdictActRul` compara ACT cu RUL): `tact` e un jurnal de postari debit/credit (`scd`/`scc`/`suma`), fara `cantitate`/`pret`/`id_articol` per linie de marfa — structural nu poate fi sursa sau tinta a unei sincronizari „cantitate/pret". Sincronizarea din S4b ramane **exclusiv RUL <-> articole**, distincta de indicatorul de verdict deja livrat. ## 1. Unde traieste actiunea **Buton nou** `cmdSincronizeazaArticole`, in randul de butoane din PAGE3, langa cele existente: ``` COMUN\clase\omodificari.vc2:12291-12305 ADD OBJECT 'pgfArticole.PAGE3.cmdAdaugaArticol' ... Left=175 Width=170 COMUN\clase\omodificari.vc2:12307-12321 ADD OBJECT 'pgfArticole.PAGE3.cmdStergeArticol' ... Left=0 Width=170 ``` Randul e la `Top=0`, latimea paginii e 764 (`omodificari.vc2:8702`); `cmdAdaugaArticol` se termina la 345 — spatiul liber pana la 764 (419px) e suficient pentru un al treilea buton. Propunere: `Left=350, Top=0, Width=250, Height=22`, caption „Sincronizeaza cu rulaje...". `lblArticoleReadOnly` (`:12776-12787`, Left=360 Top=4) ocupa aceeasi zona vizual, dar e vizibila **doar** cand `lArticoleReadOnly=.T.` — exact starea in care butonul trebuie oricum dezactivat, deci overlap-ul nu se vede niciodata simultan (acelasi tipar folosit deja pentru celelalte doua butoane). Toggle vizibilitate/enabled: se extinde blocul deja existent din `Show()`: ``` omodificari.vc2:14811-14814 This.pgfArticole.PAGE3.cmdAdaugaArticol.Enabled = !This.lArticoleReadOnly This.pgfArticole.PAGE3.cmdStergeArticol.Enabled = !This.lArticoleReadOnly This.pgfArticole.PAGE3.txtDiscountArt.ReadOnly = This.lArticoleReadOnly This.pgfArticole.PAGE3.lblArticoleReadOnly.Visible = This.lArticoleReadOnly ``` cu o linie `This.pgfArticole.PAGE3.cmdSincronizeazaArticole.Enabled = !This.lArticoleReadOnly`. **Gardare gratuita pentru ROACONT/ROAGEST**: butonul exista doar in PAGE3, care la randul ei exista doar cand `This.lAreArticoleVanzari` (`:14792-14818`, gatat deja pe `"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))`). Nu e nevoie de nicio garda noua — butonul mosteneste gating-ul deja livrat in S3/S4. ## 2. Cum se calculeaza „vechi -> nou" **Fara FK la nivel de rand** intre `trul` si `VANZARI_DETALII`/`tvd` — verificat direct pe schema Oracle (`DESCRIBE VRUL_TOT` prin `sqlplus`, 09.08.2026): view-ul sursa al lui `trul` (`ofacturare_editare.prg:33-100`, in special `:74`) are 218 coloane, **niciuna** de tip `id_vanzare_det`/`id_rulaj_vanzare`. Singura cheie comuna e **`ID_ARTICOL`**, prezenta in ambele: `VRUL_TOT.ID_ARTICOL` (confirmat pe schema) si `tvd.id_articol` (`ofacturare_editare.prg:274`, `CreeazaCursorArticoleGol`). Matching-ul e deci obligatoriu **la nivel de articol** (agregat), nu la nivel de linie individuala — nu exista alta optiune. **Agregarea RUL per articol** refoloseste formula validata pe date reale, nu una noua: `docs\cercetare\rec_suma_act.md`, sectiunea E.3, verificata exact pe doua documente (unul de test, unul de productie, E.4/E.5): ``` valoare_articol_rul = SUM(cant * pretvtva) + SUM(CASE WHEN id_tip_rulaj <> 3 THEN cante * pretvtva ELSE 0 END) cantitate_articol_rul = SUM(cant) + SUM(CASE WHEN id_tip_rulaj <> 3 THEN cante ELSE 0 END) pret_propus = valoare_articol_rul / cantitate_articol_rul -- pret mediu ponderat, daca sunt mai multe randuri RUL pe acelasi articol ``` GROUP BY `id_articol`, filtrat `sters = 0` (trul incarca deja doar randuri active, `ofacturare_editare.prg:57` cu `tlAratasterse=.F.` din apelantii de editare). **Articolele nestocate** (`in_stoc=0`, `rec_suma_act.md` E.5/E.6) nu au niciodata randuri in RUL — excluse automat din comparatie, afisate cu actiune „N/A — articol nestocat, fara corespondent in rulaje" (aceeasi coloana `in_stoc` deja incarcata in `tvd`, `ofacturare_editare.prg:301`, folosita azi doar pentru corectia verdictului RUL). **Liniile adaugate/sterse** (cerinta explicita din plan) intra natural in acelasi matching pe `id_articol`, ca diferenta de multimi: - articol activ in sursa, absent (sau `sters=1`) in tinta -> actiune **Adaugare** in tinta; - articol activ in tinta, absent (sau agregat 0) in sursa -> actiune **Semnalare** (vezi risc, punctul 6 — NU stergere automata); - articol in ambele, cantitate/pret diferite -> actiune **Modificare**; - articol in ambele, identic -> nu apare in enumerare (fara zgomot). ## 3. Cum alege utilizatorul directia Dialog modal nou, `frm_sincronizare_articole`, in `COMUN\clase\omodificari.vc2` (langa `frm_modific2024`, ca sa poata referi direct cursoarele `tvd`/`trul` din acelasi data session implicit — sectiunea 2.5 din `rec_s5_cale_scriere_vfp.md`). Deschis din `cmdSincronizeazaArticole.Click`, modal (`WindowType=1`, acelasi tipar ca `frm_modific2024` insusi si ca `frm_modifica_articol_factura`). Continut: - doi radio-butoni (optiongroup): **„Rulajul e sursa (actualizeaza articolele facturii)"** (implicit, recomandat — vezi punctul 5) / **„Articolele sunt sursa (actualizeaza rulajul)"**; schimbarea selectiei recalculeaza si reafiseaza enumerarea imediat (fara buton „recalculeaza" separat); - grid needitabil pe cursorul `propunere_sincronizare`, coloane: **Articol** (denumire + codmat), **Cantitate veche**, **Cantitate noua**, **Pret vechi**, **Pret nou**, **Actiune** (Modificare/Adaugare/Semnalare/N-A), cu `DynamicForeColor` gri pe randurile N-A (acelasi tipar ca `tvd.sters` in `grdArticoleFactura`, `omodificari.vc2:12344`); - „Aplica" — activ doar daca exista minim o linie cu actiune Modificare sau Adaugare; „Renunta". Dialogul **citeste** `tvd`/`trul` doar pentru afisare — nu le modifica pana la „Aplica". Refuzul („Renunta" sau inchiderea ferestrei) nu are nimic de desfacut, pentru ca nimic n-a fost atins: satisface direct cerinta din plan („refuzul propunerii lasa datele nemodificate"). ## 4. Ce scrie efectiv sincronizarea, si prin ce cale **Nimic in Oracle, niciodata.** `AplicaSincronizareArticole` (nou, `ofacturare_editare.prg`, langa restul helperelor pure — nu un nou punct de scriere Oracle) scrie **doar** in cursoarele VFP in-memory `tvd`/`trul`, la apasarea „Aplica": - **Directia „rulajul e sursa"**: pentru fiecare articol cu actiune Modificare — `REPLACE tvd.cantitate, tvd.pret, tvd.pret_cu_tva WITH ..., lmodificat WITH .T.`, apoi recalculul lui `valoare` cu **aceeasi formula** deja folosita in `calculeaza_valori_articol` (`omodificari.vc2:13397-13409`) — refolosita, nu reimplementata — si apel la `Thisform.ActualizeazaBaraTotaluri()` ca dupa orice editare manuala. Pentru Adaugare, se refoloseste tiparul din `AdaugaLinieTvdDinArticol` (`omodificari.vc2:13130+`), populat cu valorile din RUL. - **Directia „articolele sunt sursa"**: pentru fiecare articol cu match unic activ in `trul` — `REPLACE trul.cant, trul.pretvtva WITH ...` pe randul respectiv, fara sa atinga `id_tip_rulaj`/conturi/`scd`/`scc`. Cand exista mai multe randuri RUL active pe acelasi articol (perechi de diferenta de pret, `id_tip_rulaj=3`, sau randurile „duplicat" nelamurite din `rec_suma_act.md` E.4), linia se marcheaza **N-A — mai multe randuri RUL, verificati manual** — nu se alege automat care rand sa fie atins. Scrierea **reala** in Oracle ramane exact calea deja livrata in S5, neschimbata: `do_termin` -> `OSCRIE_IN_FISIERE` pentru `trul`/`tact` (neatins de aceasta lucrare) + `ScrieArticoleFacturaEditate` pentru `tvd` (`ofacturare_editare.prg:469-556`, comisa in S5). Sincronizarea doar **pregateste** cursoarele exact cum ar face utilizatorul manual din grid — niciun helper Oracle nou. ## 5. Alternative, cu recomandare **A. Granularitatea matching-ului: pe linie vs pe articol.** Pe linie — respins, nu exista nicio cheie (verificat pe schema, punctul 2). **Recomandat: pe articol** (`id_articol`), singura cheie comuna confirmata. **B. O singura directie vs alegere intre doua.** Planul cere explicit alegerea utilizatorului (`plan_06_editare_factura.md:226-227`). **Recomandat: ambele directii**, dar cu directia „articole -> RUL" **restransa la actualizarea cantitate/pret pe randuri RUL deja existente**, niciodata la crearea de randuri noi de contabilitate (motivat la punctul 6 — un rand RUL nou cere `cont`/`scd`/`scc` pe care sincronizarea nu le poate deduce sigur din articol). Directia „RUL -> articole" nu are aceasta limitare, pentru ca `tvd`/ `VANZARI_DETALII` nu poarta conturi contabile — de aici si recomandarea ei ca implicit in dialog. **C. Dialog modal separat vs panou inline pe PAGE3.** PAGE3 n-are loc: layout-ul complet (`omodificari.vc2:12780-12956`) foloseste toata latimea de 764px pe cele doua randuri de totaluri, iar randul de butoane are doar 419px liberi — insuficient pentru un grid de propuneri cu 6 coloane. **Recomandat: dialog modal separat**, ca `frm_modifica_articol_ factura` (acelasi tipar deja folosit in acest formular). **D. Stergerea automata de linii cand sursa nu mai are articolul.** Riscant — vezi punctul 6. **Recomandat: fara stergere automata, in nicio directie** — doar semnalare in enumerare; stergerea efectiva ramane pe butonul deja existent `cmdStergeArticol`, actionat manual dupa ce utilizatorul a vazut divergenta. ## 6. Ce poate strica - **Cel mai mare risc concret**: randurile RUL „duplicat" cu `id_tip_rulaj=0` gasite langa perechi `id_tip_rulaj=3` (`rec_suma_act.md` E.4, intrebarea 3 nerezolvata catre Marius) — pe un document cu asemenea randuri, agregarea per articol ar putea propune o cantitate/pret gresit(a). Atenuare: eticheta explicita pe dialog („propunere calculata, verificati inainte de aplicare"), *niciodata* aplicare automata sau implicita; cand exista >1 rand activ pe acelasi articol, linia se marcheaza N-A in loc sa se aleaga arbitrar (punctul 4). - **Registrul jurnal ROACONT/ROAGEST**: butonul si dialogul exista doar in interiorul gardei deja existente pe PAGE3 (`lAreArticoleVanzari`) — cod complet inert acolo, fara garda noua de adaugat (punctul 1). Risc zero suplimentar fata de ce e deja livrat in S3-S5. - **`omodificari.vc2` e partajat de toata suita** — orice atingere in afara blocului gatat pe `lAreArticoleVanzari`/PAGE3 (de ex. o modificare gresita in `Load()`/`Show()` inainte de gate) ar afecta toate notele contabile editate din ROACONT/ROAGEST, nu doar facturile. Implementarea trebuie sa ramana strict in interiorul blocului PAGE3 existent, fara sa atinga `grid1`/`tact` (grid-ul comun de note, folosit de toata suita). - **Directia „articole -> RUL" scrie in randuri de contabilitate** (`trul`) fara sa recalculeze conturile — acceptabil DOAR daca ramane strict la actualizarea cantitate/pret pe randuri deja existente (punctul 5.B), exact echivalent cu o corectie manuala facuta azi direct in grid-ul de rulaje (PAGE1) — nu introduce o cale de scriere noua sau mai permisiva decat ce exista deja. ## 7. Estimare de efort | Bucata | Zile | |---|---| | Agregare RUL per articol (formula E.3) + agregare `tvd` per articol, logica pura testabila headless | 0.5-1 | | `ConstruiestePropunereSincronizare` (matching pe `id_articol`, ambele directii, adaugat/modificat/semnalare) | 1 | | `AplicaSincronizareArticole` (mutatii in memorie pe `tvd`/`trul`, reutilizare `calculeaza_valori_articol`/`AdaugaLinieTvdDinArticol`) | 1 | | `frm_sincronizare_articole` (radio directie, grid needitabil, Aplica/Renunta) | 1-1.5 | | Buton + toggle pe PAGE3 (`txt2vcx.ps1` pe `omodificari.vc2`) | 0.5 | | Teste headless (logica pura de agregare/matching, pe cursoare construite in test, model S5) | 0.5 | | Teste UI vizibile (dialogul modal nu e testabil `-A -T`, la fel ca `frm_articol_factura`) | 0.5-1 | | **Total** | **~5-6.5 zile** | Confirma caracterizarea din plan: „cea mai mare ramasa, nu blocheaza pe nimeni" (handoff intermediar (sters)). ## Ce ramane deschis, de decis cu Marius inainte de implementare 1. Directia implicita in dialog — recomandare: „rulajul e sursa" preselectat (punctul 5.B). 2. Randurile RUL „duplicat" nelamurite (`rec_suma_act.md` E.4/F.3) — daca raman nerezolvate, orice document cu ele va produce linii N-A in loc de propunere, ceea ce e sigur dar posibil frustrant pentru utilizator pe acele documente specifice. 3. Daca se doreste si optiunea „aplica pe toate liniile deodata" vs selectie linie cu linie in grid — propunerea de mai sus e „aplica tot ce nu e N-A", cea mai simpla; o selectie fina ar adauga complexitate UI fara sa fie ceruta explicit de plan.