Files
roafacturare/docs/cercetare/rec_s4b_etapa1.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:17:17 +03:00

176 lines
12 KiB
Markdown

# 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.