# S4b etapa 1 - contractul ConstruiestePropunereSincronizare/AplicaSincronizareArticole Implementare, nu doar proiectare. Cod nou exclusiv in `COMUN\programe\ofacturare_editare.prg` (dupa `ScrieArticoleFacturaEditate`). Niciun `.vc2` atins - formularul si dialogul raman etapa 2, a altui agent. Baza: `docs\propunere_s4b_sincronizare.md` (proiectarea aprobata, punctele 2 si 4) si `docs\cercetare\rec_suma_act.md` E.3 (formula de agregare RUL, reutilizata neschimbata). ## Functii noi (publice, apelabile din etapa 2) ### `ConstruiestePropunereSincronizare(tcDirectie)` Parametru: `tcDirectie` - accepta exact doua valori, `'RUL_SURSA'` (rulajul e sursa, actualizeaza articolele facturii) sau `'ARTICOLE_SURSA'` (articolele sunt sursa, actualizeaza rulajul). Orice alta valoare (inclusiv goala) cade pe `'RUL_SURSA'` - e implicit-ul recomandat de Marius (`docs\handoff_ punct6_10082026_seara.md`, decizia 4). Lasa deschis, READWRITE, cursorul `propunere_sincronizare`: | camp | tip | continut | |---|---|---| | `id_articol` | I | cheia de matching (singura comuna intre `trul`/`tvd`) | | `denumire` | C(100) | preferata din `tvd` daca articolul exista acolo, altfel din `trul` | | `codmat` | C(30) | idem | | `cantitate_veche` | N(12,3) | valoarea curenta din cursorul-**tinta** | | `cantitate_noua` | N(12,3) | valoarea calculata din cursorul-**sursa** | | `pret_vechi` | N(14,4) | idem, pret unitar **cu TVA** | | `pret_nou` | N(14,4) | idem | | `actiune` | C(12) | `Modificare` / `Adaugare` / `Semnalare` / `N-A` | | `motiv` | C(80) | populat pe `N-A`/`Semnalare`, si pe `Adaugare` in `ARTICOLE_SURSA` | Articolele **identice** intre sursa si tinta nu apar in cursor (fara zgomot, ca in propunere.md). Retur logic: `.F.` doar cand `tvd`/`trul` nu sunt ambele deschise la apel (cursorul iese oricum creat, gol) - restul cazurilor (0 articole, document fara diferente) intorc `.T.` cu cursorul gol sau partial. **Agregarea**, per `id_articol`, separat pe `trul` si pe `tvd`, doar randuri `Nvl(sters,0)<>1`: - RUL: `cant_articol = SUM(cant) + SUM(IIF(id_tip_rulaj<>3, cante, 0))`, `val_articol = SUM(cant*pretvtva) + SUM(IIF(id_tip_rulaj<>3, cante*pretvtva, 0))` (formula E.3 neschimbata), `pret_articol = val_articol/cant_articol`. - tvd: `cant_articol = SUM(cantitate)`, `val_articol = SUM(valoare)` (coloana `tvd.valoare`, deja calculata cu TVA la incarcare/editare - **citita, nu recalculata** - `IncarcaArticoleFactura`, `ofacturare_editare.prg:318-320`, e chiar in acest fisier), `pret_articol = val_articol/cant_articol`. **Actiune**, in ordinea exacta de verificare (prima conditie adevarata castiga): 1. **Documentul e in valuta** (`tvanz.in_valuta<>0`, cand `tvanz` e deschis cu un rand) -> `N-A` pe **toate** articolele, indiferent de restul. *Decizie luata in aceasta implementare, nu era in propunere.md*: conversia RON<->valuta intre `trul.pretvtva` (presupus RON) si `tvd.pret` (in valuta documentului cand documentul e in valuta) nu a fost verificata pe date reale in bugetul alocat - mai sigur sa semnalez decat sa scriu o suma gresita. Fara aceasta garda, restul logicii (agregare, matching, Modificare/Adaugare/Semnalare) e neschimbata pe documente RON. 2. **Mai multe randuri RUL active pe acelasi articol** (`nrand_rul>1`) -> `N-A`, chiar daca agregatul ar fi identic cu tinta (verificat explicit in test, caz A6: o pereche `id_tip_rulaj=3` a carei sume egaleaza exact `tvd`, tot N-A) - decizia lui Marius/propunere.md punctul 6, generalizata la ambele directii (nu doar la aplicare, ca in propunere.md sectiunea 4 - vezi "Diferente fata de propunere" mai jos). 3. **Articol nestocat** (`tvd.in_stoc=0`, doar cand articolul exista in `tvd`) -> `N-A`. 4. **Doar in sursa** -> `Adaugare`. 5. **Doar in tinta** -> `Semnalare` (niciodata stergere automata). 6. **In ambele, diferit** -> `Modificare`; identic -> nu apare in cursor. ## `AplicaSincronizareArticole(tcDirectie, toForm)` Parametru nou fata de brief: **`toForm`, opus** (contractul descris mai jos, punctul "Ce ramane pe seama etapei 2"). Reconstruieste propunerea (apeleaza `ConstruiestePropunereSincronizare` intern - etapa 2 nu trebuie sa o apeleze separat inainte) si aplica **tot ce nu e `N-A`/`Semnalare`** - deci `Modificare` + `Adaugare`, decizia lui Marius ("fara selectie pe linie"). **Niciun `INSERT`/`UPDATE` Oracle** - doar `tvd`/`trul` in memorie. Retur numeric: cate linii au fost efectiv aplicate (nu cate erau in propunere) - formularul il foloseste ca sa stie daca sa reactualizeze bara de totaluri/gridul. - **`RUL_SURSA`**: `Modificare` -> `REPLACE tvd.cantitate/pret/discount_unitar` pe primul rand activ gasit pe articol, **pastrand `pret_cu_tva` existent pe rand** (pretul propus, mereu cu TVA per E.3, se converteste la fara-TVA prin `/proc_tvav` daca randul era asa) - decizie luata acum, nu era in propunere.md (care zicea doar "REPLACE ... pret_cu_tva WITH ..." fara sa spuna cu ce): am ales sa NU schimb convenția per-rand, ca sa nu inversez brusc semnificatia unei coloane pe care utilizatorul a vazut-o intr-un fel; `Adaugare` -> `toForm.AdaugaLinieTvdDinArticol()`, cu obiectul construit din `CreeazaPoArticolNouTvd` (tiparul cerut de brief), suprascris cu cantitate/pret din RUL, `preturi_cu_tva=1` (linie noua, fara conventie de pastrat), `cont`/`id_gestiune`/`proc_tvav` din randul RUL al articolului. - **`ARTICOLE_SURSA`**: `Modificare` -> `REPLACE` doar `cant` sau `cante` (dupa care era deja populat pe rand - verificat in test, caz C1) si `pretvtva`, pe singurul rand RUL activ (garantat unic, altfel propunerea a marcat N-A la pasul 2). Nu atinge `id_tip_rulaj`/conturi/campuri derivate (`valoare`/`tva`/etc.) - raman de recalculat de apelant daca e nevoie, in afara scopului acestei livrari. `Adaugare` -> **nu se aplica niciodata** (niciun rand RUL nou, decizia din propunere.md punctul 5.B). ## Ce ramane pe seama etapei 2 - contractul lui `toForm` Am ales varianta **"primesc obiectul formular ca parametru"**, nu "duplic tiparul separat". Fara `toForm` (sau un obiect care nu implementeaza metodele), `AplicaSincronizareArticole` tot face `REPLACE`-ul de baza pe `tvd` pentru `Modificare` (verificat in test, caz B2), dar: - **nu recalculeaza `tvd.valoare`** (ramane cea veche pana la un recalcul extern); - **nu cheama `ActualizeazaBaraTotaluri`**; - **sare complet liniile `Adaugare`** (fara `toForm.AdaugaLinieTvdDinArticol`, nu exista alta cale sa adauge linia fara sa duplice acel helper). Deci etapa 2 (butonul/dialogul din PAGE3) **trebuie sa apeleze `AplicaSincronizareArticole(tcDirectie, Thisform)`**, cu `Thisform` fiind instanta `frm_modific2024` deja incarcata (are ambele metode: `calculeaza_valori_articol`, `AdaugaLinieTvdDinArticol`). Contractul verificat prin `Pemstatus()`, nu presupus - un obiect fara aceste metode e tratat exact ca "fara toForm". ## Diferente fata de `propunere_s4b_sincronizare.md` - de confirmat cu Marius/etapa 2 1. **Documentele in valuta sunt N-A pe tot** (mai sus, punctul 1 al ordinii de actiune) - propunere.md nu mentiona deloc valuta pentru S4b. Scop deliberat restrans: fara o verificare pe un document real in valuta CU randuri RUL, nu am vrut sa livrez o conversie neverificata. Daca Marius vrea si documentele in valuta acoperite, e nevoie de o cercetare separata (confirmarea daca `trul.pretvtva` e RON sau in valuta proprie a randului RUL - `VRUL_TOT` are propriile `CURS`/`ID_VALUTA` per rand, verificat prin `DESCRIBE`, dar nu s-a confirmat ce inseamna practic pentru `PRETVTVA`). 2. **"Mai multe randuri RUL active" e N-A in ambele directii, la nivelul propunerii** (nu doar la aplicarea `ARTICOLE_SURSA`, cum sugera propunere.md sectiunea 4) - generalizare ceruta explicit de briefing-ul acestei sarcini ("niciodata alegere automata a randului", listat ca regula a `ConstruiestePropunereSincronizare`, nu doar a aplicarii). Efect: pe un document cu perechi `id_tip_rulaj=3` (diferenta de pret la marfa/produse la pret de vanzare, `rec_suma_act.md` E.1), articolul respectiv ram**a** mereu N-A, chiar daca suma agregata (E.3) ar fi corecta si identica - propunerea nu ofera Modificare/Adaugare automata pe acele articole, doar afisare informativa. 3. **`tvd` cu mai multe randuri active pe acelasi articol nu are o garda separata** - `AplicaModificare Tvd` scrie pe **primul** rand activ gasit. Cazul e rar (articol adaugat de doua ori manual pe aceeasi factura) si n-a fost cerut explicit in briefing; il semnalez ca limitare cunoscuta, nu l-am tratat ca sa nu extind scopul peste ce s-a cerut. ## Teste `COMUN\utile\Teste\editare_factura\test_s4b_sincronizare.prg`, model `test_s5_validari_articole.prg`. Complet headless, **fara Oracle** - nici `ConstruiestePropunereSincronizare`, nici `AplicaSincronizareArticole` nu ating `goExecutor`, deci testul nu are nevoie de `test_init_env_auto`/ conexiune - doar `SET PROCEDURE TO ofacturare_editare.prg` si `gnPC` setat manual. `tvd`/`trul` sunt construite direct in test (helpere `AdaugaTvd`/`AdaugaTrul`); `trul` e o structura minimala (doar campurile citite de cod), nu cele 218 coloane reale ale `VRUL_TOT`. Cazuri acoperite (sectiunea A - `ConstruiestePropunereSincronizare`, cate un caz pe ambele directii unde se aplica): identic (A1), modificare cu valori inversate corect intre directii (A2), doar-in- sursa/doar-in-tinta -> Adaugare/Semnalare cu roluri schimbate intre directii (A3/A4), articol nestocat (A5), mai multe randuri RUL active - N-A chiar cand agregatul ar fi identic (A6), randuri `sters` excluse din agregare pe ambele cursoare (A7), document in valuta -> N-A pe tot (A8), tvd/trul lipsa la apel (A9). Sectiunea B (`AplicaSincronizareArticole`, `RUL_SURSA`): aplicare completa cu `toForm` (Modificare + Adaugare, verificate valorile scrise in `tvd` si apelurile pe test-double, semnalare/N-A neatinse - B1), **fara** `toForm` (Adaugare sarita fara eroare, Modificare tot se aplica - B2), pastrarea conventiei `pret_cu_tva=0` cu conversia pretului propus (B3). Sectiunea C (`AplicaSincronizareArticole`, `ARTICOLE_SURSA`): `cant` vs `cante` pastrat dupa care era populat pe rand, Adaugare niciodata scrisa in `trul` (C1). `test-double`-ul `dummyform` (definit la finalul fisierului, tipar identic cu `dummyexecutor` din `test_s5_validari_articole.prg`) oglindeste EXACT formula din `calculeaza_valori_articol`/ `AdaugaLinieTvdDinArticol` - verifica ca `AplicaSincronizareArticole` cheama metodele corecte cu argumentele corecte, fara sa deschida formularul real sau sa atinga vreun `.vc2`. **Capcana gasita si reparata in acest bloc**: comparatii `==` intre un camp `Character` de latime fixa (`actiune`, C(12)) si un literal mai scurt (`'Modificare'`) esueaza mereu din cauza spatiilor de umplere din dreapta - VFP `==` e comparatie stricta, nu trece prin `SET EXACT`. Aparea atat in codul de productie (`AplicaSincronizareArticole`, filtrul `SCAN FOR Inlist(actiune,...)` si `DO CASE`), cat si in asertiunile testului. Reparat cu `Alltrim()` pe partea citita din camp, in ambele fisiere. **Cifra din log** (`test_s4b_sincronizare_log.txt`, rulare `vfp9.exe -A -T`): ``` REZULTAT: 35 PASS / 0 FAIL ``` Toate testate (headless, cursoare construite in test) - niciun caz "doar analizat static". Nu s-a verificat pe un document real din Oracle (nu era ceruta si nici necesara pentru logica pura), si nici comportamentul pe un document real in valuta (vezi punctul 1 din "Diferente fata de propunere" - scop restrans deliberat). ## Ce lipseste pentru punctul #6 complet (etapa 2, alt agent) 1. Butonul `cmdSincronizeazaArticole` in PAGE3 si toggle-ul de `Enabled` in `Show()` (`omodificari.vc2`, punctul 1 din propunere.md). 2. Dialogul modal `frm_sincronizare_articole` (radio pe directie, grid needitabil pe `propunere_sincronizare`, Aplica/Renunta) - punctele 2-3 din propunere.md. 3. Verificarea la salvare (`inainte_de_do_termin`) care ofera enumerarea si permite salvarea mai departe fara sincronizare - decizia 2 a lui Marius (`docs\handoff_punct6_10082026_seara.md`). 4. Apelul `AplicaSincronizareArticole(tcDirectie, Thisform)` din dialog, la "Aplica", si reactualizarea gridului/barei de totaluri folosind numarul de linii aplicate intors.