# S5 — inchiderea golurilor de acoperire (rollback, al doilea punct de intrare, linii de set) Continua `docs\livrare_s5.md`, sectiunea 5 „Ce NU e acoperit". Trei goluri, in ordinea cerută: calea de ROLLBACK la esec partial, al doilea punct de intrare (`comun.vc2:2491`), liniile din seturi de articole. Suite noi: `COMUN\utile\Teste\editare_factura\test_s5_rollback_real.prg`, `COMUN\utile\Teste\editare_factura\test_s5_al_doilea_intrare.prg`. Niciun cod de productie atins. ## A. Calea de ROLLBACK la esec partial — INCHIS, cu o descoperire importanta Suita: `test_s5_rollback_real.prg`. Log: `test_s5_rollback_real_log.txt` (rulare 10.08.2026, 01:20:31-01:20:32). **13 PASS / 0 FAIL.** **PARTEA M (mock, fara Oracle)** — completeaza `test_s5_validari_articole.prg`/B3 (care opreste esecul la a DOUA comanda, in mijlocul buclei de UPDATE): aici esecul e la PRIMA comanda a helperului (pasul 1, „marcheaza tot sters"). Dovedit: functia intoarce `.F.` imediat, **o singura** comanda incercata (`nApeluri=1`) — nicio comanda de UPDATE/INSERT/recalcul nu se mai declanseaza. **PARTEA R (real, Oracle)** — pe `id_vanzare=1049`, prin `SQLEXEC` brut (nu prin `ScrieArticoleFacturaEditate`/`goExecutor.oExecuta` — vezi descoperirea de mai jos), reproduce EXACT secventa SQL pe care helperul o genereaza pentru un caz cu esec la INSERT (linie noua cu `id_articol=999999999`, verificat inainte ca articolul chiar nu exista in `NOM_ARTICOLE`): | Pas | Comanda | Rezultat | |---|---|---| | 1 | `UPDATE ... SET STERS=1` (marcheaza tot) | reuseste | | 2 | 3x `UPDATE` (invie+actualizeaza liniile pastrate 1581/1583/1588) | reuseste | | 3a | `INSERT` linie noua VALIDA | reuseste | | 3b | `INSERT` linie noua cu `id_articol` inexistent | **ORA-02291** (FK_VANZARE_DET002) | Dovedit, cu citiri **necomise** pe aceeasi conexiune, INTRE pasi (proba de executie PARTIALA reala, nu doar teoretica): dupa pasii 1-2, cele 3 linii pastrate erau deja active si actualizate (necomis); dupa insertul valid, documentul avea deja 5 linii (necomis). Insertul invalid a oprit secventa exact acolo (simetric cu `EXIT` din `SCAN`-ul helperului) — `recalculeaza_totaluri_vanzari` nu s-a mai apelat. Tranzactia s-a inchis cu **ROLLBACK** (tiparul apelantilor: `lnSucces=Iif(...,1,-1)` → `MyInchideTranzactie(Iif(lnSucces<0,2,1))`). Verificat **independent prin sqlplus**, dupa terminarea procesului VFP: `VANZARI.COD` neschimbat (`1140898`), toate cele 4 randuri din `VANZARI_DETALII` identice octet-cu-octet cu starea de dinainte (`id_vanzare_det:sters:cantitate:pret:pret_achizitie`), zero randuri cu `id_articol=999999999`, zero tranzactii deschise. ### Descoperire, NEREPARATA (cod de productie interzis pentru acest agent) — de raportat separat `goExecutor.oExecuta()` — calea Oracle pe care `ScrieArticoleFacturaEditate` o foloseste pentru **toate** comenzile ei — **agata procesul VFP headless la o eroare Oracle REALA**, sub tranzactie manuala (`SQLSetprop(...,"Transactions",2)`), chiar daca Oracle a raspuns deja cu eroarea. Constatat de doua ori, izolat, cu un script minimal (~15 linii, sters dupa investigatie): - sesiunea Oracle arata `INACTIVE`/`SQL*Net message from client` (Oracle a raspuns, asteapta urmatoarea comanda de la client) — **nu** e o incuietoare (`v$lock`/`v$transaction`: nimic blocant); - `EnumWindows` pe procesul VFP arata **doar** fereastra principala — **nu** e un dialog Windows care asteapta input; - `SQLEXEC` brut (fara `goExecutor`) pe **exact aceeasi comanda** intoarce eroarea instant (`lnRes=-1`, `AERROR` populat corect, `alen=7`, `[1]=1526`) — deci nu e o problema a driverului Oracle/OLEDB in sine. ### CAUZA GASITA de orchestrator, 10.08.2026 — e artefact headless, NU un defect de productie Suspiciunea pe `goLog.Log()` de mai sus e **gresita**. Cauza reala, citita in cod: `ORA-02291` ajunge in `oproceduri_comune.prg` ca eroare ODBC, deci `lnEroare1 = 1526` si `lnEroare2 = 2291`. `2291` nu e in lista de reconectare (`:385`), deci executia intra pe ramura `Otherwise` (`:421-424`): ```foxpro If llShowError lnRaspuns = amessagebox('Eroare necunoscuta' + Chr(13) + lcTextEroare, 0, 'Eroare') Endif ``` **Acolo se agata: un `AMESSAGEBOX` modal, intr-un proces headless in care nimeni nu apasa OK.** `goLog.Log()` (`:427`) vine **dupa** el si nici nu apuca sa ruleze. Explica si de ce `SQLEXEC` brut intoarce eroarea instant — el nu trece prin ramura asta. `llShowError` vine din `This.lShowError` cand apelantul nu-l paseaza (`:234` vs `:243`), iar `ScrieArticoleFacturaEditate` cheama `goExecutor.oExecuta(m.lcSql)` fara al doilea argument pe toate cele patru cai (`ofacturare_editare.prg:491, 502, 531, 547`) — deci mesajul chiar se afiseaza. **Consecinta reala pentru livrare, inversa fata de ce scria mai sus**: in productie, cu utilizator in fata ecranului, comportamentul e **corect** — se afiseaza mesajul, `Exit` iese din bucla, helperul intoarce `.F.`, apelantul inchide tranzactia cu ROLLBACK. Aplicatia **nu** ramane agatata. Agatarea e capcana headless deja documentata in `docs\progres.md` („Dialogurile native VFP blocheaza testul headless la infinit"), lovita aici de un script minimal care nu avea mock pe `amessagebox`. Ce ramane, ca **wart preexistent** al infrastructurii comune (nu al S5, si neatins): textul afisat e `Eroare necunoscuta` + SQL-ul brut + `GETCALLSTACK()` — corect functional, urat pentru utilizator. Priveste tot ROA, nu doar acest lant. ## B. Al doilea punct de intrare (`comun.vc2:2491`, `afisjurcom.do_modifica`) — INCHIS Suita: `test_s5_al_doilea_intrare.prg`. Log: `test_s5_al_doilea_intrare_log.txt` (rulare 10.08.2026, 01:29:51-01:29:52, a doua trecere — vezi „Corectie" mai jos). **17 PASS / 0 FAIL.** Reproduce integral secventa `comun.vc2:2222-2569` (garzile, cursoarele notei, salvarea), mai putin partea de UI (gridul `actjur`, `Createobject([frm_modific2024], lnIdSet)` — sarit deliberat, risc de blocaj headless; verificarea vizuala ramane la Marius, per briefing). Pe **aceeasi nota** folosita de `test_s5_scriere_reala.prg`/`test_s5_discount_valuta.prg` (`id_vanzare=1049`) — singurul document de test cunoscut care are simultan rand real in `VANZARI` **si** nota in luna curenta (cerinta specifica a lui `afisjurcom.do_modifica`, `comun.vc2:2265-2268`, pe care `do_editare_factura` nu o are in aceeasi forma). Diferenta functionala fata de primul punct de intrare, singura care conteaza pentru acest test: `afisjurcom.do_modifica` **nu** apeleaza `IncarcaVanzareDinNota` direct — apeleaza `PregatesteArticoleFacturaEditare('tact')` (`comun.vc2:2436`), care populeaza `tvanz` **si** `crsArticoleFactura` pe alta cale de cod. Dovedit REAL (nu static): - `PregatesteArticoleFacturaEditare` gaseste documentul (`.T.`); - garda noua (`Used('tvanz') And Reccount('tvanz')=1`) se satisface REAL prin aceasta cale; - `tvanz.id_vanzare` corect (`1049`), `crsArticoleFactura` precarcat; - blocul nou (`comun.vc2:2491-2493`) **s-a executat** (flag verificat direct in cod, nu prin scanare de log); - lantul complet a reusit (`lnSucces=1`), tranzactia s-a inchis cu **COMMIT** (`Thisform.do_inchide_tranzactie(Iif(lnSucces<0,2,1))`, `comun.vc2:2541`) — spre deosebire de suita A, aici nu s-a provocat niciun esec, deci lantul chiar reuseste si comite, ca in fluxul real. Fara nicio modificare de articole (ca blocul C din `test_s5_discount_valuta.prg`): `COD` s-a realocat (`1140899 → 1140900`, efect normal al `finalizeaza_modificare_nota` la orice salvare, chiar fara modificari), dar discount/totaluri/numar de linii active raman identice, verificat **independent prin sqlplus** dupa terminarea procesului. ### Corectie facuta IN TIMPUL scrierii acestui test (nu a ajuns in fisierul final, dar a scris real in baza) O prima varianta a testului apela `ScrieArticoleFacturaEditate` **fara** sa incarce intai `tvd` (in fluxul real, `Load()`-ul lui `frm_modific2024` e cel care il populeaza din `crsArticoleFactura` — sarit aici deliberat). Garda EXTERNA (`Used('tvanz') And Reccount('tvanz')=1`) s-a satisfacut, dar garda INTERNA de no-op a helperului (`!Used('tvd')`) a transformat apelul intr-un **no-op tacut** (intoarce tot `.T.`) — fara sa corecteze reset-ul `STERS=0` pe care `pack_facturare.actualizeaza_vanzari` il face pe TOT documentul (acelasi mecanism documentat in `rec_s5_scriere_reala.md`). Tranzactia a **comis** reset-ul necorectat: linia `det=1582`, definitiv stearsa in `test_s5_scriere_reala.prg` (09.08.2026), a fost reinviata real in Oracle (`STERS: 1 → 0`). **Reparat imediat**, inainte de orice alta actiune: `UPDATE vanzari_detalii SET sters=1 WHERE id_vanzare_det=1582; COMMIT;`, verificat prin `sqlplus` ca restul coloanelor (cantitate, pret, pret_achizitie) au ramas neatinse. Varianta finala a testului incarca `tvd` cu `IncarcaArticoleFactura(tnIdVanzare,'tvd')` chiar dupa `PregatesteArticoleFacturaEditare` (ca Load()-ul formularului), deci helperul chiar corecteaza reset-ul — noua rulare confirma explicit, prin asertia 17, ca `det=1582` ramane `sters=1` dupa salvare. Consemnat in antetul suitei finale, ca precautie pentru cine reia/adapteaza testul. Aceasta e o capcana de test (guard-ul `!Used('tvd')` functioneaza exact cum trebuie, prevenind un no-op periculos sa arunce eroare — problema a fost ca testul nu a reprodus fidel ce face UI-ul real inainte de acest punct), **nu** un defect de productie. ## C. Liniile din seturi de articole (`id_vanzare_set` nenul) **Partea Oracle (agregarea pe seturi din `recalculeaza_totaluri_vanzari`) — NEACOPERIBILA pe datele curente, confirmat prin interogare, nu presupus:** ```sql select count(*) from vanzari v join vanzari_detalii vd on vd.id_vanzare=v.id_vanzare and vd.sters=0 where vd.id_vanzare_set is not null and extract(year from v.data_act)=2026 and extract(month from v.data_act)=8; -- 0 randuri ``` Cele mai recente documente cu linii de set active sunt din 2026-03 (`id_vanzare=1027`) si mai vechi — niciunul din luna curenta (august 2026), cerinta obligatorie a gardei de editare (`(pnAn*12)+pnLuna = (gnAn*12)+gnLuna`, verificata direct in suitele A si B de mai sus). Fara fabricare de date (interzisa explicit in briefing): golul ramane deschis, ca risc cunoscut, pana apare un document real editabil cu linii de set. **Partea helper VFP (`ScrieArticoleFacturaEditate` trateaza corect o linie cu `id_vanzare_set` nenul) — DEJA ACOPERITA, verificat prin recitirea suitei existente, fara munca noua necesara:** `test_s5_validari_articole.prg`, sectiunea B1, randul 6 („componenta de set, pastrata" `id_vanzare_set=999`, `det=558`) — asertia „clasificare: exact 3 UPDATE (liniile pastrate 555/556/ 558, inclusiv nemodificata si componenta de set)" dovedeste ca helperul trateaza o linie cu `id_vanzare_set` nenul identic cu orice alta linie pastrata (UPDATE normal, fara ramura speciala in codul VFP — `id_vanzare_set` nu apare in nicio conditie din `ScrieArticoleFacturaEditate`, confirmat si prin citirea sursei). Nu era nevoie de mock nou. ## Cifre PASS/FAIL, numarate din log | Suita | Cifra | Verdict | |---|---|---| | `test_s5_rollback_real.prg` (nou) | **13 PASS / 0 FAIL** | Rollback la esec partial: contract (mock) + stare DB (real, SQLEXEC) | | `test_s5_al_doilea_intrare.prg` (nou) | **17 PASS / 0 FAIL** | Al doilea punct de intrare, executie reala prin `comun.vc2:2491-2493` | | `test_s5_validari_articole.prg` (existent, doar recitit) | 35 PASS / 0 FAIL (neschimbat) | Confirma acoperirea existenta pentru linia de set (B1/rand 6) | ## Date de test consumate Pe `id_vanzare=1049` (acelasi document folosit de toate suitele S5 anterioare): - **`cod` realocat: `1140898 → 1140899 → 1140900`** (ambele din suita B — prima trecere, cu corectia de mai sus, apoi a doua trecere finala). Cine reia testele citeste `cod`-ul curent din `VANZARI`, nu-l presupune. - **Continutul liniilor si totalurile documentului raman identice** cu starea de dinaintea acestei lucrari (verificat exhaustiv, prin sqlplus, dupa fiecare pas): `det=1581/1583/1588` active cu aceleasi valori, `det=1582` ramane `sters=1`. - Suita A (rollback) **nu a consumat nimic** — ambele parti (mock si real) se inchid fara COMMIT. - Zero linii noi ramase, zero tranzactii deschise, zero procese `vfp9.exe` ramase — verificat prin `sqlplus`/`tasklist` la finalul lucrarii. ## Fisiere atinse - `COMUN\utile\Teste\editare_factura\test_s5_rollback_real.prg` (nou) - `COMUN\utile\Teste\editare_factura\test_s5_al_doilea_intrare.prg` (nou) - Niciun fisier de productie (`.vc2`/`.prg` de aplicatie) atins. `omodificari.vc2`, `ofacturare_comun.vc2`, `comun.vc2`, `ofacturare_editare.prg`, `COMUN\clase\ofacturare.vc2` — neatinse, conform interdictiei din briefing. Diff (fisiere noi): diff aplicat (sters).