# S7 — rotunjirea la reeditare. Rezultat Cerinta din plan (`docs\plan_06_editare_factura.md:275-280`): `verifica_total_document` insereaza o linie de corectie in `ACT_TEMP` cand totalul documentului difera de suma din note; *gata cand* trei editari consecutive nu lasa trei linii de corectie. ## Verdict **Nu se acumuleaza.** Mecanismul e marginit prin constructie: nota activa a documentului nu poate contine niciodata mai mult decat o singura generatie de linii de corectie (cel mult 2, cate una pentru fiecare din cele doua verificari ftva/tva), indiferent de cate ori a fost editat documentul inainte. Nu e nevoie de reparatie — nu exista defect. ## Dovada pe cod Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (fisierul real aplicat in baza, nu un export `all_source` — capcana deja platita in S5, vezi `docs\handoff_s5.md`). **1. `ACT_TEMP` e tabela de lucru (scratch), golita automat la fiecare COMMIT/ROLLBACK.** Verificat direct din dictionar, nu presupus: ```sql select table_name, temporary, duration from all_tables where table_name in ('ACT_TEMP','ACT'); --> ACT N (tabela persistenta) --> ACT_TEMP Y SYS$TRANSACTION (global temporary table, DURATION = SYS$TRANSACTION) ``` Un GTT cu `DURATION = SYS$TRANSACTION` se goleste singur la finalul TRANZACTIEI curente. Decizia 38 (`docs\handoff_s5.md`, deja testata cu scriere reala in S5) stabileste ca **fiecare editare ruleaza intr-o singura tranzactie manuala, incheiata cu COMMIT** — deci `ACT_TEMP` porneste mereu **goala** la inceputul fiecarei editari; nu poate purta randuri dintr-o editare anterioara. **2. `verifica_total_document` se apeleaza o singura data pe generatie, imediat dupa reconstructia notei.** `cumuleaza_note_act` (`:14070-14107`): ``` 14078 UPDATE ACT_TEMP SET STERS = 1, TVA_INCASARE = ... ... 14092 pack_facturare.cumuleaza_note_act_temp(); 14094 if pack_facturare.ntip <> pack_facturare.nTipNotaPlata then 14095 pack_facturare.verifica_total_document(); 14096 end if; ``` `cumuleaza_note_act_temp` (`:14109-14319`) marcheaza tot ce e in `ACT_TEMP` (deci doar randurile scrise in TRANZACTIA curenta, `ACT_TEMP` fiind goala la start) `STERS=1`, reconstruieste randurile prin `GROUP BY` peste ele (`:14215-14312`) si sterge randurile pre-agregare (`:14318 DELETE FROM ACT_TEMP WHERE STERS = 1`). Rezultatul, inainte de `verifica_total_document`, e un set de randuri **agregate, cate unul pe combinatie `(scd,scc,...)`**, construit exclusiv din datele editarii curente. **3. Corectia insereaza cel mult un rand per verificare, pe baza totalurilor abia calculate.** `verifica_total_document` (`:16276-16690`): calculeaza `V_TOTFTVA_VER`/`V_TOTTVA_VER` prin `SUM` peste `ACT_TEMP` (fara filtrare pe `cod`, dar `ACT_TEMP` contine oricum o singura generatie — vezi punctul 1), le compara cu `pack_facturare.ntotftva`/`ntottva` (setate mai devreme in acelasi flux) si, la neconcordanta, insereaza UN rand nou, copiat dupa randul-ancora (`scd`,`ascd`,`scc`,`ascc`, `explicatia`, etc.) cu `suma` = diferenta si `id_act = max(id_act)+1`: ``` 16349 IF NVL(pack_facturare.ntotftva, 0) <> 0 and NVL(pack_facturare.ntotftva, 0) <> V_TOTFTVA_VER THEN 16352 pack_facturare.ndifftva := pack_facturare.ntotftva - V_TOTFTVA_VER; 16353 insert into act_temp (...) select b.id_act, ..., pack_facturare.ndifftva as suma, ... 16456 from act_temp a 16457 left join (select max(id_act) + 1 as id_act, 0 as sters from act_temp) b on a.sters = b.sters 16460 where a.id_act = V_ID_TOTFTVA; 16461 END IF; 16462 IF NVL(pack_facturare.ntottva, 0) <> 0 and NVL(pack_facturare.ntottva, 0) <> V_TOTTVA_VER THEN -- acelasi tipar, insereaza a doua linie (verificarea de TVA) 16575 END IF; 16577 IF pack_facturare.ntip in (48, 49) AND (...) THEN -- a treia linie, DOAR pentru tip 48/49 (nota de credit/debit) - nu e cazul facturii (tip=1) 16688 END IF; ``` Fiecare din cele doua/trei `IF`-uri e evaluat o singura data pe apel, deci **maximum 2 linii de corectie pe generatie** (3 doar pentru `ntip in (48,49)`) — niciodata proportional cu numarul de editari anterioare. **4. Blocul vechi e retras integral la fiecare editare**, nu doar linia de corectie. `OSCRIE_IN_FISIERE(2)` (sterge nota veche) + `actualizeaza_vanzari` marcheaza tot `cod`-ul vechi `STERS=1` — confirmat empiric mai jos, pe 8 generatii reale. Deci nota **activa** contine mereu rezultatul unei singure generatii, cu cel mult 2 linii de corectie proprii — niciodata suma corectiilor din generatii anterioare. ## Dovada pe date, in tranzactie — trei editari consecutive Suita noua: `COMUN\utile\Teste\editare_factura\test_s7_rotunjire.prg` (log: `COMUN\utile\Teste\editare_factura\test_s7_rotunjire_log.txt`). Acelasi lant real ca in S5 (`OSCRIE_IN_FISIERE(2)` → `OSCRIE_IN_FISIERE(0)` → `finalizeaza_modificare_nota` → `ScrieArticoleFacturaEditate`), pe `id_vanzare = 1049`, trei treceri consecutive, **fiecare in tranzactia ei proprie, cu COMMIT** — fara nicio modificare de continut (reeditare pura, ca sa izoleze exact mecanismul `verifica_total_document`, nu efectele altor modificari). Dupa fiecare COMMIT s-a citit `VANZARI.cod` (nou) si s-a numarat in `ACT`: - randurile active (`STERS=0`) pe `cod`-ul curent; - liniile de corectie, cu semnatura exacta a INSERT-ului de mai sus: aceeasi cheie `(scd,scc,nract,dataact,explicatia)`, cel putin 2 randuri, sume **diferite** si cu diferenta **mica** (`< 5`) — nu orice pereche `(scd,scc)` duplicata, care poate fi legitim generata de doua linii de detaliu diferite ale documentului (vezi capcana de mai jos). **Rezultat: 6 PASS / 0 FAIL.** | Trecere | `cod` inainte -> dupa | randuri active `ACT` | linii de corectie | |---|---|---|---| | 1 | 1140903 -> 1140904 | 10 | **0** | | 2 | 1140904 -> 1140905 | 10 | **0** | | 3 | 1140905 -> 1140906 | 10 | **0** | Totalurile documentului au ramas neschimbate (`747.96 / 157.06 / 905.02`), confirmand ca cele trei treceri au fost reeditari pure, fara alta variabila. **Limitare, consemnata explicit**: pe compozitia acestui document, `verifica_total_document` nu declanseaza NICIODATA neconcordanta (`ntotftva = V_TOTFTVA_VER` exact, la toate cele 8 generatii verificate — vezi mai jos). Cifra `0/0/0` arata ca randurile nu cresc, dar **„zero cazuri in date nu e dovada"** — nu demonstreaza prin ea insasi ca, DACA s-ar declansa, corectiile nu s-ar acumula. Pentru asta raspunsul vine din cod (sectiunea de mai sus) si din exemplul real de mai jos. ### Capcana prinsa in propria masuratoare — semnatura gresita la prima incercare Prima versiune a interogarii de numarare (fara filtrul de diferenta mica) a gasit „3 linii de corectie" la fiecare trecere — fals pozitiv. Documentul are legitim doua linii de detaliu active (`det=1581`, `det=1583`) care genereaza fiecare propriile perechi `(371,378)`/`(378,371)`/`(4111,4428)` in notă — duplicat structural normal, cu diferente mari intre sume (ex. `126.04`, `57.48`), nu diferente de rotunjire. Corectat cu filtrul `abs(max(suma)-min(suma)) < 5 AND max(suma) <> min(suma)` (interogare separata, `sqlplus`, verificat pe `cod` 1140900-1140903 dupa corectie: 0 randuri la toate patru) inainte de rularea finala (mai sus). `NumaraCorectii` din `test_s7_rotunjire.prg` are acum filtrul corect. ## Confirmare independenta: mecanismul chiar functioneaza in productie Cautare in `ACT` (an=2026), dupa semnatura exacta a corectiei — cauta orice caz real, nu doar pe documentul de test: ```sql select cod, scd, scc, nract, dataact, count(*) nr, min(suma) smin, max(suma) smax from act where an=2026 and sters=0 and id_fact is not null group by cod, scd, scc, nract, dataact, explicatia having count(*) = 2 and min(suma) <> max(suma) and abs(max(suma)-min(suma)) < 5; ``` Gasit: `cod=1140709` (`id_fact=8008816`, an=2026 luna=3) — `id_act=122905` (`scd=4428, scc=401, suma=150`) si `id_act=122906` (acelasi `scd/scc/nract/dataact`, `suma=150.02`, diferenta `0.02`). Corespunde exact tiparului INSERT-ului: `id_act` cel mai mare = `max(id_act)+1`, restul coloanelor copiate de pe randul-ancora, `suma` = diferenta de rotunjire. **Acest document nu a fost editat niciodata dupa** (o singura valoare `cod`), deci nu arata direct comportamentul la editari repetate — dar confirma pe date reale ca mecanismul chiar insereaza, si insereaza **exact un rand** cand se declanseaza, in acord cu structura codului (un `INSERT` per `IF`). ## Istoricul complet al documentului de test — randurile nu cresc `id_vanzare=1049` / `id_fact=8009659` a fost editat de 8 ori pana acum (S5 + acest test), fiecare generatie verificata in `ACT`: | `cod` | randuri active la generare | linii de corectie | |---|---|---| | 1140887 | 10 | 0 | | 1140896 | 10 | 0 | | 1140897 | 10 | 0 | | 1140898 | 10 | 0 | | 1140900 | 10 | 0 | | 1140904 | 10 | 0 | | 1140905 | 10 | 0 | | 1140906 (curent) | 10 | 0 | Numarul de randuri e identic la fiecare generatie — regenerare completa, nu acumulare incrementala, consistent cu punctul 4 din dovada pe cod (blocul vechi se retrage integral). ## Ce NU acopera cercetarea - **Niciun caz gasit in productie unde corectia se declanseaza PE UN DOCUMENT editat de mai multe ori** — cautarea (an=2026, semnatura stricta) a gasit un singur exemplu real, pe un document editat o singura data. N-a fost fortata o neconcordanta artificiala pe `id_vanzare=1049` (ar fi cerut modificarea logicii de calcul a notei, `scrie_nota`, cateva mii de linii, in afara bugetului acestei cercetari) si nici o cautare exhaustiva pe alti ani/luni. - **Ramura `ntip in (48,49)`** (a treia linie de corectie, `:16577-16688`) — nu se aplica facturii de test (tip=1), neexercitata. - `PACK_CONTAFIN` (flux-ul care muta randurile din `ACT_TEMP` in `ACT` persistenta) a fost citit doar punctual (`INITIALIZEAZA_SCRIERE_ACT_RUL`), via `ALL_SOURCE` (read-only, `sqlplus`) — nu integral; concluzia despre golirea lui `ACT_TEMP` se bazeaza pe metadata `ALL_TABLES.DURATION` (autoritara), nu pe citirea completa a fluxului de flush. ## Date de test consumate Continuare pe **`id_vanzare = 1049`** (deja in lucru din S5, `docs\handoff_punct6_dupa_s5.md`): `cod` realocat suplimentar **1140900 -> 1140901 -> 1140902 -> 1140903** (prima rulare, cu interogarea de numarare gresita — pastrata doar ca generatie reala, nu s-a revenit peste ea) **-> 1140904 -> 1140905 -> 1140906** (rularea finala, cea raportata mai sus). **Cod curent: `1140906`.** Niciun `det` schimbat sau sters suplimentar fata de S5 (toate cele trei treceri au fost reeditari pure); totalurile documentului raman `747.96 / 157.06 / 905.02`, identice cu starea de la finalul S5. `id_vanzare = 1050` si `1037` — neatinse. ## Fisiere atinse Un singur fisier nou, in `COMUN` (necomis): `COMUN\utile\Teste\editare_factura\test_s7_rotunjire.prg` + logul `test_s7_rotunjire_log.txt`. Niciun fisier de productie (`.vc2`/`.sc2`/`.prg` de aplicatie), niciun script de migrare. Zero commit git/SVN. ## Stare finala — verificata, nu presupusa - **Tranzactii Oracle deschise**: 0 (`v$transaction` join `v$session` pe `MARIUSM_AUTO`). - **Procese `vfp9.exe` ramase**: 0 (`tasklist`). - **Documentul de test**: `id_vanzare=1049`, `cod=1140906`, `sters=0`, totaluri neschimbate.