sync SVN r18027

This commit is contained in:
2026-08-20 22:54:03 +03:00
parent 5b52cb3999
commit 3ddcbc04bf
66 changed files with 47 additions and 12608 deletions

View File

@@ -1,218 +0,0 @@
# Brief de implementare — runda 6, punctul 6, partea VFP
Explicatia TVA in dialogul de modificare a articolului din `frm_facturi`. Proiectarea completa:
`docs\propunere_runda6_punct6_plsql.md`, sectiunea "Ce ramane de facut in VFP". Acest brief e
executabil: contine liniile exacte, valorile exacte si ce s-a verificat deja.
## Interdictii (citeste-le inainte de orice)
- **NU rula `git_sync.ps1`, `roa_sync.bat`, `svn update`, `svn commit`, `git commit`, `git push`.**
- **NU atinge alt fisier** in afara de `COMUN\clase\ofacturare_comun.vc2` (+ binarul lui prin
write-back) si `docs\raport_r6_punct6_vfp.md` (raportul tau).
- **NU modifica PL/SQL** — pachetul e deja aplicat pe `MARIUSM_AUTO` si verificat.
- **NU schimba `Text1.ReadOnly` nicaieri**, nu atinge grid-ul de articole, nu atinge `omodificari.vc2`.
- Esti **singurul scriitor** pe `ofacturare_comun.vc2`. Daca gasesti modificari necomise pe el la
start, opreste-te si raporteaza.
## Starea de plecare
- `svn`/`git` curate, r18025. Textele `.??2` sunt la zi (git_sync: 0 convertite, 463 la zi).
- Oracle: `MARIUSM_AUTO.PACK_FACTURARE` are deja semnatura noua, verificata pe sursa vie:
```
PROCEDURE modifica_explicatie_articol(V_ID_VANZARE_DET IN NUMBER,
V_EXPLICATIE IN VARCHAR2,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL,
V_ID_JTVA_COLOANA IN NUMBER DEFAULT NULL);
```
Al 5-lea parametru lipsa/NULL => ramura veche (se scriu doar `EXPLICATIE` si `TAXCODE`).
## Fapte verificate pe cod — nu le re-investiga
1. `crsDetalii` (`oproceduri_facturare.prg:382-400`) are: `id_vanzare`, `id_vanzare_det`,
`proc_tvav`, `id_jtva_coloana`, `jtva_coloana`, `taxcode`, `taxname`, `sters`, `explicatie`.
**Nu are `data_act` si nu are `id_part`.**
2. `crsfacturi` (`oproceduri_facturare.prg:339-360`) are `data_act`, `id_part`, `id_vanzare`.
Randul curent din `crsfacturi` **este** antetul liniei editate: `crsDetalii` se filtreaza pe
`id_vanzare=` al randului curent din `crsfacturi` (`ofacturare_comun.vc2:3621-3623`).
3. `proc_tvav` e **multiplicator** (1.19), `cota_tva` din `vjtva_coloane` e **procent** (19).
Cota liniei = `Round((Nvl(poRec.proc_tvav,0) - 1) * 100, 2)`.
4. Filtrul de explicatii e cel din `caut_explicatie_tva` (`ocautare.prg:3193, 3220`):
`id_jtva_coloana > 0` + `cota_tva = <cota>`, pe view-ul `vjtva_coloane`.
**Nu** folosi `update_jtva_coloane` — acela adauga restrictii pe `afisat` si pe `cote_tva` de
an/luna curenta, care nu sunt in proiectarea aprobata.
5. `GetTaxCodeIdPart` (`oproceduri_comune.prg:6059-6125`) e apelabila din `frm_facturi`, isi
gestioneaza singura cursorul `jtva_coloane`, si **returneaza `.NULL.` daca `gl406` e `.F.`**
(`:6086-6088`) — de aceea apelul se face doar sub garda `gl406`, altfel ar sterge `taxcode`-ul.
6. `tlNeexigibil` = `.F.` (parametru **omis**). Confirmat, nu presupus: calea de emitere a
facturii foloseste `GetTaxCode(gnAn, gnLuna, ldDataAct, lnIdJtva, .F.)` cu ultimii trei
parametri impliciti (`ofacturare.vc2:2529` si `:3101`), iar pe jurnal de vanzari (`jv`)
`GetTaxCodeIdPart` degenereaza exact in acel apel (ramura `llJC` e `.F.`, `:6098-6110`).
Deci butonul produce acelasi `taxcode` pe care l-ar produce emiterea.
7. `n50`/`n100` = `.F.` (parametri omisi), ca in `ointroduceri.vc2:11488`.
## Modificarile — fisier `COMUN\clase\ofacturare_comun.vc2`
Ruleaza intai `vfp_symbols.ps1 -Where` ca sa confirmi domeniul fiecarei metode inainte sa editezi;
numerele de linie de mai jos sunt de la starea r18025 si se pot deplasa dupa prima editare.
### M1 — `frm_facturi.do_modifica_explicatie` (~`:4642-4659`)
Dupa `Scatter Name poRec Memo` si inainte de `If poRec.sters = 0`, ataseaza antetul pe `poRec`
(vezi faptul 1 si 2 — campurile nu exista pe `crsDetalii`):
```foxpro
AddProperty(poRec, 'data_act', crsfacturi.data_act)
AddProperty(poRec, 'id_part', Nvl(crsfacturi.id_part, 0))
```
Restul metodei ramane neschimbat (`update_saft_taxtable()` ramane unde e).
### M2 — layout `frm_modifica_articol_factura` (~`:5131`)
Randul existent cu `cbo_saft` coboara, iar deasupra lui intra randul nou de explicatie TVA.
Valori exacte:
| obiect | proprietate | din | in |
|---|---|---|---|
| `frm_modifica_articol_factura` | `Height` | 370 | **431** |
| `_shape3` | `Top` | 323 | **384** |
| `cbo_saft` | `Top` | 329 | **390** |
| `lbSaft` | `Top` | 332 | **393** |
Obiecte noi (toate in `*<PropValue>` / `ADD OBJECT`, plus linii `*< OBJECTDATA ... >` in blocul de
ZOrder de la inceputul clasei):
- `_shape4` AS `_shape` OF `_baza.vcx` — `BackStyle=0, Left=13, Top=323, Height=58, Width=348`
- `lbExplTva` AS `_label` OF `_baza.vcx` — `Caption="Explicatie TVA", Left=24, Top=332`
(diacriticele: vezi sectiunea de encoding mai jos; textul afisat corect e `Explicație TVA`)
- `cbo_expl_tva` AS `_cbbase` OF `_cb_base.vcx` — modelat dupa `cbo_saft`:
`BoundColumn=3, BoundTo=.T., ColumnCount=2, ColumnWidths="400,60", Height=24, Left=110,`
`Top=329, Width=238, RowSourceType=3`.
**`ControlSource` ramane gol** (nu legam direct de `poRec.id_jtva_coloana` — valoarea aleasa se
preia in `InteractiveChange`, ca sa putem distinge "userul a atins combo-ul" de "n-a atins").
`RowSource` se construieste in `Init` (vezi M3), nu se scrie in `ADD OBJECT`.
- `lbExplTvaInfo` AS `_label` OF `_baza.vcx` — `Caption="", Left=24, Top=355, Width=324,`
`Height=20, WordWrap=.T., ForeColor=RGB(128,0,0), Visible=.F.`
Proprietate noua de formular: `nidjtvaales`.
**Obligatoriu si intrarea `*p: nidjtvaales` in `*<DefinedPropArrayMethod>`, nu doar valoarea in
`*<PropValue>`** — fara `*p:` valoarea trece fidelity-check-ul, ajunge in binar, dar VFP nu
materializeaza proprietatea si orice acces cade cu eroarea 1734. Valoare initiala: `nidjtvaales = 0`.
### M3 — `frm_modifica_articol_factura.Init`
La finalul metodei `Init` existente (dupa blocul care citeste `citeste_lungcampexplart`), adauga
popularea combo-ului. Comportamentul cerut de proiectare, in trei ramuri:
```foxpro
Local lnCota, lcSqlTva, lcCursorTva
Thisform.nidjtvaales = 0
If Isnull(poRec.proc_tvav) Or Nvl(poRec.proc_tvav, 0) = 0
Thisform.cbo_expl_tva.Enabled = .F.
Thisform.lbExplTvaInfo.Caption = [Cota de TVA a acestei linii nu este cunoscuta; explicatia TVA nu poate fi modificata prin acest buton.]
Thisform.lbExplTvaInfo.Visible = .T.
Return
Endif
lnCota = Round((poRec.proc_tvav - 1) * 100, 2)
lcCursorTva = [crsExplTvaArt]
lcSqlTva = [select id_jtva_coloana, denumire, cota_tva from vjtva_coloane where id_jtva_coloana > 0 ] + ;
[and cota_tva = ] + Alltrim(Str(lnCota, 10, 2)) + [ order by denumire]
If goExecutor.oExecuta(lcSqlTva, lcCursorTva) And Reccount(lcCursorTva) > 0
Thisform.cbo_expl_tva.RowSource = [select denumire, cota_tva, id_jtva_coloana from ] + lcCursorTva + [ into cursor crsExplTvaCbo]
Thisform.cbo_expl_tva.Requery()
Thisform.cbo_expl_tva.Value = Nvl(poRec.id_jtva_coloana, 0)
Else
Thisform.cbo_expl_tva.Enabled = .F.
Thisform.lbExplTvaInfo.Caption = [Nu exista nicio explicatie TVA activa cu aceeasi cota ca linia facturata (cota: ] + ;
Alltrim(Str(lnCota, 10, 2)) + [ %).]
Thisform.lbExplTvaInfo.Visible = .T.
Endif
```
Note de implementare, nu le sari:
- `Alltrim(Str(lnCota,10,2))` produce `19.00`; comparatia cu `cota_tva` (NUMBER(10,0)) e corecta
in Oracle. Nu formata cu virgula.
- daca `Init` are `Return` propriu / `NODEFAULT`, verifica sa nu scurtcircuitezi codul existent.
- inchide cursorul `crsExplTvaArt` in `Destroy` daca ramane deschis (`If Used(...) : Use In ...`).
- textul mesajelor **fara diacritice** (asa sunt scrise si celelalte mesaje din aceasta clasa,
ex. `:4635`) — evita complet problema de encoding pe siruri noi.
### M4 — `cbo_expl_tva.InteractiveChange` (metoda noua)
```foxpro
PROCEDURE cbo_expl_tva.InteractiveChange
Local lnIdJtva
lnIdJtva = Nvl(This.Value, 0)
If lnIdJtva <= 0
Return
Endif
Thisform.nidjtvaales = lnIdJtva
poRec.id_jtva_coloana = lnIdJtva
If Type('gl406') = 'L' And m.gl406
poRec.taxcode = GetTaxCodeIdPart(m.gnAn, m.gnLuna, poRec.data_act, m.lnIdJtva, poRec.id_part)
Thisform.cbo_saft.Refresh()
Endif
ENDPROC
```
### M5 — `inainte_de_do_termin` (~`:5227-5235`)
Al 5-lea parametru pozitional, **literal, nu bind** — ca sa poata fi `null` curat cand userul n-a
atins combo-ul (atunci procedura ramane pe ramura veche):
```foxpro
Local lcParamJtva
lcParamJtva = Iif(Vartype(Thisform.nidjtvaales) = 'N' And Thisform.nidjtvaales > 0, ;
Alltrim(Str(Thisform.nidjtvaales)), [null])
lcSql = [begin pack_facturare.modifica_explicatie_articol(] + Alltrim(Str(poRec.id_vanzare_det)) + [,] + ;
['] + Nvl(Alltrim(OracleSpecialCharacters(poRec.explicatie)),'') + [',?gnIdUtil,?poRec.taxcode,] + lcParamJtva + [); end;]
llReturn = goExecutor.oExecuta(lcSql)
```
Restul metodei (confirmarea `amessagebox`, `Return llReturn`) ramane neschimbat. Erorile
`FACT-026..029` ies deja vizibil prin `goExecutor.oExecuta` — nu adauga cod de afisare.
### M6 — refresh dupa salvare
**Nimic de facut.** `Thisform.actualizeaza_grid2()` din `do_modifica_explicatie` (`:4654`) e
suficient: headerul nu se schimba, totalurile nu se recalculeaza.
## Encoding — capcana platita deja
Fisierele `.vc2` din acest arbore au diacriticele in **cp1250, nu cp1252**. Tool-ul `Edit` le
transforma in U+FFFD si strica textul din alte proprietati.
- **numara octetii > 0x7F inainte si dupa fiecare editare** — trebuie sa iasa acelasi numar;
- daca s-au stricat, repara cu encoding `28591`;
- pentru sirurile **noi** pe care le scrii tu: **fara diacritice**, deci problema nu apare de la
tine — dar poate aparea de la o rescriere a fisierului.
## Write-back si verificare
1. `txt2vcx.ps1 ... -AllowComun` — se ruleaza **direct, fara aprobare** (regula lui Marius).
2. Fidelity-check: FoxBin2Prg **nu pastreaza ordinea textuala** pentru `ADD OBJECT` multiple si
proprietati custom, si sorteaza cu `_` **dupa** litere. Daca primul write-back da FAIL pe
ordine: **adopta ca sursa textul regenerat din `<staging>\verify\*.vc2`**, nu incerca sa
ghicesti ordinea corecta.
3. **`mtime` nu dovedeste sincronizarea text/binar** — `txt2vcx.ps1` rescrie mtime-ul textului.
Dovada ceruta: reconverteste binarul intr-un cache temporar si fa `diff` cu textul din arbore;
raporteaza numarul de linii diferenta (trebuie 0).
## Ce raportezi
Scrie **pe disc**, la `D:\ROA\ROAFACTURARE\docs\raport_r6_punct6_vfp.md`, si raspunde in chat doar
cu `GATA` + calea. In raport:
- inventarul modificarilor cu `fisier:linie` (dupa write-back, deci pe numerotarea finala);
- confirmarea explicita ca write-back-ul e facut si **numarul de linii diferenta** la reconversie;
- numarul de octeti > 0x7F inainte si dupa;
- ce n-ai putut face si de ce;
- **nu comite nimic**; nu rula sincronizari.
Daca te apropii de ~200-250k tokens de context, opreste-te la o stare consistenta pe disc, scrie
raportul cu ce ai facut si ce ramane, si anunta — nu continua.

View File

@@ -1,139 +0,0 @@
# Brief de implementare — runda 7: sincronizarea articole-rulaje
Doua cereri ale lui Marius, 20.08.2026 (dupa proba pe ecran a rundei 6):
1. sincronizarea sa porneasca **doar din buton**, nu sa se mai arate la terminarea editarii;
2. la directia **ARTICOLE_SURSA** (articole -> rulaj) se copiaza `pretvtva`, dar **nu** si `pretv`
si `tvav`, care raman vechi.
## Interdictii (citeste-le inainte de orice)
- **NU rula `git_sync.ps1`, `roa_sync.bat`, `svn update`, `svn commit`, `svn revert`, `git commit`,
`git push`, `git stash`, `git checkout`.**
- **NU atinge `COMUN\clase\ofacturare_comun.vc2`** — are o livrare separata, verificata, necomisa.
Orice scriere acolo distruge munca altcuiva.
- **NU atinge PL/SQL**, nu te conecta la Oracle pentru scriere.
- Fisierele tale, singurele: `COMUN\clase\omodificari.vc2` (+ binarul prin write-back),
`COMUN\programe\ofacturare_editare.prg`, si raportul `docs\raport_r7_sincronizare.md`.
- Esti singurul scriitor pe ele. Daca gasesti alte modificari necomise acolo, opreste-te si raporteaza.
## M1 — sincronizarea porneste doar din buton
`COMUN\clase\omodificari.vc2`, metoda `inainte_de_do_termin` a clasei `frm_modific2024`,
blocul de la **`:14484-14494`** (numerele se deplaseaza dupa prima editare — confirma cu
`vfp_symbols.ps1 -Where`):
```foxpro
*!* al doilea punct de declansare al sincronizarii: enumerarea diferentelor la salvare, cu
*!* continuarea salvarii daca utilizatorul renunta - de aceea llRet nu se schimba aici;
*!* pe document fara linii active nu porneste: propunerea ar marca toata sursa ca "Adaugare"
IF m.llRet AND This.lAreArticoleVanzari AND !This.lArticoleReadOnly AND Used('tvd') AND m.lnLiniiActive > 0
LOCAL lcSemnaturaAcum
lcSemnaturaAcum = This.SemnaturaDivergenteSincronizare()
*!* doar divergentele aparute in sesiunea curenta deschid dialogul; N-A nu intra in semnatura
IF !Empty(m.lcSemnaturaAcum) AND !(m.lcSemnaturaAcum == This.cSemnaturaSincronizare)
This.AfiseazaDialogSincronizareArticole()
ENDIF
ENDIF
```
**Se sterge integral, cu tot cu cele patru linii de comentariu.** `RETURN m.llRet` de dupa ramane.
Nu atinge butonul `pgfArticole.PAGE3.cmdSincronizeazaArticole.Click` (`:16671`) — el ramane singura
cale de pornire.
**Nu sterge** metoda `SemnaturaDivergenteSincronizare`, proprietatea `cSemnaturaSincronizare` si
atribuirea ei de la `:14956`. Dupa aceasta stergere raman fara consumator — **semnaleaza asta in
raport**, ca decizia de curatare sa fie a lui Marius, dar nu o lua tu.
## M2 — `pretv` si `tvav` se recalculeaza la scrierea in rulaj
`COMUN\programe\ofacturare_editare.prg`, `FUNCTION AplicaModificareTrul` (**`:924`**). Azi:
```foxpro
lcCampCant = IIF(Nvl(cant,0) <> 0, 'cant', 'cante')
REPLACE (m.lcCampCant) WITH m.tnCantitateNoua, pretvtva WITH m.tnPretNouCuTva IN trul
```
Formula corecta **exista deja** si e sursa de adevar — nu inventa alta: `omodificari.vc2`,
`calculeaza_valori_rul`, ramura `pretvtva` (**`:13566-13587`**):
```foxpro
lnProcTvav = IIF(EMPTY(proc_tvav), 1, proc_tvav) - 1 && 1.19 -> 0.19
lnTVAv = ROUND(lnPretVtva * lnProcTVAv / (1 + lnProcTvav), gnPPretv)
lnPretv = lnPretvtva - lnTvav
lnValoarecTVA = ROUND(lnCantitate * lnPretvTva, gnPC)
lnValoareTVA = ROUND(lnValoarecTVA * lnProcTVAv / (1 + lnProcTvav), gnPC)
lnValoareFTva = lnValoareCtva - lnValoareTVA
REPLACE pretv, pretvtva, tvav, valoarev, valtvav, valoarevcTVA
```
Atentie la precizii, sunt **doua diferite**: `pretv`/`tvav`/`pretvtva` se rotunjesc la **`gnPPretV`**
(precizia pretului de **vanzare**), iar `valoarev`/`valtvav`/`valoarevcTVA` la **`gnPC`**.
**Nu** apela `toForm.calculeaza_valori_rul()` in locul calculului: metoda alege cursorul din
`This.pgfArticole.ActivePage` (`:13532-13533`) — `trul` doar cand pagina activa e 1. Sincronizarea
porneste de pe pagina 3, deci ar nimeri `trul_obinv`. Calculeaza in `AplicaModificareTrul`, unde
esti deja pozitionat pe randul corect din `trul`.
Ordinea conteaza: cantitatea se scrie **inainte** de a citi `cant`/`cante` pentru valori, altfel
valorile ies pe cantitatea veche.
**Include si `valoarev`, `valtvav`, `valoarevcTVA`.** Marius a numit doar `pretv` si `tvav`, dar
sincronizarea schimba si cantitatea, iar cele trei valori depind de `cantitate x pretvtva`; lasate
nescrise raman la fel de vechi ca `pretv`. E semnalat lui ca depasire fata de cerere.
Actualizeaza si comentariul-antet al lui `AplicaSincronizareArticole` (`:787-794`), care azi spune
*"ARTICOLE_SURSA scrie doar cant/pretvtva pe randul RUL deja existent"* — a devenit fals.
## Teste — obligatoriu, nu optional
Exista suite care acopera exact zona atinsa:
- `COMUN\utile\Teste\editare_factura\test_s4b_sincronizare.prg` (42 cazuri; **sectiunea C** e chiar
`AplicaSincronizareArticole` pe directia `ARTICOLE_SURSA`, care scrie in `trul`)
- `COMUN\utile\Teste\editare_factura\test_s4b_dialog.prg` (35 cazuri)
Ruleaza-le pe amandoua **dupa** modificari. Reguli platite deja, respecta-le:
- **sterge `.fxp`-ul** inainte de fiecare rulare, altfel se executa bytecode vechi;
- **o singura suita per apel** de comanda — doua rulari inlantuite in acelasi apel atarna;
- cifra se ia **numarand liniile `PASS`/`FAIL` din log**, iar dovada ca rularea a ajuns la capat e
**linia de `REZULTAT`**; fara ea rularea a fost taiata, indiferent ce zice orice raport.
(Linia de `REZULTAT` contine ea insasi cuvintele `PASS` si `FAIL` — un `Select-String` naiv
raporteaza cu unu mai mult din fiecare.)
Daca sectiunea C pica **pentru ca astepta vechiul comportament** (doar `cant`/`pretvtva`), asta e
asertiune invechita, nu regresie: actualizeaz-o si spune explicit in raport ce ai schimbat si de ce.
Daca pica altceva, **nu modifica testul** — raporteaza.
Detalii de mediu si capcane: `COMUN\docs\depanare_testare_vfp.md`, `COMUN\docs\testare-ui-vfp.md`.
## Encoding si write-back (doar pentru `.vc2`)
- `.vc2` e **CRLF pe toate liniile** si are diacriticele in **cp1250**. Numara octetii >0x7F inainte
si dupa; trebuie acelasi numar, si zero `EF BF BD`. `.prg` se editeaza normal.
- Dupa editarea `.vc2`: `txt2vcx.ps1` **imediat** (se ruleaza direct, fara aprobare):
```
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1 `
-TextFile 'D:\ROA\ROAFACTURARE\COMUN\clase\omodificari.vc2' `
-ProjectRoot 'D:\ROA\ROAFACTURARE' -CacheRoot 'D:\ROA\ROAFACTURARE' -AllowComun
```
- daca fidelity-check-ul pica pe ordine, adopta textul din `<staging>\verify\`;
- **dovada de sincronizare**: reconverteste binarul intr-un cache temporar cu `vcx2txt.ps1 -Source
... -CacheRoot <temp> -Force` si fa `diff` cu textul din arbore -> **0 linii diferenta**.
`mtime` nu dovedeste nimic.
- **Inainte de write-back verifica sa nu ruleze `vfp9.exe`** (`Get-Process vfp9`) — tine binarul
blocat. Daca ruleaza, **nu-l omori**: opreste-te si anunta-ma.
## Ce raportezi
Scrie **pe disc**, la `D:\ROA\ROAFACTURARE\docs\raport_r7_sincronizare.md`, si raspunde in chat doar
cu `GATA <cale>`. In raport: inventarul cu `fisier:linie` pe numerotarea finala; confirmarea
write-back-ului cu numarul de linii diferenta la reconversie; octetii >0x7F inainte/dupa; cifrele de
test luate din log (`PASS`/`FAIL` + prezenta liniei `REZULTAT`), pe fiecare suita; ce asertiuni ai
schimbat si de ce; ce n-ai putut face.
Daca te apropii de ~200-250k tokens de context, opreste-te la o stare consistenta pe disc
(write-back facut), scrie raportul cu ce ramane, si anunta.

View File

@@ -1,101 +0,0 @@
# Briefing — S5, testul cu scriere reala in Oracle
Misiune pentru un agent proaspat. Se citeste **impreuna cu** handoff intermediar (sters) (starea blocului) si
`COMUN\docs\reguli_lucru.md` (regulile de livrare si testare). Nu relua cercetarea — e facuta.
## De ce exista acest test
Cele sase suite headless dovedesc **doar absenta regresiei**. Lucrul care conteaza in S5 nu e atins de
niciuna dintre ele, pentru ca se intampla in Oracle, in interiorul unei tranzactii:
`pack_facturare.actualizeaza_vanzari` (`PACK_FACTURARE.pck:16020-16023`) face
`UPDATE VANZARI_DETALII SET STERS = 0` pe **tot documentul**, fara garda. E apelata din
`finalizeaza_modificare_nota`. Consecinta dubla, care e chiar motivul deciziei 38:
1. o linie marcata stearsa de VFP **inainte** de `finalizeaza_modificare_nota` s-ar pierde tacut;
2. orice linie stearsa la o editare **anterioara** e inviata la fiecare salvare ulterioara — deci
stergerea de linie n-ar deveni niciodata definitiva.
Idiomul adoptat („marcheaza tot sters, invie ce ramane", scris **dupa** `finalizeaza_modificare_nota`)
trebuie sa corecteze ambele. **Asta e ce are de dovedit testul, nu altceva.**
## Ordinea care se testeaza (decizia 38)
O singura tranzactie manuala:
```
do_deschide_tranzactie()
OSCRIE_IN_FISIERE(2, ...) -- sterge nota veche
OSCRIE_IN_FISIERE(0, ...) -- scrie nota noua
finalizeaza_modificare_nota(...) -- realiniaza COD, RESETEAZA STERS = 0
ScrieArticoleFacturaEditate(id_vanzare) -- NOU
pack_facturare.recalculeaza_totaluri_vanzari(id_vanzare, discount) -- NOU
do_inchide_tranzactie(Iif(lnSucces < 0, 2, 1))
```
Agatarea reala e in `ofacturare_comun.vc2:3828` si `comun.vc2:2491`, acelasi bloc de 3 randuri.
## Ce trebuie sa dovedeasca, punct cu punct
**Trecerea 1 — o editare cu o linie stearsa si o linie adaugata:**
- linia marcata stearsa in `tvd` ajunge cu `STERS = 1` in `VANZARI_DETALII` **dupa** ce s-a intors
`finalizeaza_modificare_nota` (deci resetul ei nu o invie);
- linia noua (`id_vanzare_det = 0`) ajunge cu `INSERT`, cu `ID_VANZARE_DET` alocat de trigger
(`TRG_VANZARI_DET_BEFOINS`), si cu `PRET_ACHIZITIE` exact valoarea tastata;
- liniile pastrate raman active (`STERS = 0`) cu cantitatile/preturile editate;
- `PRET_ACHIZITIE` pe liniile **existente** ramane **neatins** (se omite din `SET`-ul de `UPDATE`) —
se verifica valoarea dinainte vs. dupa, pe o linie careia i s-a schimbat cantitatea;
- totalurile din `VANZARI` corespund liniilor active dupa recalcul.
**Trecerea 2 — a doua editare a ACELEIASI facturi, fara nicio modificare:**
- linia stearsa la trecerea 1 **ramane stearsa**. Asta e consecinta 2 din decizia 38 si e singurul
mod de a dovedi ca idiomul repara resetul. **Fara aceasta trecere, testul nu si-a atins scopul.**
## Cum se verifica
**Nu din logul testului.** Dupa fiecare trecere, verificare **independenta prin `sqlplus`** pe
`MARIUSM_AUTO@ROA_CENTRAL`, cu `SELECT`-uri pe `VANZARI` si `VANZARI_DETALII`. Tiparul exista deja si
a fost folosit cu succes: `COMUN\utile\Teste\editare_factura\test_writeback_buton1.prg` plus
`docs\cercetare\rec_test_writeback.md` — **citeste-le intai**, sunt modelul de urmat (au facut COMMIT
real pe `id_vanzare = 1048`, cu 5 verificari confirmate independent).
Cifrele se **numara din loguri**, nu se citesc din impresie. Dovada ca rularea a ajuns la capat e
linia finala a suitei (`REZULTAT` sau `done`, dupa tipar).
## Aprobare si costuri
**Scrierea reala e aprobata de Marius** (09.08.2026), aprobarea e consemnata in handoff intermediar (sters).
**Consuma date de test ireversibil**: `cod`-ul documentului se realoca la fiecare salvare (la testul
precedent, `1140886 -> 1140893 -> 1140894`). Alege un document de test, **nu** unul folosit ca baza de
regresie de alte suite daca poti evita, si **consemneaza in raport `cod`-urile consumate**.
## Reguli care nu se incalca
- **`COMUN\clase\ofacturare.vc2` nu se atinge.**
- `actualizeaza_vanzari` si `PACK_CONTAFIN` **nu se modifica**.
- **Fara commit** — nici git, nici SVN.
- **Un singur scriitor pe fisier.** `omodificari.vc2` e inchis (livrare finala), nu-l atinge.
Fisierele tale sunt in `COMUN\utile\Teste\editare_factura\`.
- **Nu rula `git_sync.ps1`** decat daca text si binar sunt sincrone.
- **Nu porni VFP daca mai exista un `vfp9.exe` viu** care nu e al tau — verifica intai.
- Daca gasesti un defect real in codul de productie, **nu-l repara singur**: raporteaza-l cu
`fisier:linie` + citatul minim si asteapta.
## Livrabile — se scriu PE DISC, la aceste cai exacte
1. `COMUN\utile\Teste\editare_factura\test_s5_scriere_reala.prg` — suita.
2. `D:\ROA\ROAFACTURARE\docs\cercetare\rec_s5_scriere_reala.md` — raportul: ce s-a dovedit, ce nu,
`SELECT`-urile de verificare cu rezultatele lor, `cod`-urile consumate, cifrele din loguri.
3. diff aplicat (sters) — diff-ul.
Raspunsul catre orchestrator e **scurt**: „GATA" + cifrele + ce nu s-a dovedit. **Nu trimite raportul
in text** — el sta pe disc.
## Predarea contextului (REGULA ZERO)
Daca ajungi la **~200-250k** context: **opreste-te**, adu tot la o stare consistenta pe disc, scrie
handoff intermediar (sters) cu **starea** (ce e facut, ce nu, ce e
periculos: tranzactie deschisa, proces viu, date consumate), si anunta. Nu continua „ca mai e putin" —
si **nu lasa niciodata o tranzactie deschisa** in Oracle.

View File

@@ -1,125 +0,0 @@
# Cercetare pe date: `cantitate < 0` / `cantitate = 0` pe articole de vânzări (validarea de la `omodificari.vc2:14386`)
Schema verificată: `MARIUSM_AUTO`@`ROA_CENTRAL` (schema de dezvoltare, folosită și pentru rulări de
teste automate — vezi caveat la final). Toate interogările: `SET TRANSACTION READ ONLY`, încheiate
cu `ROLLBACK` (confirmat: „Rollback complete." la fiecare rulare, zero scrieri).
Sursă date: view-ul `vvanzari_articole` (JOIN `VANZARI_DETALII`+articol, expune deja `denumire`,
`pret_achizitie`), filtrat `sters = 0` (= liniile active), înjumătățit cu `VANZARI` pe `id_vanzare`
pentru `tip`/`cod`/`data_act`. Nu trag concluzii de produs — doar cifre și exemple, cerute punct cu punct.
## 1. Câte linii, pe `VANZARI.TIP`
**`cantitate < 0`** (linii active, sparte pe tip):
| TIP | linii | documente | etichetă (dacă am confirmat-o în cod) |
|---|---|---|---|
| -13 | 1 | 1 | necunoscută — vezi caveat |
| -12 | 9 | 8 | necunoscută — vezi caveat |
| -8 | 1 | 1 | necunoscută — vezi caveat |
| -7 | 1 | 1 | necunoscută — vezi caveat |
| -6 | 25 | 24 | necunoscută — vezi caveat |
| -4 | 1 | 1 | necunoscută — vezi caveat |
| -2 | 1 | 1 | necunoscută — vezi caveat |
| -1 | 3 | 2 | necunoscută — vezi caveat |
| 1 | 3 | 2 | lista de preturi (confirmat de team-lead) |
| 3 | 10 | 4 | necunoscută — nu am verificat-o în cod |
| 8 | 2 | 1 | necunoscută — nu am verificat-o în cod |
| 24 | 1 | 1 | necunoscută — nu am verificat-o în cod |
| 41 | 4 | 4 | necunoscută — nu am verificat-o în cod |
| 51 | 3 | 1 | ROAACNPRO / import (menționat în `omodificari.vc2:13125-13127`, cercetarea `rec_s8_rulaje_tip4.md`) |
Total: **65 linii active cu `cantitate < 0`, pe 52 documente distincte.**
**`cantitate = 0`** (linii active, sparte pe tip):
| TIP | linii | documente | etichetă |
|---|---|---|---|
| -6 | 2 | 2 | necunoscută — vezi caveat |
| 2 | 1 | 1 | contract (confirmat de team-lead) |
| 22 | 5 | 5 | aviz (confirmat în cod, `oproceduri_facturare.prg` etc.) |
Total: **8 linii active cu `cantitate = 0`, pe 8 documente distincte.**
Tabelele de mai sus conțin `TIP = -6`, nu `TIP = 6` — sunt valori distincte, nu le-am confundat.
> **CORECTIE a sesiunii principale, 12.08.2026 — afirmatia „doar `TIP = 4` trece prin validarea de la
> `:14386`" era GRESITA si a fost stearsa de aici si din §6.** Contaminare din misiunea anterioara a
> aceluiasi agent, care era chiar despre tip 4.
>
> Pagina de articole **nu e conditionata de tip**. `omodificari.vc2:14842`:
> `IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Reccount('tact') > 0`, apoi
> `IF Reccount('tvanz') = 1` -> `lAreArticoleVanzari = .T.`. `nTipVanzare` e doar **retinut**
> (`:14841`), niciodata folosit ca poarta. Deci **orice document cu rand in `VANZARI`** primeste `tvd`
> si trece prin `:14386`.
>
> Dovedit si empiric de matricea S8 (`rec_s8_matrice.md`): asertia „tvd incarcat cu liniile active
> reale" a trecut pe `1048` (tip 1), `1055` (tip 2), `1052` (tip 22) **si** `1054` (tip 4).
>
> **Consecinta**: absenta lui `TIP = 4` din tabele **nu e o veste buna** si nu arata ca validarea n-a
> respins nimic real. Cele 52 de documente cu `cantitate < 0` sunt exact populatia pe care validarea o
> blocheaza. Tipurile `-6` si `41` sunt **retur-transfer** (`docs\progres.md`, sectiunea #8/S8), adica
> fix cazul semnalat de Marius.
## 2. Exemple concrete (3 + 3, cele mai recente după `data_act`)
**`cantitate < 0`:**
| id_vanzare | tip | cod | data_act | denumire | cantitate | pret | id_articol | pret_achizitie |
|---|---|---|---|---|---|---|---|---|
| 1035 | 3 | 1140727 | 26-MAR-26 | DISCOUNT | -3 | 4.8 | 4294507213 | 0 |
| 1035 | 3 | 1140727 | 26-MAR-26 | DISCOUNT | -1 | 3.15 | 4294507213 | 0 |
| 1035 | 3 | 1140727 | 26-MAR-26 | DISCOUNT | -3 | 3.3 | 4294507213 | 0 |
**`cantitate = 0`:**
| id_vanzare | tip | cod | data_act | denumire | cantitate | pret | id_articol | pret_achizitie |
|---|---|---|---|---|---|---|---|---|
| 1051 | 22 | 1140907 | 10-AUG-26 | SERVICII PER BUC | 0 | 0 | 4294507490 | 0 |
| 1057 | 2 | 1140913 | 10-AUG-26 | SERVICII PER BUC | 0 | 0 | 4294507490 | 0 |
| 1056 | 22 | 1140912 | 10-AUG-26 | SERVICII PER BUC | 0 | 0 | 4294507490 | 0 |
(Toate cele 3 exemple de `cantitate=0` provin sunt pe `id_articol` completat, `pret = 0`.)
## 3. Recență
| categorie | `MAX(data_act)` | linii în ultimele 12 luni |
|---|---|---|
| `cantitate < 0` | 26-MAR-26 | 10 din 65 |
| `cantitate = 0` | 10-AUG-26 | 6 din 8 |
## 4. `pret = 0` pe linii active
**19 linii, pe 17 documente distincte**, `MAX(data_act) = 10-AUG-26`. Toate cele 19 au `id_articol`
completat (`linii_cu_articol = 19` din `19` — deci nu sunt rânduri-rest fără articol).
## 5. `pret IS NULL` pe linii active
**0 linii.** Confirmă structural ce ați bănuit: `VANZARI_DETALII.PRET` e `NOT NULL` (verificat cu
`DESC vanzari_detalii` — coloana `PRET NUMBER(22,6) NOT NULL`), deci interogarea pe `pret IS NULL`
nu putea da altceva decât 0.
## 6. `cantitate < 0` pe documente cu pagină de articole (validarea `:14386`)
Agentul a raportat aici ca „doar `TIP = 4` trece prin validare" si ca `TIP = 4` lipsind din date ar
insemna ca validarea n-a respins nimic real. **Ambele sunt gresite** — vezi caseta de corectie din §1.
Raspunsul corect, stabilit de sesiunea principala din cod: pagina de articole **nu are filtru pe
tip** (`omodificari.vc2:14842`), deci **toate** cele 52 de documente cu `cantitate < 0` si cele 8 cu
`cantitate = 0` trec prin `:14386` la editare, si toate ar fi respinse.
## Caveat pe date
- Schema e cea de **dezvoltare** (`MARIUSM_AUTO`), folosită și pentru rulările automate de teste
(vezi `docs/cercetare/rec_s8_rulaje_tip4.md` — același `id_vanzare` a fost editat de un test).
Recența (`10-AUG-26` = practic azi) poate reflecta rulări de test, nu neapărat uz curent real.
- **Valorile negative de `TIP`** (-1, -2, -4, -6, -7, -8, -12, -13) nu corespund niciunui tip de
document cunoscut din codul citit până acum — `VANZARI.TIP` e `NUMBER(2) NOT NULL`, dar coloana nu
are o constrângere de semn în schema Oracle, deci acceptă tehnic valori negative. N-am cercetat
originea lor (ar necesita căutare suplimentară în cod, în afara scopului acestei misiuni) — le
raportez ca atare, fără să presupun că sunt date de test sau eroare.
## Fișiere atinse
Niciunul (misiune read-only). Interogări Oracle read-only pe `MARIUSM_AUTO`@`ROA_CENTRAL`, cu
`ROLLBACK` la final pe toate scripturile.

View File

@@ -1,80 +0,0 @@
# Cine sunt "cele 41 de facturi" de la decizia 9, si daca `cod=1138989` e printre ele
Intrebare deschisa 4 din handoff intermediar (sters). **Se raspunde din date, nu are nevoie
de Marius.** Sursa: `MARIUSM_AUTO@ROA_CENTRAL`, 08.08.2026.
## Definitia care da exact 41
| varianta | conditie | rezultat |
|---|---|---|
| V0 | `sters = 0`, `total_cu_tva <> 0`, zero linii **active** in `VANZARI_DETALII` | **0** |
| **V1** | **fara filtru pe `sters`**, `total_cu_tva <> 0`, zero linii **active** | **41** |
| V2 | `sters = 0`, `total_cu_tva <> 0`, zero linii **deloc** (nici sterse) | 0 |
| V3 | fara filtru pe `sters`, zero linii deloc | 0 |
Deci definitia din decizia 9 e V1. Si, la desfacere dupa `STERS`:
```
STERS N
1 41
```
**Toate cele 41 sunt documente STERSE.** Liniile lor din `VANZARI_DETALII` au fost marcate `sters=1`
odata cu documentul; totalurile denormalizate din `VANZARI` au ramas ca instantaneu. Formularea din
decizia 9 ("facturi cu totaluri denormalizate si zero linii active") e corecta, dar incompleta —
partea care conteaza e ca sunt **sterse**, si de aceea "divergenta e prin design" e evidenta, nu o
concesie.
## `cod=1138989` NU e printre ele
`cod = 1138989` (tip 51, ROAACNPRO, `id_vanzare = 788`, `total_cu_tva = 13895.45`) are
`sters = 0` si **1 linie activa** in `VANZARI_DETALII` (din 1 in total). Nu indeplineste conditia V1
sub nicio varianta.
Presupunerea din `rec_suma_act.md` (sectiunea C, "comportamentul e din aceeasi familie") nu se
sustine — familia deciziei 9 e formata din documente **sterse**, iar acesta nu e sters.
## Dar divergenta lui se explica altfel: nota e DUBLATA
Cele 12 randuri `ACT` (toate `an=2019, luna=3`, `nract=290`, `dataact=31-MAR-19`, `id_set=0`,
`id_fact=8002798`) sunt **doua blocuri de cate 6**, iar al doilea e **exact 2x** primul, rand cu rand:
| cont | bloc A | bloc B | B/A |
|---|---|---|---|
| 4111/704 | 4903.10 | 9806.20 | 2 |
| 4111/704 | 3706.23 | 7412.46 | 2 |
| 4111/704 | 3067.51 | 6135.02 | 2 |
| 4111/4428 | 931.58 | 1863.16 | 2 |
| 4111/4428 | 704.21 | 1408.42 | 2 |
| 4111/4428 | 582.82 | 1165.64 | 2 |
| **total** | **13895.45** | **27790.90** | |
Blocul A insumeaza **exact** `VANZARI.TOTAL_CU_TVA = 13895.45` (baza 11676.84 vs 11676.85
denormalizat — 0.01 de rotunjire; TVA 2218.61 vs 2218.60). Suma totala `ACT` = 41686.35 = 3x
totalul, exact raportul semnalat in `progres.md`.
**Deci regula pentru suma din `ACT` se inchide si pe `cod=1138989`**: documentul are o nota
**postata de doua ori**, a doua oara la valoare dubla. Nu e o exceptie de la regula, e o anomalie de
date. Iese din categoria "neexplicat", dar **nu** intra in decizia 9.
Ramane deschis un al doilea lucru, separat: `VANZARI_DETALII` are o singura linie (2468.21 x 1, TVA
19%) care nu reconciliaza cu antetul. Asta chiar ramane neexplicat — si e un argument bun pentru C.6
(comparatia `ACT` vs `VANZARI_DETALII` e informativa, nu verdict automat).
## Descoperire colaterala: `an`/`luna` nu se deriva din `VANZARI.DATA_ACT`
`cod=1138989` are `VANZARI.DATA_ACT = 01-JAN-19`, dar nota e in `an=2019, luna=3`. Nu e izolat:
| situatie | documente |
|---|---|
| nota e in luna din `DATA_ACT` | 542 |
| nota e in **alta** luna | **78** |
| fara randuri `ACT` pe `cod` | 83 |
In datele de test cazurile sunt concentrate pe `tip = 51` (ROAACNPRO, cu `DATA_ACT` sablon
`01-JAN-19`), deci nu e dovedit ca tipar general de productie. Consecinta de implementare ramane
insa reala: `an`/`luna` se iau din **contextul notei deja incarcate** (`tact`/`actactan`, incarcate
de `IncarcaCursoareModificareNota` cu `an`/`luna` explicite), niciodata recalculate din
`VANZARI.DATA_ACT`. Pentru aceste 78 de documente, `do_editare_factura` raspunde azi "Nu exista nota
contabila pentru aceasta factura" si refuza editarea — comportament sigur, dar de consemnat ca
limitare cunoscuta.

View File

@@ -1,176 +0,0 @@
# FACT-008 la vending: pretul de achizitie se trimite cu 3 zecimale, stocul il tine cu 4
Investigatie pe productia VENDING (tunel SSH, strict citiri). Articol reclamat:
`ZAHAR STICK BRUN 4G 200BUC/SET`, `id_articol = 5155`.
## Simptom
La salvarea facturii: `Articolul ZAHAR STICK BRUN 4G 200BUC/SET nu mai e in stoc! (FACT-008)`,
desi articolul are 500 buc pe stoc si e in lista de preturi.
## Cauza
`pack_facturare.descarca_gestiune` cauta randul din `STOC` prin egalitate exacta pe pretul de
achizitie (productie, `PACK_FACTURARE` body linia **7186**, ramura `ELSE`):
```sql
AND A.PRET = V_PRET_ACHIZITIE
```
Daca `BULK COLLECT` nu aduce niciun rand, se ridica FACT-008 (linia 7247).
Datele din productie:
| sursa | valoare |
|---|---|
| `STOC.PRET` (id_articol 5155, gest. 1001, cont 371, luna 8 / 2026) | **8.5586** |
| `RUL` receptia din 06.08.2026 (id_rul 568464) | cant 500, valoare 4279.28 -> 4279.28/500 = 8.55856 -> **8.5586** |
| `OPTIUNI.PPRET` (precizia pretului de achizitie) | **3** |
Clientul VFP tine pretul corect (cursorul e definit `pret_achizitie N(20, max(gnPPret,4))`, deci 4
zecimale), dar **il serializeaza in SQL cu `gnPPret` zecimale**:
- `COMUN\clase\ofacturare.vc2:14073` — `frm_facturare_articole.do_scrie_articole`
- `COMUN\clase\ofacturare.vc2:18108` — `frm_facturare_articole2.do_scrie_articole`
- `COMUN\programe\ofacturare_stoc.prg:360` — `adauga_articol_factura_stoc`
```foxpro
Alltrim(Str(poArt.pret_achizitie,18,gnPPret))
```
`Str(8.5586, 18, 3)` = `8.559`. In `VANZARI_DETALII_TEMP.PRET_ACHIZITIE` (NUMBER(22,6)) ajunge
8.559, iar predicatul `A.PRET = V_PRET_ACHIZITIE` compara 8.5586 cu 8.559 -> 0 randuri -> FACT-008.
Verificat pe productie:
```
pret = 8.559 -> 0 randuri
pret = 8.5586 -> 1 rand
```
Ramura `tip = 3` (articole fara stoc) nu salveaza situatia: `OPTIUNI.RF_FACTURARE_FARA_STOC = 0`.
## Intinderea problemei
Nu e un caz izolat. In `STOC` exista **15 randuri** cu pret pe mai mult de 3 zecimale, toate in
luna 8 / 2026, si **toate vor da FACT-008** la facturare:
```
1070 CAFEA COVIM OROCREMA 1KG 42.3203 360 buc
1367 PULBERE ALBA -ADAOS BAUTURI CALDE 25.3857 120 buc
1433 ZAHAR STICK 5G 200BUC/SET 7.2072 1000 buc
1537 CONTOR VOLUMETRIC NECTA 19.8347 30 buc
1677 GARNITURA BUCSA MIXER NECTA NEW .4958 60 buc
1965 MOTOREDUCTOR GRUP CAFEA NECTA 152.0667 3 buc
2020 LAVAZZA BBE EXPERT GUSTO FORTE 55.8815 1188 buc (cants)
2057 LAVAZZA BBE EXPERT GUSTO PIENO 61.5397 396 buc
2261 FURTUN SILICONIC 6X9 6.6116 50 buc
2934 BUCSA TEFLON MIXER NECTA 4.7107 60 buc
3680 ZAHAR MARGARITAR 1 KG 3.3243 100 buc
4282 GARNITURA NECTA OR2037 WITT 9100 1.6528 60 buc
5133 ZAHAR STICK MXA 5G 100BUC/SET 3.6036 1000 buc
5155 ZAHAR STICK BRUN 4G 200BUC/SET 8.5586 500 buc
```
In `RUL`, preturile cu peste 3 zecimale apar **doar din 09.07.2026** incoace (20 randuri, ultimul
14.08.2026). Inainte de aceasta data nu exista niciunul — deci comportamentul a aparut recent, pe
calea de receptie (pret unitar = valoare / cantitate, rotunjit la scale-ul coloanei `STOC.PRET`,
care e 4).
## Solutii
**Rapid, fara cod (productie):** `OPTIUNI.PPRET` de la 3 la 4. `Str()` incepe sa emita 4 zecimale
si predicatul se potriveste. Aliniaza precizia de transport cu scale-ul real al `STOC.PRET`.
**Durabil, in cod:** in cele 3 puncte de mai sus, serializarea sa foloseasca aceeasi precizie ca
definitia cursorului:
```foxpro
Alltrim(Str(poArt.pret_achizitie,18,Max(gnPPret,4)))
```
Nu se recomanda relaxarea predicatului in `descarca_gestiune` (`round(A.PRET, ...)`): pe gestiuni
cu mai multe loturi ale aceluiasi articol ar putea potrivi randul gresit.
## Risc similar, neatins inca
`pretv_orig` se trimite cu `gnPPretV` (= 2 la vending), iar `STOC.PRETV` are scale 4, cu acelasi
predicat de egalitate exacta (`A.PRETV = V_PRETV_ALES`). La vending toate randurile din stoc au
`pretv = 0`, deci nu se manifesta; pe o gestiune tinuta la pret de vanzare cu pret pe 3-4 zecimale
ar da acelasi FACT-008.
## De unde vin zecimalele: importul eFactura din ROACONT (confirmat)
Ipoteza s-a confirmat pe date. Receptia care a creat stocul articolului 5155 vine din eFactura:
```
ANAF_EFACTURA_DETALII / ANAF_EFACTURA
furnizor NAVIS PROD CONCEPT SRL, NVS 1427, 06.08.2026
id_articol 5155, cantitate 500, PRET = 8.5586, VALOAREFARATVA = 4279.28
```
Sunt **doua** surse de zecimale, ambele ocolind `gnPPret`:
**1. Pretul din XML-ul furnizorului are el insusi 4 zecimale.** Cazul 5155 (8.5586) si 1433
(7.2072, NVS 1406). Se preia ca atare in `ANAF_EFACTURA_DETALII.PRET` si de acolo in NIR.
**2. Importul recalculeaza pretul unitar dupa distribuirea discounturilor, cu numarul de zecimale
scris in cod (4, respectiv 6), nu cu `gnPPret`** — `frm_import_efactura.importmodifica`,
`D:\ROA\ROACONT\COMUN\clase\anaf_efactura.vc2`:
| linie | cod | efect |
|---|---|---|
| 12727 | `Set pret = ROUND(ROUND(pret / lnTotalFaraTVAx, 6) * lnTotalCuTVAx, 6)` | 6 zecimale |
| 12744 | `lnPret = pret - ROUND(lnDiscount / cantitate, 4)` | 4 zecimale — discount pe linie |
| 12786 | `Set Pret = Round(Pret * lnProcent, 4)` | 4 zecimale — discount/transport global |
Verificare pe date, cazuri unde XML-ul avea 2 zecimale si stocul a ajuns cu 4 (deci recalculul de
la 12744 e cel care a produs zecimalele):
| articol | XML: cant / pret / valoare | valoare/cant | `STOC.PRET` |
|---|---|---|---|
| 1677 GARNITURA BUCSA MIXER | 60 / 0.50 / 29.75 | 0.4958333 | **0.4958** |
| 1965 MOTOREDUCTOR GRUP CAFEA | 3 / 152.07 / 456.20 | 152.066666 | **152.0667** |
| 1537 CONTOR VOLUMETRIC | 30 / 19.83 / 595.04 | 19.834666 | **19.8347** |
| 2934 BUCSA TEFLON MIXER | 30 / 4.71 / 141.32 | 4.7106666 | **4.7107** |
Mai departe, `frm_import_efactura.importmodifica` (`anaf_efactura.vc2:12810`) duce `d.Pret` in
cursorul NIR-ului **fara nicio rotunjire**, desi toate valorile vecine din acelasi `SELECT` trec
prin `Round(..., gnPC)`.
### De ce rotunjirea la import nu e solutia buna
Daca la import s-ar rotunji pretul la `gnPPret = 3`, pentru 5155 ar rezulta 8.559 x 500 = 4279.50,
fata de valoarea reala a facturii, 4279.28 — o diferenta de 0.22 lei intre valoarea receptionata si
cea care se descarca ulterior din stoc. Exact de aceea `STOC.PRET` are scale 4 si cursoarele din
facturare sunt declarate `N(20, max(gnPPret,4))`.
Concluzia ramane: reparatia corecta e la **transportul din facturare** (`Str(..., 18, gnPPret)` ->
`Max(gnPPret,4)`), sau, ca masura imediata, `OPTIUNI.PPRET = 4`. Importul eFactura e sursa
zecimalelor, dar zecimalele in sine sunt legitime.
## Al patrulea punct de emisie, in afara COMUN (ROAGEST)
`D:\ROA\ROAGEST\Programe\ofactureaza.prg`, liniile **267, 268, 280**, construieste acelasi apel
`pack_facturare.adauga_articol_factura(...)` cu aceeasi trunchiere (`gnPPret`, `gnPPretVal`,
`gnPPretV`). E cod propriu ROAGEST, nu in `COMUN`, deci reparatia din `COMUN` nu-l acopera. Acelasi
tipar, aceeasi solutie.
In plus, copiile `COMUN` din ROAGEST / ROACONT / ROAACNPRO sunt in urma celei din ROAFACTURARE
(`ofacturare.vc2` are acolo liniile la 13857 / 17889) — primesc reparatia la urmatorul `svn update`
plus recompilare.
## Ce mai atinge PPRET, in afara transportului
Nu e doar precizie de transport. Aceeasi optiune conduce:
- **rotunjirea pretului unitar la NIR-ul introdus manual**: `COMUN\clase\ointroduceri.vc2`, liniile
1459, 1495, 15533, 19159 — `Round(valoare / cantitate, gnPPret)`. Deci calea manuala de receptie
respecta deja `gnPPret`; doar importul eFactura o ocoleste. Cu `PPRET = 4`, si NIR-urile
introduse manual vor stoca preturi cu 4 zecimale, nu 3.
- **mastile de editare/afisare** ale pretului de achizitie: `ofacturare.vc2:3490`,
`ofacturare_comun.vc2:1643`, `ointroduceri.vc2:10071, 16390, 19638` (`get_mask(..., gnPPret)`).
De aici si concluzia despre scriptul global: dupa ce intra versiunile cu `Max(gnPPret,4)`, ridicarea
lui PPRET la 4 nu mai repara nimic, dar schimba rotunjirea receptiilor manuale si afisarea la toti
clientii.

View File

@@ -1,124 +0,0 @@
# Grid articole (pagina 3, frm_modific2024) blank la editare factura
## Defect raportat
La editarea unei facturi (`frm_modific2024`, pagina 3 "Articole factura"), pagina apare dar
grid-ul `grdArticoleFactura` apare gol.
## Ipoteza initiala (infirmata) si ce s-a dovedit real
Ipoteza initiala ("RecordSource-ul grid-ului se goleste la `Use In tvd`") a fost testata prima
data la nivel de date (headless) si infirmata: `RecordSource` a ramas `"tvd"` neschimbat. Grid-ul
insa NU se materializeaza deloc headless (`ColumnCount=0` e o datorie deja cunoscuta a mediului
de test), deci masuratoarea a fost neconcludenta, nu o infirmare reala a mecanismului.
Reprodus apoi cu un **test UI real** (formular vizibil, off-screen, `PrintWindow` - fara input
real, cf. `testare-ui-vfp.md`): `COMUN\utile\Teste\editare_factura\test_ui_grid_articole.prg`,
rulat cu `vfp_ui_harness.ps1`. Document folosit: cod=1139934/an=2021/luna=12 (id_vanzare=882) -
ales pentru ca are SI rulaje (altfel `Show()` ascunde tot `pgfArticole`, cf.
`omodificari.vc2:14254`, indiferent de articole).
Screenshot **inainte de fix**: pagina 3 e vizibila, dar grid-ul nu are NICIO coloana (headere
lipsa complet) - confirmat si prin masuratoare: `ColumnCount grid = 0`, desi `Reccount(tvd) = 2`
si `RecordSource grid = [tvd]`.
**Mecanismul real**: `IncarcaArticoleFactura` (`ofacturare_editare.prg:270-300`, apelat din
`Show()`) facea `Use In tvd` urmat de `SELECT ... INTO CURSOR tvd READWRITE` - recreaza cursorul
sub un grid deja legat pe el. Acesta e exact capcana deja documentata in
`depanare_testare_vfp.md` sectiunea 6: "*un cursor legat la grid recreat de Init lasa grid-ul
fara coloane*" - identica, doar declansata din `Show()` in loc de `Init()`. `RecordSource` (sir
text) ramane `"tvd"`, dar `Columns` colapseaza la 0 - de aici simptomul "pagina apare, grid gol".
## Arhitectura FINALA (dupa o coliziune de editare intre agenti concurenti pe `Show()`,
## descrisa in handoff intermediar (sters) - rezolvata, verificata mai jos)
Varianta livrata difera de iteratia descrisa initial mai sus (`ZAP`+`APPEND FROM` in
`IncarcaArticoleFactura` + `Refresh()` in `Show()`). Arhitectura finala muta incarcarea
articolelor INAINTE de `Createobject`, ca sa nu mai fie nevoie de nicio recreere/refresh dupa ce
grid-ul s-a legat:
1. **`ofacturare_comun.vc2:3793` (`do_editare_factura`)** si testele UI echivalente: apelantul
cheama `IncarcaArticoleFactura(id_vanzare, 'crsArticoleFactura')` INAINTE de `Createobject`,
intr-un cursor separat, neatins de grid.
2. **`omodificari.vc2:14121-14148` (`Load()`)**: `tvd` se recreeaza mereu cu structura canonica
(`CreeazaCursorTvdGol()` daca `ofacturare_editare.prg` e incarcat, altfel `CREATE CURSOR`
inline pentru ROACONT/ROAGEST), apoi, daca apelantul a pregatit `crsArticoleFactura`, il
`APPEND FROM` in `tvd` + `GO TOP` - **inainte** ca grid-ul sa se lege la constructia formularului,
deci `ColumnCount` nu mai colapseaza niciodata dupa legare.
3. **`omodificari.vc2:14249-14254` (`Show()`)**: apelul vechi `IncarcaArticoleFactura(This.nIdVanzare)`
ramane doar ca FALLBACK, pazit de `IF Reccount('tvd') = 0`, pentru apelantii care nu pre-incarca
articolele (ex. teste vechi, alti apelanti neactualizati).
4. **`omodificari.vc2:14269` (`Show()`)**: conditia de colaps a pageframe-ului extinsa de la
`RECCOUNT('trul')=0 AND RECCOUNT('trul_obinv')=0` la `... AND (!USED('tvd') OR RECCOUNT('tvd')=0)`
- fara asta, un document cu articole dar zero rulaje isi ascundea complet pagina 3 (defect 2,
independent de defectul 1).
5. **`ofacturare_editare.prg` (`CreeazaCursorArticoleGol`/`IncarcaArticoleFactura`)**: parametrizate
cu alias destinatie (implicit `'tvd'`), ca sa poata scrie fie direct in `tvd` (ramurile fara
pre-incarcare), fie in `crsArticoleFactura` (apelantii care pre-incarca).
Coliziunea descrisa in handoff (editarea mea peste editarea altui agent pe `Show()`) e rezolvata -
verificat direct in fisier (grep + citire linie cu linie) pe 08.08.2026, ~23:49: ambele fix-uri
(fallback pe `Reccount('tvd')=0` si conditia extinsa cu `tvd` la pageframe) sunt prezente, iar
mtime `.vc2`/`.vcx`/`.vct` sunt sincrone (text cu ~1s mai nou decat binarul, tiparul normal
`txt2vcx.ps1`).
## Testare (verificare 08.08.2026, dupa rezolvarea coliziunii)
- **Regresie `test_page3_articole.prg`** (watchdog, 0 dialoguri, exit code 0): rezultat identic cu
rularea anterioara coliziunii - toate PASS-urile raman PASS (`cod=1140885`, `cod=1125486`,
`cod=1137874`, `cod=1139934`/882, coliziunea pe cod cu 4 randuri VANZARI). Singurele FAIL sunt pe
`cod=1140888` (4 blocuri de test), toate cu aceeasi cauza radacina: `gasit in vanzari = .F.`
(documentul nu mai are rand in `VANZARI`) - degradare de date preexistenta, nelegata de acest fix,
confirmata stabila (acelasi rezultat, byte cu byte, in ambele rulari).
Log: `COMUN\utile\Teste\editare_factura\test_page3_articole_log.txt`.
- **UI cu captura, `cod=1140885` (zero rulaje, defectul 2)**: `test_ui_pageframe_zero_rulaje.prg` +
`vfp_ui_harness.ps1`. Rezultat: `PageCount=3`, `pgfArticole.Visible=.T.`, `lAreArticoleVanzari=.T.`,
`Reccount(trul)=0`, `Reccount(trul_obinv)=0`, `Reccount(tvd)=2`, `ColumnCount grid=14`. Screenshot
confirma vizual pagina "Articole factura" vizibila si selectata, grid populat cu 2 randuri
(MATERIALE, MANOPERA) pe toate coloanele:
`COMUN\utile\Teste\editare_factura\screenshots_after\step_0_cod_1140885_zero_rulaje.png`.
PASS.
- **UI cu captura, `cod=1139934`/an=2021/luna=12 (`id_vanzare=882`, 2 linii, are si rulaje)**:
`test_ui_grid_articole.prg` + `vfp_ui_harness.ps1`. Rezultat: `PageCount=3`,
`pgfArticole.Visible=.T.`, `lAreArticoleVanzari=.T.`, `Reccount(tvd)=2`,
`RecordSource grid=[tvd]`, `ColumnCount grid=14`. Screenshot confirma vizual grid-ul populat cu
2 randuri (ACOPERIRE FAR VOLVO F..., MANOPERA) pe toate coloanele:
`COMUN\utile\Teste\editare_factura\screenshots_after_1139934\step_0_cod_1139934_regresie_grid.png`
(mutat intr-un folder separat dupa ce prima rulare cu `-ShotsDir screenshots_after` a golit
accidental folderul si a sters captura de la `cod=1140885` de mai sus - regenerata separat,
vezi mai jos).
## Incident de verificare: stergere accidentala de screenshot, recuperat
In timpul acestei verificari, rularea testului UI pentru `cod=1139934` a folosit
`-ShotsDir screenshots_after` (acelasi folder in care exista deja captura de la `cod=1140885`
dintr-o rulare anterioara) - `vfp_ui_harness.ps1:142-143` goleste `ShotsDir` la fiecare lansare
(`Clear-Dir`), deci captura veche a fost stearsa. Nu a fost o modificare de sursa, doar o coliziune
de folder de output. Recuperat prin rerularea `test_ui_pageframe_zero_rulaje.prg` (acelasi test,
acelasi document, acelasi rezultat de date - vezi mai sus) cu acelasi `-ShotsDir screenshots_after`,
iar captura pentru `cod=1139934` a fost mutata separat in `screenshots_after_1139934\` inainte de
rerulare, ca sa nu se piarda si ea. Ambele capturi finale sunt valide si confirmate vizual.
## Ce ramane de verificat pe ecran (Marius)
Deschide o factura reala cu articole (si, ideal, cu rulaje - altfel pagina 3 poate fi complet
ascunsa, comportament preexistent nelegat de acest fix) si confirma ca grid-ul "Articole factura"
apare populat la prima deschidere a paginii, fara sa fie nevoie de navigare suplimentara.
## Descoperire separata (comunicata team-lead-ului)
`ofacturare_editare.prg` **este** inregistrat in `roacont.prg:212` (`SET PROCEDURE TO
ofacturare_editare.prg ADDITIVE`), nu doar in `roafacturare.prg`. E absent doar din `roagest.prg`
(doar `ofacturare_comun.PRG` si `ofacturare_stoc.PRG` sunt inregistrate acolo). Garda pe
`Set("Procedure")` din `Load()`/`Show()` tot trebuie sa ramana (ROAGEST are nevoie de ea), dar
impactul in ROACONT pe `comun.vc2:2436` (registrul jurnal) ramane de verificat separat.
## Stare livrare
Fara commit. Diff regenerat din starea curenta (git diff vs HEAD, in `COMUN`):
diff aplicat (sters). Write-back facut in `COMUN\clase\omodificari.vcx`/`.vct` si
`COMUN\clase\ofacturare_comun.vcx`/`.vct` (mtime text/binar sincrone, verificat 08.08.2026 23:49).
`ofacturare_editare.prg` e ASCII pur, editat direct, fara risc de encoding.
Punctul 3 din mesajul team-lead (extinderea gardei la cei 4 apelanti / ROACONT/ROAGEST) - neinceput
in aceasta verificare, in afara scopului ei (doar rulare de teste, fara editare de sursa).

View File

@@ -1,106 +0,0 @@
# Cercetare: garda "id_set distincte in nota" din `do_editare_factura` vs discount editabil (#6)
Sursa: export proaspat `PACK_FACTURARE.pck` din `MARIUSM_AUTO@ROA_CENTRAL` (facut in aceasta
sesiune, 08.08.2026), plus `SELECT text FROM user_views WHERE view_name = 'VACT_TOT'` pe aceeasi
schema. Cod verificat: `COMUN\clase\ofacturare_comun.vc2`, metoda `do_editare_factura`.
## 1. Ce scrie `scrie_discount` si unde ajunge
`PACK_FACTURARE.scrie_discount` (pck:12859-13057): la intrare face
`pack_facturare.nid_set := pack_facturare.nid_set + 5` (:12901), scrie randul `DISCOUNT` in
`ACT_TEMP` cu `ID_SET = pack_facturare.nid_set` (deci +5), cheama `scrie_tva` (tot cu acelasi
`nid_set` +5, deci si randul `TVA DISCOUNT` primeste +5) daca `V_CU_TVA = 1`, apoi **restaureaza**
`pack_facturare.nid_set` la valoarea veche (:13054) inainte de a reveni la apelant. Deci exact
randurile `DISCOUNT`/`TVA DISCOUNT` (si numai ele) ies cu `id_set` +5 fata de restul notei -
confirmat pe cod, nu doar pe progres.md anterior.
`ACT_TEMP` se copiaza in `ACT` la commit-ul tranzactiei (mecanism comun, neschimbat). `vact_tot`
(`user_views`, verificat direct) e un JOIN simplu peste `ACT`, cu `A.ID_SET` expus **neschimbat, ca
atare** - nu exista nicio normalizare/filtrare de `id_set` in view. Deci orice factura ale carei
randuri `ACT` au 2 `id_set` diferite le arata identic si `vact_tot`, si `actactan` (incarcat din
`vact_tot` in `IncarcaCursoareModificareNota`, `COMUN\programe\ofacturare_editare.prg:53`, cu
`ORDER BY id_act`).
**Descoperire suplimentara, relevanta pentru corectitudinea codului (nu doar garda)**: randurile
`DISCOUNT`/`TVA DISCOUNT` scriu `ID_FACT = -1` (variabila locala `V_ID_FACT` initializata `-1` si
niciodata reasignata in nicio ramura a `CASE`-ului din `scrie_discount`) si nu populeaza deloc
`ID_FACTD` (coloana absenta din lista INSERT) - deci raman `NULL`. Un `Go Top` orb pe `actactan`
poate ateriza pe un rand de discount si prelua `id_fact = -1` / `id_factd = NULL` in loc de valorile
reale ale documentului, daca acel rand s-ar intampla sa fie primul in ordinea `id_act`. Cu codul
actual randurile de discount se scriu mereu DUPA liniile de marfa (vezi pct. 2), deci `id_act` le
pune ultimele si `Go Top` "merge" din intamplare - dar nu e o garantie de contract, e o coincidenta
de ordine de scriere.
## 2. Cand se cheama `scrie_discount`
Confirmat 3 cai de apel la nivel de **document** (parametrul `V_DISCOUNT_FACTURA`, discountul de pe
`VANZARI.DISCOUNT`), toate cu tiparul identic "`IF V_DISCOUNT_FACTURA <> 0 THEN scrie_discount(...)`"
dupa bucla de linii:
- `scrie_factura2` (pck:6009-6212, discount la :6992-7006) - calea facturii emise **direct** (nu din
aviz), ex. `frm_facturare_articole`;
- `scrie_factura_avize` (pck:6648-7076, discount la :6985-7007) - facturare din aviz;
- (acelasi tipar exista si la nivel de **linie**, gardat de `pack_facturare.ndiscount_evidentiat = 1
AND discount_unitar <> 0`, in `scrie_factura_avize` :6919-6935, `contabilizeaza_articol` :7496,
`contabilizeaza_rata` :7594 - discount pe linie individuala, nu pe document, dar acelasi mecanism
`id_set+5`).
Deci **orice factura emisa cu codul curent, cu discount de document nenul (`VANZARI.DISCOUNT <>
0`), sau cu discount evidentiat pe cel putin o linie, iese cu 2 `id_set` distincte in `ACT`/
`vact_tot`**. Nu e un caz marginal - e calea normala pentru orice factura cu discount.
Pe `MARIUSM_AUTO` (date de test): 3 facturi cu `VANZARI.DISCOUNT <> 0`, 0 cu >1 `id_set` distinct in
`ACT` pe acelasi `cod` - verificat din nou in aceasta sesiune, acelasi rezultat ca la runda 2. Astea
sunt cele 3 randuri vechi 2008/2014 mentionate deja in `progres.md`, scrise cu o versiune de pachet
de dinainte de introducerea offset-ului `+5` (mecanism care, judecand dupa comentariile din cod,
tine de o reforma ulterioara a contabilizarii discountului). Nu exista nicio factura in datele de
test emisa cu discount **prin codul curent** - de-asta "0 cazuri in date" nu contrazice cazul
posibil prin cod, doar arata ca setul de date nu-l acopera.
## 3. Verdict asupra garzii: **trebuia inlocuita, si a fost**
Garda originala (`lnIdSetDistinct > 1` => refuza editarea) ar fi respins editarea a exact clasa de
facturi pe care decizia 17 (`docs\progres.md`, discountul de document editabil in #6) trebuie sa o
acopere. Confirmat pe cod la pct. 1-2, nu doar pe date.
**Schimbare aplicata** in `do_editare_factura` (`COMUN\clase\ofacturare_comun.vc2:3781-3805`):
in loc sa numere doar `id_set` distincte si sa refuze la >1, acum:
- daca exista un singur `id_set` - neschimbat, editare admisa (comportamentul de dinainte de runda
2);
- daca exista exact 2 `id_set` distincte **si diferenta e exact 5** (relatia exacta scrisa de
`scrie_discount`) - editare admisa, cu `id_set` **principal = cel mic** (liniile de marfa/servicii,
nu randurile de discount) transmis catre `frm_modific2024` si folosit pentru `Locate For id_set =
lnIdSet` (in loc de `Go Top` orb) la citirea `id_fact`/`id_factd` - evita exact capcana de la
pct. 1 (a lua `id_fact=-1` de pe un rand de discount);
- orice alta combinatie (>2 `id_set` distincte, sau exact 2 dar fara relatia +5) - refuza editarea,
cu acelasi mesaj ca inainte; ramane un semnal real ca nota nu e "o factura curata" editabila pe
aceasta cale.
`frm_modific2024` (`omodificari.vc2`) sustine asta structural: `Init(tnIdSet, ...)` seteaza
`This.nid_set` dintr-un SINGUR parametru (folosit pentru predefinire/validari, `:5204-5251`), dar
coloana `id_set` din grid e needitabila (`Column25.ReadOnly = .T.`) si update-ul de completare
`UPDATE tact SET id_set = This.nid_set WHERE EMPTY(NVL(id_set,0))` (`:13365`) **nu suprascrie**
randurile care au deja `id_set` populat - deci un cursor cu randuri mixte (principal + principal+5)
trece nemodificat prin formular, atata timp cat `id_set`-ul "de referinta" transmis la `Init` e cel
principal.
## Fisiere atinse
- `COMUN\clase\ofacturare_comun.vc2` (+ write-back `.vcx`/`.vct`, `txt2vcx.ps1 -AllowComun`,
fidelity check OK) - garda inlocuita, `Go Top` -> `Locate For id_set = lnIdSet`.
- `COMUN\utile\Teste\editare_factura\test_incarca_cursoare.prg` - `verifica_garda_idset_distinct`
inlocuita cu `verifica_garda_idset`, care reproduce exact mecanica noii garzi pe 4 cursoare
sintetice (1 id_set / 2 id_set +5 / 2 id_set fara relatia +5 / 3 id_set) - toate 4 + cele 2
cazuri de regresie (`cod=1140888`, `cod=1140885`) au dat rezultatul asteptat la rulare headless
(`vfp9.exe -A -T`).
- Patch: diff aplicat (sters) (netrimis la commit, asteapta review).
- Baseline: `COMUN\clase\ofacturare_comun.vc2.pre_runda3.bak`.
## Netestat
- Fluxul real UI (`frm_modific2024` deschis pe o factura cu discount de document) - nu exista
candidat in datele de test (0 facturi cu discount emise prin codul curent, cf. pct. 2). Verificat
doar prin harness fara UI, pe cursoare sintetice care reproduc exact forma datelor descrise de
cod.
- Write-back-ul real (`OSCRIE_IN_FISIERE` + `finalizeaza_modificare_nota`) pe un asemenea caz -
neatins, la fel ca in rundele anterioare (evitat deliberat, nu scrie in schema de dev intr-un test
automat).

View File

@@ -1,155 +0,0 @@
# Cercetare: Go Top orb pe tact - blocantul #1 inainte de commit (S4 PAGE3)
Verificare pe date (`MARIUSM_AUTO@ROA_CENTRAL`, 08.08.2026) a riscului semnalat in
`rec_review_ancorare_s4.md` pct. 1: `omodificari.vc2:14171`, `Show()`, face `Go Top In tact` apoi
citeste `tact.nract`/`tact.serie_act`/`tact.dataact` pentru `IncarcaVanzareNota`. Read-only, nicio
modificare de cod.
## Verdict: BUG CONFIRMAT
Pe cele **62 de note din schema de dev** unde primul rand (`vact_tot`, `sters=0`, ordonat dupa
`id_act`) e o incasare si nota chiar are un rand de vanzare legat in `VANZARI`: filtrul compus din
`IncarcaVanzareNota` (`cod + nract + serie_act + dataact`), rulat cu valorile de pe **primul rand**,
intoarce **0 randuri pe 52 din 62 (84%)** — `lAreArticoleVanzari` ar iesi `.F.` silentios desi
vanzarea exista. Pe celelalte 10/62 filtrul nimereste din intamplare (randul de incasare are, pe
acele note, aceleasi `nract`/`serie_act` ca factura).
## 1. Reconstructia multimii de risc
Prim rand din `vact_tot` (per `an+luna+cod`, `sters=0`, ordonat dupa `id_act`) cu
`Upper(explicatia) LIKE '%INCASARE%'`, legat de un rand real din `VANZARI` (exista `v.id_fact` care
apare si pe un rand din nota, `v.sters=0`). Rezultat: **62 de note** (2009-2024), fata de cele 39
raportate initial in `rec_pozitionare_actactan.md` — diferenta e metodologica (acolo criteriul de
legatura era mai ingust, "id_fact minus 1"; aici orice `id_fact` comun intre nota si `VANZARI`),
nu contrazice concluzia, o extinde.
Pe primul rand, `serie_act` e aproape mereu `NULL` (randul de incasare nu are serie proprie),
in timp ce randul facturii are o serie reala (`FFFFF`, `SSS`, `JOI1226` etc.) — vezi exemplele de
mai jos. `nract`/`dataact` coincid uneori, `serie_act` aproape niciodata.
## 2. Testul decisiv: filtrul simulat direct pe `VANZARI`
```sql
SELECT COUNT(*) FROM vanzari v
WHERE v.cod = :cod
AND NVL(v.numar_act,-1) = NVL(:nract_prim_rand,-1)
AND NVL(v.serie_act,'~') = NVL(:serie_act_prim_rand,'~')
AND TRUNC(v.data_act) = TRUNC(:dataact_prim_rand)
AND v.sters = 0
```
Rulat cu valorile primului rand (INCASARE) pentru toate cele 62 de note: **52 → 0 randuri**
(bug), **10 → 1 rand corect** (coincidenta valori identice pe ambele randuri).
## 3. Cazuri reproductibile
| an | luna | cod | prim rand (nract/serie/data) | rand factura (nract/serie/data) | id_vanzare real | filtru cu Go Top |
|---|---|---|---|---|---|---|
| 2009 | 8 | 1137874 | 5 / NULL / 27.08.2009 | 5 / FFFFF / 27.08.2009 | 506 | 0 randuri |
| 2021 | 12 | 1139934 | 13 / NULL / 31.12.2021 | 13 / (serie reala) / 31.12.2021 | 882 | 0 randuri |
`cod=1139934` e deja cunoscut in proiect ca fiind cazul de coliziune pe `VANZARI.COD` folosit pentru
validarea filtrului compus (`docs\progres.md`) — util unei sesiuni viitoare ca sa scrie testul fara
sa mai caute alt caz.
## 4. Pozitionarea corecta recomandata
Constrangere: in `Show()` nu exista `crsfacturi`, deci ancorarea pe `crsfacturi.id_fact`
(`rec_pozitionare_actactan.md` §5) nu se poate folosi aici. Criteriul trebuie evaluat strict din
`tact` (deja incarcat, ordonat dupa `id_act`).
**Recomandare**: primul rand din `tact` (in ordinea existenta, `id_act`) a carui `explicatia` NU
contine `INCASARE` (`!("INCASARE" $ Upper(Nvl(tact.explicatia,''))`); daca niciun rand nu
indeplineste conditia (nota e o incasare pura, fara linie de factura), fallback la comportamentul
actual (`Go Top`).
```
Locate For !("INCASARE" $ Upper(Nvl(explicatia,''))) In tact
IF !Found()
Go Top In tact
ENDIF
IncarcaVanzareNota(tact.cod, tact.nract, tact.serie_act, tact.dataact)
```
**Validat 62/62 (100%)** pe toata multimea de risc — filtrul cu randul gasit astfel intoarce
exact randul real (`id_vanzare`) pe fiecare din cele 62 de note, inclusiv pe cele 10 unde Go Top
"nimerea" deja din intamplare.
**Fallback-ul e sigur**: exista 42 de note in schema unde TOATE randurile au `explicatia` de tip
incasare (chitante fara linie de factura) — pe acestea nu exista o "linie de factura" de gasit,
`Go Top` ramane comportamentul corect (cauta cu randul de incasare, nu gaseste vanzare, ceea ce e
adevarat: nu exista).
## 5. Varianta respinsa: ancorare pe `FDOC`
Parea un candidat mai curat decat un filtru text pe `explicatia` (`FDOC='FACTURA'` — cautabil,
fara enumerare de variante). **Infirmata pe date**: `FDOC` e un camp **la nivel de nota**, identic
pe toate randurile ei (tipul documentului contabil: `FACTURA`, `ABONAMENT`, `BON FISCAL` etc.), nu
un marcaj per rand al liniei de factura. Pe 40 din cele 62 de note din multimea de risc nota e de
tip `ABONAMENT`/`BON FISCAL`, deci **niciun rand nu are `FDOC='FACTURA'`** desi nota chiar are o
linie de factura validata (randul cu `explicatia='NOTA 1'` sau gol). Whitelist pe `explicatia`
('NOTA 1' etc.) ar fi si mai fragil — pe cele 62 de note, randul corect apare cu `explicatia` din
cel putin 5 variante diferite (`NOTA 1`, gol/`NULL`, `RATA 1`, `PRODUCTIE`, `TVA NOTA 1`), imposibil
de enumerat exhaustiv. Blacklist pe `INCASARE` (pct. 4) a fost singurul criteriu validat 100%.
## 6. Completare ceruta: cele 10/62 "nimerite din intamplare" gasesc documentul corect?
Da, pe toate 10. Verificat explicit prin join intre `id_vanzare` gasit de filtru (cu valorile
primului rand INCASARE) si `id_vanzare`-ul real al facturii: **10/10 ACELASI document**, 0 cazuri
de document gresit. Filtrul compus nu a intors niciodata mai mult de 1 rand pe toata multimea de
risc (`MAX(randuri gasite) = 1`, 0 cazuri cu 2+) — ipoteza de unicitate `(cod, numar_act, serie_act,
data_act)`, verificata deja pe toata tabela `VANZARI` (`docs\progres.md`), se confirma si pe aceasta
submultime. Concluzie: bugul e strict "pagina lipseste" (fals negativ), nu "pagina arata datele
altui document".
## 7. Alternativa fara euristica de text: toate tripletele distincte din `tact`
In loc sa aleaga un rand anume din `tact` (fie prin `Go Top`, fie prin filtrare pe `explicatia`),
varianta testata ia **toate combinatiile distincte** `(nract, serie_act, dataact)` din randurile
notei (`sters=0`) si cauta in `VANZARI` randul care se potriveste cu **oricare** dintre ele. Testata
pe **toate cele 419 de note din schema legate de `VANZARI`** (nu doar cele 62 de risc):
| rezultat | note | % |
|---|---|---|
| gaseste exact 1 rand, si e cel corect | 400 | 95.5% |
| gaseste 1 rand, dar gresit | 0 | 0% |
| gaseste 0 randuri | 19 | 4.5% |
| gaseste 2+ randuri (ambiguu) | 0 | 0% |
Numar de triplete distincte per nota: minim 1, maxim **2**, medie 1.17 — cost neglijabil pentru o
bucla/`OR` in VFP sau Oracle (cel mult 2 incercari).
Cele 19 de "0 randuri" **nu sunt cazuri de pozitionare gresita in `tact`** — pe toate 19,
`VANZARI.DATA_ACT` difera efectiv de orice `dataact` prezent in nota (majoritatea din 2019:
`VANZARI.DATA_ACT` are placeholder-ul `01.01.2019` in loc de data reala de sfarsit de luna din
`ACT`; restul au date decalate, `NULL`, sau vanzarea e stornata `tip=-13`). Nicio alegere de rand
din `tact` poate rezolva asta — valoarea corecta pur si simplu nu exista in `ACT`. Nu e o regresie:
`Go Top` de azi rateaza exact aceleasi 19.
Pe multimea de risc (cele 62), varianta cu tripletele iese **62/62 corecta**, identic cu criteriul
text de la punctul 4. Diferenta e robustetea: nu se bazeaza pe continutul textual al `explicatia`
(orice limba/formulare/rand adaugat manual de utilizator), doar pe "care tripleta chiar exista in
`VANZARI`" — nu poate fi pacalita de o eticheta scrisa altfel.
## Recomandare finala (actualizata)
Renunta la criteriul bazat pe `explicatia` (pct. 4, respins in favoarea acestuia) si foloseste
varianta cu toate tripletele distincte `(nract, serie_act, dataact)` din `tact` (`sters=0`),
incercate pana la prima care gaseste un rand in `VANZARI`:
```
SELECT DISTINCT nract, serie_act, dataact FROM tact WHERE !sters INTO CURSOR tmp_triplete
SCAN
IncarcaVanzareNota(tact.cod, tmp_triplete.nract, tmp_triplete.serie_act, tmp_triplete.dataact)
IF Reccount('tvanz') > 0
EXIT
ENDIF
ENDSCAN
```
(sau echivalent, o singura interogare Oracle cu tripletele unite prin `OR` — decizie de
implementare, nu schimba rezultatul masurat). Nu necesita `crsfacturi`, nu necesita schimbari de
schema, nu depinde de text liber. Validat 62/62 pe multimea de risc si 400/400 (100%) pe restul
notelor legate de `VANZARI` unde exista o potrivire posibila in date; cele 19 cazuri ramase sunt o
limitare preexistenta a datelor (`VANZARI.DATA_ACT` incorect populat), nu a criteriului de
pozitionare, si afecteaza identic si comportamentul de azi.

View File

@@ -1,94 +0,0 @@
# De ce la test crapa (View Parameter) si in productie merge — INAINTE_DE_STOC
Investigatie read-only, 08.08.2026. Intrebare de la Marius: acelasi tipar `DO ... WITH gnAn, gnLuna`
exista in productie la `COMUN\programe\oproceduri_stocuri.prg:35,130,207` — de ce nu s-a manifestat
niciodata acolo?
## Raspuns
**Varianta 1, confirmata prin citirea intregului cod al procedurii apelate: inofensiv prin
constructie.** Pe acest lant nu exista niciun `?gnAn`/`?gnLuna` (bind marker SQL) — deci pasarea prin
referinta, desi masca numele `gnAn`/`gnLuna` in interiorul procedurii apelate, nu are ce sa strice.
## Dovada, pas cu pas
1. **Apelul**: `oproceduri_stocuri.prg:35,130,207` —
`DO INAINTE_DE_STOC WITH gnAn, gnLuna, tnTipGest, lnStocObinv in oinainte_de.prg`.
2. **Procedura apelata**: `COMUN\programe\oinainte_de.prg:356-411`, `PROCEDURE INAINTE_DE_STOC`,
semnatura `Parameters tnAn, tnLuna, tnTipGest, tnStocObinv, tcListaIdGestiuni`. Deci `gnAn` ajunge
accesibil in interior **doar** sub numele local `tnAn` (si `gnLuna` sub `tnLuna`) — exact tiparul
care facea `TYPE('gnAn')='U'` in testul de ieri.
3. **Singurul SQL din procedura** e la `oinainte_de.prg:389-390`:
```
lcSql = "select pack_inainte_de.inainte_de_stoc(" + Alltrim(Str(tnAn)) + "," + Alltrim(Str(tnLuna)) + "," + ;
Alltrim(Str(tnTipGest)) + "," + Alltrim(Str(tnStocObinv)) + "," + Iif(Isnull(gnIdSucursala), "null", Alltrim(Str(gnIdSucursala))) + "," + ;
Iif(Empty(Nvl(m.lnIdGestiune, '')), "null", Alltrim(Str(lnIdGestiune))) + ") as valoare from dual"
```
Toti termenii intra prin **concatenare** (`Alltrim(Str(...))`), inclusiv `tnAn`/`tnLuna` — cele
masate. **Zero caractere `?` in acest string.** `gnIdSucursala` apare si el, dar tot prin
concatenare, nu ca bind marker — deci nici acolo nu conteaza ca e vizibil sau nu global.
4. **Procedura nu face niciun alt `DO ... WITH` mai departe** — bucla `For lnGestiune = 1 To
lnGestiuni` apeleaza direct `goExecutor.oExecute(lcSql, lcCursor)` (executie Oracle), nu alta
procedura VFP. Lantul se opreste aici; nu exista o veriga suplimentara unde `tnAn`/`tnLuna` sa
piarda contextul.
Concluzie: mecanismul care bloca testul (VFP incearca sa lege `?gnAn` la o variabila devenita `'U'`
si deschide dialogul nativ "View Parameter") **nu are niciun `?gnAn`/`?gnLuna` de legat** pe acest
lant — nu pentru ca variabila ar fi vizibila, ci pentru ca nimeni nu o cere prin acel mecanism.
Utilizatorul nu vede dialogul pentru ca bug-ul latent (pasarea prin referinta) exista, dar
consecinta lui (bind marker nelegat) nu e declansata de acest cod.
## Verdict
**Inofensiv, cazul specific din intrebare.** Nu e bug latent — e cod scris corect din intamplare
(sau prin obisnuinta autorului de a concatena SQL-ul in loc sa foloseasca bind markers), care
absoarbe fara sa observe efectul secundar al `DO ... WITH`.
## Cautare extinsa: aceeasi familie in restul `COMUN\programe` si `COMUN\clase`
Cautat tiparul complet (ambele conditii): `DO ... WITH <variabila globala>` (fara paranteze, deci
prin referinta) **si** acelasi nume aparand ca `?<variabila>` undeva accesibil pe lantul de apel.
Inventarul complet de apeluri `DO ... WITH <global g*>` neconditionate (comentariile `*DO ...` nu
conteaza, cod mort):
| Apel | Fisier:linie | Verdict |
|---|---|---|
| `DO INAINTE_DE_STOC WITH gnAn, gnLuna, tnTipGest, lnStocObinv` | `oproceduri_stocuri.prg:35,130,207` | **Inofensiv** — vezi mai sus |
| `DO fisa_magazie_fifo WITH gnTipGest,pnIdGestiune,pcNumeGestiune,PcCodul` | `rulaje.vc2:8243` | **Inofensiv** — vezi mai jos |
| `*DO schimba_firma WITH gnHandle,GCS,lcschemaParola` | `ostartfirma.prg:168` | cod mort (comentat) |
| `*DO schimba_firma WITH gnHandle,lcSchema,...` | `ferestre_comune.vc2:859`, `ferestre_oracle.vc2:858` | cod mort (comentat) |
**`fisa_magazie_fifo`** (`orapoarte.prg:266-...`, `Lparameters tnTipGest, tnIdGestiune,
tcNumeGestiune, tnIdArticol, tcSerie`): `gnTipGest` ajunge accesibil doar sub `tnTipGest`. Cautare
`\?gnTipGest\b` in tot `COMUN` gaseste 8 hituri, **niciunul in `orapoarte.prg`** — toate in fisiere
fara legatura cu acest lant de apel (`gest_selectii.db2`, `oproceduri_facturare.prg`,
`configurare.vc2`, `oschimbare_pret.vc2`, apeluri din alte fluxuri, nu din `fisa_magazie_fifo`).
Functia foloseste `tnTipGest` doar prin comparatie directa (`If tnTipGest = 6`) si transmis mai
departe ca parametru catre `caut_gestiune(tnTipGest, gnIdUtil)` — niciodata ca bind marker SQL.
Inofensiv, acelasi motiv ca la INAINTE_DE_STOC.
**Nu exista alt caz** in `COMUN\programe` / `COMUN\clase` unde ambele conditii (DO...WITH pe un
global + `?acelasi_nume` pe lantul de apel) sa se intalneasca simultan.
### Gasit in cautare, dar NU pe acest tipar (semnalat totusi)
`oinainte_de.prg:258`, procedura `test_casa`:
```
lcSql = [select PACK_INAINTE_DE.TEST_CASA(?gnAn,?gnLuna,?pcContTestCasa,?gnIdSucursala) AS VALOARE FROM DUAL]
```
Foloseste `?gnAn`/`?gnLuna`/`?gnIdSucursala` ca bind markers reali. **Dar** apelul catre `test_casa`
(`ocasabanca.prg:66`: `Do test_casa With lcCont In oinainte_de.prg`) paseaza **doar** `lcCont` —
`gnAn`/`gnLuna` nu sunt deloc argumente, deci nu sunt mascate pe acest lant. Nu se incalca conditia
ceruta (ambele trebuie sa se intample **impreuna**), deci nu e cazul cautat — dar e o structura
fragila: daca in viitor cineva ar adauga `gnAn`/`gnLuna` ca parametri suplimentari la `test_casa`
fara paranteze, ar reproduce exact blocajul de ieri, de data asta in productie (dialogul "View
Parameter" vazut de utilizator). Nu e remediat — doar semnalat, in afara scopului intrebarii.
## Remediu propus (NEAPLICAT)
Niciunul necesar pentru cazurile gasite — sunt inofensive prin constructie. Daca se doreste totusi
o plasa de siguranta impotriva viitoarelor regresii de acest tip: paranteze la orice `DO ... WITH`
care paseaza globale catre o procedura ale carei SQL-uri sunt necunoscute/in schimbare —
`DO INAINTE_DE_STOC WITH (gnAn), (gnLuna), tnTipGest, lnStocObinv` — cost zero, elimina clasa de bug
la sursa. **Nu s-a aplicat nicio modificare de cod** — cod de productie neatins, conform interdictiei.

View File

@@ -1,79 +0,0 @@
# Cercetare: de unde se citesc `id_set` / `id_fact` / `id_factd` in `do_editare_factura`
Context: decizia 24 din `docs\progres.md` cere **scoaterea completa** a garzii pe `id_set` adaugata in
runda 3, cu avertismentul ca pozitionarea din care se citesc `id_fact`/`id_factd` nu are voie sa cada
pe un rand de discount. Nota de fata verifica premisa pe date si stabileste pozitionarea corecta.
Sursa: `MARIUSM_AUTO@ROA_CENTRAL`, 08.08.2026, interogari directe pe `ACT` si `VANZARI`.
## 1. Premisa "randurile de discount au `ID_FACT = -1`" **nu se confirma in `ACT`**
Distributia `ACT.ID_FACT` pe toata tabela:
| valoare | randuri | ani |
|---|---|---|
| pozitiv | 66360 | 0-2026 |
| negativ | 32 | 2008-2019 |
| NULL | 0 | — |
| zero | 0 | — |
Zero randuri cu `id_fact <= 0` din 2020 incoace. `ACT.ID_FACTD` nu e niciodata NULL (65849 de zerouri,
541 pozitive, 2 negative in 2006).
Pe cele 27 de note cu randuri `DISCOUNT` / `TVA DISCOUNT`: randurile de discount au **acelasi
`id_fact` si acelasi `id_set`** ca restul notei, si `id_factd = 0`. Coerent cu decizia 24 —
`cumuleaza_note_act_temp` normalizeaza inainte de `ACT`; `-1` din `scrie_discount` nu ajunge acolo.
**Concluzie**: randul de discount nu e periculos. Constatarea din `rec_garda_idset.md` pct. 1 era
corecta pe sursa PL/SQL, dar valorile nu supravietuiesc pana in `ACT`.
## 2. Randul periculos e **INCASAREA**, si problema e reala
Note legate de `VANZARI` cu mai multe `id_fact` distincte: **78**. Tiparul, verificat pe exemple:
primul rand dupa `id_act` este `INCASARE` / `INCASARE NUMERAR`, cu `id_fact` = `id_fact`-ul facturii
**minus 1** (chitanta isi are propriul `id_fact`, alocat inaintea facturii).
```
COD TIP AN LUNA VANZARI.ID_FACT primul rand ACT explicatia
1137874 44 2009 8 5040267 5040266 INCASARE
1138549 1 2014 1 8001118 8001117 INCASARE NUMERAR
```
**39 de facturi** in schema de dev pe care un `Go Top` orb pe `actactan` (ordonat dupa `id_act`)
preia `id_fact`-ul **chitantei**, nu al facturii. E comportamentul codului din runda 1/2, nu ceva
introdus de runda 3.
Pe 38 din cele 39, `VANZARI.ID_FACT` **exista** ca `id_fact` pe cel putin un rand al notei, deci o
pozitionare `Locate For id_fact = <id_fact-ul din crsfacturi>` il gaseste. Al 39-lea e un rand vechi
fara corespondent.
## 3. Cat conteaza fiecare valoare, la destinatie
`PACK_CONTAFIN.finalizeaza_modificare_nota` (`COMUN\docs\PACK_CONTAFIN.pck:8601-8651`):
- `tnIdSet` — conduce `CASE`-ul; pentru facturi (25xxx) cade pe `ELSE`, deci nu face nimic in plus
fata de `actualizeaza_vanzari`;
- `tnIdFact` — folosit **doar** pe ramura `tnIdSet IN (31003, 31004, 31005, 31011)`:
`UPDATE NOM_LUCRARI SET ID_FACT = tnIdFact ... WHERE ID_FACT IS NULL`. Ramura e **vie** si pentru
documente din `VANZARI`: 35 de note cu `id_set = 31011`, 5 cu `31003`, 1 cu `31005`. Semantic
trebuie sa fie `id_fact`-ul **facturii**, adica exact `VANZARI.ID_FACT`;
- `tnIdFactD` — apare doar in cod comentat (ramurile 90011/90013). Azi e **complet nefolosit**.
## 4. `id_set` unic pe nota — confirmat
Note legate de `VANZARI`, dupa numarul de `id_set` distincte: **558 cu unul singur**, 1 cu doua — si
aceea e randul-gunoi `cod = 0, an = 0, luna = 0` (`id_set` 46 si 10208), nu o factura. Decizia 24 se
confirma pe date; garda din runda 3 poate iesi fara inlocuitor.
## 5. Pozitionarea corecta
`lnIdFact` e deja citit corect din `crsfacturi` (`VANZARI.ID_FACT`) la inceputul metodei si folosit
pentru garda eFactura. Nu trebuie rescris din `actactan` — poate doar sa se strice. Deci:
- se cauta in `actactan` randul cu `id_fact = lnIdFact`; daca se gaseste, de acolo se iau `id_set` si
`id_factd`;
- daca `lnIdFact` nu e utilizabil (0/NULL — 283 de randuri `VANZARI` au `ID_FACT` NULL, in principal
avize si transferuri) sau nu are corespondent in nota, se cade pe `Go Top` si se ia `id_fact` de
acolo, ca inainte.
Fara garda, fara mesaj de refuz.

View File

@@ -1,451 +0,0 @@
# Runda 5 - editare inline pe pagina Articole (proiectare, fara aplicare)
Document de proiectare pentru cerinta: pe formularul unificat `frm_modific2024`, pagina 3
"Articole factura" (`pgfArticole.PAGE3.grdArticoleFactura`, cursor `tvd`), sa devina editabile
inline `serie`, `lot`, `explicatie` si `proc_tvav`, iar coloanele cu nomenclator (`denumire`,
`nume_gestiune`, `nume_val`, `taxcode`, `id_jtva_coloana`) sa deschida dialogul de cautare si pe
`InteractiveChange`, nu doar din `But_modificaR.Click`.
Linii citate din `COMUN\clase\omodificari.vc2` (17059 linii) si
`COMUN\programe\ofacturare_editare.prg` (1140 linii), stare de pe disc la data cercetarii -
fisierul e in lucru, **reconfirma numerele de linie cu `vfp_symbols.ps1` inainte de orice
`txt2vcx.ps1`**.
Fiecare bloc de cod de mai jos e marcat **VERIFICAT** (citit direct din sursa) sau **PRESUPUS**
(judecata de proiectare, nu extrasa din cod existent).
---
## 0. Constatare care schimba premisa punctului A din brief
**VERIFICAT.** Intrebarea "`cCantitateArt`/`cPretArt` marcheaza `lmodificat` si conteaza asta la
salvare" are raspuns clar: **`lmodificat` nu e citit nicaieri ca filtru de salvare.**
`ScrieArticoleFacturaEditate` (`ofacturare_editare.prg:473-571`) are doar doua `SCAN`-uri pe
`tvd`:
- `ofacturare_editare.prg:502` - `SCAN FOR id_vanzare_det > 0 AND Nvl(sters,0) <> 1` (UPDATE
liniilor existente)
- `ofacturare_editare.prg:534` - `SCAN FOR id_vanzare_det = 0 AND Nvl(sters,0) <> 1` (INSERT
liniilor noi)
Niciunul nu conditioneaza pe `lmodificat`. Comentariul de la `ofacturare_editare.prg:498` o spune
explicit: *"invie si actualizeaza liniile pastrate - toate cele active din alias, nu doar
lmodificat, pasul de mai sus le-a marcat pe toate"*. Singurele scrieri ale campului
(`AplicaAdaugareTvd:857`, `ComutaSters:1034`, `DuplicaLinie:1053`,
`ModificaNomenclator:1108`, `calculeaza_valori_articol` in `omodificari.vc2:13521`) nu au nicio
citire-pereche - `lmodificat` e scris dar niciodata citit. E camp vestigial (poate util pentru o
extensie viitoare, gen indicator vizual "linie modificata"), **nu un defect de blocat livrarea**.
Consecinta pentru A: coloanele noi (`serie`, `lot`, `explicatie`, `proc_tvav`) **nu au nevoie sa
seteze `lmodificat` ca sa se salveze** - se salveaza oricum, ca toate liniile active. Recomand
totusi sa-l seteze, din consecventa cu restul coloanelor editabile si ca sa nu ramana singurele
exceptii daca cineva incepe sa-l citeasca in viitor - cost zero, un `REPLACE` in plus.
---
## 1. Defect real gasit pe drum: UPDATE-ul nu scrie `serie`/`lot`/`explicatie`
**VERIFICAT - trebuie reparat in aceeasi livrare**, exact cum a semnalat brief-ul.
UPDATE-ul liniilor existente (`ofacturare_editare.prg:505-517`):
```foxpro
lcSql = [update vanzari_detalii set sters = 0, cantitate = ] + Alltrim(Str(Nvl(cantitate,0),18,3)) + ;
[, pret = ] + Alltrim(Str(Nvl(pret,0),18,4)) + [, pret_cu_tva = ] + Alltrim(Str(Nvl(pret_cu_tva,0))) + ;
[, pret_achizitie = ] + Alltrim(Str(Nvl(pret_achizitie,0),18,4)) + ;
[, proc_tvav = ] + Alltrim(Str(Nvl(proc_tvav,0),18,4)) + ;
[, discount_unitar = ] + Alltrim(Str(Nvl(discount_unitar,0),18,4)) + ;
[, id_articol = ] + Alltrim(Str(Nvl(id_articol,0))) + ;
[, id_gestiune = ] + Iif(Isnull(id_gestiune),[NULL],Alltrim(Str(id_gestiune))) + ;
[, id_valuta = ] + Iif(Isnull(id_valuta),[NULL],Alltrim(Str(id_valuta))) + ;
[, id_jtva_coloana = ] + Iif(Isnull(id_jtva_coloana),[NULL],Alltrim(Str(id_jtva_coloana))) + ;
[, taxcode = ] + Iif(Isnull(taxcode),[NULL],Alltrim(Str(taxcode))) + ;
[, cont = ] + Iif(Empty(Nvl(cont,'')),[NULL],['] + OracleSpecialCharacters(Alltrim(Nvl(cont,''))) + [']) + ;
[, id_utils = ] + Alltrim(Str(gnIdUtil)) + [, dataoras = sysdate ] + ;
[where id_vanzare_det = ] + Alltrim(Str(id_vanzare_det)) + [ and id_vanzare = ] + Alltrim(Str(m.tnIdVanzare))
```
`proc_tvav` **e deja in lista** (linia 508) - editarea cotei TVA persista deja corect pe linii
existente, nimic de reparat acolo. Lipsesc doar `serie`, `lot`, `explicatie` - prezente in
INSERT-ul liniilor noi (`ofacturare_editare.prg:535-544`, aceeasi forma
`Iif(Empty(Nvl(...,'')),[NULL],['] + OracleSpecialCharacters(...) + [']`) dar absente din UPDATE.
Fara reparatie, editarea inline propusa la punctul A de mai jos s-ar pierde tacut la salvare pe
orice linie deja salvata (`id_vanzare_det > 0`) - exact scenariul cel mai comun (factura cu
articole sincronizate din rulaj).
### Propunere - `ofacturare_editare.prg`, dupa linia 515 (`, cont = ...`), inainte de linia 516
(`, id_utils = ...`)
```foxpro
[, cont = ] + Iif(Empty(Nvl(cont,'')),[NULL],['] + OracleSpecialCharacters(Alltrim(Nvl(cont,''))) + [']) + ;
[, serie = ] + Iif(Empty(Nvl(serie,'')),[NULL],['] + OracleSpecialCharacters(Alltrim(Nvl(serie,''))) + [']) + ;
[, lot = ] + Iif(Empty(Nvl(lot,'')),[NULL],['] + OracleSpecialCharacters(Alltrim(Nvl(lot,''))) + [']) + ;
[, explicatie = ] + Iif(Empty(Nvl(explicatie,'')),[NULL],['] + OracleSpecialCharacters(Alltrim(Nvl(explicatie,''))) + [']) + ;
[, id_utils = ] + Alltrim(Str(gnIdUtil)) + [, dataoras = sysdate ] + ;
```
Trei linii noi, tipar identic cu cel deja folosit pentru `cont` pe acelasi UPDATE si pentru
`serie`/`explicatie`/`lot` pe INSERT (`:538-540`). Comentariul de la `:503-504` ("toate campurile
pe care grila si nomenclatoarele le pot schimba") ramane valabil fara modificare.
---
## 2. Coloane care devin editabile inline (`serie`, `lot`, `explicatie`)
**VERIFICAT** - definitiile actuale, `omodificari.vc2`:
| coloana | linie definitie | `ControlSource` | `ReadOnly` azi |
|---|---|---|---|
| `cSerieArt` (Column3) | `:12359-12365` | `tvd.serie` | `.T.` |
| `cLotArt` (Column4) | `:12366-12372` | `tvd.lot` | `.T.` |
| `cExplicatieArt` (Column13) | `:12441-12447` | `tvd.explicatie` | `.T.` |
Niciuna nu are azi `Text1.When`/`Text1.Valid` propriu (singurele evenimente pe pagina 3 sunt cele
listate la `omodificari.vc2:16678-16770`, verificat prin citire directa a intervalului).
### Garda de editare - condifia de baza vs. cea extinsa a lui `cPretAchizitieArt`
**VERIFICAT.** Doua garde diferite exista azi pe pagina 3:
```foxpro
* cCantitateArt.Text1.When si cPretArt.Text1.When - garda de baza
IF Thisform.lArticoleReadOnly OR Nvl(tvd.id_vanzare_set,0) <> 0
RETURN .F.
ENDIF
* cPretAchizitieArt.Text1.When - garda extinsa, cu o conditie in plus
IF Thisform.lArticoleReadOnly OR Nvl(tvd.id_vanzare_set,0) <> 0 OR Nvl(tvd.id_vanzare_det,0) <> 0
RETURN .F.
ENDIF
```
**PRESUPUS** (nu exista comentariu care sa explice; judecata de proiectare pe baza codului
inconjurator): conditia suplimentara `Nvl(tvd.id_vanzare_det,0) <> 0` de pe pret de achizitie
blocheaza editarea manuala pe liniile **deja legate de o vanzare persistata** (`id_vanzare_det`
nenul inseamna linie care exista deja in `VANZARI_DETALII`, deci a venit dintr-o miscare de stoc
reala). Sustinut de garda de la salvare (`omodificari.vc2:14457-14460`): un avertisment de "pret
de achizitie 0" apare doar pentru linii **noi** (`id_vanzare_det = 0`) - semn ca liniile vechi au
deja o valoare de incredere, mostenita din stoc, si nu trebuie rescrisa manual dupa fapt (ar
strica marja/COGS calculat la vremea vanzarii). E o garda de **integritate financiara pe o
valoare derivata**, nu una generica de editare.
`serie`/`lot`/`explicatie` sunt campuri **descriptive/text**, nu valori derivate din stoc -
corectarea unei greseli de tastare pe o linie deja salvata (serie gresita, lot gresit, o
explicatie de corectat) e exact scenariul uzual de folosit al acestei livrari, inclusiv pe linii
vechi. Recomand garda de baza (fara conditia `id_vanzare_det`) pentru toate trei. Daca exista un
motiv de trasabilitate (serie/lot leaga factura de un lot fizic expediat, iar schimbarea lui dupa
livrare ar fi o problema de conformitate) - e o decizie de business, nu una pe care o pot confirma
din cod; semnalez-o explicit ca punct de validat cu tine inainte de aplicare.
### Propunere - `omodificari.vc2`, dupa `cPretArt.Text1.Valid` (`:16759`, inainte de
`cPretArt.Text1.When`)
```foxpro
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cSerieArt.Text1.When
IF Thisform.lArticoleReadOnly OR Nvl(tvd.id_vanzare_set,0) <> 0
RETURN .F.
ENDIF
Thisform.oldvalue = This.Value
ENDPROC
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cSerieArt.Text1.Valid
IF NVL(Thisform.oldvalue,'') <> NVL(This.Value,'')
REPLACE lmodificat WITH .T. IN tvd
Thisform.oldvalue = This.Value
ENDIF
ENDPROC
```
Identic (nume schimbat) pentru `cLotArt` (`tvd.lot`) si `cExplicatieArt` (`tvd.explicatie`).
Coloana 3 din `Column3.ReadOnly = .T.`, 4 din `Column4.ReadOnly = .T.`, 13 din
`Column13.ReadOnly = .T.` trec pe `.F.` (`omodificari.vc2:12365`, `:12372`, `:12447`).
---
## 3. Cazul delicat: `proc_tvav` (Column9, `cProcTvavArt`)
### 3.1 Forma stocata - VERIFICAT, nu presupus
`tvd.proc_tvav` e in forma multiplicativa (1.21, nu 21), confirmat in trei locuri independente:
- `CreeazaPoArticolNouTvd`... `calculeaza_valori_articol` (`omodificari.vc2:13511-13521`):
`lnProcTvav = NVL(proc_tvav, 1)` si `lnValoare = ... * lnPretNet * lnProcTvav` - daca ar fi
forma "21", valoarea liniei ar fi de 21x mai mare, evident gresit.
ei
- `ArticoleNotaEditor.ModificaNomenclator`, ramura `id_jtva_coloana`
(`ofacturare_editare.prg:1099-1101`): `proc_tvav WITH (Nvl(loCauta.cota_tva, 0) + 100) / 100` -
`cota_tva` vine din nomenclator ca "21", transformarea explicita in "1.21" e in cod.
- `CautaExplicatieTva` (`ofacturare_editare.prg:1117-1120`):
`lnCotaFiltru = Round((Nvl(tvd.proc_tvav, 0) - 1) * 100, 2)` - transformarea inversa, "1.21" ->
"21".
### 3.2 Precedent direct in acelasi grid family: editare libera in forma "1.xx"
**VERIFICAT.** Coloana `cProc_tva` de pe grila de rulaje (`pgfArticole.PAGE1.grdRulaje`,
`omodificari.vc2:8956-8963`, si dubletul ei pe `PAGE2.grdRulajeObinv`) e **deja editabila
direct**, in forma bruta:
```foxpro
Column24.ControlSource = "proc_tva", ;
Column24.Format = "R", ;
Column24.InputMask = "9.99", ;
Column24.Name = "cProc_tva", ;
Column24.ReadOnly = .F., ;
```
cu evenimentul (`omodificari.vc2:16337-16343`):
```foxpro
PROCEDURE pgfArticole.PAGE1.grdRulaje.cProc_tva.Text1.Valid
IF NVL(thisform.oldvalue, 0) <> NVL(this.Value, 0)
Thisform.calculeaza_valori_rul(this.ControlSource)
thisform.oldvalue = this.Value
ENDIF
ENDPROC
```
Utilizatorul tasteaza deja "1.21", nu "21", pe grila de rulaje din **acelasi formular**.
`cProcTvavArt` de pe grila de articole are deja azi `Column9.InputMask = "9.99"`
(`omodificari.vc2:12417`, mostenit inca de cand coloana era doar de afisare) - acelasi format,
gata pregatit.
### 3.3 Recomandare: pastreaza forma "1.21", nu converti la "21"
**Argument**: forma "1.21" e cea stocata in `proc_tvav`, cea folosita direct de
`calculeaza_valori_articol`, si cea deja tastata de utilizatori pe grila de rulaje din acelasi
formular - e conventia existenta, nu una noua. Varianta "utilizatorul tasteaza 21" ar cere:
- o proprietate ajutatoare separata pe care sa se afiseze/editeze (`Text1` legat printr-o
expresie de transformare, `ControlSource` nu mai poate fi direct `tvd.proc_tvav`), pentru ca
grid column text boxes nu pot face transformarea la afisare fara sa piarda editarea directa pe
`ControlSource`;
- conversia dus-intors (`/100+1` la citire, `(x-1)*100` la scriere) e exact riscul semnalat -
o greseala de semn sau de ordine ar strica toate liniile la urmatoarea recalculare, silentios
(nu exista validare care sa prinda "1900" in loc de "19" introdus gresit);
- ar fi **singura** coloana din tot formularul cu conventia asta, cand exact aceeasi valoare, pe
aceeasi pagina, la un tab distanta (rulaje), se editeaza deja in forma "1.21".
Nu recomand conversia. Ramane `InputMask = "9.99"`, neschimbat.
### 3.4 Riscul real: corelarea cu `id_jtva_coloana`/`taxcode` se poate dezincroniza
**VERIFICAT ca risc, nu ca deja tratat.** Spre deosebire de `cProc_tva` de pe rulaje (unde
`trul` nu are corelare SAF-T de taxcode legata de cota), pe `tvd` cota TVA e legata explicit de
`id_jtva_coloana` si de `taxcode`:
- `ModificaNomenclator`, ramura `id_jtva_coloana` (`ofacturare_editare.prg:1099-1104`): alegerea
unei explicatii TVA scrie **impreuna** `id_jtva_coloana` si `proc_tvav`, apoi cheama
`UpdateExplicatieSAFTArt()` care recoreleaza `taxcode` din `id_jtva_coloana`
(`omodificari.vc2:15049-15073`, verificat: `lnIdJtva = tvd.id_jtva_coloana`,
`GetTaxCodeIdPart(...)`, `Replace taxcode With m.lnTaxCode In tvd`).
- Daca utilizatorul editeaza `proc_tvav` direct (tasteaza "1.19" peste "1.21"), fara sa treaca si
prin nomenclatorul de explicatie TVA, **`id_jtva_coloana` si `taxcode` raman la cota veche** -
`cExplicatieTvaArt` (coloana 16) ar continua sa arate explicatia de 21% langa o cota de 19%, iar
`taxcode`-ul folosit la raportarea SAF-T ar fi cel corelat cu explicatia gresita. E o
inconsistenta silentioasa, nu doar cosmetica - `taxcode` alimenteaza raportarea SAF-T 406.
Pe grila de rulaje, editarea directa a lui `proc_tva`/`proc_tvav` **nu recoreleaza nimic** legat
de TVA (`trul` nu poarta `taxcode`/`id_jtva_coloana` in acelasi fel) - acolo riscul asta nu
exista, deci precedentul de la 3.2 nu acopera si problema asta.
### 3.5 Doua variante, cu recomandare
**Varianta A (recomandata) - blocheaza corelarea veche in loc s-o lase gresita**
```foxpro
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cProcTvavArt.Text1.When
IF Thisform.lArticoleReadOnly OR Nvl(tvd.id_vanzare_set,0) <> 0
RETURN .F.
ENDIF
Thisform.oldvalue = This.Value
ENDPROC
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cProcTvavArt.Text1.Valid
IF NVL(Thisform.oldvalue, 0) <> NVL(This.Value, 0)
SELECT tvd
REPLACE id_jtva_coloana WITH NULL, taxcode WITH NULL
Thisform.calculeaza_valori_articol()
Thisform.oldvalue = This.Value
This.Parent.Parent.Refresh()
ENDIF
ENDPROC
```
Dupa editare manuala a cotei, `cExplicatieTvaArt` si `cTaxcodeArt` raman **goale** pe randul
respectiv - semnal vizibil, imediat, ca utilizatorul trebuie sa aleaga din nou explicatia TVA
(punctul 4 de mai jos ii da exact calea: click sau tastare pe coloana Explicatie TVA deschide
dialogul, filtrat deja pe noua cota introdusa). `taxcode WITH NULL` inseamna insa ca linia nu mai
are cod SAF-T pana la re-alegere - de verificat cu tine daca exista vreo validare la salvare care
sa avertizeze pe `taxcode` nul (nu am gasit una explicita pentru `tvd`, doar cea de pret de
achizitie 0 de la `:14457`); daca nu exista, merita adaugata odata cu asta, ca sa nu scape o
factura fara cod SAF-T la salvare.
**Varianta B (respinsa) - lasa corelarea veche neatinsa, ca pe rulaje**
Doar `Thisform.calculeaza_valori_articol()` in `Valid`, fara sa atinga
`id_jtva_coloana`/`taxcode`. Simetrica cu precedentul de la 3.2, dar pe `tvd` inseamna taxcode
SAF-T incorect ramas silentios dupa o editare manuala de cota - risc pe raportare fiscala, nu doar
UX. Nu o recomand.
**Nu am incercat o a treia varianta ("re-coreleaza automat")** - ar insemna sa caut in
`crsJtvaTemp` o explicatie care sa aiba exact noua cota si sa o aplic fara dialog; las-o
deoparte pentru ca poate exista mai mult de o explicatie pe aceeasi cota (JC vs JV, exigibil vs
neexigibil - vezi parametrii `tlTipEx` din `caut_explicatie_tva`,
`ocautare.prg:3174-3181`), iar alegerea automata a uneia ar fi arbitrara exact acolo unde conteaza
sa fie corecta.
---
## 4. Nomenclatoarele pe `InteractiveChange`
**VERIFICAT** - sablonul de referinta indicat de tine (`Grid1.Column15/16/17` pe grila de note,
`omodificari.vc2:5890-5940`) e construit pe **`do_modifica`** generic (parametri `pcx_item`,
`pccontrol`, `pnid_item`, cautare in cursorul `xitems`) - un mecanism diferit fata de
`ArticoleNotaEditor.ModificaNomenclator`, care primeste **doar `pccontrol`** si decide singur ce
cautare deschide printr-un `DO CASE` pe numele campului
(`ofacturare_editare.prg:1073-1090`). **Sablonul se preia doar partial**: pattern-ul
`GotFocus` seteaza `pccontrol`+`pncolumnorder`, `InteractiveChange` cheama dialogul - dar apelul e
catre `ArticoleNotaEditor.ModificaNomenclator(pccontrol)`, fara `pcx_item`/`pnid_item` (nu exista
in semnatura ei).
Cele 5 coloane cu nomenclator (`AreNomenclator`, `ofacturare_editare.prg:1004-1007`:
`'denumire', 'nume_gestiune', 'nume_val', 'taxcode', 'id_jtva_coloana'`) au deja `Text1.GotFocus`
care seteaza `pccontrol`+`pncolumnorder` (`omodificari.vc2:16700-16704` `cDenumireArt`,
`:16723-16727` `cExplicatieTvaArt` cu `pccontrol` explicit `'tvd.id_jtva_coloana'` pentru ca
`ControlSource` e o expresie, `:16729-16733` `cGestiuneArt`, `:16750-16754` `cTaxcodeArt`,
`:16755-16759` `cValutaArt`) - functioneaza deja azi desi coloanele sunt `ReadOnly = .T.` (un
textbox readonly primeste `GotFocus` la navigare cu sagetile, doar nu accepta tastare). Asta e
mecanismul care tine `But_modificaR` sincronizat cu coloana curenta (punctul 5).
### Propunere - adauga `Text1.InteractiveChange` pe fiecare, `ReadOnly` -> `.F.`
```foxpro
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cDenumireArt.Text1.InteractiveChange
LOCAL loEditor
loEditor = Createobject('ArticoleNotaEditor', Thisform)
loEditor.ModificaNomenclator(Thisform.pccontrol)
ENDPROC
```
Identic (nume schimbat) pentru `cGestiuneArt`, `cValutaArt`, `cTaxcodeArt`, `cExplicatieTvaArt` -
`Thisform.pccontrol` e deja setat corect de `GotFocus`-ul existent al fiecareia. `Column1.ReadOnly`
(`:12354`), `Column11.ReadOnly` (`:12428`), `Column12.ReadOnly` (`:12434`), `Column14.ReadOnly`
(`:12453`), `Column16.ReadOnly` (`:12474`) trec pe `.F.`.
### Risc: caracterul tastat inainte de deschiderea dialogului
**VERIFICAT ca exista deja acelasi risc, neremediat, in sablonul citat.** In `do_modifica`
(`omodificari.vc2:13960` si dublurile ei), `REPLACE`-ul cu valoarea aleasa se face **doar** in
ramura `IF gnButon = 1` (`omodificari.vc2:14019-14025` pentru `tact`) - la anulare (`gnButon <> 1`
sau formular inchis fara alegere), campul **nu e restaurat**. Cum coloana e `ReadOnly = .F.`,
`InteractiveChange` se declanseaza pe **prima tasta** apasata (Value s-a schimbat deja cu acel
caracter inainte ca evenimentul sa ruleze) - daca utilizatorul apasa o litera si apoi renunta la
dialog, acel caracter ramane in camp.
Pe `ArticoleNotaEditor.ModificaNomenclator` e identic: `REPLACE`-ul final
(`ofacturare_editare.prg:1093-1107`) e dupa `IF gnButon <> 1: RETURN .F. ENDIF`
(`:1082-1084`) - la anulare, campul nu se atinge, deci caracterul parazit ramane in `tvd.denumire`
(sau alt camp editat). Fara refresh explicit pe ramura de anulare (`RefreshGrid()` e apelat doar
dupa `REPLACE`, in afara ramurii de `RETURN` timpuriu), gridul ramane cu textul modificat vizual
pana la urmatoarea reimprospatare.
**Nota:** `lmodificat` nu se atinge pe ramura de anulare, dar cum am aratat la punctul 0, asta nu
opreste totusi salvarea - daca linia era oricum activa (`sters<>1`), caracterul parazit ar fi
scris in Oracle la urmatoarea salvare a documentului, indiferent de `lmodificat`.
**Propunere de atenuare (in plus fata de sablon, nu cerinta din brief):** in
`ArticoleNotaEditor.ModificaNomenclator`, muta `This.RefreshGrid()` inainte de
`RETURN .F.` din garda de anulare, ca sa readuca vizual valoarea corecta din cursor peste orice
caracter tastat:
```foxpro
IF gnButon <> 1
This.RefreshGrid()
RETURN .F.
ENDIF
```
`RefreshGrid()` doar re-deseneaza gridul din cursor (`This.oForm.pgfArticole.PAGE3.grdArticoleFactura.Refresh()`,
`ofacturare_editare.prg:1136-1138`) - nu scrie nimic, deci sigur de adaugat si pe ramura de
anulare. Nu rezolva 100% (daca utilizatorul apasa doua taste rapid inainte ca dialogul sa apuce sa
se deschida, a doua tasta tot ar intra), dar acopera cazul uzual (o tasta, apoi Esc pe dialog).
`But_modificaR` ramane functional neschimbat: click-ul lui cheama tot
`ArticoleNotaEditor.ModificaNomenclator(thisform.pccontrol)` (`omodificari.vc2:15319-15322`,
verificat), acelasi `pccontrol` pe care acum si `InteractiveChange`-ul il foloseste - **nu se
dubleaza deschiderea dialogului**, sunt doua declansatoare catre aceeasi metoda, niciodata
concurente (butonul se apasa explicit, `InteractiveChange` doar la tastare in celula).
---
## 5. `BeforeRowColChange`/`AfterRowColChange` - raman neschimbate
**VERIFICAT.**
```foxpro
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.AfterRowColChange
Lparameters nColIndex
DODEFAULT(m.nColIndex)
Thisform.but_modificaR.Enabled = (Thisform.pncolumnorder = m.nColIndex) AND !Thisform.lArticoleReadOnly
ENDPROC
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.BeforeRowColChange
LPARAMETERS nColIndex
IF Thisform.pncolumnorder # m.nColIndex
STORE [] TO Thisform.pccontrol
ENDIF
ENDPROC
```
Nu au nevoie de nicio schimbare:
- Coloanele cu nomenclator continua sa seteze `pncolumnorder` in `GotFocus` (neschimbat la
punctul 4) - `But_modificaR` ramane activat/dezactivat exact ca azi.
- Coloanele noi editabile fara nomenclator (`cSerieArt`, `cLotArt`, `cExplicatieArt`,
`cProcTvavArt`) **nu primesc `GotFocus` care sa seteze `pccontrol`/`pncolumnorder`** - la fel ca
`cCantitateArt`/`cPretArt`/`cPretAchizitieArt` azi (verificat: niciuna dintre astea trei nu are
`Text1.GotFocus` in intervalul `:16678-16770`). Cand utilizatorul navigheaza pe una din ele,
`pncolumnorder` ramane la valoarea din ultima coloana cu nomenclator vizitata, deci
`Thisform.pncolumnorder = m.nColIndex` e fals si `But_modificaR` ramane dezactivat - corect,
pentru ca aceste patru coloane n-au nomenclator si nu trebuie sa activeze butonul.
Nu propun sa le adaug `GotFocus` - ar activa gresit `But_modificaR` pe coloane fara cautare.
---
## 6. Ordinea coloanelor - doar de retinut, nu de aplicat acum
`cExplicatieTvaArt` (Column16) e ultima coloana din grid, dupa `cValoareArt` (Column15) -
`ColumnOrder`-ul azi urmeaza indicii (1-16). Mutarea ei langa `cTaxcodeArt` (Column14) ar cere
renumerotarea `ColumnOrder` pe toate cele 16 coloane (nu doar schimbarea pozitiei uneia), cost
deja notat in `docs\propunere_runda4_note_sincronizare_tva.md:242-244`. Ramane a doua iteratie,
separata de livrarea asta.
---
## Rezumat - ce e usor, ce e delicat
**Usor, cu incredere mare:**
- `serie`/`lot`/`explicatie` editabile inline (punctul 2) - tipar identic cu `cPretArt`, fara
recalcul necesar.
- UPDATE-ul din `ScrieArticoleFacturaEditate` (punctul 1) - trei linii, tipar deja folosit alaturi
(`cont` pe acelasi UPDATE, `serie`/`lot`/`explicatie` pe INSERT-ul de doua ori mai jos).
- Nomenclatoarele pe `InteractiveChange` (punctul 4) - `GotFocus` deja exista pe toate cele 5
coloane, doar `ReadOnly` si `InteractiveChange` lipsesc.
- `BeforeRowColChange`/`AfterRowColChange` (punctul 5) - zero schimbari, verificat ca raman
corecte.
**Delicat, cere o decizie a ta inainte de aplicare:**
- `proc_tvav` (punctul 3) - forma "1.21" e clar cea corecta de pastrat (precedent direct pe
aceeasi pagina, la rulaje), dar corelarea cu `id_jtva_coloana`/`taxcode` la editare manuala e un
risc real de raportare SAF-T netratat de niciun precedent existent. Recomand Varianta A
(goleste corelarea, forteaza re-alegere) - confirma daca vrei asta sau preferi sa nu atingi
`id_jtva_coloana`/`taxcode` deloc (Varianta B, mai simpla dar cu riscul asumat).
- Garda de editare pe `serie`/`lot`/`explicatie` - am recomandat garda de baza (fara restrictia
`id_vanzare_det<>0` de la pret de achizitie), dar e o decizie de business (trasabilitate lot pe
linii deja livrate), nu una pe care am putut-o confirma din cod.
- Riscul caracterului parazit la deschiderea nomenclatorului pe `InteractiveChange` (punctul 4) -
exista deja, neremediat, in sablonul `do_modifica` de pe grila de note; am propus o atenuare de
o linie (`RefreshGrid()` pe ramura de anulare) care nu era in cerinta ta, spune daca o vrei in
livrare sau ramane pentru alta runda.

View File

@@ -1,427 +0,0 @@
# Diagnostic — linii fara articol pe facturi emise din CONTRACT (rate)
**Scop.** Doua erori pe facturi emise din contract (explicatie "CONTRACT"), pe formularul unificat
`frm_modific2024` (`COMUN\clase\omodificari.vc2`) + `COMUN\programe\ofacturare_editare.prg`:
- **EROAREA 1** (la deschidere): `Field ID_ARTICOL does not accept null values`,
`CONSTRUIESTEPROPUNERESINCRONIZARE`, linia 652 (INSERT in `agg_tvd`).
- **EROAREA 2** (la salvare): `Linia '' nu are articol asociat.`
Cazul de test: factura 10/08/2026, SSS 549, ROMFAST S.R.L., explicatie "CONTRACT", contract
"1/21.07.2020" — grid cu linia "RATA 2" (fara codmat, fara pret de achizitie) si linia "A2" (cu
codmat 8003510000203). Doar diagnostic — nu s-a atins niciun fisier de cod.
## Concluzia scurta
**Nu e un defect de generare.** Liniile de tip "rata" dintr-un contract sunt, prin proiectare, linii
`VANZARI_DETALII` fara articol de nomenclator — `id_articol` chiar e `NULL` in Oracle pe randul
respectiv, iar denumirea vizibila ("RATA 2") vine din `explicatie`, nu din `nom_articole.denumire`.
Asta era deja documentat in `docs\plan_13_unificare_formular_facturare.md:2275` inainte sa apara
eroarea curenta — planul #13 stia despre ele, dar guarda de la salvare (`omodificari.vc2:14450`) si
cursoarele de agregare pentru sincronizare RUL<->TVD (`ofacturare_editare.prg:617,640`) au fost scrise
pornind de la premisa contrara, ca `id_articol` "vine mereu completat din sursa"
(`docs\progres.md:166`). Premisa aia era gresita exact pentru liniile de rata, si cele doua erori sunt
consecinta directa.
---
## 1. De unde vine linia fara articol — proiectare, nu defect
Generarea facturii din contract (butonul de facturare al `ROACONTRACTE`, `goContract`, tipurile de
document `2/6/52` — `ofacturare_comun.prg:261-297`) construieste liniile prin
`creeaza_facturacrs` (`ofacturare_comun.prg:1793`, cursorul `crsfacturacrs`, camp
`id_articol N(20) null` — **explicit nullable**) si le populeaza prin `prelucreaza_facturacrs`
(`ofacturare_comun.prg:1836-1840`):
```
Insert Into (tcCursorDestinatie) (id_articol, ...) ;
SELECT CAST(IIF(TYPE(tcCursorSursa+".id_articol")='U',0,id_articol) as N(20)) as id_articol, ...
```
`TYPE(...)='U'` verifica daca **exista coloana** `id_articol` in cursorul sursa (0 doar cand lipseste
complet), nu daca valoarea e NULL — deci daca sursa are coloana `id_articol` si pe randul de rata ea
e NULL, NULL trece mai departe neschimbat, nu devine 0.
Structura de rate a contractului insasi confirma asta — cursorul istoric pentru rate
(`ofacturare_comun.prg:1905-1908`, comentat, dar pastrat ca document al formei) nu are deloc coloana
`id_articol`:
```
*!* Create Cursor crsfactura(id_rata N(20), id_temp N(20), den_rata c(100), ..., denumire c(100), ...)
```
**Deja documentat inainte de eroarea curenta.** Cercetarea S4 din planul #13 a stabilit exact acelasi
lucru cand a analizat `cursor_contract`:
> `docs\plan_13_unificare_formular_facturare.md:2273-2275`:
> „`cursor_contract` produce deja doua cursoare — `V_CURSOR` (`crsarticole`, prin delegare la
> `cursor_preturi`) si `V_CURSOR2` (`crsarticole1`, articole **sau** rate). Filtrarea se aplica curat
> doar pe jumatatea `crsarticole`; **randurile de rata n-au `id_articol`** si raman needitate."
Concluzie punct 1: **legitim, prin proiectare**. Randul de rata reprezinta o transa de plata dintr-un
contract (esalonare), nu un articol din nomenclator — nu exista ce `id_articol` sa i se puna, si codul
de generare stie asta de multa vreme.
## 2. Ce accepta baza de date
**Confirmat pe Oracle** (`MARIUSM_AUTO@ROA_CENTRAL`, interogare proprie `ALL_TAB_COLUMNS`, SELECT
strict, plus datele masurate de team-lead pe `VANZARI`/`VANZARI_DETALII`):
| Coloana | Tip | Nullable | Observatie |
|---|---|---|---|
| `ID_ARTICOL` | NUMBER | **Y** | are FK `FK_VANZARE_DET002 -> NOM_ARTICOLE.PK_ARTICOL`; in `NOM_ARTICOLE` **nu exista niciun articol cu `id_articol = 0`** (count = 0) |
| `PRET` | NUMBER | **N** | singura coloana NOT NULL din setul verificat |
| `PRET_CU_TVA` | NUMBER | Y | flag 0/1, nu pret |
| `PRET_ACHIZITIE` | NUMBER | Y | |
| `PROC_TVAV` | NUMBER | Y | multiplicator TVA (ex. 1.21), nu procent |
| `DISCOUNT_UNITAR` | NUMBER | Y | |
| `CANTITATE` | NUMBER | Y | |
| `SERIE` / `LOT` / `EXPLICATIE` | VARCHAR2 | Y | |
| `ID_VANZARE_SET` / `ID_GESTIUNE` | NUMBER | Y | |
Confirmare directa pe date reale (interogare team-lead): factura `VANZARI.id_vanzare=1055` (SSS 549,
tip=2, `id_ctr=234`), linia `id_vanzare_det=1603` are efectiv `id_articol = NULL`,
`id_gestiune = NULL`, `pret_achizitie = NULL`, `explicatie = 'RATA 2'`, `cantitate=1`, `pret=100`,
`proc_tvav=1.21`, `id_valuta=3` — deja salvata asa, deci coloana accepta NULL in productie (nu doar
teoretic in DDL). In toata baza exista deja **20 de linii active** (`sters=0`) cu `id_articol IS
NULL`, din 1124 in total — starea nu e un caz izolat.
Concluzie punct 2: `ID_ARTICOL` e **nullable, cu FK** catre `NOM_ARTICOLE`. Asta e critic pentru
reparatie (punctul 6, mai jos): daca s-ar scrie `0` in loc de `NULL` pe o linie fara articol, FK-ul ar
respinge scrierea (`ORA-02291`), pentru ca `id_articol = 0` nu exista in `NOM_ARTICOLE`.
## 3. De unde se incarca `tvd` si de ce `denumire` iese goala
`tvd` se incarca prin `IncarcaArticoleFactura` (`ofacturare_editare.prg:305-`), din view-ul
`VVANZARI_ARTICOLE`, definit recent chiar pentru acest formular:
```sql
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql:7-34
CREATE OR REPLACE VIEW VVANZARI_ARTICOLE AS
select vd.id_vanzare, ..., vd.id_articol, ..., vd.explicatie, ...,
na.denumire, na.codmat, ng.nume_gestiune, nv.nume_val
from vanzari_detalii vd
left join nom_articole na on na.id_articol = vd.id_articol
...
```
Spre deosebire de `fact_vfacturi_detalii` (punctul 2), **acest view nou nu are fallback** pe
`vd.explicatie` cand `vd.id_articol` e NULL — `na.denumire` vine direct dintr-un `LEFT JOIN` pe
`nom_articole`, iar cand `id_articol` e NULL, join-ul nu gaseste nimic si `denumire` iese **NULL**.
Cursorul `tvd` insusi e declarat cu `id_articol N(20) NULL` (`ofacturare_editare.prg:278`,
`omodificari.vc2:14769`), asa ca incarcarea nu crapa aici — doar `denumire` ramane goala pe randul de
rata.
Asta explica exact mesajul din EROAREA 2: `Alltrim(Nvl(denumire,''))` (`omodificari.vc2:14450`) da
sir gol pentru ca `tvd.denumire` e cu adevarat NULL pe acel rand, nu pentru ca s-a gasit randul gresit.
In grid, textul vizibil "RATA 2" **nu vine din coloana Denumire** (`Column1.ControlSource =
"tvd.denumire"`, `omodificari.vc2:12349`), ci din coloana **Explicatie** de mai la dreapta
(`Column13.ControlSource = "tvd.explicatie"`, `omodificari.vc2:12444`) — coloana Denumire e goala pe
acel rand, exact ca in mesajul de eroare.
Concluzie punct 3: `denumire` **poate** iesi NULL pe linia de rata, si chiar iese — cauza e absenta
fallback-ului pe `explicatie` in `VVANZARI_ARTICOLE`, spre deosebire de view-ul mai vechi
`fact_vfacturi_detalii` care il are.
## 4. Cine a pus garda `Nvl(id_articol,0) = 0` si de ce
Garda de la `omodificari.vc2:14450` (in `frm_modific2024.inainte_de_do_termin`) a intrat in
`COMUN` prin commit-ul `1c42ae0` — *"#6 editare factura emisa: S5 - scrierea sumelor editate in
Oracle"*, 10.08.2026 — inainte de S4 (cautarea articolelor pe server) si inainte de decizia 18/S4b
despre liniile sintetice. Nu exista niciun commit ulterior care sa modifice acel `IF`.
**Nu era gandita ca plasa pentru rate.** Documentatia proprie a proiectului o descrie explicit ca
ramura considerata **imposibil de declansat** pe fluxul normal:
> `docs\progres.md:157,166`:
> | `:14402` | `Nvl(id_articol,0) = 0` | blocheaza |
> ...
> „Din cele trei, `Isnull(pret)` e ramura moarta si `id_articol` vine **mereu completat din sursa**,
> deci singura care ar fi putut pica e chiar cea de cantitate."
Asta e in contradictie directa cu ce arata investigatia S4 din acelasi plan (`plan_13:2275`, punctul 1
de mai sus), scrisa in aceeasi fereastra de timp — **liniile de rata nu au `id_articol` de la sursa**.
Contradictia nu a fost observata pentru ca cele doua fire de lucru (S5/garda de salvare, si S4/cautarea
articolelor din `cursor_contract`) nu s-au intersectat pana acum, pe date reale de contract cu rate.
Concluzie punct 4: garda **nu** a fost pusa pentru ca cineva stia ca `VANZARI_DETALII.ID_ARTICOL` e
NOT NULL in Oracle (ar fi contrazis chiar codul de generare din `ofacturare_comun.prg`, punctul 1) —
a fost pusa ca validare generica de continut ("linia trebuie sa aiba un articol"), pe premisa (falsa
pentru rate) ca `id_articol` vine intotdeauna populat. E o garda prea stricta pentru liniile de rata,
nu o reflectare a unei constrangeri de baza de date.
## 5. Ce mai crapa in aval, cu `id_articol` NULL pe un rand `tvd`/`trul`
Lista completa, `fisier:linie`, a locurilor care folosesc `id_articol` fara `Nvl` sau il compara cu
`=` (deci sensibile la NULL):
| Loc | Cod | Efect cu `id_articol` NULL |
|---|---|---|
| `ofacturare_editare.prg:617` | `CREATE CURSOR agg_rul (id_articol N(20), ...)` | camp declarat **fara** `NULL` — pe hartie acelasi tipar ca la `agg_tvd`, dar **confirmat inaccesibil in practica** (vezi punctul 7: `RUL.ID_ARTICOL` e `NOT NULL` in Oracle, 0 randuri active cu NULL) |
| `ofacturare_editare.prg:626,630-631` | `LOCATE FOR id_articol = trul.id_articol` apoi `INSERT INTO agg_rul ... VALUES (trul.id_articol, ...)` | fara risc practic — `trul.id_articol` nu poate fi NULL (punctul 7) |
| `ofacturare_editare.prg:640,647,651-652` | `CREATE CURSOR agg_tvd (id_articol N(20), ...)` + `INSERT INTO agg_tvd ... VALUES (tvd.id_articol, ...)` | **cauza directa a EROAREA 1** — camp NOT NULL, valoare NULL |
| `ofacturare_editare.prg:660-663` | `SELECT id_articol FROM agg_rul UNION SELECT id_articol FROM agg_tvd INTO CURSOR crs_articole_unite` | **verificat empiric (punctul 7)**: `UNION` trateaza NULL=NULL ca echivalent la deduplicare — oricate randuri NULL ar aduce `agg_tvd`, `crs_articole_unite` primeste **un singur** rand NULL |
| `ofacturare_editare.prg:671,687` | `LOCATE FOR id_articol = crs_articole_unite.id_articol` (o data pe `agg_rul`, o data pe `agg_tvd`) | **verificat empiric (punctul 7), corecteaza ipoteza initiala**: `LOCATE FOR camp = NULL` **nu gaseste niciodata**, in VFP, nici cand campul chiar contine NULL pe randul cautat — nu doar cand valorile difera. Efectul nu e o pereche falsa „Adaugare”+„Semnalare” (ipoteza initiala, neconfirmata) — e mai simplu si mai tacut: randul NULL din `crs_articole_unite` iese cu `llGasitSursa=.F.` **si** `llGasitTinta=.F.` pe ambele ramuri, cade in `OTHERWISE` cu 0=0, si **nu genereaza nicio linie in `propunere_sincronizare`** — dispare complet din sincronizare, fara eroare, fara semnalare |
| `ofacturare_editare.prg:849,887,923` | `LOCATE FOR id_articol = m.tnIdArticol` in `AplicaModificareTvd`, `AplicaAdaugareTvd`, `AplicaModificareTrul` | irelevant in practica daca se aplica reparatia recomandata la punctul 7 (Varianta B) — `propunere_sincronizare` nu mai ajunge sa contina randuri cu `id_articol` NULL, deci aceste functii nu sunt niciodata chemate cu `tnIdArticol` NULL |
| `omodificari.vc2:14450` | `IF Nvl(id_articol,0) = 0` | **cauza directa a EROAREA 2** — garda generica, prea stricta pentru rate |
Concluzie punct 5: reparatia nu se opreste la `agg_tvd` (linia care a crapat primul) — `agg_rul` are
aceeasi lipsa de `NULL` pe declaratie, dar dovedit inaccesibila (punctul 7). Mecanismul de potrivire
pe `id_articol =` din sincronizarea RUL<->TVD (`:671`, `:687`) e **verificat empiric ca nu functioneaza
pentru NULL**, cu efect de disparitie tacuta din propunere, nu de eroare — vezi analiza completa si
reparatia recomandata la punctul 7.
## 6. `Nvl(...,0)` la scriere in `ScrieArticoleFacturaEditate` — campuri care distrug informatie
Chiar daca EROAREA 1 si EROAREA 2 s-ar repara (cursoare + garda), `ScrieArticoleFacturaEditate`
(`ofacturare_editare.prg`) tot ar strica un rand de rata la prima salvare reusita, pentru ca **atat
UPDATE-ul liniilor pastrate (`:505-517`), cat si INSERT-ul liniilor noi (`:535-547`)** trec mai multe
campuri prin `Nvl(camp,0)` inainte sa le scrie in Oracle — convertind orice NULL legitim in `0`
literal:
```
-- UPDATE (linii existente), ofacturare_editare.prg:505-510
lcSql = [update vanzari_detalii set sters = 0, cantitate = ] + Alltrim(Str(Nvl(cantitate,0),18,3)) + ;
[, pret = ] + Alltrim(Str(Nvl(pret,0),18,4)) + [, pret_cu_tva = ] + Alltrim(Str(Nvl(pret_cu_tva,0))) + ;
[, pret_achizitie = ] + Alltrim(Str(Nvl(pret_achizitie,0),18,4)) + ;
[, proc_tvav = ] + Alltrim(Str(Nvl(proc_tvav,0),18,4)) + ;
[, discount_unitar = ] + Alltrim(Str(Nvl(discount_unitar,0),18,4)) + ;
[, id_articol = ] + Alltrim(Str(Nvl(id_articol,0))) + ;
[, id_gestiune = ] + Iif(Isnull(id_gestiune),[NULL],Alltrim(Str(id_gestiune))) + ;
...
-- INSERT (linii noi), ofacturare_editare.prg:535-546
lcSql = [insert into vanzari_detalii (id_vanzare, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, discount_unitar, ] + ;
[id_gestiune, cont, id_valuta, id_jtva_coloana, serie, explicatie, taxcode, lot, pret_achizitie, id_utils, dataoras) values (] + ;
Alltrim(Str(m.tnIdVanzare)) + [,] + Alltrim(Str(Nvl(id_articol,0))) + [,] + Alltrim(Str(Nvl(cantitate,0),18,3)) + [,] + ;
Alltrim(Str(Nvl(pret,0),18,4)) + [,] + Alltrim(Str(Nvl(pret_cu_tva,0))) + [,] + Alltrim(Str(Nvl(proc_tvav,0),18,4)) + [,] + ;
Alltrim(Str(Nvl(discount_unitar,0),18,4)) + [,] + Iif(Isnull(id_gestiune),[NULL],Alltrim(Str(id_gestiune))) + [,] + ;
...
Alltrim(Str(Nvl(pret_achizitie,0),18,4)) + [,] + Alltrim(Str(gnIdUtil)) + [, sysdate)]
```
Observatie de proiectare: **`id_gestiune`, `id_valuta`, `id_jtva_coloana`, `taxcode`, `cont`, `serie`,
`explicatie`, `lot` sunt deja tratate corect**, cu `Iif(Isnull(...),[NULL],...)` — modelul corect
exista deja in acelasi bloc de cod, doar ca nu s-a aplicat si celor sase campuri de mai jos.
Evaluare camp cu camp, cu nullabilitatea confirmata la punctul 2:
| Camp | Nullable in Oracle | `Nvl(...,0)` e corect? | Motiv |
|---|---|---|---|
| **`id_articol`** | Y, **cu FK** spre `NOM_ARTICOLE` | **NU — critic** | `0` nu exista in `NOM_ARTICOLE` (count=0) — pe o linie de rata cu `id_articol` real NULL, scrierea ar da **`ORA-02291`** (violare FK), nu doar ar corupe tacut o valoare. Chiar daca EROAREA 2 s-ar repara la nivel de garda VFP, salvarea tot ar crapa aici, doar cu un mesaj Oracle mai putin clar decat "nu are articol asociat". **Singurul din lista care produce eroare dura, nu doar corupere tacuta.** |
| **`pret_achizitie`** | Y | **NU** | Pe randul real `id_vanzare_det=1603` e deja NULL in productie — "cost de achizitie necunoscut/nu se aplica" (rata n-are cost de achizitie, nu e marfa). `Nvl(...,0)` il transforma in "cost de achizitie efectiv zero", care e o valoare falsa pentru orice calcul de marja/profit facut ulterior direct din `VANZARI_DETALII` (in afara VFP) |
| **`proc_tvav`** | Y | **Probabil nu, cu rezerva** | E un multiplicator TVA (1.21, nu 21%) — `0` nu inseamna "fara TVA" in aceasta conventie, ci ar corupe orice calcul care-l inmulteste (baza * 0 = 0). Pe randul de test `proc_tvav` e deja completat (1.21), deci `Nvl` nu se declanseaza acolo — dar daca exista/ar exista vreun rand cu `proc_tvav` NULL, scrierea lui ca `0` e o valoare periculoasa, nu neutra. **NEVERIFICAT**: daca exista azi randuri reale cu `proc_tvav` NULL (n-am interogat) |
| **`discount_unitar`** | Y | **Risc scazut** | NULL si 0 sunt aproape echivalente semantic ("fara discount") — conversia schimba tipul valorii, nu sensul ei practic. Recomandat de aliniat la tiparul `Isnull` din acelasi bloc, mai mult pentru consistenta decat pentru un bug observat |
| **`cantitate`** | Y | **Risc scazut, deja filtrat in amonte** | Garda `omodificari.vc2:14386` (`Nvl(cantitate,0) <= 0` -> blocheaza cu "are cantitatea 0") ruleaza **inaintea** lui `ScrieArticoleFacturaEditate` si opreste salvarea pe orice rand cu cantitate NULL sau <= 0. Pana la aceasta functie, `cantitate` e deja garantat non-NULL si non-zero pe calea normala — `Nvl(...,0)` de aici e defensiv, nu activ distructiv |
| **`pret`** | **N (NOT NULL)** | **DA — corect** | Coloana Oracle nu accepta NULL; garda `omodificari.vc2:14394` (`Isnull(pret)` -> blocheaza) opreste deja NULL inainte de scriere. `Nvl(pret,0)` e aici plasa de siguranta potrivita pentru o coloana NOT NULL, nu o corupere |
| **`pret_cu_tva`** | Y | **Risc scazut** | E flag boolean 0/1 (comentariu `:501` in acelasi fisier: "pret_cu_tva e flag (0/1), nu pret"), nu o valoare cu semnificatie de "lipsa" — `Nvl(...,0)` echivaleaza NULL cu "fara TVA in pret", o valoare implicita rezonabila pentru un flag |
Concluzie punct 6: din cele sase campuri, **doar `id_articol` produce o eroare Oracle dura** (FK) daca
nu se repara — e blocajul real, dincolo de garda VFP. **`pret_achizitie` corupe tacut date reale deja
existente** (randul 1603 chiar are NULL azi) fara sa arunce nicio eroare. `proc_tvav` e risc teoretic,
neconfirmat pe date. Restul (`discount_unitar`, `cantitate`, `pret_cu_tva`) sunt scrieri defensive
fara efect practic distructiv, iar `pret` e deja corect (coloana NOT NULL + garda VFP dedicata).
## 7. Sarim liniile fara articol la sursa, sau reparam matching-ul cu NULL — analiza cu dovada VFP
**Date suplimentare masurate de team-lead pe Oracle**: `RUL.ID_ARTICOL` e **NOT NULL** in Oracle
(`all_tab_columns.nullable = 'N'`), si exista **0** randuri `RUL` active cu `id_articol IS NULL`.
Documentul de test (SSS 549, cod 1140920) are **un singur rulaj**: `RUL.id_rul=10947,
id_articol=4294507173, cant=0, cante=1, pretvtva=121, id_tip_rulaj=0` — si **doua** linii `tvd`
(rata fara articol + articolul 4294507173). Deci pe acest document, `agg_rul` n-ar primi niciodata
un rand NULL — doar `agg_tvd` (din `tvd`) aduce randul de rata.
**Concluzie directa**: rândul `ofacturare_editare.prg:617` (`agg_rul` declarat fara `NULL`) e nesigur
pe hartie, dar **dovedit inaccesibil** — `trul`, incarcat din `RUL`, nu poate aduce NULL, constrangerea
Oracle il blocheaza la sursa. Merita uniformizat pentru consistenta cu `tvd`/`agg_tvd` (defensiv,
cost zero), dar nu e un bug activ.
### Testat empiric, nu presupus
Am rulat un test izolat in VFP (`vfp9.exe -A -T`, fara Oracle, fara formulare), reproducand exact
structura din `ConstruiestePropunereSincronizare` — `test_null_locate_union.prg`, pastrat in
scratchpad, log complet in `test_null_locate_union.log` (acelasi director). Rezultate:
| Test | Ce verifica | Rezultat masurat |
|---|---|---|
| 1 | `SELECT id_articol FROM agg_rul UNION SELECT id_articol FROM agg_tvd` cu 1 rand NULL in `agg_tvd` | **2 randuri** in rezultat: unul NULL, unul cu valoare — `UNION` pastreaza NULL-ul, nu-l pierde |
| 2 | Acelasi UNION, dar cu **2** randuri NULL in `agg_tvd` (simuleaza doua rate pe acelasi document) | Tot **2 randuri** in rezultat — `UNION` trateaza cele doua NULL-uri **ca echivalente** la deduplicare (comportament SQL standard de grupare, diferit de semantica lui `=`) |
| 3 | `LOCATE FOR id_articol = m.lnTest`, cu `m.lnTest = .NULL.`, pe un cursor care chiar are un rand cu `id_articol` NULL | **`FOUND() = .F.`** — nu gaseste randul, desi acesta exista |
| 4 | `LOCATE FOR id_articol = crs_articole_unite.id_articol`, exact tiparul de la `:671`/`:687`, cu ambele parti NULL | **`FOUND() = .F.`** — confirma acelasi lucru intre doua cursoare, nu doar cu un memvar |
**Interpretare, aplicata pe `ConstruiestePropunereSincronizare`**: daca EROAREA 1 s-ar repara doar prin
adaugarea `NULL` la declaratiile `agg_rul`/`agg_tvd` (Varianta A propusa initial), randul NULL din
`crs_articole_unite` (Test 1/2 arata ca **exista**, indiferent de cate rate sunt) ar ajunge in bucla
principala de la `:665-773`. Acolo, `LOCATE FOR id_articol = crs_articole_unite.id_articol` **pe
`agg_rul` si pe `agg_tvd`, ambele** (Test 3/4) **nu gaseste nimic**, chiar daca `agg_tvd` chiar contine
randul cautat. Rezultatul: `lnNrandRul=0` si `lnNrandTvd=0` raman la valorile implicite, deci
`llGasitSursa=.F.` si `llGasitTinta=.F.` **pe ambele ramuri** — nu se potriveste niciun `CASE` din
`DO CASE` (nici „Adaugare", nici „Semnalare"), cade in `OTHERWISE` cu `0=0`, si **liniei de rata nu i
se genereaza nicio linie in `propunere_sincronizare`**. **Corectez aici ipoteza din punctul 5 al
raportului initial** („pereche falsa Adaugare+Semnalare") — nu e o pereche falsa, e o **disparitie
tacuta**, mai greu de observat: randul nu genereaza nici eroare, nici avertizare, doar lipseste din
propunere, din `SemnaturaDivergenteSincronizare` (`omodificari.vc2:14839`, filtreaza pe aceleasi trei
actiuni) si deci din dialogul de sincronizare.
### Raspunsul la intrebare: Varianta B (excludere la sursa) e cea corecta
Team-lead a propus alternativa: `ConstruiestePropunereSincronizare` sare complet peste liniile `tvd`
(si, defensiv, `trul`) fara `id_articol`, cu `SCAN FOR Nvl(sters,0) <> 1 AND !Isnull(id_articol)` in
loc de `SCAN FOR Nvl(sters,0) <> 1` la `:619` (trul->agg_rul) si `:642` (tvd->agg_tvd).
**E varianta corecta**, din trei motive, toate confirmate mai sus:
1. **`id_articol` e chiar cheia de comparatie a mecanismului** — comentariul din capul fisierului
(`ofacturare_editare.prg:14`: "agregate pe id_articol, in ambele sensuri") o spune explicit. O
linie fara cheie nu poate, prin definitie, sa aiba corespondent — nu e un caz special de tratat cu
grija, e un rand care nu apartine acestui mecanism.
2. **Rezultatul practic al Variantei B e identic cu ce se intampla azi accidental prin Varianta A**
(randul de rata nu ajunge in `propunere_sincronizare`, deci nu apare in dialog, nu declanseaza
„Aplica" pe el) — dar **prin filtrare explicita**, nu prin efectul secundar, neverificat de nimeni
pana acum, al lui `LOCATE FOR ... = NULL`. Daca cineva „repara" mai tarziu comparatiile cu
`Nvl(id_articol,-1) = Nvl(...,-1)` (Varianta A din reparatia initiala, sau orice alt developer care
descopera si "repara" fara sa stie de aceasta analiza), comportamentul s-ar schimba brusc de la
"dispare tacut" la "participa la matching" — cu riscul semnalat deja in reparatia initiala, ca mai
multe rate distincte de pe acelasi document s-ar agrega/compara laolalta (confirmat acum de Testul
2: `UNION` le trateaza deja ca un singur grup NULL). Varianta B evita complet acest risc, pentru ca
liniile de rata nici nu ajung sa fie agregate.
3. **Varianta B rezolva si EROAREA 1 la radacina**, fara sa mai fie nevoie sa se adauge `NULL` la
declaratiile `agg_rul`/`agg_tvd` — daca linia cu `id_articol` NULL nu mai intra in bucla de `SCAN`
care face `INSERT INTO agg_tvd`, nu mai exista nicio incercare de a insera NULL intr-un camp NOT
NULL. (Adaugarea `NULL` la declaratii ramane totusi recomandata, ca plasa de siguranta ieftina,
independent de asta.)
**Efect pe cazul concret SSS 549, cu Varianta B**: `agg_rul` are 1 rand (`4294507173`), `agg_tvd` are
1 rand (`4294507173`, linia de rata fiind sarita la `SCAN`). `crs_articole_unite` are 1 rand. Randul
de rata nu apare nicaieri in `propunere_sincronizare` — nici „Adaugare", nici „Semnalare", nici „N-A".
La `SemnaturaDivergenteSincronizare`, semnatura nu contine nimic despre rata — deschiderea/inchiderea
dialogului de sincronizare nu e afectata de ea, la deschidere sau la salvare. La „Aplica" (daca
utilizatorul il apasa pentru articolul real), `AplicaModificareTrul`/`AplicaAdaugareTvd`/
`AplicaModificareTvd` nu sunt niciodata chemate cu `tnIdArticol` NULL, pentru ca randul de rata nu
ajunge in `propunere_sincronizare` ca sa fie scanat de `AplicaSincronizareArticole`
(`ofacturare_editare.prg:785-822`, bucla `SCAN FOR Inlist(Alltrim(actiune), 'Modificare',
'Adaugare')` la `:802`) — deci ingrijorarea din punctul 5 despre `:849/887/923` nu se mai
materializeaza.
---
## Reparatie propusa (NU aplicata)
### EROAREA 1 — cursoarele de agregare
**Recomandare finala (dupa analiza si testul din punctul 7): Varianta B — exclude liniile fara
`id_articol` la sursa**, in `ConstruiestePropunereSincronizare`:
```
SELECT trul
SCAN FOR Nvl(sters,0) <> 1 AND !Isnull(id_articol)
...
```
(`ofacturare_editare.prg:619`, azi `SCAN FOR Nvl(sters,0) <> 1`)
```
SELECT tvd
SCAN FOR Nvl(sters,0) <> 1 AND !Isnull(id_articol)
...
```
(`ofacturare_editare.prg:642`, azi `SCAN FOR Nvl(sters,0) <> 1`)
E coerent cu faptul ca `id_articol` e chiar cheia de comparatie a mecanismului (comentariul din capul
fisierului, `:14`) — o linie fara cheie n-are cum sa aiba corespondent, la fel cum deja se trateaza
separat cazul "Articol nestocat, fara corespondent in rulaje" (`:730-732`). Filtrul de mai sus **repara
si EROAREA 1** — nu mai ajunge nicio valoare NULL la `INSERT INTO agg_tvd`/`agg_rul`, deci declaratiile
cursoarelor n-ar mai avea nevoie sa accepte `NULL` ca sa nu crape. Recomandat totusi, ca plasa de
siguranta ieftina si pentru consistenta cu `tvd`:
```
CREATE CURSOR agg_rul (id_articol N(20) NULL, denumire C(100), codmat C(30), cant N(12,3), val N(16,4), nrand I)
CREATE CURSOR agg_tvd (id_articol N(20) NULL, denumire C(100), codmat C(30), cant N(12,3), val N(16,4), nrand I, in_stoc I)
```
(`ofacturare_editare.prg:617`, `:640`)
**Varianta alternativa, respinsa**: doar adauga `NULL` la declaratii si repara comparatiile `id_articol
= X` cu o forma NULL-safe (`Nvl(id_articol,-1) = Nvl(m.tnIdArticol,-1)`, acelasi tipar folosit in
`ofacturare_comun.prg:1334,1349`). Tehnic ar functiona, dar **verificat empiric (punctul 7, Test 2)**:
`UNION`-ul deja trateaza toate liniile NULL ca **un singur grup** — cu aceasta varianta, doua sau mai
multe rate distincte de pe acelasi document s-ar agrega si compara laolalta, ca si cum ar fi acelasi
"articol". Nu aduce niciun beneficiu fata de Varianta B (rezultatul pentru randul de rata tot nu
trebuie sa apara in sincronizare), doar risc suplimentar — de aceea nu e recomandata.
### EROAREA 2 — garda de la salvare
Trei variante, e o decizie de produs:
- **Varianta A — restrange garda la liniile cu articol real posibil**: cere articol doar cand linia
nu e de tip rata — de exemplu cand `id_vanzare_set = 0` **si** linia are `pret_achizitie` (semn ca
vine dintr-un flux de articole reale), sau invers, sare garda cand `Isnull(id_articol) AND
!Empty(explicatie)` (semnul unei linii "sintetice" descrise prin `explicatie`, ca ratele).
- **Varianta B — scoate garda complet** pentru randuri cu `id_articol` NULL de la incarcare (nu
adaugate manual in sesiunea curenta) — presupune sa distingi in `tvd` intre "NULL de la Oracle" si
"NULL pentru ca utilizatorul a adaugat un rand si n-a ales inca un articol" (al doilea caz chiar
trebuie blocat).
- **Varianta C — transforma in intrebare (confirmare), nu blocaj** — la fel ca gardul de pret de
achizitie 0 de doua linii mai jos (`:14458-14464`), las utilizatorul sa decida daca salveaza cu
randul fara articol.
Recomandarea de continut (nu de aplicat acum): **Varianta A**, pentru ca pastreaza garda utila pe
cazul ei real (rand adaugat manual din nomenclator, fara articol ales din greseala), fara sa oblige
verificarea de tip "e linie de rata veche" peste tot.
### Scrierea in Oracle — `ScrieArticoleFacturaEditate` (punctul 6)
Minim, pe ambele blocuri (UPDATE `:505-510`, INSERT `:535-546`), aliniaza `id_articol` si
`pret_achizitie` la tiparul `Iif(Isnull(...),[NULL],...)` deja folosit pentru `id_gestiune`/
`id_valuta`/`id_jtva_coloana`/`taxcode` in acelasi bloc:
```
[, id_articol = ] + Iif(Isnull(id_articol),[NULL],Alltrim(Str(id_articol))) + ;
...
[, pret_achizitie = ] + Iif(Isnull(pret_achizitie),[NULL],Alltrim(Str(pret_achizitie,18,4))) + ;
```
**`id_articol` e obligatoriu de facut** — altfel, chiar dupa ce EROAREA 1 si EROAREA 2 s-ar repara,
prima salvare a unei facturi cu linie de rata ar cadea pe `ORA-02291` (FK spre `NOM_ARTICOLE`, care
n-are rand cu `id=0`). `pret_achizitie` e recomandat, ca sa nu se scrie tacut "cost de achizitie 0" pe
un rand care azi are NULL. `proc_tvav` — de decis dupa ce se clarifica NEVERIFICAT-ul de mai jos (daca
poate fi NULL pe un rand real, acelasi tipar se aplica si lui). `discount_unitar`, `cantitate`,
`pret_cu_tva` — opional, doar pentru consistenta cu restul blocului, fara bug observat.
### `VVANZARI_ARTICOLE` — fallback pe `explicatie`
Independent de cele doua erori, view-ul (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql`)
ar putea capata acelasi fallback ca `fact_vfacturi_detalii`:
`NVL2(vd.id_articol, na.denumire, vd.explicatie) as denumire` — ca sa nu mai iasa coloana Denumire
goala pe randurile de rata (in prezent doar coloana Explicatie arata ceva, iar Denumire ramane goala
in grid, ceea ce probabil nu era intentionat cand s-a proiectat view-ul).
## NEVERIFICAT
- **Constrangerea DDL exacta pe `VANZARI_DETALII.ID_ARTICOL` — REZOLVAT.** Confirmat direct pe Oracle
(`ALL_TAB_COLUMNS`, `MARIUSM_AUTO@ROA_CENTRAL`): `ID_ARTICOL` e `NULLABLE = Y`, cu FK
`FK_VANZARE_DET002` spre `NOM_ARTICOLE.PK_ARTICOL`; `NOM_ARTICOLE` n-are niciun rand cu `id_articol =
0`. Vezi tabelul din punctul 2. (Scriptul original de `CREATE TABLE` tot nu e in
`D:\ROA\DATABASE\SCRIPTURI_CLAR` — dar nu mai e nevoie de el, DDL-ul curent s-a citit direct din
dictionarul de date.)
- **Daca exista azi randuri reale in `VANZARI_DETALII` cu `proc_tvav` NULL** — n-am interogat asta
direct (doar am confirmat ca *poate* fi NULL, `NULLABLE=Y`). Pe randul de test `proc_tvav=1.21`, deci
nu se declanseaza acolo. Daca nu exista niciodata NULL pe randuri reale, `Nvl(proc_tvav,0)` din
punctul 6 e o plasa de siguranta fara efect, la fel ca la `pret`/`cantitate`; daca exista, e periculos
(baza * 0 = 0 in orice recalcul din afara VFP). Se poate lamuri cu un `SELECT count(*) FROM
vanzari_detalii WHERE sters=0 AND proc_tvav IS NULL`.
- **De ce n-a crapat `agg_rul` — REZOLVAT.** `RUL.ID_ARTICOL` e **NOT NULL** in Oracle
(`all_tab_columns.nullable = 'N'`, masurat de team-lead), si exista **0** randuri `RUL` active cu
`id_articol IS NULL`. Documentul de test (SSS 549) are un singur rulaj, cu articolul real
`4294507173` — rata n-are corespondent in `RUL`. Deci `trul` nu poate aduce niciodata NULL, iar
declaratia `agg_rul (id_articol N(20), ...)` fara `NULL` (`:617`), desi nesigura pe hartie, e
dovedit inaccesibila pe calea normala — nu doar "n-a crapat inca", ci "nu poate crapa" atat timp cat
constrangerea Oracle ramane in vigoare. Vezi punctul 7.
- **Comportamentul VFP la NULL in `UNION` si `LOCATE FOR` — REZOLVAT, testat empiric.** Vezi punctul 7:
test izolat `vfp9.exe -A -T`, script pastrat in
`C:\Users\mmari\AppData\Local\Temp\claude\D--ROA-ROAFACTURARE\81fbd7c8-41d4-4341-a83a-95736e6dc6d3\scratchpad\test_null_locate_union.prg`,
log in acelasi director (`test_null_locate_union.log`) — fisiere temporare de sesiune, nu in
arborele proiectului. `UNION` trateaza NULL=NULL ca echivalent la deduplicare; `LOCATE FOR camp =
NULL` nu gaseste niciodata, nici cand campul chiar contine NULL pe randul cautat.
- **Cate linii de rata existente in productie ar fi afectate** de reparatia recomandata (Varianta B) —
nu am interogat Oracle pentru un numar total (team-lead a masurat deja 20 de linii active cu
`id_articol IS NULL` in toata baza, punctul 2 — dar nu si cate din ele sunt pe facturi cu rulaje
care ar trece prin `ConstruiestePropunereSincronizare`).

View File

@@ -1,411 +0,0 @@
# Cercetare: totaluri goale pe factura editata din aviz (LISTA FACTURI, AVIZE SI PROFORME)
Simptom: dupa editarea din formularul unificat a unei facturi emise DIN AVIZ, randul facturii in
lista principala apare cu **Total fara TVA / Total TVA / Total cu TVA GOALE** (NULL, nu 0.00).
Randul avizului de dedesubt ramane corect. Regresie fata de comportamentul anterior.
Diagnostic STRICT — nu s-a modificat niciun fisier de cod, nu s-a facut write-back, nu s-a comis
nimic.
> **Depasit partial.** Acest raport se opreste la "cauza probabila, NEVERIFICAT pe date".
> Interogarea pe `MARIUSM_AUTO@ROA_CENTRAL` a inchis intre timp intrebarea: linia facturii are
> `ID_VALUTA = 2` (EURO) fara rand corespunzator in `VANZARI_CURSURI`, iar ramura de conversie
> valutara a procedurii produce NULL. Concluzia si reparatia sunt in
> `docs\propunere_runda5_articole_valuta_rate.md`, punctul 2. Ce ramane valabil aici: analiza
> lantului de scriere si infirmarea ipotezei cu `id_vanzare_set`/`id_vanzare_det`.
## 1. De unde vin coloanele Total fara TVA / Total TVA / Total cu TVA
Gridul `grid_facturi` din `frm_facturi` (`COMUN\clase\ofacturare_comun.vc2:1172` obiectul,
`:1203-1208` coloanele `cTotal_cu_tva`/`cTotal_fara_tva`/`cTotal_tva`) e legat pe cursorul
`crsfacturi` (`RecordSource = "crsFacturi"`, `COMUN\clase\ofacturare_comun.vc2:2238`), populat prin
`gencursor('poFacturi','crsfacturi', lcSelect, ...)`.
Cursorul e umplut din UNA din doua proceduri Oracle, **alese la RUNTIME de utilizator**, printr-un
prompt (`Clase\ofundal_facturare.vc2:944-953`, `Page4.Cw1.do_actiune`):
```
lnOptiune = xmenu('Vizualizare \<standard;Vizualizare \<experimentala (mai rapida)')
If m.lnOptiune = 1
Do vizualizare_facturi In oproceduri_facturare.prg && FACT_VFACTURI
Else
Do vizualizare_facturi2 In oproceduri_facturare.prg && FACT_VFACTURI2
Endif
```
Cele doua proceduri (`COMUN\programe\oproceduri_facturare.prg:314` si `:398`) selecteaza
`total_fara_tva, total_tva, total_cu_tva` din **doua view-uri Oracle diferite** cu semantica
diferita — asta e cheia problemei:
- **`FACT_VFACTURI`** ("standard"): coloanele sunt **calculate live**, printr-un `SUM(...)`
peste `vanzari_detalii` (si `vanzari_seturi` pentru liniile de tip set). Nu citeste niciodata
coloanele stocate din `VANZARI`.
Definitie: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_11_COMUN_FACT_VFACTURI.sql:192-194`
(`a.suma_fara_tva - a.disc_fara_tva_ron as total_fara_tva`, etc.), agregarea la
`:408-460` (subquery `vd`, `LEFT JOIN` intre `v.id_vanzare` si rezultatul agregat).
- **`FACT_VFACTURI2`** ("experimentala"): coloanele sunt citite **direct** din `VANZARI` (coloane
stocate), fara recalcul la interogare — comentariul din cod spune explicit de ce:
"*totalurile ... sunt calculate in vanzari, in loc sa fie luate din vanzari_detalii, pentru
rapiditate; totalurile sunt completate in vanzari la insert into vanzari*"
(`COMUN\programe\oproceduri_facturare.prg:397-399`).
Definitie view: `ff_2026_08_06_11_COMUN_FACT_VFACTURI.sql:682-684`
(`a.total_fara_tva, a.total_tva, a.total_cu_tva` din `vanzari a`).
**NEVERIFICAT**: nu stiu ce optiune a ales Marius la momentul screenshot-ului (`standard` sau
`experimentala`) — promptul e interactiv, nu am gasit un default hard-codat. Concluzia de mai jos
e valabila pentru amandoua variantele, din motive diferite (vezi §3).
## 2. Ce scrie salvarea din formularul unificat pe partea de factura
`ScrieArticoleFacturaEditate` (`COMUN\programe\ofacturare_editare.prg:473-571`), apelata cu
`tvanz.id_vanzare` (id-ul corect al FACTURII, verificat — `tvanz` vine din
`IncarcaVanzareNota`/`IncarcaVanzareDinNota`, care interogheaza `VANZARI` dupa
`cod/nract/serie_act/data_act` ale notei editate, deci nu e confuzie aviz/factura; apelurile reale
sunt in `COMUN\clase\comun.vc2:2492` si `COMUN\clase\ofacturare_comun.vc2:3829`, ambele in ramura
`"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))`):
1. **Marcheaza sters=1 TOATE liniile active** din `vanzari_detalii` pentru acel `id_vanzare`
(`:492-495`).
2. **Reinvie/actualizeaza** liniile pastrate, cele cu `id_vanzare_det > 0` din cursorul `tvd`
(`:498-518`) — UPDATE pe cheia `id_vanzare_det`, camp cu camp: `sters, cantitate, pret,
pret_cu_tva, pret_achizitie, proc_tvav, discount_unitar, id_articol, id_gestiune, id_valuta,
id_jtva_coloana, taxcode, cont, id_utils, dataoras`. **NU** atinge `id_vanzare_set` — coloana nu
apare in lista SET, deci valoarea existenta in DB ramane neschimbata la UPDATE.
3. **Insereaza** liniile noi (`id_vanzare_det = 0`, `:522-540`) — la fel, **fara** `id_vanzare_set`
in lista de coloane (linii noi ies cu `id_vanzare_set` implicit NULL in DB).
4. **Doar daca 1-3 au reusit complet** (`IF m.llSucces`, verificat dupa fiecare pas, `EXIT` la
primul esec Oracle — deci un esec partial NU ajunge la pasul urmator), cheama procedura Oracle
de recalcul totaluri (`:559-563`, vezi §3).
Pe o factura din aviz **fara** articole compuse ("seturi"), toate liniile din `tvd` au
`id_vanzare_set = 0/NULL` — confirmat de cercetarea anterioara `docs/livrare_s5.md:117-118`:
"*Liniile din seturi de articole (id_vanzare_set nenul) — neacoperibil pe datele actuale, nu din
omisiune: interogare pe MARIUSM_AUTO, zero documente cu id_vanzare_set nenul*". Deci pentru cazul
din screenshot (SSS 100037), branch-ul de "seturi" din view/procedura Oracle (vezi §3) probabil nu
se activeaza — **ramane insa NEVERIFICAT direct pe acest document**, n-am rulat SQL pe schema
reala.
**Nu am gasit nicio scriere directa pe antetul `VANZARI` in `ScrieArticoleFacturaEditate` in afara
apelului la procedura Oracle de la pasul 4** — totalurile de antet nu sunt niciodata calculate in
VFP, sunt lasate integral pe seama Oracle.
## 3. Procedura Oracle de recalcul — cea mai probabila cauza
`ScrieArticoleFacturaEditate` (`ofacturare_editare.prg:559-563`) cheama necondiționat:
```
lcSql = [begin pack_facturare.recalculeaza_totaluri_vanzari(] + Alltrim(Str(m.tnIdVanzare)) + [,] + ;
Iif(m.llDiscountNul,[NULL],Alltrim(Str(m.lnDiscount,18,4))) + [); end;]
llSucces = goExecutor.oExecuta(m.lcSql)
```
Aceasta procedura este **cod nou**, introdus in exact aceeasi runda de modificari ca bug-ul
raportat:
- Script-ul care o documenteaza in `SCRIPTURI_CLAR` e
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(comentariu la inceput: "*adauga procedura recalculeaza_totaluri_vanzari*").
- Apelul din VFP a fost adaugat in commit-ul `1c42ae0` din `COMUN`
("*#6 editare factura emisa: S5 - scrierea sumelor editate in Oracle*"), **10.08.2026**, adica o
zi dupa ce procedura Oracle a fost capturata in script (09.08.2026) — coerent cu "procedura noua
in DB, apoi codul VFP care o cheama".
Corpul procedurii (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024-16228`) face un
`SELECT ... INTO` cu agregare (SUM) peste vanzari_detalii/vanzari_seturi — **aceeasi structura**
ca formula din `FACT_VFACTURI` (§1): UNION ALL intre liniile normale
(`nvl(a.id_vanzare_set,0)=0 and a.id_vanzare=V_ID_VANZARE and a.sters=0`) si liniile "set"
(`vanzari_seturi` join `vanzari_detalii`), apoi `SUM(pack_facturare.calculeaza_total_fara_tva_fact(...))`
etc. Rezultatul e scris **necondiționat**, fara nicio garda `NVL`:
```
update vanzari
set discount = lnDiscountFactura,
...
total_fara_tva = lnTotalFaraTVA,
total_tva = lnTotalTVA,
total_cu_tva = lnTotalCuTVA,
...
where id_vanzare = V_ID_VANZARE;
```
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16211-16223`)
Contrast direct cu scriptul de backfill istoric din aceeasi runda
(`ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`), care recalculeaza aceleasi coloane dar explicit
**"fill-only"**: `v.total_fara_tva = NVL(v.total_fara_tva, src.c_ftva)`, cu comentariu propriu care
spune raspicat ca scrierea trebuie sa fie doar completare de goluri, "*niciodata peste o valoare
deja scrisa*". `recalculeaza_totaluri_vanzari` **nu are aceasta garda** — daca `SELECT INTO`
calculeaza `NULL` pe oricare coloana (SUM peste zero randuri, sau peste randuri toate NULL),
UPDATE-ul suprascrie o valoare corecta deja existenta cu NULL.
**Cand poate iesi NULL din SUM**: in Oracle, `SUM()` peste un set de randuri produs de un
`LEFT JOIN` fara nicio potrivire da NULL (nu 0), iar daca TOATE liniile relevante ale documentului
ies cu pret/discount NULL din subquery (de ex. `vanzari_seturi` fara rand pentru
`id_vanzare_set`-ul cerut, in branch-ul de "seturi"), rezultatul agregat pe acel document e NULL.
Pentru un document fara linii de tip "set" (cazul obisnuit, §2), acest branch nu ar trebui sa se
activeze — dar **nu am verificat pe date reale** daca liniile facturii SSS 100037 au ramas cu
`sters=0` dupa salvare sau daca vreo alta conditie (curs valutar lipsa in `vanzari_cursuri` pentru
`id_valuta`-ul liniei, de ex.) a produs NULL in agregat.
## 4. Regresia, concret — NEVERIFICAT complet, dar convergenta puternica
Nu exista fisiere `.bak.vc2` pentru `ofacturare_editare.prg` (bak-urile mentionate in cerere —
`omodificari.pre_cantitate.bak.vc2` etc. — sunt pentru `omodificari.vc2`, o clasa diferita; codul
de scriere efectiva a articolelor si totalurilor traieste in `ofacturare_editare.prg`, care nu are
un `.bak`). Istoricul git (`COMUN`) arata insa clar ca **intreg mecanismul de recalcul al
totalurilor pe factura editata e cod nou din 09-10.08.2026** (S5), introdus in acelasi pachet cu
extinderea UPDATE-ului mentionata in cerere (`id_articol, id_gestiune, id_valuta, proc_tvav,
taxcode, cont, pret_achizitie, discount_unitar, id_jtva_coloana`) — commit-urile ulterioare
(S4b etapa 2, 11.08.2026) nu ating aceasta zona.
**Ipoteza suspectata explicit de cerere** (UPDATE-ul extins scrie campuri NULL peste linii
existente si strica `id_vanzare_set`/`id_vanzare_det`) **NU se confirma din citirea codului**:
UPDATE-ul de la pasul 2 (§2) nu atinge deloc coloana `id_vanzare_set`, iar `id_vanzare_det` e
folosit doar in clauza `WHERE`, niciodata scris. Round-trip-ul cursorului `tvd` (incarcat din
`VVANZARI_ARTICOLE`, view 1:1 pe `vanzari_detalii` — vezi
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql`, fara nicio
agregare) e coerent camp cu camp.
**Cauza cea mai probabila, cu incredere medie-mare**: nu o eroare in codul VFP de scriere a
liniilor, ci procedura Oracle noua `pack_facturare.recalculeaza_totaluri_vanzari`
(§3) — cod introdus in aceeasi runda, apelat necondiționat dupa fiecare salvare de factura editata
din formularul unificat, care scrie fara garda NULL peste coloanele de total din `VANZARI`. Pe
"vizualizarea standard" (`FACT_VFACTURI`), acelasi tip de agregare NULL-propagabila se repeta live
la fiecare afisare a listei, deci simptomul ar aparea indiferent care view e activ.
## 5. Trigger/procedura declansata la INSERT — nu se aplica aici
Nu exista trigger Oracle care sa recalculeze totalurile la INSERT in `vanzari_detalii` — scrierea
e facuta explicit, o singura data, prin apelul manual la `recalculeaza_totaluri_vanzari` de la
finalul lui `ScrieArticoleFacturaEditate` (§3). Punctul 4 din cererea initiala nu se aplica.
## Reparatie propusa (fara aplicare)
Minimal, in `D:\ROA\DATABASE\SCRIPTURI_CLAR\...\PACK_FACTURARE.sql` /
`recalculeaza_totaluri_vanzari`: inlocuit UPDATE-ul necondiționat cu varianta "fill-safe" folosita
deja in scriptul de backfill — fie NVL pe fiecare coloana (`total_fara_tva = NVL(lnTotalFaraTVA,
total_fara_tva)` etc., pastreaza valoarea veche daca recalculul da NULL), fie un `RAISE`/log
explicit cand agregatul iese NULL, ca sa nu treaca neobservat. Inainte de asta, e nevoie de o
verificare directa pe schema reala (SQL pe `vanzari_detalii`/`vanzari_seturi` pentru
`id_vanzare`-ul facturii SSS 100037) ca sa se confirme ce ramura produce NULL — recomand asta ca
prim pas al oricarei remedieri, nu modificarea pe ghicite.
## Ce ramane NEVERIFICAT (runda 1)
- Ce optiune de vizualizare (standard/experimentala) a fost activa la momentul screenshot-ului.
- Continutul real al `vanzari_detalii`/`vanzari_seturi` pentru factura SSS 100037 dupa editare —
**verificat in runda 2** (mai jos), pe baza interogarilor SQL facute de team-lead + interogari
proprii read-only, cu `sqlplus`.
---
# Runda 2 — cauza confirmata prin SQL pe date reale
Team-lead a interogat direct Oracle (`MARIUSM_AUTO@ROA_CENTRAL`) si a stabilit cauza imediata:
linia `VANZARI_DETALII.id_vanzare_det = 1602` (singura linie activa a facturii `id_vanzare = 1054`,
SSS 100037) are `ID_VALUTA = 2` (EURO), desi documentul e `IN_VALUTA = 0` si linia-sursa din aviz
avea `ID_VALUTA = 3` (RON). `VANZARI_CURSURI` nu are niciun rand pentru `id_vanzare = 1054`, deci
`recalculeaza_totaluri_vanzari` calculeaza `pret_ron = NULL` pentru acea linie (branch-ul de
conversie valutara, fara curs) si `total_fara_tva/total_tva/total_cu_tva` ies `NULL`. Am confirmat
independent, cu SQL read-only propriu (`sqlplus`, vezi mai jos), amploarea si mecanismul.
## 1. Cine scrie `id_valuta` pe linia de articol — CONFIRMAT
Singura cale prin care `tvd.id_valuta` se schimba pe o linie **existenta** e nomenclatorul de
valuta din grid: `ArticoleNotaEditor.ModificaNomenclator`, ramura `nume_val`
(`COMUN\programe\ofacturare_editare.prg:1093-1094`):
```
CASE m.lcCamp == 'nume_val'
REPLACE nume_val WITH loCauta.nume_val, id_valuta WITH loCauta.id_valuta
```
`loCauta = caut_valuta()` — nomenclatorul standard de valute, deschis fara nicio conditie legata
de `tvanz.in_valuta`. Singura garda de la inceputul procedurii (`:1069`) e
`Nvl(tvd.id_vanzare_set,0) <> 0` (blocheaza doar liniile din seturi de articole) — **nu exista
nicio garda legata de tipul documentului**, deci utilizatorul poate alege orice valuta pe orice
linie normala, indiferent daca factura e in RON sau in valuta.
UPDATE-ul care duce `id_valuta` in `VANZARI_DETALII` e in
`ScrieArticoleFacturaEditate` (`COMUN\programe\ofacturare_editare.prg:512`):
```
[, id_valuta = ] + Iif(Isnull(id_valuta),[NULL],Alltrim(Str(id_valuta))) + ;
```
**Confirmat prin diff cu `COMUN\programe\ofacturare_editare.prg.pre_runda_butoane.bak:501-505`**:
inainte de runda curenta, UPDATE-ul pe liniile existente scria doar
`sters, cantitate, pret, pret_cu_tva, id_utils, dataoras` — **`id_valuta` nu era in lista**. Deci
inainte, o alegere de valuta pe o linie existenta (facuta prin `nume_val`) se pierdea silentios la
salvare (UI arata schimbarea, dar UPDATE-ul n-o scria) — inofensiv, dar si fara efect. Extinderea
UPDATE-ului (runda curenta, aceeasi runda care a adaugat si `recalculeaza_totaluri_vanzari`, vezi
runda 1 §3-4) e cea care face ca alegerea gresita de valuta sa ajunga acum in `VANZARI_DETALII` si
sa strice totalurile. **Simptomul e nou din exact acest motiv — confirmat pe cod, nu presupunere.**
## 2. Ce ar trebui sa fie corect pe un document `in_valuta = 0` — CONFIRMAT
`VANZARI_CURSURI` e scris o singura data, la EMITERE, de `pack_facturare.scrie_cursuri`
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:14538-14548`):
```sql
PROCEDURE scrie_cursuri(V_ID_VANZARE IN NUMBER) IS
BEGIN
INSERT INTO VANZARI_CURSURI (ID_VANZARE, ID_VALUTA, CURS, MULTIPLICATOR)
SELECT DISTINCT V_ID_VANZARE, ID_VALUTA, CURS, MULTIPLICATOR
FROM VANZARI_DETALII_TEMP
WHERE ID_VALUTA <> pack_facturare.nid_moneda_nationala;
END scrie_cursuri;
```
Sursa e tabela de STAGING folosita la emitere (`VANZARI_DETALII_TEMP`), nu `VANZARI_DETALII` live —
deci **nu exista niciun mecanism care sa adauge/actualizeze `VANZARI_CURSURI` cand se schimba
`id_valuta` pe o linie DUPA emitere**, din editarea unificata sau din oricare alt punct. Raspuns
direct la intrebare: **da, e conceptual posibil ca o linie sa aiba alta valuta decat RON pe un
document in lei** (mecanismul de facturare mixta exista, cu curs propriu per linie in
`VANZARI_CURSURI`) — dar **doar daca acea alegere s-a facut la emitere**, cand `scrie_cursuri`
inca ruleaza si scrie perechea `(id_vanzare, id_valuta) -> curs`. O schimbare **post-emitere**,
prin editarea unificata, nu are nicio cale sa creeze acel rand — deci orice `id_valuta` diferit de
RON ales dupa emitere e, prin constructie, orfan (fara curs), garantand `pret_ron = NULL` in
`recalculeaza_totaluri_vanzari`.
## 3. Toate caile prin care agregatele pot iesi NULL — enumerare + masurare pe date reale
Din citirea corpului `recalculeaza_totaluri_vanzari`
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024-16228`):
1. **Zero randuri active in `VANZARI_DETALII`** pentru `id_vanzare` (toate `sters=1`, sau
document fara nicio linie) — `SUM()`/`MAX()` peste zero randuri = NULL pentru toate coloanele.
2. **Linie cu `id_valuta` diferit de moneda nationala, fara rand in `VANZARI_CURSURI`** pentru acea
pereche `(id_vanzare, id_valuta)` — cazul masurat mai jos (Marius). `LEFT JOIN vc` nu gaseste
nimic, `vc.curs`/`vc.multiplicator` = NULL, `pret_ron` = NULL pentru acea linie. Daca **toate**
liniile documentului sunt afectate, `SUM()` total iese NULL; daca doar unele, `SUM()` ignora
randurile NULL si totalul iese **trunchiat, nu NULL** (lipseste contributia liniei respective,
fara sa fie vizibil ca eroare).
3. **Linie din "set de articole" (`id_vanzare_set <> 0`) fara rand corespunzator in
`VANZARI_SETURI`** — acelasi tipar ca #2, prin `LEFT JOIN vanzari_seturi b`. Neconfirmat pe date
reale (branch aproape neutilizat — vezi `docs/livrare_s5.md:117-118`, zero documente cu
`id_vanzare_set` nenul cunoscute anterior).
4. **`VANZARI.in_valuta` NULL** — `nvl(lnInValuta,-1) > -1` devine fals, exclude tot branch-ul de
"seturi"; nu produce NULL pe branch-ul normal, dar poate goli branch-ul 2 pe un document compus
doar din linii de set.
5. **Argument NULL in `pack_facturare.calculeaza_total_fara_tva_fact`/`_tva_fact`** (ex.
`proc_tvav` sau `cantitate` NULL pe o linie) — acelasi tipar trunchiat/total, dupa cate linii
sunt afectate.
**Masurat pe schema reala** (`MARIUSM_AUTO@ROA_CENTRAL`, SELECT-uri read-only, script-urile
folosite raman in `scratchpad`, nu s-a scris nimic in baza):
| Categorie (din cele 235 `VANZARI.total_cu_tva IS NULL`) | Numar documente |
|---|---|
| Total `VANZARI` cu `total_cu_tva IS NULL` | **235** |
| Fara nicio linie activa in `VANZARI_DETALII` (cauza #1, veche — carve-out cunoscut, vezi
`ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, "facturile fara nicio linie activa raman
neatinse, prin decizie de produs") | **120** |
| Linie cu `id_valuta` diferit de RON si fara rand in `VANZARI_CURSURI` (cauza #2 — **exact
bug-ul curent**) | **1** (chiar SSS 100037 / `id_vanzare=1054`, confirmat `IN_VALUTA=0`) |
| Neexplicat de #1 sau #2 (alta cauza, preexistenta, in afara scopului acestei cercetari) |
**114** — din acestea, 109 au `VANZARI.discount_evidentiat IS NULL` (posibil o cauza separata,
nelegata de `id_valuta`; neinvestigat in continuare, iese din scopul cererii) |
**Concluzie importanta**: bug-ul de `id_valuta` orfan e **izolat la 1 singur document in toata
baza**, in acest moment — consistent cu faptul ca UPDATE-ul extins care il face vizibil e cod de
cateva zile (runda curenta). Nu e nevoie de o reparare in masa; celelalte 234 de documente cu total
NULL au alta cauza (majoritar #1, cunoscuta si acceptata prin design) si nu trebuie atinse de
reparatia acestui bug.
## 4. Reparatie propusa, in trei straturi (fara aplicare)
### Client (VFP)
In `ArticoleNotaEditor.ModificaNomenclator`
(`COMUN\programe\ofacturare_editare.prg:1069`), extinde garda existenta ca sa blocheze
nomenclatorul de valuta pe liniile unui document care nu e in valuta:
```
IF !This.Editabil() OR Reccount('tvd') = 0 OR !This.AreNomenclator(m.lcCamp) OR Nvl(tvd.id_vanzare_set, 0) <> 0 ;
OR (m.lcCamp == 'nume_val' AND Nvl(tvanz.in_valuta,0) <> 1)
RETURN .F.
ENDIF
```
(presupune ca `tvanz` e vizibil din contextul clasei — de verificat la implementare; alternativ,
o proprietate `This.oForm.lDocInValuta` populata la incarcare). Efect: pe o factura in RON,
coloana `nume_val` devine needitabila, la fel ca liniile din seturi — elimina posibilitatea de a
introduce `id_valuta` orfan prin UI.
### DB — `recalculeaza_totaluri_vanzari`
Doua schimbari, ambele minime:
1. **Restrange conditia de conversie** la cazul in care documentul insusi e in valuta, nu cand
linia are o valuta diferita de RON pe un document RON (elimina exact vulnerabilitatea gasita):
```sql
-- inainte:
(case when (lnInValuta = 1 or vd.id_valuta <> pack_def.GetIdMonedaNationala())
then ROUND(vc.curs * vd.pret / vc.multiplicator, lnPreciziePretV)
else vd.pret end) as pret_ron
-- dupa:
(case when lnInValuta = 1
then ROUND(NVL(vc.curs,1) * vd.pret / NVL(vc.multiplicator,1), lnPreciziePretV)
else vd.pret end) as pret_ron
```
Motivatie: pe un document `in_valuta=0`, `vd.pret` e prin definitie deja in RON (asa scrie
`ScrieArticoleFacturaEditate` liniile, indiferent de `id_valuta` afisat) — conversia n-are ce sa
faca acolo, indiferent ce `id_valuta` a ajuns (corect sau nu) pe linie. Pastrez `NVL(vc.curs,1)`
doar ca ultima plasa de siguranta pe ramura `lnInValuta=1` (document CHIAR in valuta) — daca
acolo lipseste cursul e o eroare de date reala care merita semnalata, nu ascunsa; de discutat cu
Marius daca `NVL(...,1)` e acceptabil acolo sau daca ar trebui sa opreasca salvarea cu eroare in
loc sa scrie o suma gresita tacut. **Nu propun `NVL` necondiționat pe ramura de conversie
reala** — ar masca o factura in valuta cu curs lipsa, scriind un total fals dar nenul, mai greu
de observat decat un NULL.
2. **Plasa finala de siguranta la UPDATE**, dupa modelul "fill-safe" deja folosit in
`ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, ca sa nu se mai poata suprascrie tacut o valoare
corecta cu NULL, indiferent ce cauza noua ar aparea in viitor:
```sql
update vanzari
set total_fara_tva = NVL(lnTotalFaraTVA, total_fara_tva),
total_tva = NVL(lnTotalTVA, total_tva),
total_cu_tva = NVL(lnTotalCuTVA, total_cu_tva),
...
where id_vanzare = V_ID_VANZARE;
```
Cu un `dbms_output`/log undeva (tabela de erori aplicative, daca exista una) cand recalculul
iese NULL, ca sa nu treaca neobservat un caz nou.
### Date (script propus, NU RULAT)
```sql
-- 1. Corectie punctuala pentru SSS 100037 (id_vanzare=1054): id_valuta-ul liniei 1602 revine la
-- RON, aliniat cu documentul (in_valuta=0) si cu linia-sursa din aviz (id_vanzare_det=1599,
-- care avea deja id_valuta=3).
UPDATE VANZARI_DETALII
SET ID_VALUTA = pack_def.GetIdMonedaNationala()
WHERE ID_VANZARE_DET = 1602
AND ID_VANZARE = 1054;
COMMIT;
-- 2. Retrigger recalcul dupa fix (acelasi apel pe care il face si formularul la salvare)
BEGIN
pack_facturare.recalculeaza_totaluri_vanzari(1054);
END;
/
COMMIT;
```
Restrans STRICT la acest document — cele 234 de alte randuri cu total NULL au alta cauza (§3) si nu
intra in scopul acestei reparatii. Daca se doreste o baleiere completa dupa ce fix-ul de la client
e livrat (sa nu mai apara alte cazuri noi), propun mai intai o interogare de monitorizare
periodica (varianta de query C/F de mai sus), nu o corectie automata in masa.
## Ce ramane NEVERIFICAT (runda 2)
- Cauza celor 114 documente "neexplicate" (mult mai vechi, posibil legate de
`discount_evidentiat IS NULL`) — iese din scopul cererii, semnalez doar ca exista.
- Daca `tvanz` e efectiv vizibil ca alias in contextul `ArticoleNotaEditor.ModificaNomenclator` la
momentul apelului (necesar pentru fix-ul de client propus) — de verificat la implementare, nu am
urmarit domeniul complet de vizibilitate al claselor `.vc2`.

View File

@@ -1,327 +0,0 @@
# R6 - butonul "Modificare articol" din frm_facturi + explicatie TVA - diagnostic
Raspuns la cerinta: pe formularul de facturare `frm_facturi` (lista facturi/avize/proforme, NU
`frm_modific2024`), butonul care edita azi `explicatie`+`taxcode` sa expuna si "explicatie TVA",
cu sincronizare automata `explicatie TVA -> taxcode`. Fiecare afirmatie e **VERIFICAT** (citit din
sursa, `fisier:linie`) - nu apare nimic marcat NESTABILIT, tot ce s-a cerut s-a putut confirma din
cod.
---
## A. Localizarea `frm_facturi`
Clasa `frm_facturi` (nu form `.scx` separat) - definita in
`COMUN\clase\ofacturare_comun.vc2:1168`:
```
DEFINE CLASS frm_facturi AS _frmbase OF "_frm_base.vcx"
```
Lant de mostenire: `frm_facturi -> _frmbase (_frm_base.vc2:7) -> _form (_baza.vc2:157) -> form`.
Instantiat din `roafacturare.prg` (meniul principal), nu are `.scx` propriu - toata definitia e in
binarul `ofacturare_comun.vcx`.
---
## B. Butonul "Modificare articol"
Obiect **`But_modifica2`**, definit in `frm_facturi` la `ofacturare_comun.vc2:1436-1444`:
```
ADD OBJECT 'But_modifica2' AS but_modifica WITH ;
Anchor = 12, ;
caction = do_modifica_explicatie, ;
Caption = "", ;
Left = 710, ;
Name = "But_modifica2", ;
TabIndex = 27, ;
ToolTipText = "Modificare explicatie articol", ;
Top = 341
*< END OBJECT: ClassLib="cmd_butoane.vcx" BaseClass="commandbutton" />
```
`Caption` e gol (e un buton cu picture, nu text); `ToolTipText` = "Modificare explicatie
articol". Clasa `but_modifica` (`cmd_butoane.vc2:184-197`) nu are `Click` propriu - mosteneste
`buton.Click` din `_cmd_base.vc2:41-71`, care e un dispatcher generic:
```
PROCEDURE Click
Local lcAction, lcCommand, lcListaParametri
lcAction = This.cAction
...
If Type('this.parent') = 'O' And Pemstatus(This.Parent,lcAction,5)
lcCommand = [this.Parent.] + lcAction + lcListaParametri
&lcCommand
Else
If Type('this.parent.PARENT') = 'O' And Pemstatus(This.Parent.Parent,lcAction,5)
lcCommand = [this.Parent.Parent.] + lcAction + lcListaParametri
&lcCommand
Else
If Pemstatus(Thisform,lcAction,5)
lcCommand = [thisform.] + lcAction + lcListaParametri
&lcCommand
Endif
Endif
Endif
ENDPROC
```
Cu `caction = do_modifica_explicatie` si parintele `But_modifica2` fiind direct `frm_facturi`
(fara container intermediar cu aceeasi metoda), rezultatul e efectiv `thisform.do_modifica_explicatie()`.
Corpul integral al metodei (`ofacturare_comun.vc2:4642-4659`):
```
PROCEDURE do_modifica_explicatie
If Reccount('crsDetalii') > 0
update_saft_taxtable()
Select crsDetalii
Scatter Name poRec Memo
poRec.explicatie = NVL(poRec.explicatie, ' ')
If poRec.sters = 0
ofrmmodificare = Createobject("frm_modifica_articol_factura")
ofrmmodificare.Show()
If gnButon = 1
Thisform.actualizeaza_grid2()
Endif
Endif
Endif
ENDPROC
```
---
## C. Ce deschide butonul
Un formular modal separat: **`frm_modifica_articol_factura`**
(`ofacturare_comun.vc2:5129-5253`), clasa `AS frm_termin_renunt OF "_frm_child.vcx"` (dialog
copil standard, cu `But_termin1`/`But_renunt1`). Titlu: `Lb_titlu_alb_b121.Caption = "Modifică
explicație articol"` (`:5163`).
Campuri editabile azi, cu `ControlSource` si linii:
| control | tip | ControlSource | label | linie |
|---|---|---|---|---|
| `Ed_tx_simplu1._edbase1` | editbox | `porec.explicatie` | "Explicație" (+ "( maxim N caractere )" adaugat in `Init`) | `:5213-5223` |
| `cbo_saft` | combobox | `poRec.taxcode` | "Cod taxa" (`lbSaft`) | `:5185-5199` |
`cbo_saft` e legat direct pe `poRec.taxcode` (BoundColumn=4), cu:
```
RowSource = "Select taxname, tip, procent_taxa, taxcode From saft_taxtable where tva = 1 order by taxcode Into Cursor crsTaxTableX"
```
adica lista tuturor codurilor SAF-T (cursor local `saft_taxtable`, populat de
`update_saft_taxtable()` apelat inainte de deschidere) - **nu exista azi nicio legatura cu
explicatia TVA**; userul alege direct un `taxcode` dintr-o lista plata de coduri fiscale.
Niciun alt camp (cantitate, pret, articol, id_jtva_coloana) nu e pe acest dialog.
---
## D. Cum se scrie inapoi - si confirmarea premisei "nu modifica valorile"
Salvare in `inainte_de_do_termin` (`ofacturare_comun.vc2:5225-5233`):
```
PROCEDURE inainte_de_do_termin
Local llReturn
If amessagebox("Doriti sa salvati modificarile?",4+32,"Confirmare salvare explicatie") = 6
lcSql = [begin pack_facturare.modifica_explicatie_articol(] + Alltrim(Str(poRec.id_vanzare_det)) + [,] + ;
['] + Nvl(Alltrim(OracleSpecialCharacters(poRec.explicatie)),'') + [',?gnIdUtil,?poRec.taxcode); end;]
llReturn = goExecutor.oExecuta(lcSql)
Else
llReturn = .F.
Endif
Return llReturn
ENDPROC
```
Scrierea e **direct in Oracle**, prin `pack_facturare.modifica_explicatie_articol(id_vanzare_det,
explicatie, id_util, taxcode)`. Cercetare anterioara deja confirmase corpul acestei proceduri
(`docs\cercetare\rec_cale_vanzari_detalii.md:195-199`, citat identic aici pentru trasabilitate):
```sql
UPDATE VANZARI_DETALII SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE
WHERE ID_VANZARE_DET = V_ID_VANZARE_DET
```
**Confirmat: doar 2 coloane se scriu** (`explicatie`, `taxcode`). Nu se ating `cantitate`, `pret`,
`pret_cu_tva`, `proc_tvav`, `id_jtva_coloana` si nu exista niciun apel de recalcul de totaluri
(`gcs.vact_tot`/`vrul_tot`, note contabile, rulaje) in acest flux - premisa utilizatorului
("campuri care nu modifica valorile") **e corecta pentru starea de azi a acestor doua campuri**.
---
## E. "Explicatie TVA" ca data + sincronizarea cu taxcode - sablonul deja aplicat in frm_modific2024
Campul din baza este **`id_jtva_coloana`** (nu `taxcode`) - nomenclator `vjtva_coloane`
(`id_jtva_coloana, denumire, cota_tva`). `taxcode` e un cod SAF-T/e-Factura separat, derivat din
`id_jtva_coloana` + context (partener, data, tip TVA), nu ales direct de nomenclatorul de
explicatii.
Sablonul de sincronizare cerut de utilizator **exista deja**, aplicat in runda 5 pe
`frm_modific2024` / `pgfArticole.PAGE3.grdArticoleFactura` (cursor `tvd`), documentat in
`docs\raport_runda5_omodificari.md` si `docs\propunere_runda4_note_sincronizare_tva.md` (sectiunea
5, "varianta B"). Cod citit direct din sursa curenta (`COMUN\programe\ofacturare_editare.prg`,
`ArticoleNotaEditor.ModificaNomenclator`, `:1100-1108`):
```
CASE m.lcCamp == 'id_jtva_coloana'
*!* cota vine din explicatia aleasa, ca la randul de nota; taxcode-ul SAF-T se recoreleaza dupa ea
REPLACE id_jtva_coloana WITH loCauta.id_jtva_coloana, ;
proc_tvav WITH (Nvl(loCauta.cota_tva, 0) + 100) / 100
This.oForm.UpdateExplicatieSAFTArt()
This.oForm.calculeaza_valori_articol()
```
`UpdateExplicatieSAFTArt` (`omodificari.vc2:15049-15074`) e "jumatatea taxcode" a sincronizarii -
deriva `taxcode` din `id_jtva_coloana` prin functia comuna `GetTaxCodeIdPart`:
```
PROCEDURE updateexplicatiesaftart
IF !m.gl406
RETURN
ENDIF
...
lnIdJtva = tvd.id_jtva_coloana
lnIdPart = Nvl(tAct.id_partc, tAct.id_partd)
...
lnTaxCode = GetTaxCodeIdPart(m.gnAn, m.gnLuna, m.ldDataAct, m.lnIdJtva, m.lnIdPart, m.llN50, m.llN100, m.llNeexigibil)
Replace taxcode With m.lnTaxCode In tvd
This.pgfArticole.PAGE3.grdArticoleFactura.cTaxcodeArt.Refresh()
ENDPROC
```
Relatia exacta explicatie TVA -> taxcode: **`id_jtva_coloana` (+ an/luna/data act/partener/steaguri
N50-N100/neexigibil) -> `GetTaxCodeIdPart()` -> `taxcode`**. Nu e un `Do Case` simplu pe
`id_jtva_coloana`; `GetTaxCodeIdPart` (functie globala, `COMUN\programe\oproceduri_comune.prg:6059-6125`,
NEschimbata pentru aceasta cerere) incapsuleaza toata logica SAF-T 406.
**Garda `gl406`**: `UpdateExplicatieSAFTArt` (si perechile ei `Rul`/simplu) nu fac nimic daca
`gl406 = .F.` (`COMUN\programe\oinit_optiuni.prg:524`, flag global de optiuni). Adica sincronizarea
taxcode <- explicatie TVA e activa doar cand raportarea SAF-T 406 e activata pentru firma curenta -
de verificat la implementare daca `frm_facturi` are acces la acelasi `gl406`.
---
## F. Cum se deschide azi nomenclatorul de explicatie TVA
Doua rutine **globale, refolosibile din orice formular** (nu legate de contextul lui
`frm_modific2024`):
1. **`caut_explicatie_tva(tnIdJtva, tnPornire, tlDesktop, tlTipEx, tnCotaTva)`**
(`COMUN\programe\ocautare.prg:3174-3238`) - deschide dialogul de cautare pe
`select id_jtva_coloana, denumire, cota_tva from vjtva_coloane`, filtrat implicit pe
`id_jtva_coloana > 0` (cand `tlTipEx` e gol) si, daca `tnCotaTva > 0`, suplimentar pe
`cota_tva = tnCotaTva` (`:3218-3220`, filtru adaugat tot in runda asta). Returneaza obiectul
`loCauta` din `cauta_alfa` (are `.id_jtva_coloana`, `.denumire`, `.cota_tva`).
2. **`GetTaxCodeIdPart(an, luna, data_act, id_jtva, id_part, n50, n100, neexigibil)`**
(`COMUN\programe\oproceduri_comune.prg:6059-6125`) - returneaza `taxcode`-ul SAF-T.
Apelul concret cu filtrul de cota (`ArticoleNotaEditor.CautaExplicatieTva`,
`ofacturare_editare.prg:1142-1147`):
```
PROCEDURE CautaExplicatieTva
LOCAL lnCotaFiltru
lnCotaFiltru = Round((Nvl(tvd.proc_tvav, 0) - 1) * 100, 2)
RETURN caut_explicatie_tva(Nvl(tvd.id_jtva_coloana, -1), , , , m.lnCotaFiltru)
ENDPROC
```
Ambele functii sunt `.prg`-uri globale incarcate `ADDITIVE` la pornirea aplicatiei (deci apelabile
si din `frm_facturi`) - **nu cer nicio extragere de cod**, doar apel direct.
---
## G. `frm_facturi` deja afiseaza explicatia TVA - doar needitabil
Grila `grid_detalii` din `frm_facturi` are deja doua coloane needitabile pentru asta
(`ofacturare_comun.vc2:1679-1687`):
```
Column24.ControlSource = "jtva_coloana", ;
Column24.Name = "cExplicatieTVA", ;
Column24.ReadOnly = .T., ;
...
Column25.ControlSource = "jtva_coloana_ex", ;
Column25.Name = "cExplicatieTVAEx", ;
Column25.ReadOnly = .T., ;
```
cu header-ele "Explicatie TVA" / "Explicatie TVA 2" (`:1920-1946`). `jtva_coloana`/`jtva_coloana_ex`
sunt (dupa tiparul din restul fisierului, ex. `:2107-2109`) denumirile derivate din
`jtva_coloane`/`jtva_coloane_explicatii`, nu ID-uri brute - afisare, nu editare. Nu exista alt
traseu in `frm_facturi` unde utilizatorul alege interactiv o explicatie TVA (nu la adaugare de
linie - acest formular nu are adaugare de linii, e formular de listare/editare metadate, nu de
compunere a facturii).
Concluzie G: campul exista deja ca **read-only** in grid; nu trebuie adaugata coloana in grid,
doar expunerea editabila in dialogul de modificare (punctul C).
---
## H. Riscul - taxcode NU e neutru daca se copiaza tot sablonul
**Rezultat cheie, marcat apasat:** sablonul din E (runda 5, `frm_modific2024`) **nu e neutru
valoric**. La alegerea unei explicatii TVA, pe langa `taxcode`, se rescrie explicit **`proc_tvav`**
(cota TVA a liniei) si se cheama **`calculeaza_valori_articol()`**, care recalculeaza valoarea
liniei si bara de totaluri (`docs\raport_runda5_omodificari.md`, linia 41 din tabelul de apeluri
`ActualizeazaBaraTotaluri`, si citatul de la punctul E de mai sus). Adica in acel context,
"explicatie TVA" **schimba** valorile documentului daca explicatia aleasa are alta cota decat cea
curenta - filtrul de cota din `caut_explicatie_tva` (punctul F) doar restrange lista, nu blocheaza
o alegere cu cota diferita daca userul o cauta explicit sau daca linia are `proc_tvav` gol/0
(caz in care filtrul nu se aplica deloc, `ocautare.prg:3218`: `If ... tnCotaTva > 0`).
Pentru `taxcode` insusi, in fluxul actual: `taxcode` **nu e citit nicaieri in calculul
totalurilor VFP** - cautare in `COMUN\programe\ofacturare_comun.prg` si in schema campurilor din
`ofacturare_comun.vc2` (punctul D) nu arata `taxcode` folosit in nicio expresie de `valctva`,
`valtva`, `total_cu_tva` sau similar; acelea se calculeaza din `proc_tvav`/`pret`/`cantitate`,
coloane distincte. `taxcode` e strict o eticheta de raportare SAF-T/e-Factura, stocata pe linie.
**Deci verdictul depinde strict de ce se implementeaza:**
- **Daca se reface `pack_facturare.modifica_explicatie_articol` sa scrie DOAR `id_jtva_coloana`
(nou parametru) + `taxcode` derivat, FARA sa atinga `proc_tvav`** - si daca dialogul de
`frm_facturi` foloseste `caut_explicatie_tva` cu filtrul de cota calculat din
`poRec.proc_tvav` curent (ca sa nu se poata alege o explicatie de alta cota) - atunci
operatia ramane neutra valoric, exact ce cere utilizatorul. Aceasta e o **varianta redusa**
a sablonului din E, nu o copiere 1:1.
- **Daca se copiaza sablonul din E exact cum e** (inclusiv rescrierea `proc_tvav` +
recalcul) - operatia **NU mai e neutra**: ar schimba cota TVA si valorile unei facturi
**deja emise**, fara mecanismul de recalcul de totaluri/note/rulaje care exista in
`frm_modific2024` (acolo editarea e pe cursoare locale, salvata printr-un flux dedicat de
recalcul; `frm_facturi` nu are asa ceva - e un UPDATE punctual pe o singura linie, deja
emisa). Risc fiscal si de coerenta (SAF-T/e-Factura ar raporta un `taxcode` care nu mai
corespunde cu `proc_tvav`/totalurile deja emise, sau invers, daca doar taxcode se
sincronizeaza fara proc_tvav).
**Recomandare de proiectare** (schita, fara cod aplicat): varianta redusa de mai sus -
dialog `frm_modifica_articol_factura` capata un al treilea camp (combo pe explicatie TVA,
`caut_explicatie_tva` filtrat pe cota curenta a liniei, ca in `CautaExplicatieTva`), la alegere
se recalculeaza doar `taxcode` local (`GetTaxCodeIdPart`, acelasi apel ca `UpdateExplicatieSAFTArt`
dar fara `REPLACE proc_tvav`), iar la salvare `pack_facturare.modifica_explicatie_articol` capata
un parametru nou `id_jtva_coloana` (schimbare PL/SQL, in afara acestui repo VFP - de coordonat
separat) pe langa `explicatie`/`taxcode` existente. Cod nou estimat: mic pe partea VFP (~30-40
linii: un combo nou + un handler de alegere + un parametru in apelul SQL), dar cere **schimbare de
pachet Oracle** (`pack_facturare.modifica_explicatie_articol`) care nu exista in acest working
copy si trebuie facuta/aprobata separat in `DATABASE`.
---
## Rezumat surse
| ce | fisier:linie |
|---|---|
| clasa `frm_facturi` | `COMUN\clase\ofacturare_comun.vc2:1168` |
| butonul `But_modifica2` | `COMUN\clase\ofacturare_comun.vc2:1436-1444` |
| dispatcher `buton.Click` | `COMUN\clase\_cmd_base.vc2:41-71` |
| `do_modifica_explicatie` | `COMUN\clase\ofacturare_comun.vc2:4642-4659` |
| dialog `frm_modifica_articol_factura` | `COMUN\clase\ofacturare_comun.vc2:5129-5253` |
| salvare (`inainte_de_do_termin`) | `COMUN\clase\ofacturare_comun.vc2:5225-5233` |
| grid `cExplicatieTVA`/`cExplicatieTVAEx` (read-only) | `COMUN\clase\ofacturare_comun.vc2:1679-1687`, `:1920-1946` |
| sablon sincronizare (runda 5) | `COMUN\programe\ofacturare_editare.prg:1100-1147`, `COMUN\clase\omodificari.vc2:15049-15074` |
| `caut_explicatie_tva` | `COMUN\programe\ocautare.prg:3174-3238` |
| `GetTaxCodeIdPart` | `COMUN\programe\oproceduri_comune.prg:6059-6125` |
| `gl406` | `COMUN\programe\oinit_optiuni.prg:524` |
| procedura Oracle (citata din cercetare anterioara) | `docs\cercetare\rec_cale_vanzari_detalii.md:195-199` |

View File

@@ -1,236 +0,0 @@
# Cercetare: eticheta care duce la editarea facturii emise
Sarcina: gasirea locului (locurilor) unde apare textul care duce la editarea facturii emise
(formularul `frm_modific2024`, `COMUN\clase\omodificari.vc2`), pentru a evalua propunerea
utilizatorului de a-l redenumi in **"editare factura (note, rulaje, articole)"**.
Concluzie scurta: **nu exista un bar de meniu `.mnx` clasic** cu acest text. Punctul real de
intrare e un **popup dinamic construit din `xmenu(...)` intr-un fisier `.vc2`** (deci
write-back-abil prin `txt2vcx.ps1`, nu editare manuala in IDE cum s-a presupus initial in brief).
Vezi punctul C pentru corectia asta.
---
## A. Toate locurile unde apare eticheta / unde s-ar putea afla
Cautari facute (rezultate zero pentru textul cerut in `.mnx`/`.mn2`):
- `grep -in "modific\|editeaz\|editare" Meniuri\vanzare1.mn2 ... vanzare5.mn2` -> **zero hit-uri**.
- `grep -ln "modific\|editeaz\|editare" Meniuri\*.mn2 COMUN\meniuri\*.mn2` -> doar
`Meniuri\model.mn2` si `Meniuri\roafacturare.mn2`, ambele **irelevante**:
- `Meniuri\roafacturare.mn2:50` — `DEFINE BAR 3 OF Ajutor PROMPT "\<Jurnal modificari"` (jurnal
de audit al aplicatiei, nu editare factura).
- `Meniuri\model.mn2:14` — `DEFINE BAR 2 OF Shortcut PROMPT "\<Modificare raport"` (editare
raport definit de utilizator, nu factura).
- Niciun `.mnx`/`.mn2` din proiect sau din `COMUN\meniuri\` nu contine referinta la
`frm_modific2024` sau `ofacturare_editare` (verificat cu grep pe toate cele 43 de fisiere unde
apare `frm_modific2024` — niciunul e `.mn2`).
**Locul real** e un buton de toolbar generic + un popup `xmenu()` construit in cod, in
`COMUN\clase\ofacturare_comun.vc2`, pe formularul `frm_facturi` (lista de facturi emise):
1. **Butonul de toolbar** — clasa `but_modifica`, definita generic in
`COMUN\clase\cmd_butoane.vc2:182-197`:
```
DEFINE CLASS but_modifica AS buton OF "_cmd_base.vcx"
caction = inainte_de_do_modifica
...
ToolTipText = "Modificare (CTRL+M)"
```
Acest buton e adaugat **direct in clasa de baza a tuturor formularelor**,
`COMUN\clase\_frm_base.vc2:1103` (`ADD OBJECT 'But_modifica1' AS but_modifica ...`), deci apare
pe **toate** formularele-lista din suita ROA (parteneri, personal, stocuri, note contabile,
facturi etc.), nu doar pe cel de facturi. Textul lui (`"Modificare (CTRL+M)"`) e generic si
hardcodat, nu vine din `Locale\` (vezi punctul C).
2. **Popup-ul specific facturii** — `frm_facturi` (definit in `ofacturare.vcx`/`ofacturare_comun.vc2`)
suprascrie handler-ul butonului cu propriul sau meniu contextual:
```
COMUN\clase\ofacturare_comun.vc2:4928-4937
PROCEDURE inainte_de_do_modifica
Local lnOptiune
lnOptiune = xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')
Do Case
Case lnOptiune = 1
This.do_modifica()
Case lnOptiune = 2
This.do_editare_factura()
Endcase
ENDPROC
```
Textul exact de azi al liniei (verificat byte cu byte, ASCII curat, fara diacritice corupte):
`xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')` —
`COMUN\clase\ofacturare_comun.vc2:4930`.
Deci utilizatorul care apasa butonul "Modificare (CTRL+M)" pe lista de facturi vede un
mini-meniu cu **doua optiuni**:
- "Modificare date factura" (accelerator D) → `This.do_modifica()` — ruta generica
`afisjurcom.do_modifica` din `comun.vc2:2222-2572` (aceeasi metoda partajata de foarte multe
tipuri de documente din suita, cu ramuri Do Case pentru fiecare).
- "Editare factura (articole, cantitati, preturi)" (accelerator F) →
`This.do_editare_factura()` — **asta e ruta directa catre `frm_modific2024`**.
## B. Lantul real: eticheta -> comanda -> `frm_modific2024`
```
frm_facturi (lista facturi emise)
-> but_modifica1 (mostenit din _frm_base, ToolTipText "Modificare (CTRL+M)")
-> click -> inainte_de_do_modifica() [ofacturare_comun.vc2:4928]
-> xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')
[ofacturare_comun.vc2:4930]
-> optiunea 2 -> This.do_editare_factura() [ofacturare_comun.vc2:3715]
-> seteaza tipul, verifica drepturi (This.lactiv3), Reccount('crsfacturi')...
-> Omodif = Createobject([frm_modific2024], lnIdSet) [ofacturare_comun.vc2:3796]
-> Omodif.Show()
```
Confirmare independenta a clasei tinta prin `vfp_symbols.ps1 -Where "ofacturare_comun.vc2:4935"`:
`frm_facturi.inainte_de_do_modifica [method] ofacturare_comun.vc2:4928-4937`.
**Alte cai catre acelasi formular**, gasite prin `grep -rn frm_modific2024` (43 fisiere), toate
insa in alt context, nu din meniul principal de facturare al utilizatorului final:
- `COMUN\clase\comun.vc2:2439` — `afisjurcom.do_modifica` (metoda generica, ramura
`IF gnAn >= ... ELSE Createobject([frm_modific])`) — e ruta **veche/generica** de "nota
contabila", partajata cu toate produsele ROA (nu specifica facturii).
- `COMUN\clase\anaf_efactura.vc2:13087` — deschidere `frm_modific2024` din fluxul e-Factura ANAF
(verificare/validare, nu din butonul de lista).
- `COMUN\ferestre\frm_import_note_facturi_clienti.sc2:898` si
`COMUN\ferestre\frm_initializare_facturi_balanta.sc2:1806` — fluxuri de import/initializare,
nu editarea unei facturi deja emise, deci **nu intra in scopul cerut**.
Pentru cererea utilizatorului ("editarea facturii emise"), **singurul loc relevant si cu text
apropiat de ce isi doreste** este `ofacturare_comun.vc2:4930` (optiunea 2 din popup).
## C. Cum se schimba textul in practica
- **Nu e in `.mnx`/`.mn2`.** Nu exista un `DEFINE BAR ... PROMPT` cu acest text nicaieri in
proiect. Deci nu e nevoie de editare manuala in IDE-ul de meniuri.
- **E literal, hardcodat intr-o metoda `.vc2`**: `COMUN\clase\ofacturare_comun.vc2:4930`, in
interiorul clasei `frm_facturi`, metoda `inainte_de_do_modifica`. Fisierul `.vc2` **este**
write-back-abil prin `txt2vcx.ps1` (regula generala din `CLAUDE.md`: doar `.vc2`/`.sc2` accepta
scriere inapoi in binar). Deci schimbarea propusa de utilizator **se poate face prin editare
text + `txt2vcx.ps1`**, nu necesita interventie manuala in IDE.
- Text propus de utilizator ca inlocuitor pentru optiunea 2: in loc de
`"Editare \<factura (articole, cantitati, preturi)"` ar deveni ceva de forma
`"Editare \<factura (note, rulaje, articole)"` — pastrand acceleratorul `\<F`. (Nu am facut
modificarea — doar raportez unde s-ar face, conform interdictiilor primite.)
- **`ToolTipText = "Modificare (CTRL+M)"`** din `cmd_butoane.vc2:194` **NU trebuie schimbat** — e
folosit de zeci de formulare din toata suita ROA (am numarat >100 instantieri `but_modifica` in
`.vc2`/`.sc2`, majoritatea in `onomenclatoare.vc2`, `onom_sal.vc2`, `oparteneri.vc2` etc.,
niciunul specific facturii). Un text de forma "editare factura..." pus acolo ar minti pe toate
celelalte ecrane (parteneri, personal, stocuri...).
- **Nu exista mecanism `.mnx` compilat separat de regenerat** pentru aceasta schimbare — nefiind
vorba de un `.mnx`, nu se aplica `git_sync.ps1`/recompilare de meniu; e nevoie doar de rebuild-ul
normal al proiectului dupa modificarea `.vcx` (Project > Build din VFP IDE), conform fluxului
standard descris in `CLAUDE.md`.
## C-bis. Localizare (`Locale\`)
- Directorul `Locale\` **exista dar e complet gol** in aceasta copie de lucru
(`find D:\ROA\ROAFACTURARE\Locale -type f` → 0 fisiere). Nu exista tabele DBF de traducere
incarcate aici.
- Nu exista niciun apel `Traduc(...)`/`goApp.Traduc(...)` in jurul `ToolTipText`-ului `but_modifica`
sau al liniei `xmenu(...)` de mai sus — am cautat in `cmd_butoane.vc2` si `_frm_base.vc2`
(`grep -n "Traduc\b"` → zero hit-uri in ambele). Singura potrivire pentru "traduc" in tot
proiectul e in `comun.vc2:2157-2158`, cod comentat (`*!* IF THIS.filtru_traducere()#""`),
nefolosit si nelegat de acest buton.
- **Concluzie**: textul e o constanta Romana hardcodata direct in `.vc2`, nu vine din nicio sursa
de traducere. Nu exista alte limbi de verificat pentru acest text.
## D. Captions reale ale paginilor din `frm_modific2024`
Premisa utilizatorului ("trei pagini: Note, Rulaje si Articole") **nu se potriveste exact cu
Caption-urile din formular**. Am verificat cu:
```
vfp_symbols.ps1 -Find frm_modific2024 -> class frm_modific2024 omodificari.vc2:6375-16836
awk 'NR==6375,NR==16836' omodificari.vc2 | grep -n "AS _pageframe\|PAGE.\.Caption"
```
Rezultat: **exista un singur `PageFrame`** in formular, `pgfArticole`
(`omodificari.vc2:8714-8732`, adica linia absoluta ≈ `6375+2341` in indexare relativa), cu
**3 pagini**, niciuna captionata "Note":
```
COMUN\clase\omodificari.vc2:8725 PAGE1.Caption = "Rulaje materii prime, materiale, marfuri, obiecte inventar (303)"
COMUN\clase\omodificari.vc2:8728 PAGE2.Caption = "Rulaje obiecte inventar in folosinta (8039)"
COMUN\clase\omodificari.vc2:8731 PAGE3.Caption = "Articole factura"
```
Nu exista nicio pagina/tab captionat literal **"Note"**. Zona de "note" (liniile notei contabile —
coloane precum "Explicatie C", "Cont", "Cota TVA" etc., verificate prin
`grep -n "Caption = \"" ` in intervalul clasei) e **corpul principal al formularului** (grid-ul de
baza `grid1`), nu o pagina din pageframe. Deci structura reala e:
- corp principal (nepaginat) = liniile notei contabile ("Note" e denumirea informala folosita in
discutii, nu un Caption efectiv);
- `pgfArticole.PAGE1` + `PAGE2` = doua variante de "Rulaje" (materiale 303 / obiecte inventar 8039);
- `pgfArticole.PAGE3` = "Articole factura".
Titlul formularului insusi e generic: `Lb_titlu_alb_b121.Caption = "MODIFICARE"`
(`omodificari.vc2:6934`, in Init-ul clasei `frm_modific2024`), nu mentioneaza nici el "factura" sau
"note/rulaje/articole".
**Recomandare pentru textul de meniu**: formularea "note, rulaje, articole" e conceptual corecta
(reflecta cele 3 zone reale), dar nu e un citat al Caption-urilor efective de pe ecran — daca se
doreste consistenta stricta cu ce vede utilizatorul, ar trebui fie sa se ajusteze si Caption-urile
paginilor (schimbare separata, in afara scopului acestei cercetari), fie sa se accepte ca eticheta
de meniu descrie zonele functional, nu litera Caption-urilor.
## E. Pagina Articole e conditionata
**Da, e strict conditionata** — nu apare intotdeauna. Dovezi:
```
COMUN\clase\omodificari.vc2:14927 This.lAreArticoleVanzari = .F.
COMUN\clase\omodificari.vc2:14928 IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Reccount('tact') > 0
COMUN\clase\omodificari.vc2:14929 IncarcaVanzareDinNota('tact')
COMUN\clase\omodificari.vc2:14930 IF Reccount('tvanz') = 1
COMUN\clase\omodificari.vc2:14931 This.lAreArticoleVanzari = .T.
COMUN\clase\omodificari.vc2:14932 This.nIdVanzare = tvanz.id_vanzare
COMUN\clase\omodificari.vc2:14933 This.nTipVanzare = tvanz.tip
COMUN\clase\omodificari.vc2:14934 This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))
COMUN\clase\omodificari.vc2:14935 ENDIF
COMUN\clase\omodificari.vc2:14936 ENDIF
...
COMUN\clase\omodificari.vc2:14941 IF This.lAreArticoleVanzari
COMUN\clase\omodificari.vc2:... This.pgfArticole.PageCount = 3
COMUN\clase\omodificari.vc2:... ELSE
COMUN\clase\omodificari.vc2:14958 This.pgfArticole.PageCount = 2
COMUN\clase\omodificari.vc2:14959 This.but_nouR.Enabled = .F.
COMUN\clase\omodificari.vc2:14960 ENDIF
```
Conditia exacta pentru ca pagina "Articole factura" sa fie vizibila:
1. formularul a fost deschis din contextul de editare factura (`"OFACTURARE_EDITARE" $
Upper(Set("Procedure"))` — adica din lantul `frm_facturi -> do_editare_factura`, nu din ruta
generica `do_modifica`);
2. nota are cel putin o linie de tip articol (`Reccount('tact') > 0`);
3. exista **exact o** vanzare (factura) legata de nota (`Reccount('tvanz') = 1`).
Daca oricare din conditii lipseste, `pgfArticole.PageCount = 2` — pagina "Articole factura" **nu
apare deloc**, ramanand doar cele doua pagini de "Rulaje". In plus, chiar cand pagina apare, poate
fi **doar-citire** (`This.lArticoleReadOnly = EsteInEFactura(...)`) daca factura a fost deja
trimisa in e-Factura ANAF (`omodificari.vc2:14934`, plus dezactivari vizibile in
`omodificari.vc2:14943-14947`: `but_nouR.Enabled`, `txtDiscountArt.ReadOnly`,
`lblArticoleReadOnly.Visible`, `cmdSincronizeazaArticole.Enabled`).
**Implicatie pentru textul de meniu**: un text care promite mereu "articole" (ex. "editare factura
(note, rulaje, articole)") poate **minti in unele cazuri** — cand nota nu are exact o vanzare
asociata sau nu are linii de tip articol, utilizatorul nu vede pagina de articole deloc, desi
eticheta i-a promis-o. E o observatie de continut, nu un blocaj — decizia de a accepta acest risc
(eticheta descrie "ce se poate edita in general", nu "ce va vedea de fiecare data") apartine
utilizatorului/echipei.
## Sumar raspunsuri la intrebarile din brief
| Intrebare | Raspuns |
|---|---|
| Exista eticheta `.mnx` literala? | **NU** — cautare completa in toate `.mn2`-urile proiectului si `COMUN\meniuri\`, zero hit-uri pe "editare factura" sau variante |
| Unde e reala eticheta? | `COMUN\clase\ofacturare_comun.vc2:4930`, in `xmenu()`, clasa `frm_facturi`, metoda `inainte_de_do_modifica` |
| Textul de azi | `Editare \<factura (articole, cantitati, preturi)` |
| Vine din `Locale\`? | Nu — `Locale\` e goala, nu exista apel `Traduc()` in jurul acestui text |
| Se schimba in `.mnx` sau in cod? | In cod (`.vc2`), write-back prin `txt2vcx.ps1` — **nu** necesita editare manuala in IDE |
| Paginile se numesc Note/Rulaje/Articole? | Nu literal — un singur pageframe cu 3 pagini: "Rulaje materii prime..." / "Rulaje obiecte inventar..." / "Articole factura"; zona de note e corpul principal, nepaginat |
| Pagina Articole e mereu prezenta? | Nu — conditionata de 3 conditii (`omodificari.vc2:14927-14936`), poate lipsi sau fi doar-citire |
Nimic din NESTABILIT — toate afirmatiile au dovada `fisier:linie` verificata direct in text.

View File

@@ -1,255 +0,0 @@
# Cercetare R6 — nomenclatoare in gridul Articole (frm_modific2024)
Zona: `COMUN\clase\omodificari.vc2` (formularul `frm_modific2024`), gridul
`pgfArticole.PAGE3.grdArticoleFactura`. Dispecerul comun e in
`COMUN\programe\ofacturare_editare.prg`, clasa `ArticoleNotaEditor`.
## A. Inventarul coloanelor gridului Articole
Bloc `ADD OBJECT 'pgfArticole.PAGE3.grdArticoleFactura'` incepe la
`omodificari.vc2:12330`; lista completa a coloanelor (`ControlSource` + `Name`) e la
`omodificari.vc2:12349-12471`.
| Coloana (Name) | ControlSource | Linie ControlSource | Legata de nomenclator? |
|---|---|---|---|
| cDenumireArt (Column1) | `tvd.denumire` | `omodificari.vc2:12349` | DA — articol |
| cCodmatArt (Column2) | `tvd.codmat` | `omodificari.vc2:12356` | nu |
| cSerieArt (Column3) | `tvd.serie` | `omodificari.vc2:12363` | nu |
| cLotArt (Column4) | `tvd.lot` | `omodificari.vc2:12370` | nu |
| cCantitateArt (Column5) | `tvd.cantitate` | `omodificari.vc2:12377` | nu |
| cPretArt (Column6) | `tvd.pret` | `omodificari.vc2:12386` | nu |
| cPretAchizitieArt (Column7) | `tvd.pret_achizitie` | `omodificari.vc2:12395` | nu |
| cPretCuTvaArt (Column8) | `tvd.pret_cu_tva` | `omodificari.vc2:12404` | nu (checkbox propriu) |
| cProcTvavArt (Column9) | `tvd.proc_tvav` | `omodificari.vc2:12412` | nu |
| cDiscountUnitarArt (Column10) | `tvd.discount_unitar` | `omodificari.vc2:12421` | nu |
| **cGestiuneArt (Column11)** | `tvd.nume_gestiune` | `omodificari.vc2:12430` | **DA — gestiune** |
| **cValutaArt (Column12)** | `tvd.nume_val` | `omodificari.vc2:12437` | **DA — valuta** |
| cExplicatieArt (Column13) | `tvd.explicatie` | `omodificari.vc2:12444` | nu (text liber) |
| **cTaxcodeArt (Column14)** | `tvd.taxcode` | `omodificari.vc2:12451` | **DA — cod TVA SAF-T** |
| cValoareArt (Column15) | `tvd.valoare` | `omodificari.vc2:12458` | nu (calculat) |
| **cExplicatieTvaArt (Column16)** | `Iif(Seek(Nvl(tvd.id_jtva_coloana,0),'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')` | `omodificari.vc2:12467` | **DA — explicatie TVA / id_jtva_coloana** |
Cele 5 coloane legate de un nomenclator sunt exact cele acceptate de dispecer —
vezi `ArticoleNotaEditor::AreNomenclator` (`ofacturare_editare.prg:978-981`):
```
PROCEDURE AreNomenclator
LPARAMETERS tcControl
RETURN Inlist(This.NumeCamp(m.tcControl), 'denumire', 'nume_gestiune', 'nume_val', 'taxcode', 'id_jtva_coloana')
ENDPROC
```
Observatie importanta pentru cauza de la punctul D: **`cTaxcodeArt` si `cExplicatieTvaArt` au
Column.ReadOnly = .F.** (`omodificari.vc2:12379` / linia `Column14.ReadOnly = .F.` la
`omodificari.vc2` in blocul 12451-12457, respectiv `Column16.ReadOnly = .F.` in blocul
12467-12471), dar **controlul `Text1` din interiorul lor are `ReadOnly = .T.`** — vezi
`omodificari.vc2:12759` (cTaxcodeArt.Text1) si `omodificari.vc2:12594` (cExplicatieTvaArt.Text1).
Diferenta reala dintre ele nu e ReadOnly-ul (identic), ci **daca `ControlSource` e camp direct
sau expresie** — vezi punctul D.
## B. Ce evenimente exista azi pe fiecare coloana cu nomenclator
| Coloana | InteractiveChange | DblClick | Buton lateral "Modificare" (`But_modificaR`) |
|---|---|---|---|
| cDenumireArt | DA — `omodificari.vc2:16710-16714` | **nu exista** | DA (comun, vezi E) |
| cGestiuneArt | DA — `omodificari.vc2:16739-16743` | **nu exista** | DA (comun) |
| cValutaArt | DA — `omodificari.vc2:16826-16830` | **nu exista** | DA (comun) |
| cTaxcodeArt | DA — `omodificari.vc2:16815-16819` | **nu exista** | DA (comun) |
| cExplicatieTvaArt | DA — `omodificari.vc2:16728-16732` (exista, dar nu se declanseaza — vezi D) | **nu exista** | DA (comun) |
Corpul e identic pentru toate cele 5 (exemplu, taxcode):
```
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cTaxcodeArt.Text1.InteractiveChange
Local loEditor
loEditor = Createobject('ArticoleNotaEditor', Thisform)
loEditor.ModificaNomenclator(Thisform.pccontrol)
ENDPROC
```
Confirmare ca `DblClick` nu exista deloc pe `grdArticoleFactura` sau pe coloanele lui: cautare
`InteractiveChange|DblClick` in tot `omodificari.vc2` — singurele `DblClick` din fisier sunt pe
alt grid, `grdRequest` (`omodificari.vc2:390` si `omodificari.vc2:408`, vezi F).
## C. Dispecerul comun — `ArticoleNotaEditor::ModificaNomenclator`
Definit in `COMUN\programe\ofacturare_editare.prg:1069-1137`, clasa `ArticoleNotaEditor`
(`ofacturare_editare.prg:947-1165`).
Semnatura: `PROCEDURE ModificaNomenclator LPARAMETERS tcControl` — primeste un string de forma
`alias.camp` (ex. `tvd.taxcode`, `tvd.id_jtva_coloana`).
Garda de intrare (`ofacturare_editare.prg:1073-1076`):
```
lcCamp = This.NumeCamp(m.tcControl)
IF !This.Editabil() OR Reccount('tvd') = 0 OR !This.AreNomenclator(m.lcCamp) OR Nvl(tvd.id_vanzare_set, 0) <> 0
RETURN .F.
ENDIF
```
`Do Case` de deschidere a nomenclatorului (`ofacturare_editare.prg:1078-1089`):
```
DO CASE
CASE m.lcCamp == 'denumire'
loCauta = caut_articol()
CASE m.lcCamp == 'nume_gestiune'
loCauta = caut_gestiune()
CASE m.lcCamp == 'nume_val'
loCauta = caut_valuta()
CASE m.lcCamp == 'id_jtva_coloana'
loCauta = This.CautaExplicatieTva()
OTHERWISE
loCauta = This.CautaCodTva()
ENDCASE
```
Discrimineaza dupa **numele campului** extras din `tcControl` (nu dupa numele coloanei din grid,
nu dupa un parametru separat) — `NumeCamp` (`ofacturare_editare.prg:966-974`) taie tot ce e
inainte de primul `.`. `taxcode` cade pe ramura `OTHERWISE` (`CautaCodTva`,
`ofacturare_editare.prg:1152-1158`).
Dupa alegere, al doilea `Do Case` (`ofacturare_editare.prg:1096-1132`) scrie valorile alese
inapoi in `tvd`, cu ramura dedicata pentru `id_jtva_coloana`
(`ofacturare_editare.prg:1124-1129`, recalculeaza `proc_tvav` si cheama
`This.oForm.UpdateExplicatieSAFTArt()` + `calculeaza_valori_articol()`).
## D. De ce "explicatie TVA" nu se declanseaza pe InteractiveChange
**Nu e o garda care blocheaza si nu e o ramura lipsa in dispecer** — `id_jtva_coloana` e in
`AreNomenclator` (`ofacturare_editare.prg:980`) si are ramura proprie in `Do Case`
(`ofacturare_editare.prg:1085-1086`). Handlerul `InteractiveChange` exista si e corect
(`omodificari.vc2:16728-16732`).
**Cauza e ca evenimentul `InteractiveChange` pur si simplu nu se produce niciodata din tastare
pe aceasta coloana**, pentru ca `ControlSource`-ul ei e o expresie, nu un camp direct:
```
Column16.ControlSource = "Iif(Seek(Nvl(tvd.id_jtva_coloana,0),'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')"
```
(`omodificari.vc2:12467`)
versus `cTaxcodeArt`:
```
Column14.ControlSource = "tvd.taxcode"
```
(`omodificari.vc2:12451`)
In VFP, un `Textbox` intr-o coloana de grid cu `ReadOnly = .T.` (asa cum sunt ambele,
`omodificari.vc2:12759` si `omodificari.vc2:12594`) nu accepta editare directa de text, dar cand
`Column.ControlSource` e un **camp real dintr-un cursor navigabil** ("bound field"), gridul
VFP declanseaza cautarea incrementala nativa la tastare (type-ahead / seek pe recordset): tasta
apasata muta recordul curent la prima potrivire, ceea ce schimba valoarea afisata legata de acel
camp — iar aceasta schimbare de `Value` e cea care declanseaza `InteractiveChange`. Cand
`ControlSource` e o **expresie** (ca la `cExplicatieTvaArt`, care afiseaza denumirea din
`crsJtvaTemp` via `Seek`, nu campul `tvd.id_jtva_coloana` in sine), gridul nu are un camp
indexabil pe care sa faca seek — mecanismul de cautare incrementala nu se activeaza, deci
`Value`-ul textboxului nu se schimba niciodata din tastare, deci `InteractiveChange` nu are cum
sa se declanseze prin tastare pe aceasta coloana.
Chiar comentariul din cod confirma asta, la `GotFocus`-ul aceleiasi coloane
(`omodificari.vc2:16723`):
```
*!* coloana afiseaza denumirea din crsJtvaTemp, deci ControlSource e expresie - campul editat se da explicit
```
adica autorul stia deja ca `ControlSource` e o expresie si de-aia a scris explicit campul real
(`tvd.id_jtva_coloana`) in `Thisform.pccontrol` la `GotFocus`, in loc sa se bazeze pe
`This.ControlSource` (cum fac celelalte coloane). Explicatia asta e chiar mecanismul care face ca
butonul lateral sa functioneze — vezi E.
Dublu-click nu se declanseaza pe nicio coloana pentru ca **pur si simplu nu exista niciun handler
`DblClick`** pe `grdArticoleFactura` (vezi B) — nu e o problema specifica de "explicatie TVA", e
valabil identic si pentru `taxcode`, doar ca la `taxcode` utilizatorul are alternativa tastarii,
care functioneaza (camp direct).
## E. Cum ajunge butonul lateral "Modificare" sa deschida explicatie TVA
Handler: `PROCEDURE But_modificaR.Click`, `omodificari.vc2:15319-15326`:
```
PROCEDURE But_modificaR.Click
Local lcRul, loEditor
IF thisform.pgfArticole.ActivePage = 3
loEditor = Createobject('ArticoleNotaEditor', Thisform)
loEditor.ModificaNomenclator(thisform.pccontrol)
RETURN
ENDIF
...
ENDPROC
```
Cheia e ca butonul **nu citeste `ControlSource`-ul coloanei curente** — foloseste direct
`Thisform.pccontrol`, o proprietate a formularului scrisa de fiecare `GotFocus` al coloanei
active, inainte ca utilizatorul sa apuce sa tasteze ceva. Pentru `cExplicatieTvaArt`, `GotFocus`
scrie explicit campul real (`omodificari.vc2:16722-16726`):
```
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cExplicatieTvaArt.Text1.GotFocus
*!* coloana afiseaza denumirea din crsJtvaTemp, deci ControlSource e expresie - campul editat se da explicit
Thisform.pccontrol = 'tvd.id_jtva_coloana'
Thisform.pncolumnorder = This.Parent.ColumnOrder
ENDPROC
```
Pentru celelalte 4 coloane, `GotFocus` foloseste `Alltrim(This.ControlSource)` direct (ex.
`cTaxcodeArt.Text1.GotFocus`, `omodificari.vc2:16810-16813`), ceea ce functioneaza pentru ele
pentru ca acolo `ControlSource` chiar e campul real.
Deci calea functioneaza pentru ca **`GotFocus` — care se declanseaza la orice intrare in celula,
prin click simplu, tab sau navigare cu sagetile, indiferent de tipul de `ControlSource` — a
populat deja `Thisform.pccontrol` corect inainte ca butonul sa fie apasat.** Butonul nu depinde
deloc de `InteractiveChange`; de-aia merge la explicatie TVA desi tastarea nu merge.
## F. Cost estimat pentru DblClick pe fiecare coloana cu nomenclator
**Exista deja un sablon direct aplicabil**, chiar in cadrul modelului de "coloana Text1 in grid cu
InteractiveChange care deschide ceva" — `grdRequest`, alt grid din acelasi formular:
```
PROCEDURE grdRequest.cLabelItem.Text1.DblClick
Thisform.lEditMode = .T.
ENDPROC
```
(`omodificari.vc2:390-392`)
```
PROCEDURE grdRequest.cValoareDefaultText.Text1.DblClick
If !Empty(Nvl(crsRequest.fis_lista, ''))
Thisform.Selectfromlookup()
Endif
ENDPROC
```
(`omodificari.vc2:408-412`)
Confirmare pentru VFP: intr-adevar, evenimentul se pune pe **controlul din interiorul coloanei**
(`Column.Text1.DblClick`), exact cum arata cele doua exemple de mai sus si exact cum sunt puse
deja `InteractiveChange` si `GotFocus` pe `grdArticoleFactura` (`Column.Text1.InteractiveChange`,
`Column.Text1.GotFocus`) — nu exista niciun `DblClick` pus pe nivelul `Column` insusi in tot
fisierul. Modelul `grdRequest` e potrivit ca forma (unde se pune evenimentul), dar nu ca
continut — acolo `DblClick` face lucruri specifice acelui grid (comuta un mod de editare / arata
un lookup din alta zona), nu apeleaza un dispecer de nomenclator.
Sablonul de continut potrivit e chiar `InteractiveChange`-urile deja existente pe
`grdArticoleFactura` (`omodificari.vc2:16710-16714`, `16728-16732`, `16739-16743`,
`16815-16819`, `16826-16830`) — toate identice, 3 linii:
```
Local loEditor
loEditor = Createobject('ArticoleNotaEditor', Thisform)
loEditor.ModificaNomenclator(Thisform.pccontrol)
```
Costul e mic si uniform: adaugarea a 5 metode noi `DblClick` (una per coloana cu nomenclator),
fiecare cu acelasi corp de 3 linii de mai sus. Pentru ca toate cele 5 `GotFocus` seteaza deja
`Thisform.pccontrol` corect — fie din `ControlSource` (denumire, gestiune, valuta, taxcode), fie
explicit (explicatie TVA, `omodificari.vc2:16724`) — folosirea lui `Thisform.pccontrol` in
`DblClick` functioneaza uniform pe toate cele 5, inclusiv pe explicatie TVA, fara sa depinda de
mecanismul de seek care lipseste la coloana cu `ControlSource` expresie. Practic acelasi apel pe
care il face deja `But_modificaR.Click` (`omodificari.vc2:15319-15326`, vezi E).
Nu e nevoie de nicio schimbare in dispecer (`ModificaNomenclator`) sau in `AreNomenclator` — ele
deja accepta toate cele 5 campuri.

View File

@@ -1,229 +0,0 @@
# R6: zecimale la Pret si aspect gri al campurilor editabile, grid Articole (frm_modific2024)
Cercetare, fara modificari de cod. Zona: `COMUN\clase\omodificari.vc2`, clasa `frm_modific2024`
(liniile 6375-16838), grid-ul de articole al facturii editate: `pgfArticole.PAGE3.grdArticoleFactura`
(nu vechiul `Grid1`/`Column1..44` — acela e alt grid, RecordSource `"tACT"`, folosit pentru notele
contabile, nu pentru articolele facturii; grid-ul de articole are 16 coloane numite, ADD OBJECT la
`omodificari.vc2:12330`).
## Partea 1 — zecimalele la pret
### A. Coloanele monetare din grid si masca lor
RecordSource al grid-ului = cursorul `tvd` (creat in `COMUN\programe\ofacturare_editare.prg:278`,
populat in `IncarcaArticoleFactura`, `ofacturare_editare.prg:294-328`, din view-ul Oracle
`VVANZARI_ARTICOLE`).
| Coloana (Header) | Name | ControlSource | Format/InputMask | Linie |
|---|---|---|---|---|
| Pret | cPretArt | `tvd.pret` | `get_mask(12,gnPPRET)` | `omodificari.vc2:12391` |
| Pret achizitie | cPretAchizitieArt | `tvd.pret_achizitie` | `get_mask(12,gnPPRET)` | `omodificari.vc2:12400` |
| Pret cu TVA | cPretCuTvaArt | `tvd.pret_cu_tva` | **fara Format/InputMask** | `omodificari.vc2:12404-12409` |
| Discount unitar | cDiscountUnitarArt | `tvd.discount_unitar` | `get_mask(12,gnPPRET)` | `omodificari.vc2:12426` |
| Cantitate | cCantitateArt | `tvd.cantitate` | `get_mask(12,gnPCANT)` | `omodificari.vc2:12382` |
| Valoare | cValoareArt | `tvd.valoare` | `get_mask(14,gnPa)` | `omodificari.vc2:12463` |
| TVA % | cProcTvavArt | `tvd.proc_tvav` | `"9.99"` (fix, 2 zecimale) | `omodificari.vc2:12417` |
Structura cursorului `tvd` (`ofacturare_editare.prg:278-281`):
```
pret N(14,4) NULL, pret_cu_tva N(14,4) NULL, proc_tvav N(6,4) NULL, discount_unitar N(14,4) NULL,
..., pret_achizitie N(14,4) NULL, ..., cantitate N(12,3) NULL (nu e in acest citat, vezi mai jos)
```
Precizia campurilor din cursor e **hardcodata** (4 zecimale la pret/pret_cu_tva/discount_unitar/
pret_achizitie, 3 la cantitate — vezi linia completa `ofacturare_editare.prg:278`), independent de
`gnPPret`/`gnPCant`. Asta e doar plafonul de stocare; ce se AFISEAZA il decide InputMask-ul de pe
coloana (cand exista).
### B. De unde vin cele 3 zecimale afisate la "Pret"
**Nu dintr-un bug de masca lipsa.** Coloana "Pret" (`cPretArt`, `tvd.pret`) ARE InputMask legat de
o variabila globala: `get_mask(12,gnPPRET)`, `omodificari.vc2:12391`. Daca `gnPPRET` = 3 in mediul
curent, InputMask-ul produce exact 3 zecimale — comportamentul e "controlat de o variabila globala",
exact ce cere utilizatorul. **Problema e ca variabila legata e cea gresita**: `gnPPRET`, nu
`gnPPretV` — vezi punctul D.
### C. Ce este `gnPPret` si care e sablonul consacrat
`gnPPret` e o variabila PUBLIC incarcata din firma (parametru `OPTIUNI.PPRET`), documentata deja in
cercetarea anterioara `docs\cercetare\rec_fact008_pret_achizitie_zecimale.md:28`:
> `OPTIUNI.PPRET` (precizia pretului de achizitie) = **3** (in mediul de productie investigat acolo)
N-am gasit in acest proiect linia exacta unde `gnPPret` e citit din `OPTIUNI` si atribuit (cautare
in `COMUN\programe\oinit_optiuni.prg` — gaseste `gnPA`, `gnPC`, `gnPCurs`, `gnPPretV`, `gnZ`, dar
nu `gnPPret`; probabil incarcat generic, printr-un mecanism care nu foloseste literal sirul
`"gnPPret ="`) — **NESTABILIT exact unde**, dar valoarea si semantica (precizie **achizitie**) sunt
confirmate atat de raportul anterior cat si de tiparul de folosire de mai jos.
Exista o familie de variabile de precizie, cu roluri distincte, confirmate prin comentarii si prin
folosire consecventa in cod:
- `gnPPret` — precizie **pret de achizitie** (cost). Folosire: `ofacturare_stoc.prg:364`
`Alltrim(Str(poArticol.pret_achizitie,18,Max(gnPPret,4)))`; masti pe `pret_achizitie` in
`ointroduceri.vc2:16390,19638` (`get_mask(12,gnPPret)`, coloana NIR).
- `gnPPretV` — precizie **pret de vanzare**, comentata explicit
`COMUN\programe\oinit_optiuni.prg:515`: `Store 0 To gnPPretV && nr. de zecimale pret vanzare`.
Folosire masiva in `ofacturare_comun.prg` (peste 40 de locuri: `pretftva`, `pretctva`,
`pretv_orig`, `discountftva` etc. — toate rotunjite/formatate cu `gnPPretV`) si, esential, la
**scrierea efectiva a pretului de vanzare al liniei de factura**:
`ofacturare_stoc.prg:367` — `Alltrim(Str(poArticol.Pret,18,Iif(poDate.in_valuta=1,gnPVal,gnPPretV)))`
(in functia care insereaza randul in `VANZARI_DETALII`, aceeasi coloana `PRET` pe care o incarca
`tvd.pret`).
- `gnPCant` — precizie cantitate (`get_mask(12,gnPCANT)`, `omodificari.vc2:12382`).
- `gnPc` (`gnPC`) — precizie de calcul/valoare (`get_mask(14,gnPa)`/`gnPc` la coloana Valoare).
- `gnPa` (`gnPA`) — precizie de afisare generica (folosit la `cValoareArt`).
Sablonul consacrat de constructie a mastii: `get_mask(nrCaractere, nrZecimale)`
(`COMUN\programe\proceduri_comune.prg:1090-1110`) — construieste un sir `"999 999.999"` cu atatea
`9` dupa punct cate zecimale i se dau; **nu exista logica speciala in `get_mask`**, deci diferenta
de comportament vine strict din CE variabila i se paseaza la fiecare coloana.
### D. Modelul corect exista deja, in acelasi fisier de clase — `ofacturare.vc2`
`COMUN\clase\ofacturare.vc2:3489-3498`, grid de rulaje/stoc, doua coloane invecinate:
```
Column5.ControlSource = "pret", ;
Column5.InputMask = (get_mask(14,gnPPret)), ; && cost / achizitie
Column5.Name = "cPret", ;
...
Column6.ControlSource = "pretv", ;
Column6.InputMask = (get_mask(14,gnPPretV)), ; && vanzare
Column6.Name = "cPretv", ;
```
Acelasi tipar apare si in `COMUN\clase\ocomenzi.vc2:1174`
(`This._grdrow2.cPret.InputMask = get_mask(14,gnPPretV)` — pret de vanzare pe comanda) si in
`ointroduceri.vc2:10071,16390,19638` (`get_mask(...,gnPPret)` — toate pe campuri de **achizitie**,
in formularele de NIR).
**Regula confirmata in tot codul suitei: campul care reprezinta pretul de ACHIZITIE foloseste
`gnPPret`; campul care reprezinta pretul de VANZARE al liniei foloseste `gnPPretV`.** Chiar scrierea
in Oracle a coloanei `VANZARI_DETALII.PRET` (aceeasi coloana din care se umple `tvd.pret`) respecta
aceasta regula: `ofacturare_stoc.prg:367` foloseste `gnPPretV`, nu `gnPPret`.
### Concluzie Partea 1 — defectul
`omodificari.vc2:12391` — `Column6.InputMask = (get_mask(12,gnPPRET))` pentru **`cPretArt`**
(`tvd.pret`, coloana "Pret" = pretul de VANZARE al liniei) foloseste variabila gresita din familie:
**`gnPPRET`** (precizie achizitie) in loc de **`gnPPretV`** (precizie vanzare, cea corecta pentru
acest camp). Coloana vecina, `cPretAchizitieArt` (`omodificari.vc2:12400`, `tvd.pret_achizitie`),
foloseste corect `gnPPRET` — e evident ca la scrierea acestei sectiuni de grid s-a copiat masca de
la achizitie si pe coloana de vanzare de langa ea.
Daca in mediul reclamat `OPTIUNI.PPRET` (achizitie) = 3 si `OPTIUNI.PPRETV` (vanzare) e alta
valoare (tipic 2), exact asta explica simptomul: coloana "Pret" (vanzare) afiseaza 3 zecimale
pentru ca "imprumuta" precizia de achizitie, nu pe cea de vanzare.
Coloana "Pret cu TVA" (`cPretCuTvaArt`) nu are deloc InputMask (`omodificari.vc2:12404-12409`) — nu
e sursa celor "3 zecimale" reclamate la "Pret", dar e un defect inrudit: afiseaza precizia bruta a
campului din cursor (4 zecimale, `pret_cu_tva N(14,4)`), necontrolata de nicio variabila globala.
**Schita de interventie** (fara aplicare): `Column6.InputMask = (get_mask(12,gnPPRET))` ->
`Column6.InputMask = (get_mask(12,gnPPretV))` la `omodificari.vc2:12391`, dupa modelul din
`ofacturare.vc2:3497` / `ocomenzi.vc2:1174`.
## Partea 2 — fundalul gri al campurilor editabile
### E-F. Sursa reala a griului si divergenta Column.ReadOnly vs Text1.ReadOnly
Confirmat: aproape toate coloanele au `DynamicForeColor` (nu `BackColor`) la nivel de coloana —
un singur `IIF` pentru culoarea textului dupa `tvd.sters`/`id_vanzare_set`, identic pe toate cele
16 coloane (ex. `omodificari.vc2:12350`). **Niciun `DynamicBackColor`, niciun `Enabled=.F.`** pe
coloane. Culoarea de fundal reala vine din proprietatea `BackColor` fixa a controlului `Text1`
inclus in fiecare coloana (`ADD OBJECT '...Text1' AS textbox`), si **nu coincide cu
`Column.ReadOnly`**:
| Camp | Column.ReadOnly | Column linie | Text1.ReadOnly | Text1.BackColor | Text1 linii |
|---|---|---|---|---|---|
| Denumire (cDenumireArt) | `.F.` (editabil) | 12354 | **`.T.`** | **225,225,225 (gri)** | 12527-12536 |
| Cod material (cCodmatArt) | `.T.` | 12361 | `.T.` | 225,225,225 | 12506-12515 (consistent) |
| Serie (cSerieArt) | `.F.` (editabil) | 12368 | **`.T.`** | **225,225,225 (gri)** | 12734-12743 |
| Lot (cLotArt) | `.F.` (editabil) | 12375 | **`.T.`** | **225,225,225 (gri)** | 12631-12640 |
| Cantitate (cCantitateArt) | `.F.` | 12384 | `.F.` | 255,255,255 (alb) | 12485-12494 (consistent) |
| Pret (cPretArt) | `.F.` | 12393 | `.F.` | 255,255,255 (alb) | 12673-12682 (consistent) |
| Pret achizitie (cPretAchizitieArt) | `.F.` | 12402 | `.F.` | 255,255,255 (alb) | 12652-12661 (consistent) |
| TVA % (cProcTvavArt) | `.F.` (editabil) | 12419 | **`.T.`** | **225,225,225 (gri)** | 12713-12722 |
| Discount unitar (cDiscountUnitarArt) | `.T.` | 12428 | `.T.` | 225,225,225 | 12548-12557 (consistent) |
| Gestiune (cGestiuneArt) | `.F.` (editabil) | 12435 | **`.T.`** | **225,225,225 (gri)** | 12610-12619 |
| Valuta (cValutaArt) | `.F.` (editabil) | 12442 | **`.T.`** | **225,225,225 (gri)** | 12797-12806 |
| Explicatie (cExplicatieArt) | `.F.` (editabil) | 12449 | **`.T.`** | **225,225,225 (gri)** | 12569-12578 |
| Taxcode (cTaxcodeArt) | `.F.` (editabil) | 12456 | **`.T.`** | **225,225,225 (gri)** | 12755-12764 |
| Valoare (cValoareArt) | `.T.` | 12465 | `.T.` | 225,225,225 | 12776-12785 (consistent) |
| Explicatie TVA (cExplicatieTvaArt) | `.F.` (editabil) | 12472 | **`.T.`** | **225,225,225 (gri)** | 12589-12598 |
**9 din 16 coloane sunt declarate editabile la nivel de `Column.ReadOnly=.F.` dar controlul `Text1`
inclus e `ReadOnly=.T.` cu fundal gri (225,225,225).** In VFP, cand o coloana de grid contine un
control custom (nu editorul implicit), editabilitatea reala vine din controlul inclus, nu din
`Column.ReadOnly` — deci aceste 9 campuri **nu accepta tastare directa in celula**, indiferent ce
spune `Column.ReadOnly`. Asta explica de ce aratau gri: griul e corect pentru "nu se editeaza prin
tastare directa".
Pentru unele din ele exista insa un mecanism real de editare, prin dialog, nu prin tastare:
`cGestiuneArt.Text1.GotFocus` (`omodificari.vc2:12734`... de fapt liniile de cod sunt la
`16734-16744`) seteaza `Thisform.pccontrol` si `InteractiveChange` deschide un editor:
```
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.cGestiuneArt.Text1.InteractiveChange (16739)
loEditor = Createobject('ArticoleNotaEditor', Thisform)
loEditor.ModificaNomenclator(Thisform.pccontrol)
```
Acelasi tipar la `cExplicatieTvaArt` (`16722-16732`). Pentru `cSerieArt`, `cLotArt`, `cExplicatieArt`
exista doar un handler `.When` (`16745`, `16716`, `16804`) care blocheaza intrarea in celula cand
`Thisform.lArticoleReadOnly` sau `id_vanzare_set<>0`, dar **niciun cod care sa comute
`Text1.ReadOnly` la `.F.`** — n-am gasit nicio atribuire dinamica `.Text1.ReadOnly = ...` in tot
corpul clasei `frm_modific2024` pentru aceste coloane (cautare exhaustiva pe intervalul
6375-16838). **Concluzie verificabila pe cod**: cu configuratia actuala, `cSerieArt`, `cLotArt`,
`cExplicatieArt`, `cValutaArt`, `cTaxcodeArt`, `cDenumireArt`, `cProcTvavArt` **nu au niciun
mecanism de editare activ in acest grid** — nici tastare (Text1.ReadOnly=.T.), nici editor prin
dublu-clic/InteractiveChange (spre deosebire de cGestiuneArt/cExplicatieTvaArt, care au editor).
Daca utilizatorul le percepe ca "editabile", fie testeaza cu date/build vechi, fie editarea se
intampla prin alta cale neinventariata aici (buton `but_modificaR` — vezi mai jos, NESTABILIT unde
e definit click-ul lui).
`Thisform.but_modificaR.Enabled` se seteaza in `AfterRowColChange`
(`omodificari.vc2:16678-16682`) dupa coloana curenta si `!lArticoleReadOnly`, sugerand un buton
extern de "Modifica" — dar **n-am gasit definitia obiectului `but_modificaR` sau handler-ul lui de
`Click` in `omodificari.vc2`** (cautare `grep -n "but_modificaR"` in tot fisierul: doar cele 7
referinte de mai sus, toate `.Enabled = ...` sau `.Visible = ...`, niciun `ADD OBJECT` sau
`PROCEDURE but_modificaR...Click`). **NESTABILIT**: butonul e probabil mostenit dintr-o clasa
parinte in afara acestui fisier — nu s-a investigat mai departe (ar necesita cautare in
`_frm_base.vcx`/alte `.vc2` din `COMUN\clase`, in afara scopului "aceasta clasa").
### G. Conventia de culoare in restul aplicatiei
**Exista o conventie, si e vizibila chiar in acelasi fisier de clasa**, pe pagina "Rulaje"
(`pgfArticole.PAGE1.grdRulaje`, alt tab al aceluiasi formular):
- editabil prin tastare directa: `BackColor = 255,255,255` (alb) + `ReadOnly = .F.` — ex.
`cPret.Text1`, `omodificari.vc2:9990-9999`.
- editabil prin dialog (GotFocus seteaza `pccontrol`, InteractiveChange deschide editor):
`BackColor = 160,255,205` (**verde deschis**) + `ReadOnly = .F.` — ex. `cGest.Text1`
(`omodificari.vc2:9689-9698`) si `cDenumire.Text1` (`omodificari.vc2:9595-9604`).
- needitabil: `BackColor = 225,225,225` (gri) + `ReadOnly = .T.`.
Pe pagina "Articole" (`grdArticoleFactura`, PAGE3 — grid-ul reclamat), campul `cGestiuneArt` are
**exact acelasi mecanism de editare prin dialog** ca `cGest` de pe pagina Rulaje (acelasi tipar
GotFocus/InteractiveChange/`ArticoleNotaEditor`), dar culoarea lui e **gri + ReadOnly=.T.**
(`omodificari.vc2:12610-12619`), nu verde + ReadOnly=.F. ca omologul lui de pe Rulaje. **Asta e
dovada directa a inconsistentei**: acelasi tipar functional (editare prin editor extern) e codat
cu doua culori diferite in doua pagini ale aceluiasi formular — verde (corect, semnaleaza "se
editeaza altfel") pe Rulaje, gri (gresit, semnaleaza "nu se editeaza") pe Articole.
## Rezumatul defectelor gasite (fara aplicare)
1. **Zecimale**: `Column6.InputMask` la `cPretArt` (`omodificari.vc2:12391`) foloseste `gnPPRET`
(precizie achizitie) in loc de `gnPPretV` (precizie vanzare) — schita: schimbat argumentul.
2. **Aspect gri**: 9 coloane (`cDenumireArt`, `cSerieArt`, `cLotArt`, `cProcTvavArt`,
`cGestiuneArt`, `cValutaArt`, `cExplicatieArt`, `cTaxcodeArt`, `cExplicatieTvaArt`) au
`Column.ReadOnly=.F.` dar `Text1.ReadOnly=.T.` + `BackColor=225,225,225` — contrazic propria
lor declaratie de editabilitate. Dintre ele, doar `cGestiuneArt` si `cExplicatieTvaArt` au
mecanism real de editare (editor extern) si ar trebui colorate verde
(`160,255,205`, ca modelul de pe Rulaje) + `ReadOnly=.F.` pe Text1; restul (`cDenumireArt`,
`cSerieArt`, `cLotArt`, `cProcTvavArt`, `cValutaArt`, `cExplicatieArt`, `cTaxcodeArt`) fie n-au
deloc cale de editare activa in cod (raman corect needitabile — atunci `Column.ReadOnly` ar
trebui pus `.T.` ca sa nu mai contrazica aspectul), fie editarea lor se face pe o cale
neidentificata in aceasta cercetare (butonul `but_modificaR`, NESTABILIT unde e definit).

View File

@@ -1,53 +0,0 @@
# Regresie dupa modificarea `omodificari.vc2` (validare cantitate + garda S4b)
Context: s-au schimbat doua puncte in `COMUN\clase\omodificari.vc2` (write-back deja facut si
verificat inainte de aceasta rulare):
1. `:14386` — validarea de cantitate `Nvl(cantitate,0) <= 0` -> `= 0` (negativul e legitim: retur,
storno, linii de discount).
2. `:14438` — blocul de sincronizare S4b de la salvare a primit garda
`AND Used('tvd') AND m.lnLiniiActive > 0`.
Scopul acestei rulari: confirmarea ca suita de regresie ramane la baseline dupa aceste doua
schimbari.
## Rezultate
Fiecare suita a fost rulata individual (o singura lansare de `vfp9.exe -A -T` per apel de tool),
cu `.FXP`/`.fxp` sterse inainte de fiecare rulare si mtime-ul logului verificat imediat dupa,
inainte de a citi vreo cifra.
| suita | baseline | obtinut | mtime log | exit code | verdict |
|---|---|---|---|---|---|
| `test_adauga_linie_articol` | 20 PASS / 0 FAIL | 20 PASS / 0 FAIL | 2026-08-13 03:05:52 | 0 | OK, la baseline |
| `test_adauga_linie_valuta` | 16 PASS / 0 FAIL | 16 PASS / 0 FAIL | 2026-08-13 03:06:02 | 0 | OK, la baseline |
| `test_verdict_act_rul` | 26 PASS / 0 FAIL | 26 PASS / 0 FAIL | 2026-08-13 03:06:12 | 0 | OK, la baseline |
| `test_incarca_vanzare_din_nota` | 5 PASS / 0 FAIL (`done`) | 5 PASS / 0 FAIL (`done`) | 2026-08-13 03:06:25 | 0 | OK, la baseline |
| `test_efactura_readonly` | 23 PASS / 0 FAIL | 23 PASS / 0 FAIL | 2026-08-13 03:06:35 | 0 | OK, la baseline |
| `test_s4b_sincronizare` | 42 PASS / 0 FAIL | 42 PASS / 0 FAIL | 2026-08-13 03:06:44 | 0 | OK, la baseline |
| `test_s4b_dialog` | 35 PASS / 0 FAIL | 35 PASS / 0 FAIL | 2026-08-13 03:06:54 | 0 | OK, la baseline |
| `test_s7_rotunjire` | 6 PASS / 0 FAIL | 6 PASS / 0 FAIL | 2026-08-13 03:07:06 | 0 | OK, la baseline |
Deja rulate anterior de sesiunea principala (nu au fost reluate in aceasta misiune):
| suita | baseline | obtinut |
|---|---|---|
| `test_s5_validari_articole` | 35/0 | 35/0 (la baseline) |
| `test_page3_articole` | 14/2 | 14/2 (la baseline) |
## Suite care au cerut rerulare
Niciuna. Toate cele 8 suite rulate in aceasta misiune au pornit curat, au scris logul cu mtime
proaspat imediat dupa lansare si au iesit cu `exit=0` la prima incercare — nu a fost nevoie de
nicio garda de timp (`timeout`) declansata si nicio rerulare.
## Procese ramase
Zero procese `vfp9.exe` ramase dupa terminarea rularilor (verificat cu
`Get-Process vfp9 -ErrorAction SilentlyContinue`, rezultat gol).
## Concluzie
Toate cele 8 suite din lista primita plus cele 2 deja rulate de sesiunea principala (10 suite in
total) raporteaza cifre identice cu baseline-ul. Nu s-a observat nicio regresie in urma celor doua
modificari din `omodificari.vc2`.

View File

@@ -1,202 +0,0 @@
# Review S4 runda 1 (PAGE3 "Articole factura") - ancorare coloane + calitate cod
Review de cod, fara nicio modificare aplicata. Obiect: diff aplicat (sters)
(`COMUN\clase\omodificari.vc2` clasa `frm_modific2024`, `COMUN\programe\ofacturare_editare.prg`).
Context citit: `docs\cercetare\rec_s4_runda1.md`, `docs\progres.md` (sectiunea "#6, S4 runda 1" si
decizia/nota despre pozitionarea in `actactan`), `COMUN\docs\reguli_lucru.md`,
`COMUN\docs\capcana_grid_controlsource.md`.
## 1. Riscul cel mai important - NU e despre coloane, e o pozitionare oarba in `tact` (corectitudine)
`omodificari.vc2:14171`, in `Show()`:
```
IF Reccount('tact') > 0
Go Top In tact
IncarcaVanzareNota(tact.cod, tact.nract, tact.serie_act, tact.dataact)
...
```
`tact` poate avea MAI MULTE randuri pentru aceeasi nota (o factura + o incasare, etc.), ordonate
dupa `id_act` (`ofacturare_editare.prg:54`, `order by id_act`). Exact acest tipar - "Go Top orb pe
`actactan`/`tact`" - e documentat ca riscant in `docs\cercetare\rec_pozitionare_actactan.md` (§2):
pe **39 de facturi in schema de dev**, primul rand dupa `id_act` e `INCASARE`/`INCASARE NUMERAR`,
cu `id_fact` = facturii minus 1, nu al facturii insesi. Acolo era vorba de `id_fact`; aici codul
nou citeste `tact.nract`/`tact.serie_act`/`tact.dataact` de pe randul gasit de `Go Top` - daca
INCASAREA are propriile ei `nract`/`serie_act`/`dataact` (referinta la chitanta, nu la factura),
filtrul compus din `IncarcaVanzareNota` (`cod + nract + serie_act + dataact`) cauta valorile
GRESITE in `VANZARI` -> 0 randuri gasite -> `lAreArticoleVanzari = .F.` **silentios**, desi factura
chiar are vanzare asociata. Nu inseamna eroare vizibila, ci pagina PAGE3 lipsind pe documente care
ar trebui sa o aiba.
Nu e o certitudine (n-am verificat direct daca `nract`/`serie_act`/`dataact` difera intre randul
INCASARE si randul facturii pe cele 39 de documente), dar riscul e concret si documentat de proiect
insusi pentru exact acelasi tipar de pozitionare, pe alt camp din acelasi cursor. Testele existente
(`test_page3_articole.prg:70` si `:167`) **repeta acelasi `Go Top In tact`**, deci nu-l acopera -
"zero cazuri in date" nu e dovada ca nu exista (`COMUN\docs\reguli_lucru.md` pct. 6).
**Recomandare**: inainte de a inchide runda, verifica direct pe unul din cele ~39 de documente din
`rec_pozitionare_actactan.md` (sau cauta altele cu `INCASARE` ca prim rand si rand de vanzare in
`VANZARI`) daca `Go Top` da alt `nract`/`serie_act`/`dataact` decat cel corect. Daca da, nu exista
azi in cod un anchor gata de refolosit la momentul `Show()` (`ales` e flag de UI, populat de
utilizator din grid, gol la deschidere - nu ajuta aici); ar trebui gasit un criteriu de pozitionare
mai bun, posibil analog cu ce se recomanda in `rec_pozitionare_actactan.md` §5 pentru `id_fact`.
Efort: mic ca sa verifici (o interogare + 1-2 randuri de test), nedeterminat ca sa remediezi -
depinde ce arata verificarea. **Prioritate maxima inainte de commit**, e mai important decat
subiectul de ancorare de coloane cerut initial.
## 2. Cerinta principala: ancorarea codului de structura tabelelor
### Ce e hardcodat azi, si ce se rupe
Structura e prezenta in **3-4 locuri separate**, toate manuale:
1. Lista de coloane din `SELECT` (`ofacturare_editare.prg:201-208`, `IncarcaArticoleFactura`) -
20 coloane explicite (`vd.id_vanzare_det ... nv.nume_val`).
2. `CREATE CURSOR tvd` din fallback-ul de eroare Oracle, **in aceeasi functie**
(`ofacturare_editare.prg:214-216`) - acelasi 20 de campuri, aceleasi tipuri, scrise separat.
3. `CREATE CURSOR tvd` placeholder din `Load()` (`omodificari.vc2`, ~14076, `If !Used('tvd') ...`)
- a treia copie, byte-cu-byte aceeasi structura ca (2), intr-un alt fisier.
4. Coloanele gridului `grdArticoleFactura` (`omodificari.vc2`, ADD OBJECT ~12258-12454) - 13
`ControlSource`/`Header.Caption`/`Width`/`InputMask`, cate un bloc pe coloana.
**Coloana ADAUGATA in `VANZARI_DETALII`** (sau in `nom_articole`/`nom_gestiuni`/`nom_valute`): NU
rupe nimic. `SELECT`-ul explicit o ignora, cursorul `tvd` nu o capata, gridul (13 coloane fixe) nu o
cere. Zero impact pana cineva decide s-o afiseze - caz in care tot trebuie atinse (1) si (4) oricum,
indiferent de strategia de ancorare aleasa.
**Coloana STEARSA sau REDENUMITA** dintre cele 20 folosite: `SELECT`-ul explicit din (1) pica pe
Oracle -> `lnSucces < 0` -> se intra pe fallback-ul (2), cursorul `tvd` gol, `IncarcaArticoleFactura`
returneaza `.T.` **fara niciun mesaj vizibil** (vezi punctul 3 mai jos). Practic: pagina PAGE3 arata
goala, silentios, fara semnal ca ceva s-a stricat structural. Acesta e cazul real de reparat cand se
schimba schema - nu adaugarea de coloane.
**Cat de des se intampla la ROA**: dupa `docs\progres.md` (decizia 2, sursa DDL e schema de
dezvoltare `MARIUSM_AUTO`, aplicata prin scripturi de migrare versionate) - stergerea/redenumirea de
coloane pe tabele active ca `VANZARI_DETALII` nu pare o practica frecventa (schimbarile de schema
documentate in sesiune sunt adaugari de coloane/view-uri, nu redenumiri). Deci riscul real e rar, dar
cand se intampla azi e **silentios**, nu zgomotos - asta conteaza mai mult decat frecventa.
### Variante de ancorare, cu ce pierde fiecare
- **A. Grid construit dinamic din `AFIELDS()` + dictionar de etichete.**
Elimina nevoia sa atingi (4) cand se schimba coloanele afisate implicit, dar tot trebuie sa
intretii un dictionar {camp -> caption/width/format} undeva - muti hardcodarea din `.vcx` intr-un
`.prg`, n-o elimini. Cost mare: e o **schimbare de tipar fara precedent** pe acest formular -
gridurile surori `grdRulaje`/`grdRulajeObinv` de pe PAGE1/PAGE2 (`omodificari.vc2:8694`,
`:10517`, 63 si 61 de coloane) sunt 100% declarative in `.vcx`, cu `ColumnOrder`/`DynamicForeColor`/
`InputMask` per coloana - cautabile cu `vfp_symbols.ps1`/grep. Un grid dinamic ar fi unicat in tot
formularul (si, dupa cat am vazut, in restul clasei) - o datorie de intretinut de unul singur, nu
un castig, exact contrariul principiului "consistenta cu codul din jur" din brief. Nu recomand.
- **B. `SELECT *` in loc de lista explicita de coloane.**
Pentru coloana ADAUGATA nu aduce niciun beneficiu fata de azi (gridul tot leaga doar 13 coloane
numite, indiferent cate vin din `SELECT`). Pentru coloana STEARSA/REDENUMITA e **mai rau**: azi
eroarea e prinsa curat la nivel de SQL (`lnSucces < 0`, punct de control unic); cu `SELECT *` pe un
join direct pe 4 tabele, interogarea SQL reuseste oricum (nu refera explicit campul lipsa), iar
eroarea apare abia la binding-ul gridului pe un `ControlSource` inexistent - exact tipul de
capcana (dialog nativ VFP) pe care runda asta a trebuit sa-l ocoleasca separat pentru `tvd`
(`rec_s4_runda1.md`, blocajul #3). Tiparul corect pentru `SELECT *` folosit deja de gridurile
surori (`trul`, `trul_obinv`, `tact`) nu e pe join brut, ci pe un **view Oracle dedicat**
(`vrul_tot`, `vact_tot`, `vrul_obinv_tot` - vezi `ofacturare_editare.prg:54,69,100`) care izoleaza
exact coloanele si numele expuse catre VFP. Replicarea corecta a tiparului ar insemna un view nou
`vvanzari_articole`/similar pentru `VANZARI_DETALII` - fezabil, dar e o **migrare de schema Oracle**
(`scripturi-migrare-db.md`), nu o editare VFP; cost si coordonare mai mari decat editarea `.vc2`.
Merita luat in calcul DACA schema chiar incepe sa se miste des pe zona asta, nu acum pentru o
runda "doar afisare".
- **C. Coloane declarate (ca azi), plus garda care semnaleaza divergenta la rulare.**
Nu schimba nimic structural - pastreaza controlul total pe ordine/latime/format, consistent 1:1
cu `grdRulaje`/`grdRulajeObinv`. Cere doar sa nu mai fie inghitita silentios eroarea Oracle: azi
`IncarcaVanzareNota`/`IncarcaArticoleFactura` (`ofacturare_editare.prg:174-177`, `:213-217`)
returneaza `.T.` cu cursor gol pe `lnSucces < 0`, spre deosebire de funcita sora
`IncarcaCursoareModificareNota` din ACELASI FISIER (`ofacturare_editare.prg:59-62`), care afiseaza
`AMESSAGEBOX(goExecutor.cEroare,...)`. Adaugarea aceluiasi `AMESSAGEBOX` (sau macar un log) pe cele
doua functii noi transforma o coloana stearsa/redenumita dintr-un gol tacut intr-un semnal vizibil
- fara sa schimbe deloc modul in care se intretine gridul. Cost: cateva linii, minim.
- **D. Lasat asa cum e.**
Argument real: e runda 1, "doar afisare" (`rec_s4_runda1.md`), iar tiparul (SQL explicit + grid
declarat) e **identic** cu ce exista deja de ani pe acelasi formular pentru `trul`/`trul_obinv` in
partea de campuri neprovenite direct din view (vezi Column3-Column15 la `grdRulaje`,
`omodificari.vc2:8721-8829`, multe cu `ControlSource` pe nume simplu de camp). Nu e o liabilitate
noua introdusa de diff, e consistenta cu practica existenta. Singurul gol real fata de sora ei e
lipsa mesajului de eroare (punctul C), nu structura declarativa insasi.
### Recomandare
**C**, nu A sau B: adauga `AMESSAGEBOX` (dupa modelul `IncarcaCursoareModificareNota`) pe cele doua
`lnSucces < 0` din `IncarcaVanzareNota`/`IncarcaArticoleFactura`. E schimbarea cu cel mai bun raport
cost/beneficiu - cateva linii, zero impact pe tipar, transforma exact riscul real (coloana
stearsa/redenumita) dintr-un gol silentios intr-un semnal vizibil. Grid dinamic (A) sau `SELECT *`
pe join brut (B) NU merita azi - ambele fie muta hardcodarea in alta parte fara sa reduca
intretinerea, fie inrautatesc raspunsul la exact riscul pe care vor sa-l elimine. Daca la un moment
dat `VANZARI_DETALII` incepe sa-si schimbe structura des, varianta corecta e B **cu view Oracle
dedicat** (ca la `trul`/`tact`), nu grid dinamic.
## 3. Duplicare de cod - structura cursorului `tvd`/`tvanz` scrisa manual de mai multe ori
- `CREATE CURSOR tvanz (...)` (6 campuri) apare **de doua ori in aceeasi functie**,
`ofacturare_editare.prg:161` si `:175` (`IncarcaVanzareNota`), byte-cu-byte identic. Fix simplu:
un singur `CREATE CURSOR` la inceputul functiei / dupa cele doua conditii de iesire timpurie, in
loc de doua copii separate la 14 linii distanta. Efort: mic, cateva minute.
- `CREATE CURSOR tvd (...)` (20 campuri) apare **in doua fisiere diferite**: fallback-ul din
`IncarcaArticoleFactura` (`ofacturare_editare.prg:214-216`) si placeholder-ul din `Load()`
(`omodificari.vc2`, ~14076). Identice ca structura. O functie comuna in `ofacturare_editare.prg`
(ex. `CreeazaCursorTvdGol`) apelata din ambele locuri ar elimina a treia copie manuala si ar
garanta ca raman sincronizate cand se adauga/scoate un camp. Efort: mic-mediu (o functie noua +
doua puncte de apel, testat deja indirect de suita existenta).
## 4. Alte observatii de calitate
- **Pozitiv**: toate `ControlSource`-urile noului grid `grdArticoleFactura` sunt calificate cu
`tvd.` (`omodificari.vc2`, Column1-Column13, ex. `"tvd.denumire"`, `"tvd.codmat"`) - exact regula
din `COMUN\docs\capcana_grid_controlsource.md` pentru formulare cu 2+ grid-uri (formularul are
acum trei: `grdRulaje`, `grdRulajeObinv`, `grdArticoleFactura`). De comparat cu gridurile surori
`grdRulaje`/`grdRulajeObinv`, unde o parte din coloane au `ControlSource` NECALIFICAT (ex.
`"dataact"`, `"codmat"`, `"denumire"`, `"pret"`, `"cant"` la `omodificari.vc2:8728-8785`) - expuse
in teorie la exact capcana descrisa in document daca alt cursor ajunge sa fie workarea curenta.
E o expunere preexistenta, nu introdusa de acest diff, si gridul respectiv nu pare sa fi avut
probleme raportate - semnalez doar ca informatie, nu ca ceva de reparat acum.
- **Comentariu usor peste norma**: header-ul `IncarcaVanzareNota` (`ofacturare_editare.prg:144-147`)
are 4 linii; regula permite 2-3 pentru contract nebanal (`reguli_lucru.md` pct. 2). Continutul e
util (parametri, capcana cod-neunic, cursor lasat deschis) - as comprima usor, nu as sterge
informatie. Nu blocant.
- **`GO`/`Recno()`**: singura pozitionare noua e `Go Top In tact` (discutata la punctul 1) - nu e
cazul "GO pe un Recno() capturat/primit ca parametru" din `conventie_go_recno.md`, deci acea
conventie specifica nu se aplica direct, dar tot e o pozitionare pe un cursor cu mai multe randuri
posibile, deci riscul de fond e inrudit.
- **`ALTER TABLE` pe cursor din `goExecutor.oExecute()`**: nu se foloseste in diff, nu se aplica.
- Nu am gasit cod mort introdus, nici nume inconsistente - `lAreArticoleVanzari`/`nIdVanzare`/
`nTipVanzare` respecta exact conventia Hungarian deja folosita pe restul clasei
(`lavertizatexigibilizare`, `nid_set` etc.).
- Nimic de refolosit ratat: n-am gasit o functie comuna existenta pentru "gaseste randul din
VANZARI pentru o nota" sau "incarca liniile unei vanzari" inainte de acest diff - functiile noi
chiar completeaza un gol, nu dubleaza ceva ce exista deja (conform si cu `rec_s4_runda1.md`).
## Ce NU merita schimbat
- Tiparul declarativ al gridului (`ColumnN.ControlSource`/`Header.Caption`/`Width` scrise manual in
`.vcx`) - e identic cu tiparul din PAGE1/PAGE2, cautabil cu `vfp_symbols.ps1`, si schimbarea lui
ar fi o inconsistenta noua, nu o simplificare reala (vezi Variantele A/B mai sus).
`ReadOnly = .T.` pe grid si pe fiecare `Text1` e corect si suficient pentru o runda "doar
afisare" - nu trebuie dus mai departe acum.
`PageCount` comutat intre 2 si 3 in `Show()` e simplu si testat, nu are nevoie de alta
arhitectura.
- Placeholder-ul `CREATE CURSOR tvd` in `Load()` ca sa evite dialogul nativ "Open" - solutia corecta
pentru capcana documentata deja in `rec_s4_runda1.md`; singura problema e ca structura lui e
duplicata (punctul 3), nu ca exista.
- Filtrul compus `cod + nract + serie_act + dataact` din `IncarcaVanzareNota` - justificat solid de
`docs\progres.md` (`VANZARI.COD` nedovedit unic, coliziune verificata pe `cod=1139934`), corect
implementat si testat pe cazul de coliziune. Nu-l simplifica inapoi la `cod` singur.
## Recomandare finala
Inainte de commit, in ordinea asta:
1. Verifica riscul de la punctul 1 (`Go Top In tact`) pe un caz real cu `INCASARE` ca prim rand -
e singurul lucru care poate face pagina PAGE3 sa lipseasca gresit pe facturi reale.
2. Adauga `AMESSAGEBOX` pe erorile Oracle din `IncarcaVanzareNota`/`IncarcaArticoleFactura` (punctul
2, varianta C) - raspunsul corect si ieftin la cerinta de ancorare a lui Marius.
3. Opional, daca ramane timp: elimina cele doua duplicari de `CREATE CURSOR` (punctul 3).
Restul (structura declarativa a gridului, filtrul compus, placeholder-ul din `Load()`) e in regula
asa cum e si nu merita atins.

View File

@@ -1,159 +0,0 @@
# Extinderea la cei 5 apelanti `frm_modific2024` + linia din `roagest.prg`
09.08.2026. Extinde solutia "cursorul se pregateste inainte de `Createobject`" (aplicata deja doar
la `do_editare_factura`, `ofacturare_comun.vc2:3792`) la ceilalti 4 apelanti care fac
`Createobject([frm_modific2024])` fara sa pre-incarce nimic, plus linia lipsa din `roagest.prg`.
## Helper-ul nou: `PregatesteArticoleFacturaEditare(tcAliasAct)`
`COMUN\programe\ofacturare_editare.prg:319-340`. O singura functie, un singur apel per apelant,
inainte de `Createobject`:
```
IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure"))
PregatesteArticoleFacturaEditare('tact')
ENDIF
```
Reutilizeaza integral ce exista deja, fara sa rescrie nimic:
- **descoperirea**: `IncarcaVanzareDinNota(tcAliasAct)` — incearca toate tripletele distincte
`(cod, nract, serie_act, dataact)` din alias pana gaseste un rand in `VANZARI` (decizia 27);
populeaza `tvanz` si salveaza/restaureaza singura workarea si pozitia curenta pe alias.
- **pre-incarcarea**: daca a gasit (`Reccount('tvanz') = 1`), `IncarcaArticoleFactura(tvanz.id_vanzare,
'crsArticoleFactura')` — exact acelasi apel ca in `do_editare_factura`.
Helper-ul insusi mai adauga doar restaurarea workarei de la intrare (`Select()`/`SELECT (...)`,
acelasi tipar ca in `IncarcaVanzareDinNota`), pentru ca `IncarcaArticoleFactura` isi lasa
workarea curenta pe cursorul nou creat, nu pe cea a apelantului.
**Decizie**: cand nu gaseste randul (`tact` lipsa/gol, sau nota fara `VANZARI`), **nu creeaza
`crsArticoleFactura` deloc** — nu-l creeaza gol. Motivul: `Load()` din `frm_modific2024`
(`omodificari.vc2:14144`, fisier interzis, neatins) testeaza `IF Used('crsArticoleFactura')`
inainte de `APPEND FROM`; daca helper-ul nu-l deschide, comportamentul e identic cu azi — cursorul
canonic gol creat de `Load()`, plus fallback-ul din `Show()` (`IF Reccount('tvd') = 0`) ramane
calea activa exact ca inainte de aceasta lucrare. Fara mesaj de eroare pe calea "nu gaseste" —
mesajele Oracle raman doar in `IncarcaVanzareNota`/`IncarcaArticoleFactura`, pe erori Oracle
propriu-zise (neschimbate).
Nu strica workarea/pozitia apelantului in niciun caz (gasit, negasit, sau eroare Oracle).
## Cei 4 apelanti tratati
| Fisier:linie apel | Clasa.metoda | Context |
|---|---|---|
| `COMUN\clase\comun.vc2:2435-2437` | `afisjurcom.do_modifica` | **registrul jurnal, viu in ROACONT** |
| `COMUN\clase\anaf_efactura.vc2:13084-13086` | `frm_import_efactura.importmodifica` | import eFactura achizitie |
| `COMUN\ferestre\frm_initializare_facturi_balanta.sc2:1803-1806` | `form1.modificanote` | initializare solduri |
| `COMUN\ferestre\frm_import_note_facturi_clienti.sc2:895-898` | `form1.modificanote` | import note facturi clienti |
Fiecare: apel gardat cu `"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))` inainte de
`Createobject([frm_modific2024]...)`, plus curatenie `If Used('crsArticoleFactura') / Use In
crsArticoleFactura / Endif` alaturi de restul cursoarelor eliberate la iesirea din metoda (acolo
unde apelantul le elibereaza deja — toti 4 o fac).
**Doi din cei patru sunt no-op-uri sigure, nu utile azi**, dar consistente cu cerinta ("fiecare
apelant primeste apelul"):
- `anaf_efactura.importmodifica` importa o **factura de achizitie** (comentariul de la linia 13104
o confirma), nu are legatura cu `VANZARI` — `tact` la momentul apelului e construit din
`cnote_contabile` (achizitie), fara `cod` corespunzator unei vanzari. Discovery nu gaseste nimic,
helper-ul e no-op curat.
- Cele doua `.sc2` (`frm_initializare_facturi_balanta`, `frm_import_note_facturi_clienti`) creeaza
note **noi** (`llNotaNoua = .T.`) — `cod`-ul se genereaza abia **dupa** ce formularul se inchide
(`SELECT seq_cod.nextval`, dupa `Show(1, ...)`). La momentul apelului `tact` n-are inca `cod`
legat de o vanzare existenta, deci discovery nu gaseste nimic, la fel no-op.
Doar `afisjurcom.do_modifica` (registrul jurnal) atinge azi documente cu rand real in `VANZARI` pe
aceasta cale — acolo helper-ul chiar precarca articolele, la fel ca la `do_editare_factura`.
## `Show()` ramane fallback — cod (probabil) mort pentru calea tratata
Nu s-a atins `omodificari.vc2` (fisier interzis). Fallback-ul din `Show()`
(`IF Reccount('tvd') = 0 THEN IncarcaArticoleFactura(...)`) ramane pe loc, neschimbat. Pentru cei
5 apelanti tratati acum (`do_editare_factura` + cei 4 de mai sus), `tvd` va fi deja plin dupa
`Load()` cand precarcarea a gasit ceva, deci fallback-ul nu se mai declanseaza — la fel cum se
intampla deja pentru `do_editare_factura`. Ramane cod activ doar pentru apelantii care nu pre-incarca
deloc (niciunul azi, dupa aceasta lucrare) si pentru orice viitor apelant care nu adopta tiparul.
**Recomandare**: nu se scoate — e in fisierul interzis si decizia e a lui Marius, dar merita
observat ca azi n-a mai ramas niciun apelant cunoscut care sa-l exercite.
## Linia din `roagest.prg`
`D:\ROA\ROAGEST\Programe\roagest.prg:260`: `SET PROCEDURE TO ofacturare_editare.prg ADDITIVE`,
adaugata imediat dupa `ofacturare_comun.PRG` (acelasi loc relativ ca in `roacont.prg:212`, deja
comis in SVN r18006). Precoditie verificata inainte de editare: `Test-Path
D:\ROA\ROAGEST\COMUN\programe\ofacturare_editare.prg` — fisierul exista pe disc (copia de `COMUN`
din ROAGEST fusese adusa la r18010, per `progres.md`).
## Testare
Suita `test_page3_articole.prg` extinsa cu o asertie noua (`verifica_precarcare_articole`,
`:544-614`): reproduce exact secventa din `afisjurcom.do_modifica` (`IncarcaCursoareModificareNota`
+ rebuild `tact` cu `cu_Tva` + `PregatesteArticoleFacturaEditare('tact')` + `Createobject` +
`Show()`), si verifica **workarea lui `tvd` identica intre `Load` si `Show`** — indicatorul deja
folosit in suita pentru "fallback-ul din `Show()` nu a reinchis cursorul". Inainte de aceasta
lucrare, pe calea negardata (`verifica_recordsource_grid`, cazul F), workarea diferea: **5 dupa
Load, 26 dupa Show**. Pe calea noua (cazul A, cu precarcare): **5 dupa Load, 5 dupa Show** — identic,
deci fallback-ul n-a mai reinchis cursorul.
Rulat sub `watchdog_vfp.ps1 -AutoDismiss`, `.fxp`-uri vechi sterse inainte de fiecare rulare,
`loForm.ClassLibrary` confirmat pe copia editata (nu ROACONT):
| Suita | Inainte | Dupa | Exit / dialoguri |
|---|---|---|---|
| `test_page3_articole.prg` | 13 PASS / 2 FAIL | **14 PASS / 2 FAIL** | 0 / 0 |
| `test_incarca_vanzare_din_nota.prg` | 5/5 | **5/5** | 0 / 0 |
Cele 2 FAIL raman identice cu inainte — artefactul headless deja documentat (datoria 7, eroare 1925
`Unknown member COLUMN5`), neatins de aceasta lucrare. Cifra noua (+1 PASS) e exact asertia adaugata;
nicio alta cifra nu s-a miscat — fara regresie.
**Netestat**: fluxul complet prin `afisjurcom.do_modifica`/`importmodifica`/cele doua `.sc2`
(formularele lor nu se instantiaza headless — cer `crsfacturi`/`crsDetaliiFacturiTemp` populate
printr-un flux real, aceeasi limitare documentata deja pentru `frm_facturi`). Testul nou reproduce
secventa de cod linie cu linie, nu formularul intreg — verifica helper-ul si interactiunea cu
`Load()`/`Show()`, nu drumul UI complet pana la el.
## Encoding si write-back
Toate editarile facute **pe octeti** (script Perl, `binmode :raw`, fara nicio decodare/reencodare),
cu match exact pe ancore unice (verificat cate o singura potrivire per inlocuire inainte de scriere).
Cens de octeti >0x7F identic inainte/dupa pe toate fisierele atinse, zero secventa `EF BF BD` dupa:
| Fisier | Cens >0x7F (inainte = dupa) |
|---|---|
| `comun.vc2` | 1× `ee` |
| `anaf_efactura.vc2` | 1× `e3` |
| `frm_initializare_facturi_balanta.sc2` | 0 |
| `frm_import_note_facturi_clienti.sc2` | 0 |
| `ofacturare_editare.prg` | 0 |
| `roagest.prg` | 1× `a9` |
Write-back text->binar cu `txt2vcx.ps1 -AllowComun`, toate 4 (`comun.vc2`, `anaf_efactura.vc2`,
cele doua `.sc2`) — fidelity check **OK** pe toate, cens neschimbat dupa refresh-ul cache-ului text.
`ofacturare_editare.prg`, `roagest.prg` si `test_page3_articole.prg` sunt surse directe (`.prg`),
fara pas de write-back binar.
## Fisiere atinse, stare write-back
| Fisier | Modificare | Write-back |
|---|---|---|
| `COMUN\programe\ofacturare_editare.prg` | helper nou + antet rescris | N/A (sursa directa) |
| `COMUN\clase\comun.vc2` + `.vcx`/`.VCT` | apel gardat + cleanup | **FACUT**, fidelity OK |
| `COMUN\clase\anaf_efactura.vc2` + `.vcx`/`.vct` | apel gardat + cleanup | **FACUT**, fidelity OK |
| `COMUN\ferestre\frm_initializare_facturi_balanta.sc2` + `.scx`/`.SCT` | apel gardat + cleanup | **FACUT**, fidelity OK |
| `COMUN\ferestre\frm_import_note_facturi_clienti.sc2` + `.scx`/`.sct` | apel gardat + cleanup | **FACUT**, fidelity OK |
| `D:\ROA\ROAGEST\Programe\roagest.prg` | linie `SET PROCEDURE` noua | N/A (sursa directa) |
| `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` | asertie noua | N/A (nu are binar, doar git) |
Neatinse (interzise): `COMUN\clase\omodificari.vc2`, `COMUN\clase\ofacturare_comun.vc2`.
**Fara commit** — diff-ul: diff aplicat (sters) (2 sectiuni: `COMUN` din
`comun.git`, `ROAGEST\Programe\roagest.prg` din `roagest.git`).
## Ce ramane pentru decizia lui Marius
- Aproba diff-ul si cele doua write-back-uri (COMUN, ROAGEST) inainte de commit.
- Rebuild ROACONT ramane la Marius (deja notat in `progres.md`) — abia dupa el PAGE3 apare efectiv
in registrul jurnal acolo. Rebuild ROAGEST, similar, dupa linia noua din `roagest.prg`.
- Observatia despre fallback-ul din `Show()` (probabil cod mort pentru toti apelantii cunoscuti
azi) — informativa, nu se actioneaza fara aprobare (fisier interzis).

View File

@@ -1,132 +0,0 @@
# S4 runda 2 (PAGE3 "Articole factura") - view VVANZARI_ARTICOLE, pozitionare in tact, garda ROACONT/ROAGEST
Runda 2 pe fisierele deja livrate in runda 1 (diff aplicat (sters), netrimis inca la
commit). Diff: diff aplicat (sters) (`COMUN\programe\ofacturare_editare.prg`
+ `COMUN\clase\omodificari.vc2`, clasa `frm_modific2024`). Fara commit.
## Ce s-a schimbat
1. **`IncarcaArticoleFactura`** trece pe view-ul Oracle `VVANZARI_ARTICOLE` (creat si validat separat, script
`ff_2026_08_08_01_COMUN_VVANZARI_ARTICOLE.sql`, `versiune_db.txt` bumpat): `select * from vvanzari_articole where
id_vanzare = <n> and sters = 0 order by id_vanzare_det`, in loc de lista de 20 de coloane + join
pe 4 tabele. Coloana noua `id_vanzare` (era doar in `WHERE`, acum si in `SELECT`).
2. **Pozitionarea in `tact`** - `IncarcaVanzareDinNota(tcAliasAct)`, functie noua: incearca toate
combinatiile distincte `(nract, serie_act, dataact)` din alias (`sters=0`) pana gaseste un rand
in `VANZARI`, in loc de `Go Top In tact` orb. Repara blocantul documentat in
`rec_gotop_tact_s4.md`/`rec_review_ancorare_s4.md`: pe notele unde primul rand e o INCASARE,
`Go Top` citea `nract`/`serie_act`/`dataact` de pe randul gresit si PAGE3 lipsea silentios.
Salveaza/restaureaza workarea (`Select()`) si `Recno()` pe alias - fara requery intre timp,
restaurarea e sigura. `IncarcaVanzareNota` isi pastreaza contractul (4 scalari), refolosita
neschimbata ca functie de interogare pe un singur triplet.
3. **Garda ROACONT/ROAGEST** - `"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))` inainte de blocul
PAGE3 din `Show()` si in `Load()`. Verificat headless (`test_set_procedure.prg`) ca
`SET("PROCEDURE")` intoarce lista COMPLETA a fisierelor incarcate prin `ADDITIVE` succesive (nu
doar ultimul), deci garda ramane corecta indiferent cate alte `SET PROCEDURE` urmeaza dupa
`ofacturare_editare.prg` in secventa de start. Fara garda, `frm_modific2024` (folosit si de
ROACONT/ROAGEST) ar fi apelat functii inexistente acolo si ar fi rupt "registru jurnal >
modificare" in ambele produse. Pe guard-fail: `PageCount = 2`, fara eroare, fara mesaj.
`roacont.prg`/`roagest.prg` NU au fost atinse (decizie cross-proiect, semnalata separat).
4. **`AMESSAGEBOX`** pe caile de eroare Oracle din `IncarcaVanzareNota`/`IncarcaArticoleFactura`
(`goExecutor.cEroare`, acelasi tipar ca `IncarcaCursoareModificareNota`) - o coloana
stearsa/redenumita in schema devine semnal vizibil, nu cursor gol tacut.
5. **O singura structura pentru cursorul gol**: `CreeazaCursorTvdGol()` (21 campuri, `id_vanzare`
nou) apelata din fallback-ul de eroare si din `Load()` cand garda trece; `CreeazaCursorTvanzGol()`
analog pentru `tvanz`. `Load()` pastreaza un fallback inline separat (20 campuri, nemodificat)
pentru cazul in care garda NU trece (ROACONT/ROAGEST) - `ofacturare_editare.prg` nefiind incarcat
acolo, functia comuna nu exista, deci placeholder-ul trebuie sa ramana literal in `.vc2`. E
singura duplicare ramasa, inerenta constrangerii, nu scapata din vedere.
## Corectii aplicate dupa livrarea initiala (runda 2b)
Team-lead a aplicat direct 2 corectii mici in `ofacturare_editare.prg` (sub 5 linii), plus o corectie
suplimentara gasita si aplicata de mine cand am adaugat testul cerut la punctul 2:
1. **`cod` in `SELECT DISTINCT`, nu de pe randul curent.** Varianta initiala citea
`Evaluate(tcAliasAct + '.cod')` de pe randul curent al `tact` inainte de bucla. `Show()` cheama
`This.CalculeazaTotal()` chiar inainte de blocul PAGE3, iar aceea poate lasa `tact` la EOF (nu
restaureaza pozitia daca era deja EOF). La EOF, `cod` citit asa iese gol -> exact clasa de bug pe
care runda asta o repara. Acum `cod` intra si el in `SELECT DISTINCT cod, nract, serie_act,
dataact FROM (tcAliasAct) WHERE sters=0` si se citeste din cursorul de triplete, nu de pe pozitia
curenta a apelantului.
2. **Fisierul era LF-only** (0 CR / 296 LF), mostenit din runda 1, nu din runda 2 - convertit la CRLF
(acum 302 CR / 302 LF dupa fixul de mai jos, zero LF izolat, zero octeti non-ASCII), consistent cu
restul `.prg`-urilor din `COMUN\programe`.
3. **Gasit de mine la testul cerut la punctul 2 de mai jos**: restaurarea pozitiei pe `tact` la
iesirea din `IncarcaVanzareDinNota` facea `Go (lnRecnoOrigine) In (tcAliasAct)` necondiționat -
daca pozitia initiala era chiar EOF, `Recno()` intoarce `Reccount()+1`, iar `GO` la acel numar
pica cu eroarea VFP 5 "Record is out of range" (capturata de `ON ERROR`, dar restaurarea nu se mai
facea - risc real, latent din prima versiune, nu introdus de corectiile 1-2). Fix: `GO` doar daca
`lnRecnoOrigine` e in intervalul valid; altfel `Go Bottom` + `Skip` (restaureaza tot la EOF).
## Rezultat suita (dupa corectiile de mai sus)
`COMUN\utile\Teste\editare_factura\test_page3_articole.prg`, sub
`watchdog_vfp.ps1 -AutoDismiss`: **exit 0, 0 dialoguri, 10/10 PASS** (7 din runda 1 + 3 noi):
- `cod=1137874` (an=2009,luna=8) -> `id_vanzare=506` - blocantul din runda 2, azi cu `Go Top` orb ar
fi dat 0 randuri;
- `cod=1139934` (an=2021,luna=12) -> `id_vanzare=882` - coliziune `VANZARI.COD` + Go Top orb, cazul
cel mai riscant (ambele probleme suprapuse);
- garda ROACONT/ROAGEST: cu `ofacturare_editare.prg` scos din `SET PROCEDURE` (lista reconstruita
fara el, restul procedurilor pastrate) - `PageCount` ramane 2, fara eroare.
`test_incarca_vanzare_din_nota.prg` (izolat, direct pe functii): **exit 0, 0 dialoguri, 5/5 PASS**
(4 din livrarea initiala + 1 nou, cazul EOF cerut de team-lead: `tact` pozitionat deliberat la EOF
`Go Bottom + Skip` inainte de apel, verifica `id_vanzare=506` gasit corect **si** `Eof(tact)` ramas
`.T.` dupa apel - proba directa ca fixul de la punctul 1 si de la punctul 3 chiar functioneaza
impreuna). Neschimbate si nerulate din nou (nu au fost atinse de corectii):
`test_incarca_articole_view.prg` (3/3 PASS la livrarea initiala), `test_set_procedure.prg`.
Write-back `.vc2` -> `.vcx`/`.vct`: `txt2vcx.ps1 -AllowComun`, fidelity check OK (nu s-a mai atins
`.vc2` in runda 2b - corectiile sunt strict in `.prg`).
## Ce NU e acoperit
- Calea de eroare Oracle propriu-zisa (`lnSucces < 0`) din `IncarcaVanzareNota`/`IncarcaArticoleFactura`
- netestabila fara sa rupi deliberat conexiunea; verificata doar static, pe simetrie cu
`IncarcaCursoareModificareNota` (acelasi tipar, deja in productie).
- Cursorul `tvd` gol pe calea de eroare reala (doar `CreeazaCursorTvdGol()` testat direct, nu prin
declansarea efectiva a unei erori SQL).
- Fluxul UI complet (utilizator care deschide efectiv `frm_modific2024` din formular, nu din test) -
neschimbat fata de runda 1, ramane netestat prin UI real.
## Abateri de la briefing
Niciuna in scop; o adaugare: testul garda ROACONT/ROAGEST foloseste reconstructia listei
`SET(PROCEDURE)` (capturare + `SET PROCEDURE TO` fara argumente + redeschidere ADDITIVE a restului),
nu `RELEASE PROCEDURE <fisier>` (comanda nu exista in VFP pentru un singur fisier din lista) - metoda
verificata sa pastreze toate celelalte proceduri incarcate de care `Show()` are nevoie.
## Runda 2, inchidere (08.08.2026, predare de la s4-runda2)
Trei sarcini mici, preluate cand `omodificari.vc2` avea deja textul rundei 2 editat dar
**write-back-ul nu era facut** (`.vc2` mai nou decat `.vcx`):
1. **Marcaje `*!* DD.MM.YYYY autor - ...`** deasupra celor doua blocuri din runda 2
(`omodificari.vc2:14076` in `Load()`, `:14172` in `Show()`) - textul era deja scris, doar
write-back-ul lipsea.
2. **Comentariu pe ramura `ELSE`** din `Load()` (`omodificari.vc2:14081`) - explica de ce
`CREATE CURSOR tvd` se repeta identic acolo (ROACONT/ROAGEST nu incarca
`ofacturare_editare.prg`, deci `CreeazaCursorTvdGol()` nu exista pe acele produse) - deja
scris odata cu punctul 1.
3. **`ROACONT\Programe\roacont.prg:212`**: `SET PROCEDURE TO ofacturare_editare.prg ADDITIVE `,
inserata langa `ofacturare_comun.prg`/`oproceduri_facturare.prg` (grupul de facturare),
convenind cu stilul local (majuscule, spatiu final). Deblocheaza PAGE3 pe ROACONT (decizia 5
din `progres.md`), garda ramane activa pentru ROAGEST (neatins, inca in discutie).
Write-back `.vc2` -> `.vcx`/`.vct`: `txt2vcx.ps1 -AllowComun`, fidelity check OK. Suita re-rulata
dupa write-back, sub `watchdog_vfp.ps1 -AutoDismiss`: `test_page3_articole.prg` **10/10 PASS**,
`test_incarca_vanzare_din_nota.prg` **5/5 PASS**, ambele exit 0 si 0 dialoguri. `roacont.prg`
verificat byte-cu-byte: CR/LF simetrice (1221->1222, +1 linie), octetul non-ASCII unic pastrat
neatins (0xA9), fara scriere prin Edit/Write (doar `[IO.File]::ReadAllText/WriteAllText` cu
codepage 1252). Fara commit git/SVN pe niciunul din cele doua proiecte.
### Acceptat constient: un roundtrip Oracle per triplet in `IncarcaVanzareDinNota`
`IncarcaVanzareDinNota` incearca tripletele distincte `(nract, serie_act, dataact)` din `tact`
pe rand, cate un apel `IncarcaVanzareNota` (deci un roundtrip Oracle) per triplet, pana gaseste un
rand in `VANZARI`. Masurat pe toate cele **419 note** legate de `VANZARI` (decizia 27,
`progres.md`): maxim **2** triplete per nota, medie **1.17** pe toata multimea. Nu se comaseaza
intr-un singur SQL cu `OR` pe toate tripletele: ar complica interogarea (listă variabila de
`OR`-uri) si ar schimba contractul lui `IncarcaVanzareNota`, care ramane o functie de interogare pe
**un singur triplet**, refolosibila neschimbata si in alte cai. Cu maxim 2 roundtrip-uri pe cazul
cel mai rau, costul e neglijabil fata de complexitatea adaugata.

View File

@@ -1,164 +0,0 @@
# S4 runda 3, sub-blocul B — adaugare si stergere de linii pe pagina de articole
Stare: **stergerea logica e implementata si scrisa in binar**; **adaugarea (dialog
`frm_articol_factura`) NU e implementata** — sub-blocul s-a dovedit prea mare pentru un
singur context si a fost impartit, conform aprobarii din briefing. Cercetarea de contract
pentru adaugare e completa si predata mai jos / in handoff intermediar (sters),
ca urmatorul agent sa nu o reia.
## Ce s-a implementat: stergerea logica de linie
`COMUN\clase\omodificari.vc2` (`frm_modific2024`), pagina `pgfArticole.PAGE3`:
- **Buton nou `cmdStergeArticol`** ("Sterge / Restaureaza linie"), adaugat pe PAGE3
deasupra grid-ului (`omodificari.vc2:12260-12274`; grid-ul `grdArticoleFactura` a fost
mutat cu `Top=26, Height=81` ca sa-i faca loc, pastrand `Anchor=15` — se comporta identic
la resize).
- **`Click` handler** (`omodificari.vc2:15962-15970`): comuta `tvd.sters` intre 0 si 1 pe
linia curenta din cursor si seteaza `lmodificat=.T.`; `Refresh()` pe grid. **Stergere
logica, niciodata fizica** — randul ramane in cursor, exact cum a cerut briefingul, ca S5
sa poata scrie `STERS=1` in Oracle pe linia respectiva.
- **Marcaj vizual**: `DynamicForeColor` adaugat pe toate cele 14 coloane ale grid-ului —
`IIF(tvd.sters=1,RGB(150,150,150),RGB(0,0,0))`. Randul marcat pentru stergere ramane
vizibil (nu dispare din grid), dar apare gri, pana la salvare — tiparul cerut explicit de
plan (`C.5`: liniile sterse "raman vizibile (tacuate/strikethrough) pana la salvare").
Tiparul `DynamicForeColor` e deja folosit in clasa (`omodificari.vc2:693` etc.), nu e
concept nou.
- **`sters` era deja in structura cursorului `tvd`** din runda 1/B.1 (campul vine direct din
`VVANZARI_ARTICOLE`/`VANZARI_DETALII.STERS`, tipat `I NULL`) — nu a fost nevoie de o
coloana noua, nici in `ofacturare_editare.prg:CreeazaCursorArticoleGol`, nici in ramura
`ELSE` din `omodificari.vc2:Load()`. Cele doua structuri raman identice (verificat byte
cu byte dupa editare — nu au fost atinse).
**Nu s-a atins**: `ofacturare_editare.prg`, `Load()`, `Show()`, dialogul de adaugare/editare
de linie. Zero scriere Oracle — editare strict in memorie, ca in tot restul rundei 3.
### Fisier OBJECTDATA / manifest
`ADD OBJECT`-ul nou are intrarea lui `*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.cmdStergeArticol" .../>`
in manifestul clasei (`omodificari.vc2:6660`), cerinta FoxBin2Prg pentru orice control nou.
## Write-back si integritate de octeti
- **Prima scriere text a picat fidelity-check-ul** (ordine `ADD OBJECT`/metoda diferita de
cea regenerata de FoxBin2Prg — capcana deja cunoscuta, "nu pastreaza ordinea textuala la
roundtrip"). Rezolvat prin adoptarea textului regenerat din `<staging>\verify\omodificari.vc2`
ca sursa, conform procedurii stabilite. **A doua rulare a `txt2vcx.ps1 -AllowComun`: OK.**
- **Capcana de encoding lovita si reparata in aceeasi sesiune**: primul `Edit` pe fisier a
re-encodat TOT fisierul (tiparul deja documentat — orice scriere cu un tool care nu
pastreaza octeti nativ corupe caracterele >0x7F din tot fisierul, nu doar linia atinsa).
Cens **inainte** de a incepe: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`. Dupa primul `Edit`:
**`6 ef / 6 bf / 6 bd`** — cele doua linii `Caption = "CTRL+F = Terminare; ESC =
Renunțare; CTRL+N = Adăugare; CTRL+D = Ștergere"` (deja reparate o data in sesiunea
anterioara, per `progres.md`) au fost stricate din nou. Reparat byte-cu-byte cu Perl
(inlocuire directa a celor 3 secvente `EF BF BD` cu octetii cp1250 corecti — `0xFE`
pentru ț, `0xE3` pentru ă, `0xAA` pentru Ș, aceeasi mapare documentata in
`conventie_encoding_cp1252.md`), **inainte** de write-back. Cens final, verificat de doua
ori (inainte si dupa `txt2vcx.ps1`): **`2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** — identic cu
starea de plecare. **Toate editarile ulterioare din aceasta sesiune au folosit exclusiv
text ASCII** (fara diacritice) tocmai ca sa evite sa mai declanseze acest tipar.
- `.vc2`/`.vcx`/`.VCT` au acelasi mtime (12:18) — write-back proaspat, nimic stale.
## Diff
diff aplicat (sters) — **scopat strict pe modificarile mele**, nu pe tot ce e
necomis in `omodificari.vc2` (fisierul are si munca altor agenti din aceeasi zi, inca
necomisa). Reconstruit dintr-un baseline `.pre_runda3b.bak` obtinut prin reversul exact al
celor 4 editari facute (nu am luat backup INAINTE de prima editare — lectie pentru viitor,
notata mai jos). 162 linii, un singur fisier.
## Testare
**Regresie headless, fara schimbare fata de linia de baza masurata la inceputul sesiunii**
(sub watchdog, `.fxp` sters inainte de fiecare rulare, exit 0, zero dialoguri):
| Suita | Inainte | Dupa |
|---|---|---|
| `test_page3_articole.prg` | 14 PASS / 2 FAIL | 14 PASS / 2 FAIL (identic) |
| `test_incarca_vanzare_din_nota.prg` | 5/5 | 5/5 |
Cele 2 FAIL raman artefactul headless cunoscut de la datoria 7 (`ColumnCount`/`ReadOnly` nu
se materializeaza sub `-A -T`) — nu au legatura cu sub-blocul B.
**Test nou, headless-cu-formular-vizibil (`vfp_ui_harness.ps1`)**:
`COMUN\utile\Teste\editare_factura\test_ui_sterge_linie.prg`, pe `cod=1139934/id_vanzare=882`
(document cu 2 linii si rulaje, ca sa nu se colapseze `pgfArticole`). Verifica: butonul
exista si e vizibil; linia 1 incarcata cu `sters=0`; dupa primul click — `sters=1`,
`lmodificat=.T.`, linia 2 (alta linie din cursor) **neatinsa**; dupa al doilea click pe
aceeasi linie — `sters=0` (restaurare, comutare pe acelasi buton, nu buton separat).
**Rezultatul din log** (doua rulari VFP separate, ambele au ajuns pana la capat, vezi mai jos
pentru problema de infrastructura care a impiedicat doar captura de ecran, nu executia):
- **Prima rulare**: cele **7 asertii de comportament** au trecut (buton exista/vizibil,
`sters=0` la incarcare, `sters=1`+`lmodificat` dupa stergere, linia 2 neatinsa). Testul mai
avea atunci doua asertii suplimentare care verificau culoarea `DynamicForeColor` prin
`EVALUATE()` direct din test — acelea au picat cu eroarea 1929 "THIS can only be used
within a method" (motiv neclar, legat de evaluarea unei proprietati de coloana de grid in
afara unui context de metoda) — **scoase din test** dupa aceasta rulare (dovada vizuala
vine din captura de ecran, nu dintr-un `EVALUATE` redundant).
- **A doua rulare, dupa scoaterea EVALUATE-urilor**: **toate cele 7 asertii PASS, zero
erori** — inclusiv restaurarea (al doilea click pe acelasi buton readuce `sters` la 0).
Confirma ca singurele erori din prima rulare erau din verificarea mea suplimentara, nu din
codul feature-ului.
**Capturile de ecran NU s-au putut obtine in aceasta sesiune** — problema de infrastructura,
nu de cod:
- Modul headless (`vfp9.exe -A -T`, folosit de `watchdog_vfp.ps1`) raspunde instant (rulare
de control: 5.8s pana la exit 0).
- Modul cu formular vizibil (`vfp9.exe -A`, fara `-T`, folosit de `vfp_ui_harness.ps1`) **a
esuat sistematic, de doua ori la rand, cate 8 incercari fiecare** — testul nu a scris
'start' in fisierul lui de log in 30 de secunde, in niciuna din cele 16 incercari
cumulate. La PRIMA rulare, la un moment dat log-ul a ajuns totusi pana la `READY 0` (deci
procesul a rulat pana la capat, cu toate asertiile PASS notate mai sus) — dar orchestratorul
`vfp_ui_harness.ps1` nu a detectat pornirea la timp si a continuat sa relanseze, ajungand
la timeout pe pasul de captura fara sa produca un PNG.
- Enumerarea (read-only) a ferestrelor vizibile de pe masina, facuta ca sa inteleg cauza, a
aratat **desktop-ul activ al lui Marius** (RustDesk, VS Code, Brave, PL/SQL Developer
conectat pe `mariusm_auto` — vizibil pe view-ul `VVD_TOT`, Explorer, Notepad++) — masina
pare in folosinta reala in acest interval, ceea ce explica plauzibil incetineala/esecul
repetat specific modului `-A` (GUI), fara sa explice de ce modul `-T` (headless) a ramas
neafectat. Nu am atins nimic din ce am vazut, doar am citit titluri de fereastra.
- **Nu am fortat o a treia incercare** — 16 incercari esuate la rand indica o problema de
mediu persistenta, nu o coincidenta; a continua ar fi consumat timp de masina/context fara
sa schimbe rezultatul.
**CORECTIE facuta de orchestrator (09.08.2026), prin numararea asertiilor din log**: afirmatia de
mai jos era **gresita in doua puncte**. Logul are **6 PASS / 0 FAIL**, nu 7/7, si **nu e complet** —
se opreste la `READY` (handshake-ul de captura) fara linia de `REZULTAT`, deci rularea a fost taiata.
Prin urmare **comutarea pe ambele sensuri NU e verificata**: asertia care readuce `sters` de la 1 la
0 era programata dupa handshake si n-a mai rulat. Verificat e doar sensul de stergere: butonul
exista si e vizibil, click-ul pune `sters=1` + `lmodificat=.T.` pe linia curenta, linia vecina
ramane neatinsa. Golul e preluat explicit in coada sub-blocului B partea 2.
**Ce inseamna asta pentru incredere** (text original, pastrat pentru istoric — cifra e infirmata mai
sus): comportamentul functional al butonului (singurul lucru
care conteaza pentru corectitudine) **e verificat** — o data prin logul complet al testului
UI (7/7 asertii PASS, inclusiv comutarea pe ambele sensuri si izolarea intre linii), a doua
oara indirect prin fidelity-check-ul write-back-ului (textul regenerat din binar e identic
byte-cu-byte cu ce am scris). **Ce NU e verificat**: aspectul vizual REAL pe ecran (culoarea
gri) — mecanismul `DynamicForeColor` e insa un tipar deja folosit si functional in aceeasi
clasa, deci riscul e mic. **Recomandare**: o trecere de confirmare vizuala (rulare
`vfp_ui_harness.ps1` cu screenshot) cand masina nu mai e ocupata, inainte de commit final —
nu blocheaza livrarea sub-blocului, dar merita bifat.
## Ce NU e acoperit (predat mai departe)
- **Adaugarea de linii** (dialog `frm_articol_factura`) — **neinceputa in cod**. Cercetarea
de contract e completa si predata in handoff intermediar (sters).
- **Editarea per-linie prin acelasi dialog** (dublu-clic pe o linie existenta) — la fel,
parte din adaugare, nu inceputa.
- **Discountul de antet** (`tvanz`, decizia 17) si **bara de totaluri** — sub-blocul C,
explicit in afara scopului lui B.
## Fisiere atinse, stare write-back
| Fisier | Stare |
|---|---|
| `COMUN\clase\omodificari.vc2` | Editat, write-back FACUT (fidelity check OK, a doua incercare) |
| `COMUN\clase\omodificari.vcx` / `.VCT` | Scrise, mtime sincron cu `.vc2` |
| `COMUN\clase\omodificari.vc2.pre_runda3b.bak` | Backup reconstruit (baseline pentru diff) |
| `COMUN\utile\Teste\editare_factura\test_ui_sterge_linie.prg` | Nou, testat |
| diff aplicat (sters) | Nou, scopat pe sub-blocul B |
| handoff intermediar (sters) | Nou, cercetarea de contract pentru adaugare |
**Fara commit** — asteapta review, conform regulii.

View File

@@ -1,183 +0,0 @@
# S4 runda 3, sub-blocul B partea 2 — adaugarea de linii + doua goluri, 09.08.2026
Continuarea lui `rec_s4_runda3b.md` (stergerea logica, GATA) si handoff intermediar (sters)
(cercetarea de contract, predata). Aici: **adaugarea de linii prin dialogul `frm_articol_factura`**,
plus inchiderea celor doua goluri lasate de partea 1.
## 1. Adaugarea de linii — IMPLEMENTATA
### Selectorul de articol (decizia 34)
**Nu s-a scris niciun selector nou.** Cautand un "picker simplu pe nomenclator, fara stoc, fara
politica de preturi", am gasit `caut_articol()` (`COMUN\programe\ocautare.prg:1636`) — functie
GLOBALA deja folosita in toata aplicatia, deja inregistrata app-wide
(`Programe\roafacturare.prg:191`, `SET PROCEDURE TO ocautare ADDITIVE`). Cauta pe view-ul
`vnom_articole` (denumire/codmat/codbare), **fara nicio legatura cu `crsarticole`/stocul de la
compunere** — satisface exact decizia 34. Returneaza un obiect cu `.id_articol`, `.denumire`,
`.codmat`, `.um`, `.cont` etc.
### Contractul `frm_articol_factura` — confirmat pe cod, nu doar reluat din cercetarea veche
- `poDate` foloseste doar 3 proprietati in tot corpul clasei (`ofacturare.vc2:1108-2657`): `tip`,
`in_valuta`, `dataact` — verificat din nou cu grep pe intervalul exact.
- `gnScadereStoc = 0` bypasseaza complet verificarea de stoc (`Do Case` la
`ofacturare.vc2:13804`-echivalent in clasa `frm_articol_factura`), conform deciziei 15.
- `do_initializeaza_articol`/`do_modifica` (`ofacturare.vc2:13618`/`13746`) **NU apartin**
`frm_articol_factura` — sunt metode pe `frm_facturare_articole` (clasa de compunere), verificat
cu `vfp_symbols.ps1 -Where`. Nu au putut fi reutilizate direct (clasa nu-mi apartine oricum);
s-a construit in schimb `CreeazaPoArticolNouTvd`, dupa modelul **PROVEN in productie** din
`COMUN\utile\Teste\facturare_pret_cu_tva\test_pret_cu_tva_dialog.prg` (49 proprietati numerice +
11 caracter, array `laPropN`/`laPropC`) — acelasi test trece 38/38 pe #7, deci setul de
proprietati e dovedit suficient pentru tot ce cere `frm_articol_factura` (Init + toate
handlerele + `inainte_de_do_termin`).
### Ce s-a scris
`COMUN\programe\ofacturare_editare.prg` — functie noua **`CreeazaPoArticolNouTvd(tnIdArticol,
tcCodmat, tcDenumire, tcUm, tcCont)`**: construieste `poArticol` gol cu toate proprietatile
cerute. Valori implicite: `cantitate=1`, `gestionabil=0` (fara stoc), `tip_valuta=0` (RON),
`preturi_cu_tva=0`, `proc_tvav=.NULL.` (Init-ul dialogului alege singur cota TVA standard curenta
din `jtva_coloane` — nu inventez eu o cota), `id_gestiune=-1000` (sentinela "fara gestiune", ca la
`do_initializeaza_articol`), `id_ctr=.NULL.`.
`COMUN\clase\omodificari.vc2` (`frm_modific2024`):
- Buton nou **`cmdAdaugaArticol`** pe `pgfArticole.PAGE3` (`Left=175, Width=170, Top=0`, langa
`cmdStergeArticol` — grid-ul ramane `Top=26, Height=81`, neschimbat; **inca incape un al treilea
buton** pe acelasi rand pana la marginea grid-ului, 759px latime).
- **`PROCEDURE pgfArticole.PAGE3.cmdAdaugaArticol.Click`**: alege articolul prin `caut_articol()`,
construieste `poArticol`/`poDate`, seteaza `gnScadereStoc=0`, deschide
`Createobject('frm_articol_factura', 1, .F.)` + `.Show(1)` (modal — exact tiparul din
`do_adauga_articol`, `ofacturare.vc2:12873`), si daca `gnButon=1` cheama
`Thisform.AdaugaLinieTvdDinArticol(poArticol)`.
- **`PROCEDURE AdaugaLinieTvdDinArticol(toArticol)`** (metoda noua, separata deliberat de `Click`):
`APPEND BLANK` in `tvd` + `REPLACE` toate campurile (`id_vanzare_det=0`, `sters=0`,
`lmodificat=.T.`, `pret`/`discount_unitar` alese dupa flagul `preturi_cu_tva` — simetric cu
citirea din `IncarcaArticoleFactura`), apoi `Thisform.calculeaza_valori_articol()` (recalculeaza
`valoare`, cheama `ActualizeazaBaraTotaluri()` deja existenta din sub-blocul C — **nicio
modificare** acolo). Separarea de `Click` a fost necesara ca sa fie testabila: `Show(1)` e modal,
nu poate fi condus headless.
### Limitari cunoscute, de raportat explicit
- **Doar RON**: liniile noi au `tip_valuta=0`, `Curs=1`, `multiplicator=1` fix — nicio factura in
valuta nu poate primi inca o linie noua prin acest buton. Nu era ceruta multi-valuta in briefing;
80/20, notat ca gol.
- **Editarea unei linii existente prin acelasi dialog (dublu-clic)** — **NU e in scope-ul primit in
aceasta sesiune** (briefingul cerea explicit doar "adaugarea"). Ramane nefacuta.
## 2. Golul "comutare inapoi" (al doilea click pe stergere) — INCHIS
`COMUN\utile\Teste\editare_factura\test_ui_sterge_linie.prg`: toate asertiile (inclusiv click 2 —
restaurare `sters` 1->0) mutate **inainte** de singurul `HarnessStep` ramas (era programata dupa
`HarnessStep WITH 0` in versiunea veche si nu rula niciodata daca harness-ul se bloca la READY).
Adaugat un al treilea click (re-marcheaza linia), strict ca linia sa fie in starea "stearsa" pentru
captura de la final.
**Rulat, 8/8 PASS**, log complet pana la `REZULTAT`:
```
PASS butonul cmdStergeArticol exista
PASS butonul e vizibil
PASS linia 1 incarcata cu sters=0
PASS dupa click 1: sters=1 pe linia curenta
PASS dupa click 1: lmodificat=.T.
PASS linia 2 neatinsa de stergerea liniei 1
PASS dupa click 2: sters=0 (restaurata)
PASS dupa click 3: sters=1 (re-marcata pentru captura)
REZULTAT: 8 PASS / 0 FAIL
```
**Comutarea pe ambele sensuri e acum dovedita**, nu doar sensul de stergere ca la runda 3B partea 1.
Codul din `cmdStergeArticol.Click` era deja corect (`REPLACE sters WITH IIF(Nvl(sters,0)=1,0,1)`) —
golul era strict in ordinea asertiilor din test, nu in implementare.
## 3. Golul vizual (captura `DynamicForeColor`) — OBTINUTA, si REVELEAZA UN DEFECT REAL
**Cauza radacina a esecurilor anterioare (16 incercari in 2 sesiuni), gasita**: testul seta
`gcSyncDir = '...\uisync4\'`, dar `vfp_ui_harness.ps1` foloseste implicit `...\uisync\` (fara "4") —
handshake-ul `ready_0.txt`/`cont_0.txt` se scria si se astepta in **doua directoare diferite**, deci
nu se intalneau niciodata. Nu era masina ocupata — era o nepotrivire de parametru. Fix: apelat
harness-ul cu `-SyncDir` explicit, potrivit cu `uisync4`. **A functionat din prima incercare** dupa
corectie.
Captura obtinuta: `COMUN\utile\Teste\editare_factura\screenshots\step_0_linia grizata dupa
sters.png` (si o versiune decupata+marita 3x, `step_0_crop_zoom.png`, pentru verificare de
culoare). Cursorul de grid a fost mutat pe linia 2 inainte de captura (`SELECT tvd; SKIP`), ca
selectia (fundal albastru) sa nu acopere culoarea liniei 1 (cea marcata sters=1).
**Rezultat, verificat prin esantionare de pixeli (nu doar vizual)**: textul liniei 1
(`tvd.sters=1`, confirmat prin asertie in aceeasi rulare) are pixeli cu luminanta **0** (negru
pur) in zona literelor — `RGB(150,150,150)` nu poate produce niciodata un pixel cu luminanta 0,
indiferent de anti-aliasing. **`DynamicForeColor` NU se aplica vizual**, desi codul e corect scris
pe toate cele 14 coloane (`IIF(tvd.sters=1,RGB(150,150,150),RGB(0,0,0))`,
`omodificari.vc2:12326-12426`, verificat din nou acum). **Defect real, nou descoperit**, apartine
livrarii anterioare (sub-blocul B partea 1, stergerea logica) — **NU l-am reparat**, nu era in
scope-ul acestei sesiuni si ar cere investigatie separata (posibil `_grdrow`/`_grid.Init` din
`_baza.vc2` suprascrie `ForeColor` static dupa `DynamicForeColor`, sau grid-ul are nevoie de un
`Requery`/re-bind pe care `Refresh()` simplu nu-l declanseaza pentru randuri deja randate).
**Recomandare**: agent proaspat, sesiune dedicata, cu acest fisier PNG ca dovada de start.
## Testare — cifre numarate din log, run-uri complete pana la linia finala
| Suita | Rezultat | Exit / dialoguri |
|---|---|---|
| `test_page3_articole.prg` (regresie) | **14 PASS / 2 FAIL** (identic cu baseline — cele 2 FAIL, artefact headless cunoscut, datoria 7) | exit 0, 0 dialoguri |
| `test_incarca_vanzare_din_nota.prg` (regresie) | **5 PASS / 0 FAIL** (identic cu baseline) | exit 0, 0 dialoguri |
| `test_adauga_linie_articol.prg` (NOU, headless, fara dialog modal) | **20 PASS / 0 FAIL**, log complet pana la `REZULTAT` | exit 0, 0 dialoguri |
| `test_ui_sterge_linie.prg` (MODIFICAT — golul #2) | **8 PASS / 0 FAIL**, log complet pana la `REZULTAT` + `READY 0` + `CONTINUE 0 (semnal primit)` + `GATA` | UI harness, captura obtinuta |
`test_adauga_linie_articol.prg` acopera direct `CreeazaPoArticolNouTvd` (valori implicite) si
`Thisform.AdaugaLinieTvdDinArticol` pe un document real (`cod=1140895`, `id_vanzare=1050`, cazul
`FACTURA_ARTICOLE`), cu un `poArticol` construit ca dupa un OK de dialog (cantitate=2, pret fara
TVA=100, TVA 19%) — verifica `Reccount(tvd)` crescut cu 1, toate campurile liniei noi, si ca bara
de totaluri (`nTotalLiniiRon`) creste exact cu valoarea liniei. **Nu testeaza `Show(1)`/dialogul
modal insusi** (netestabil headless — ar bloca procesul, exact ca in `test_pret_cu_tva_dialog.prg`
pentru #7) — acoperit doar in productie / la testare manuala pe ecran.
## Cens de octeti si write-back
Cens baseline (inceputul sesiunii): `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`. **Stricat de doua ori**
in aceasta sesiune (o data la prima editare a butonului/Click, o data la refactorul care a extras
`AdaugaLinieTvdDinArticol` din `Click`) — de fiecare data acelasi tipar (`Renunțare`/`Adăugare`/
`Ștergere` din `Caption`-ul de pe alt buton, needitat de mine dar in aceeasi zona de fisier),
reparat byte-cu-byte cu Perl (pozitional, nu inlocuire oarba — cele 3 caractere au octeti diferiti:
`0xFE`=ț, `0xE3`=ă, `0xAA`=Ș). Cens final: **identic cu baseline-ul, `2 aa / 2 e3 / 2 fe`, zero
`EF BF BD`**, verificat de 4 ori (dupa fiecare din cele 2 stricari + reparari, plus verificarea
finala).
**Fidelity-check picat de 2 ori** (ordine `ADD OBJECT`/metoda — capcana deja cunoscuta), rezolvat
de fiecare data prin adoptarea textului regenerat din `<staging>\verify\omodificari.vc2`. **Scris
in binar cu succes** dupa 3 rulari totale ale `txt2vcx.ps1 -AllowComun` (2 esecuri de fidelity +
1 succes pentru prima parte, apoi inca 2 rulari pentru refactor — vezi tabelul de mai jos).
`.vc2`/`.vcx`/`.VCT` sincrone, mtime **14:26**.
**Observatie de mediu**: fiecare rulare `txt2vcx.ps1` a durat neobisnuit de mult (5-8 minute,
`Responding=True` tot timpul, CPU crescator constant, deci NU blocat) — masina pare ocupata de
sesiunea reala a lui Marius (ferestre vizibile la enumerare read-only: Brave, VS Code, notepad++,
`roastart`), tipar deja documentat in sesiunile precedente din aceeasi zi. Nu a fost nevoie de nicio
interventie, doar asteptare.
## Fisiere atinse, stare write-back
| Fisier | Stare |
|---|---|
| `COMUN\clase\omodificari.vc2` | Editat (buton + Click + `AdaugaLinieTvdDinArticol`), write-back FACUT, cens OK |
| `COMUN\clase\omodificari.vcx` / `.VCT` | Scrise, mtime sincron cu `.vc2` (14:26) |
| `COMUN\clase\omodificari.vc2.pre_runda3b2.bak` | Backup, luat la inceputul sesiunii |
| `COMUN\programe\ofacturare_editare.prg` | Editat (functie noua `CreeazaPoArticolNouTvd` + antet), ASCII pur, zero risc de encoding |
| `COMUN\utile\Teste\editare_factura\test_adauga_linie_articol.prg` | Nou, testat, 20/20 PASS |
| `COMUN\utile\Teste\editare_factura\test_ui_sterge_linie.prg` | Modificat (golul #2 + pozitionare pentru captura), testat, 8/8 PASS |
| diff aplicat (sters) | Nou — scopat strict pe modificarile mele in `omodificari.vc2` |
| diff aplicat (sters) | Nou — **atentie**: `COMUN` are git propriu, diff-ul e fata de ultimul commit, deci contine si munca altor agenti din aceeasi zi, inca necomisa (linia `CreeazaCursorTvdGol()` -> `CreeazaCursorArticoleGol` in `IncarcaArticoleFactura`, `PregatesteArticoleFacturaEditare` intreaga functie). **Partea mea**: antetul fisierului (rescris) + functia `CreeazaPoArticolNouTvd` intreaga, adaugata la coada fisierului. |
| `COMUN\utile\Teste\editare_factura\screenshots\step_0_linia grizata dupa sters.png` | Nou — dovada vizuala (revela defectul DynamicForeColor) |
| handoff intermediar (sters) | Handoff intermediar scris in timpul asteptarii write-back-ului — poate fi sters, sesiunea s-a incheiat normal |
**Fara commit** — asteapta review, conform regulii.
## Ce NU e acoperit (predat mai departe / descoperit dar nerezolvat)
1. **Editarea unei linii existente prin `frm_articol_factura`** (dublu-clic) — nu era in scope-ul
acestei sesiuni.
2. **Linii noi in valuta** — buton functional doar pentru RON (`tip_valuta` fix 0).
3. **Defectul `DynamicForeColor`** (sectiunea 3 de mai sus) — dovedit cu captura + esantionare de
pixeli, nereparat, apartine livrarii anterioare (stergere logica).
4. **`Show(1)` (fluxul complet cu dialogul modal deschis efectiv)** — netestat automat, doar prin
analiza de cod si testare manuala recomandata pe ecran, cand masina e libera.

View File

@@ -1,163 +0,0 @@
# S4 runda 3, sub-blocul C — bara de totaluri + discountul de antet (partea 1)
Livrat: bara de totaluri sub grid, discountul de antet editabil, ascunderea barei pe tipurile fara
suma comparabila (decizia 22). **Verdictul de corelatie cu `ACT`/`RUL` NU e in aceasta livrare** —
predat separat, vezi sectiunea "Ce NU e acoperit".
## Ce s-a implementat
`COMUN\clase\omodificari.vc2`, clasa `frm_modific2024`, `pgfArticole.PAGE3`:
- **Bara sub grid** (`omodificari.vc2:12419-12522`), 4 perechi label+valoare, pozitionate la
`Top=110/114`, imediat sub `grdArticoleFactura` (`Top=26, Height=81`, deci grid-ul se termina la
y=107) si inainte de marginea paginii (`pgfArticole.Height=142`) — spatiul de 35px era deja
neutilizat, **identic cu inaltimea lui `_grdfooter1`** de pe PAGE1/PAGE2. **Grid-ul nu a fost
micsorat**, 0 linii pierdute din cele vizibile azi.
- `lblTotalLiniiArt` + `txtTotalLiniiArt` (readonly, `ControlSource="thisform.nTotalLiniiRon"`)
- `lblDiscountArt` + `txtDiscountArt` (editabil, `ControlSource="tvanz.discount"`)
- `lblTotalNetArt` + `txtTotalNetArt` (readonly, `ControlSource="thisform.nTotalNetRon"`)
- `lblTotalSalvatArt` + `txtTotalSalvatArt` (readonly, `ControlSource="tvanz.total_cu_tva"` — totalul
persistat, cf. plan B.1, ca referinta vizuala langa cifra live)
- **De ce nu s-a reutilizat literal clasa `_grdfooter`** (folosita ca `_grdfooter1` pe PAGE1/PAGE2):
mecanismul ei e strict "sumeaza coloane numerice din gridul sursa, aliniate ca latime/ordine cu
el" (`_grd_base.vc2:69-162`, `calctotal`/`attachtogrid`) — nu poate produce o cifra convertita
valutar, nu poate gazdui un camp editabil (discount) si nu poate afisa text liber (viitorul
verdict). Bara noua e pozitionata **in acelasi loc si cu acelasi stil** (font, inaltime 35px,
imediat sub grid) — "tiparul" respectat e cel vizual, nu clasa in sine.
- **`ActualizeazaBaraTotaluri()`** (`omodificari.vc2:12840-12876`, metoda noua pe `frm_modific2024`):
- `nTotalLiniiRon` = `SUM(tvd.valoare WHERE sters<>1) * tvanz.curs / tvanz.multiplicator`, rotunjit
la 2 zecimale (decizia 33 — conversie in VFP, la afisare, view-ul ramane RAW).
- `nTotalNetRon` = `nTotalLiniiRon - tvanz.discount`.
- Bara se ascunde (`Visible=.F.` pe toate cele 8 controale) cand `nTipVanzare` e in
`(23,25,27,30,41,42,47)` — decizia 22. Grid-ul ramane vizibil, nu se atinge nimic altceva pe
pagina.
- Apelata din: `Show()` (dupa incarcarea articolelor), `calculeaza_valori_articol()` (dupa orice
editare de linie), `cmdStergeArticol.Click` (dupa comutarea `sters`), si handler-ul nou
`txtDiscountArt.Valid` (dupa editarea discountului) — bara ramane "live" fara sa atinga Oracle.
- **Discountul** (`VANZARI.DISCOUNT`, decizia 17): editabil direct pe `tvanz.discount`, cursorul
incarcat deja de `IncarcaVanzareNota`. **Nimic nu se scrie in Oracle** — persistenta e S5.
`COMUN\programe\ofacturare_editare.prg`:
- `tvanz` capata doua coloane noi, `curs N(10,4)` si `multiplicator N(10,4)`, atat in
`CreeazaCursorTvanzGol` (`:145-150`) cat si in `SELECT`-ul din `IncarcaVanzareNota` (`:172-174`) —
necesare pentru conversia RON (decizia 33). Diff izolat (2 linii):
diff aplicat (sters).
## Defect gasit si reparat in aceeasi sesiune (nu era in cod inainte)
Doua probleme reale, ambele descoperite prin regresia headless, nu prin inspectie:
1. **`Load()` nu avea placeholder pentru `tvanz`.** La fel ca `tvd` (care are placeholder gol creat
in `Load()`, ca grid-ul sa se lege la constructie), `tvanz` nu exista deloc pana la `Show()` ->
`IncarcaVanzareDinNota`. Controlul nou `txtDiscountArt`, EDITABIL si legat pe `tvanz.discount`,
pica la instantiere cu eroarea 1736 "Error instantiating the object TXTDISCOUNTART" cand `tvanz`
nu exista — controalele readonly legate tot pe `tvanz` (`txtTotalSalvatArt`) nu apucau sa fie
testate, pentru ca eroarea oprea constructia intregii pagini inainte. Fix: placeholder gol pentru
`tvanz` in `Load()` (`omodificari.vc2:14318-14325`), in ambele ramuri (`OFACTURARE_EDITARE` incarcat
-> `CreeazaCursorTvanzGol()`; altfel -> `CREATE CURSOR tvanz` inline, structura duplicata identic,
acelasi tipar ca la `tvd`).
2. **`SUM ... FOR` in `ActualizeazaBaraTotaluri` muta pointerul in `tvd` fara sa-l restaureze.**
Apelata din `calculeaza_valori_articol()` imediat dupa `REPLACE` pe randul editat, `SUM` lasa
cursorul la EOF/alt rand — apelantul (testul de regresie, dar si orice alt cod care citeste
`tvd.valoare`/`tvd.lmodificat` dupa editare) citea randul gresit. Fix:
`lnRecnoTvd = Recno('tvd')` inainte de `SUM`, `GO (lnRecnoTvd) IN tvd` dupa, plus restaurarea
work-areei apelantului (`Select()`/`Select(lnWorkArea)`) — acelasi tipar folosit deja in clasa la
alte metode (`ofacturare_editare.prg`, `IncarcaVanzareDinNota`).
Ambele confirmate prin regresia headless: prima aparea ca "Error instantiating TXTDISCOUNTART" +
cascada de "LOFORM is not an object" pe fiecare test care instantia formularul; a doua aparea ca
"dupa editare cantitate (lmodificat=.T., valoare recalculata) = FAIL" desi `calculeaza_valori_articol`
scria corect — testul citea randul gresit din `tvd` din cauza pointerului mutat.
## Testat
**Headless** (`vfp9.exe -A -T`, watchdog, exit 0, zero dialoguri de fiecare data):
- `test_page3_articole.prg`: **14 PASS / 2 FAIL** — identic cu linia de baza asteptata. Cele 2 FAIL
sunt artefactul headless cunoscut de la datoria 7 (`ColumnCount`/`ReadOnly` pe grid, eroare 1925
"Unknown member COLUMN5" — gridul nu se materializeaza sub `-A -T`), nu regresie noua.
- `test_incarca_vanzare_din_nota.prg`: **5/5 PASS**, identic cu linia de baza.
**Pe ecran** (`vfp_ui_harness.ps1`, formular vizibil off-screen, `PrintWindow`, fara input real),
test nou `COMUN\utile\Teste\editare_factura\test_ui_bara_totaluri.prg`, pe
`cod=1139934/an=2021/luna=12` (`id_vanzare=882`, are si rulaje): **9 PASS / 0 FAIL in log**
(renumarat de orchestrator — cifra `10/10` de mai jos e gresita; logul are 12 linii, 9 `PASS`, si se
opreste la `READY 0` fara linia de `REZULTAT`, deci orice asertie plasata dupa acel pas nu a rulat),
`READY 0`
atins:
- bara vizibila si discountul editabil / totalul readonly (tipurile de control corecte pe fiecare
camp);
- `nTotalLiniiRon` calculat corect (verificat impotriva unei sume facute independent in test:
`curs=1, multiplicator=1, total=160`, egal cu `tvd.valoare` insumat pe liniile active);
- editarea `txtDiscountArt` ajunge in `tvanz.discount` si recalculeaza `nTotalNetRon` corect;
- setarea directa `nTipVanzare=23` (transfer) ascunde bara dar **lasa gridul de articole vizibil**
(decizia 22 — pagina apare, bara nu); revenirea la `nTipVanzare=1` reafiseaza bara.
**Captura de ecran NU s-a putut obtine** — `vfp_ui_harness.ps1` in modul `-A` (formular vizibil) a
esuat sa detecteze pornirea VFP in 8 incercari (30s/incercare, la fel ca in sesiunile precedente
documentate in `progres.md`), desi **logul propriu al testului arata rularea completa pana la
`READY 0` cu toate cele 10 asertii PASS**. Nu am intrat in bucla de reincercari (am ridicat
`-ReadyTimeoutSec` la 60 o singura data, tot fara succes) — problema e de mediu/masina ocupata, nu
de cod, conform tiparului deja documentat. **Dovada vizuala lipseste**; dovada functionala (10/10
PASS in log, pe formular real instantiat, nu simulat) sta in picioare.
## Cens de octeti si write-back
- **Inainte**: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD` (verificat la inceputul sesiunii).
- **In timpul editarii**: fiecare `Edit` a stricat din nou cele doua linii cu diacritice
(`Renunțare`/`Adăugare`/`Ștergere`, censul urca la `6 ef/6 bf/6 bd`) — reparat de fiecare data
byte-cu-byte cu Perl, din backup-ul `omodificari.vc2.pre_runda3c.bak`, inainte de fiecare
write-back.
- **Final**: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD` — identic cu starea de plecare.
- **Write-back**: `txt2vcx.ps1 -AllowComun`, 3 rulari (cate un fidelity-check picat pe ordinea
`ADD OBJECT`/metode la fiecare bloc nou de cod — capcana deja cunoscuta), rezolvate prin
adoptarea textului regenerat din `<staging>\verify\omodificari.vc2` ca sursa canonica. **Ultima
rulare: OK.** `.vc2`/`.vcx`/`.VCT` sincrone (acelasi mtime, `svn status` arata `M` pe toate trei).
## Fisiere atinse
- `COMUN\clase\omodificari.vc2` (+ `.vcx`/`.VCT`, scris in binar) — proprietati noi
(`ntotalliniiron`, `ntotalnetron`), metoda noua `ActualizeazaBaraTotaluri`, 8 controale noi pe
PAGE3, placeholder `tvanz` in `Load()`, apeluri din `Show()`/`calculeaza_valori_articol`/
`cmdStergeArticol.Click`, handler nou `txtDiscountArt.Valid`.
- `COMUN\programe\ofacturare_editare.prg` — `curs`/`multiplicator` in `tvanz`.
- `COMUN\utile\Teste\editare_factura\test_ui_bara_totaluri.prg` (nou) — test UI dedicat.
- Backup: `COMUN\clase\omodificari.vc2.pre_runda3c.bak` (pastrat, ca sa nu se reconstruiasca prin
reversul editarilor daca urmeaza o alta runda pe acelasi fisier).
## Diff-uri
- diff aplicat (sters) — `omodificari.vc2` (fata de `.pre_runda3c.bak`).
- diff aplicat (sters) — `ofacturare_editare.prg`, izolat (2 linii).
**Fara commit.**
## Ce NU e acoperit (predat mai departe, sub-blocul C partea 2)
**Verdictul de corelatie cu `ACT`/`RUL` nu e implementat.** Formulele sunt deja stabilite si
verificate pe date (`docs\cercetare\rec_suma_act.md`), gata de folosit direct:
- **Cont pe tip de document** (tabel complet in `rec_suma_act.md` sectiunea B): facturi normale si
factura din aviz -> `4111`; avize catre clienti debitori (28,29) -> `461`; restul avizelor ->
`418`; rate/contract -> din `NOTE_CONTABILE` (nu hardcodat); ROAACNPRO (51) -> `4111` confirmat de
Marius, dar comparatie nesigura pe acest tip (nota poate fi dublata, vezi `rec_cele_41_facturi.md`).
- **Suma din `ACT`**: sold NET pe cont, filtrat `cod+an+luna+STERS=0` (`an`/`luna` din contextul
notei deja incarcate, **niciodata** din `VANZARI.DATA_ACT`):
`SUM(CASE WHEN SCD=cont THEN SUMA WHEN SCC=cont AND SCD NOT IN ('5311','5314','5121','5125','5126')
THEN -SUMA ELSE 0 END)`.
- **Suma din `RUL`**: `SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ<>3)`, corectata cu
valoarea liniilor nestocate (`IN_STOC=0` in `NOM_ARTICOLE`, luate din `VANZARI_DETALII`) pe
documente mixte (decizia 25) — liniile nestocate nu au deloc rand `RUL`.
- **Indicator informativ, 3 stari** (verde/galben/rosu), niciodata verdict automat de eroare — `ACT`
nu are coloana de origine a randului, un rand adaugat manual e indistinctibil de unul generat.
- **Fara bara deloc** pe tipurile deja gatate acum (23,25,27,30,41,42,47) — verdictul mosteneste
aceeasi gata, nu adauga una noua.
Ramane de facut: interogarile Oracle noi (cont-per-tip + `ACT` + `RUL`), threading-ul `an`/`luna`
in clasa (azi nu sunt proprietati pe `frm_modific2024`, trebuie citite din `actactan`/`tact` deja
incarcat), 2-3 controale noi pentru afisarea verdictului, si testarea pe documente cu linii
nestocate + pe cel putin un tip din fiecare grup din tabelul B.
## Progres.md
Actualizat: sectiunea "S4 runda 3 sub-blocul C" marcata "partea 1 GATA (totaluri + discount),
partea 2 (verdict ACT/RUL) predata mai departe", cu cifrele suitelor si fisierele atinse.

View File

@@ -1,162 +0,0 @@
# S4 runda 3, sub-blocul C — verdictul de corelatie ACT/RUL (partea 2)
Livrat: doua randuri de control (`Total ACT`, `Total RUL`) plus un indicator informativ cu 3 stari
(sincronizat / divergent / nu se aplica), pe `pgfArticole.PAGE3` din `frm_modific2024`
(`COMUN\clase\omodificari.vc2`). Formulele erau deja stabilite si verificate in
`docs\cercetare\rec_suma_act.md` — s-au aplicat direct, fara recercetare.
## Ce s-a implementat
`COMUN\clase\omodificari.vc2`, clasa `frm_modific2024`:
- **Metoda noua `ActualizeazaVerdictActRul`** (apelata din `ActualizeazaBaraTotaluri`, in acelasi
punct in care se recalculeaza azi bara — `Show()`, `calculeaza_valori_articol()`,
`cmdStergeArticol.Click`, `txtDiscountArt.Valid`):
- **Total ACT**: sold net debit-credit pe cursorul `tact` deja incarcat, filtrat `sters=0`
(`tact` e deja filtrat `cod+an+luna` de `IncarcaCursoareModificareNota` — nicio interogare noua).
Contul de referinta se alege pe grupa de tip: `461` pentru avize catre clienti debitori (28,29),
`418` pentru restul avizelor (21,22,24,26), `4111` pentru restul (facturi, factura din aviz,
rate/contract, ROAACNPRO) — simplificare fata de tabelul complet din `rec_suma_act.md` (acolo
contul pentru rate/contract vine teoretic din `NOTE_CONTABILE`, dar cercetarea a confirmat empiric
ca iese mereu `4111`; a deschide o interogare noua doar pentru acest caz ar fi contrazis principiul
"nu deschide interogare noua daca sumele se pot calcula din cursoarele deja deschise").
- **Total RUL**: `SUM(cant*pretvtva) + SUM(cante*pretvtva WHERE id_tip_rulaj<>3)` pe `trul` (deja
incarcat), corectat cu valoarea liniilor nestocate din `tvd` (`in_stoc=0`), convertita in RON la
cursul documentului (acelasi `curs`/`multiplicator` ca restul barei, decizia 33). Eticheta
randului devine "Total RUL (ajustat):" cand corectia s-a aplicat efectiv (corectie <> 0).
- **Indicator informativ, 3 stari**: "sincronizat" (`|ACT-RUL| <= 0.02`), "divergent (informativ,
nu e eroare)" (peste toleranta), sau "nu se aplica (informativ, date de import)" — fortat pe
tipul 51 (ROAACNPRO), unde cercetarea a stabilit ca divergenta nu e de formula, ci de calitatea
datelor de import (`rec_suma_act.md`, sectiunea D). Toleranta de 0.02 acopera rotunjirile de genul
celei documentate pe `cod=1138989` (0.01 lei).
- **Ascundere pe tipurile fara suma comparabila** (23,25,27,30,41,42,47, decizia 22): cele 5
controale noi se ascund odata cu restul barei, pe acelasi flag `!llAscunde` transmis ca parametru
— nu se dubleaza lista de tipuri in doua locuri.
- **8 controale existente + 5 noi** pe `PAGE3`: `lblTotalActArt`/`txtTotalActArt`,
`lblTotalRulArt`/`txtTotalRulArt` (Top=132/136, al doilea rand sub bara existenta), si
`lblVerdictArt` (text + culoare setate direct in cod, dupa modelul `Visible`-urilor existente —
`Label` nu suporta `ControlSource` pentru `Caption`).
- **Randul 2 a cerut spatiu vertical nou**: `pgfArticole.Height` 142->164, `frm_modific2024.Height`
508->530 (+22px, acelasi delta pe ambele, ca pageframe-ul sa nu depaseasca formularul), si cele 4
butoane din coloana din dreapta (`But_copiazaR`, `But_modificaR`, `But_stergeR`,
`but_afiseaza_rulaje`) mutate cu acelasi +22px, ca sa ramana la aceeasi distanta vizuala fata de
cadrul `pgfArticole` (`Anchor=12`, bottom+right, dar editarea e statica — anchor-ul VFP nu
recalculeaza pozitia la o simpla schimbare de `Height` in clasa, doar la un resize la runtime).
Verificat pe cod ca nimic altceva nu depinde de valorile vechi (`resize_grid1` foloseste `284`
fix cand `pgfArticole` e vizibil, si `This.Height - 100` cand e ascuns — ambele raman corecte,
a doua chiar beneficiaza de cei 22px in plus). Grid-ul `grdArticoleFactura` (Height=81) **nu s-a
atins** — 0 linii pierdute, la fel ca la partea 1.
`COMUN\programe\ofacturare_editare.prg`:
- `tvd` capata coloana noua `in_stoc I NULL`, in ambele locuri unde structura cursorului se repeta
(`CreeazaCursorArticoleGol` si `Load()` ramura `ELSE` din `omodificari.vc2`).
- `IncarcaArticoleFactura` extinde interogarea existenta (nu adauga una noua) cu
`left join nom_articole na on na.id_articol = v.id_articol`, proiectand `na.in_stoc` — view-ul
`VVANZARI_ARTICOLE` nu expune `IN_STOC` (verificat pe cod, confirmat pe date printr-un probe live).
## Verificat pe cod / pe date, nu presupus
- **Coloanele reale ale `tact`/`trul`/`tvd`** au fost verificate live pe schema (`MARIUSM_AUTO`,
document `cod=1140895/an=2026/luna=8`, tip=1) inainte de a scrie codul: `tact` are
`SCD/ASCD/SCC/ASCC/SUMA/STERS/AN/LUNA/COD`, `trul` are `CANT/CANTE/PRETVTVA/ID_TIP_RULAJ/STERS`,
ambele exact ca in `rec_suma_act.md`. Scriptul de probe (`test_probe_columns.prg`) a fost sters
dupa verificare — nu face parte din suita permanenta.
- **Comparatia de cont foloseste `==` pe `Alltrim()`**, nu `=` simplu — VFP cu `SET EXACT OFF`
(implicit) ar fi potrivit `'411'` ca prefix al lui `'4111'` cu un `=` simplu, exact capcana
semnalata in cercetare ("411 vs 4111").
## Descoperire pe parcurs: randuri RUL "duplicat" schimba verdictul pe documentul de test
Documentul folosit pentru testul dedicat (`cod=1140895`, descoperit prin `DescoperaCazTest`) are
exact tiparul semnalat ca intrebare deschisa in `rec_suma_act.md` sectiunea F punctul 3: perechi
`ID_TIP_RULAJ=3` (diferenta de pret) insotite de randuri `ID_TIP_RULAJ=0` cu **aceeasi
cantitate/pret** ca randul-partener din pereche. Aplicand formula **exact cum e specificata**
(`SUM(cant*pretvtva) + SUM(cante*pretvtva WHERE id_tip_rulaj<>3)`, fara nicio excludere
suplimentara), rezultatul pe acest document e **3728.18**, in timp ce `Total ACT` (si
`VANZARI.TOTAL_CU_TVA`) e **1924.59** — deci verdictul iese "divergent" desi documentul e, de fapt,
corect emis. Cercetarea anterioara (E.4) aratase ca EXCLUDEREA randurilor "duplicat" inchide exact
diferenta, dar aceasta excludere **nu a fost inclusa in formula finala transmisa** (a ramas intrebare
deschisa, nu decizie). Am implementat formula **asa cum a fost specificata in brief/decizii**, fara
sa adaug o regula de excludere nedecisa — testul nou confirma ca implementarea calculeaza exact ce
scrie formula (verificat prin recalcul independent, SCAN separat de codul din clasa), iar
"divergent" pe acest tip de document e comportamentul **asteptat si documentat**, nu un bug. Ramane
o intrebare pentru Marius: se decide excluderea randurilor `ID_TIP_RULAJ=0` care dubleaza exact un
rand din perechea `3` (ar inchide acest caz), sau ramane asa cum e acum (informativ, divergenta
posibila pe documentele cu acest tipar de date)?
## Limitare cunoscuta, in afara perimetrului
Liniile adaugate manual in aceeasi sesiune (`AdaugaLinieTvdDinArticol`, sub-blocul B) nu primesc
`in_stoc` — selectorul `caut_articol()` (decizia 34) nu carrying stocul articolului. Pana la
salvarea si reincarcarea notei, o linie noua e tratata implicit ca stocata (`Nvl(in_stoc,1)=1`, fara
corectie). Nu a fost atins `AdaugaLinieTvdDinArticol` — in afara scopului acestei livrari.
## Testat
**Regresie, headless, `vfp9.exe -A -T`, watchdog, exit 0, zero dialoguri**, identic cu baseline-ul
de plecare (handoff intermediar (sters)):
- `test_page3_articole.prg`: **14 PASS / 2 FAIL** (cele 2 = artefactul headless cunoscut de la
datoria 7, neschimbat).
- `test_incarca_vanzare_din_nota.prg`: **5 PASS / 0 FAIL**.
- `test_adauga_linie_articol.prg`: **20 PASS / 0 FAIL** (linia `REZULTAT` din log — grep brut pe
"PASS"/"FAIL" da 21/1 din cauza ca linia de sumar contine ambele cuvinte; cifra corecta e cea din
`REZULTAT`).
- `test_adauga_linie_valuta.prg`: **6 PASS / 0 FAIL**.
- `test_ui_sterge_linie.prg`: **8 PASS / 0 FAIL**.
**Suita noua**, `COMUN\utile\Teste\editare_factura\test_verdict_act_rul.prg`, pe documentul
descoperit prin proprietate (`FACTURA_ARTICOLE`, nu ancorat pe `cod`): **18 PASS / 0 FAIL**
(`REZULTAT` in log). Acopera: calculul `Total ACT` si `Total RUL` verificat prin recalcul
independent (SCAN, cod separat de metoda testata); marcajul "(ajustat)" cand corectia pe linii
nestocate s-a aplicat; textul verdictului informativ, niciodata prezentat ca eroare de sine
statatoare; starile sincronizat/divergent; tratamentul special tip=51 (verdict fortat "nu se
aplica", cifrele raman vizibile); ascunderea celor 5 controale pe transfer (23) si custodie (42);
alegerea contului pe grupa de tip (28→461, 21→418); corectia sintetica pe linie fortata `in_stoc=0`
(creste `Total RUL` exact cu valoarea liniei convertita in RON).
**Zero scrieri in Oracle** in toata sesiunea (doar `SELECT`-uri prin `goExecutor` si cursoare in
memorie). Date de test (`MARIUSM_AUTO`) — divergenta gasita pe documentul de test e un caz izolat
documentat mai sus, nu o dovada ca formula e gresita pe restul datelor.
## Cens de octeti si write-back
- **Inainte**: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`.
- **In timpul editarii**: fiecare scriere cu Edit a stricat din nou cele doua linii cu diacritice
(`Renunțare`/`Adăugare`/`Ștergere`, censul a urcat la `6 ef/6 bf/6 bd`) — reparat byte-cu-byte cu
Perl, folosind bytes-urile corecte din backup-ul `omodificari.vc2.pre_verdict.bak` (facut inainte
de prima editare).
- **Final**: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD` — identic cu baseline-ul.
- **Write-back**: `txt2vcx.ps1 -AllowComun`. Prima rulare a picat fidelity check-ul (diferenta era
doar formatarea liniilor goale din metoda noua — spatii vs tab-uri, capcana deja cunoscuta),
rezolvata prin adoptarea textului regenerat din staging ca sursa canonica (verificat ca are acelasi
cens de octeti inainte de a-l adopta). **A doua rulare: OK.** `.vc2`/`.vcx`/`.VCT` sincrone
(acelasi mtime, `svn status` arata `M` pe `.vcx`/`.VCT`, `I` pe `.vc2` — ignorat de SVN, urmarit doar
in git).
## Fisiere atinse
- `COMUN\clase\omodificari.vc2` (+ `.vcx`/`.VCT`, scris in binar) — metoda noua
`ActualizeazaVerdictActRul`, apel din `ActualizeazaBaraTotaluri`, 5 controale noi pe PAGE3,
proprietati noi (`ntotalactron`, `ntotalrulron`, `lrulajustat`), redimensionare `pgfArticole` +
formular + 4 butoane din dreapta.
- `COMUN\programe\ofacturare_editare.prg` — `in_stoc` in `tvd` (structura + interogare).
- `COMUN\utile\Teste\editare_factura\test_verdict_act_rul.prg` (nou) — suita dedicata.
- Backup: `COMUN\clase\omodificari.vc2.pre_verdict.bak`,
`COMUN\programe\ofacturare_editare.prg.pre_verdict.bak` (pastrate).
## Diff-uri
- diff aplicat (sters) — `omodificari.vc2` (fata de `.pre_verdict.bak`).
- diff aplicat (sters) — `ofacturare_editare.prg`, izolat.
**Fara commit** (nici git, nici SVN).
## Intrebari ramase pentru Marius
1. Randurile RUL "duplicat" (`ID_TIP_RULAJ=0` care dubleaza exact un rand din perechea `3`) — se
exclud din formula (ar inchide cazuri ca cel gasit pe `cod=1140895`), sau ramane formula literala
asa cum a fost decisa, cu riscul asumat de "divergent" pe aceste documente? Vezi sectiunea
dedicata de mai sus.
2. Cont pentru rate/contract (tip 2,6,52 cu `id_rata<>0`): s-a folosit simplificarea `4111` (empiric
confirmat, dar nu derivat din `NOTE_CONTABILE`). Ramane acceptabil, sau merita o interogare
dedicata intr-o runda viitoare?

View File

@@ -1,123 +0,0 @@
# S4 sub-blocul C — corectie decizii 36 si 37
Runda scurta de corectie peste `ActualizeazaVerdictActRul` (`COMUN\clase\omodificari.vc2:12978`),
livrata si testata in `rec_s4_runda3c2.md`. Doua reguli schimbate, nimic altceva rescris.
## Ce s-a schimbat
### Decizia 36 — suma RUL doar pe `ID_TIP_RULAJ = 0`
Formula veche (`SUM(cant*pretvtva) + SUM(cante*pretvtva WHERE id_tip_rulaj<>3)`) inlocuita cu:
```
SUM (Nvl(cant,0) + Nvl(cante,0)) * Nvl(pretvtva,0) ;
TO lnTotalRul FOR Nvl(sters,0) = 0 AND Nvl(id_tip_rulaj,0) = 0
```
Randurile `ID_TIP_RULAJ = 3` (miscari virtuale de diferenta de pret) nu mai intra deloc in suma —
nicio euristica de excludere pe potrivire de valoare, doar filtrul semantic cerut.
### Decizia 37 — cont de referinta pe rate/contract accepta 4111/411/461
Adaugat un caz nou in `DO CASE` pe `This.nTipVanzare`, pentru tip 2/6/52, care seteaza `lcCont`
la o lista delimitata de conturi in loc de un singur cont; comparatiile `Alltrim(...) == m.lcCont`
au fost inlocuite cu `(','+Alltrim(...)+',') $ m.lcCont` (echivalent cu `INLIST`, dar pastreaza o
singura variabila `lcCont` in loc sa ramifice codul de sumare). Restul tipurilor de document
(avize 461/418, facturi obisnuite) raman pe un singur cont, neschimbate.
## Cod
`COMUN\clase\omodificari.vc2`, metoda `ActualizeazaVerdictActRul` (linii 13004-13039 dupa editare):
```
DO CASE
CASE INLIST(This.nTipVanzare, 28, 29)
lcCont = ',461,'
CASE INLIST(This.nTipVanzare, 21, 22, 24, 26)
lcCont = ',418,'
CASE INLIST(This.nTipVanzare, 2, 6, 52)
lcCont = ',4111,411,461,'
OTHERWISE
lcCont = ',4111,'
ENDCASE
...
SUM (IIF((','+Alltrim(Nvl(scd,''))+',') $ m.lcCont, Nvl(suma,0), ;
IIF((','+Alltrim(Nvl(scc,''))+',') $ m.lcCont AND !INLIST(Alltrim(Nvl(scd,'')), '5311','5314','5121','5125','5126'), -Nvl(suma,0), 0))) ;
TO lnTotalAct FOR Nvl(sters,0) = 0
...
SUM (Nvl(cant,0) + Nvl(cante,0)) * Nvl(pretvtva,0) ;
TO lnTotalRul FOR Nvl(sters,0) = 0 AND Nvl(id_tip_rulaj,0) = 0
```
Diff complet: diff aplicat (sters).
## Test actualizat
`COMUN\utile\Teste\editare_factura\test_verdict_act_rul.prg`:
- Calculul manual (SCAN independent) de Total RUL actualizat la noua formula (doar
`ID_TIP_RULAJ = 0`, `cant+cante`).
- Asertia care astepta "divergent" pe `cod=1140895` a fost **inversata**: acum verifica explicit
`Total ACT == 1924.59`, `Total RUL == 1924.59` si verdict "sincronizat" — nu doar egalitatea
celor doua totaluri (o asertie care ar trece si daca ambele ar cadea pe zero).
- Asertii noi, prin mutatie in memorie pe cursoarele deja incarcate (fara scriere in Oracle):
- un rand `ID_TIP_RULAJ = 3` cu cantitatea marita cu 1000 nu modifica Total RUL;
- un rand `ID_TIP_RULAJ = 0` cu cantitatea marita cu 1 modifica Total RUL cu exact `pretvtva`;
- pe tip=2, contul mutat pe `411` intra in Total ACT (impreuna cu `4111`/`461`);
- pe tip=6, contul mutat pe `461` intra in Total ACT (impreuna cu `4111`/`411`);
- pe tip=1 (fara rata), acelasi rand mutat pe `461` NU mai intra — contul ramane strict `4111`,
verificand ca extinderea nu s-a scapat pe tipurile obisnuite de factura.
Diff complet: diff aplicat (sters).
## Testat
Regresie, headless (`vfp9.exe -A -T`, watchdog, `-AutoDismiss`), exit 0, zero dialoguri, rulata
DUPA ultima editare de cod (verificat pe mtime: binarele si `.prg`-ul de test la `18:34`/`18:39`,
logurile de test la `18:40`-`18:42`):
| Suita | Rezultat | Baseline |
|---|---|---|
| `test_page3_articole.prg` | 14 PASS / 2 FAIL | identic (cele 2 = artefact headless cunoscut, datoria 7) |
| `test_incarca_vanzare_din_nota.prg` | 5 PASS / 0 FAIL | identic |
| `test_adauga_linie_articol.prg` | 20 PASS / 0 FAIL | identic |
| `test_adauga_linie_valuta.prg` | 6 PASS / 0 FAIL | identic |
| `test_ui_sterge_linie.prg` | 8 PASS / 0 FAIL | identic |
| `test_verdict_act_rul.prg` | **26 PASS / 0 FAIL** | (18 inainte de runda; 8 asertii noi) |
Pe documentul de test (`cod=1140895`, descoperit prin `DescoperaCazTest('FACTURA_ARTICOLE', ...)`,
deterministic pe schema `MARIUSM_AUTO`): `Total ACT = 1924.59`, `Total RUL = 1924.59` (dupa
excluderea perechilor `ID_TIP_RULAJ=3`), verdict **sincronizat** — confirma exact cifra ceruta
(`121.00 + 2*121.01 + 1259.07 + 302.50 = 1924.59`).
Zero scrieri in Oracle (doar `SELECT`-uri prin `goExecutor`, mutatii pe cursoare in memorie
READWRITE, restaurate la valorile initiale inainte de QUIT).
## Cens de octeti si write-back
- **Inainte de editare**: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD` (verificat pe backup
`omodificari.vc2.pre_runda3c3.bak`, facut inainte de prima editare).
- **Editarea cu Edit a stricat din nou** cele doua linii cu diacritice ("Renuntare"/"Adaugare"/
"Stergere", liniile 4104 si 8670) — acelasi tipar cunoscut din runda anterioara (`FE E3 AA` ->
3x `EF BF BD`). Reparat byte-cu-byte cu Perl, restaurand exact bytes-urile din backup-ul curat.
- **Dupa reparare**: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD` — identic cu baseline.
- **Write-back**: `txt2vcx.ps1 -AllowComun`, OK din prima rulare. `.vc2`/`.vcx`/`.VCT` sincrone
(acelasi mtime, `18:34`).
## Fisiere atinse
- `COMUN\clase\omodificari.vc2` (+ `.vcx`/`.VCT`, scris in binar) — cele doua reguli din
`ActualizeazaVerdictActRul`.
- `COMUN\utile\Teste\editare_factura\test_verdict_act_rul.prg` — formula RUL actualizata, asertia
inversata pe `cod=1140895`, 8 asertii noi (excludere `ID_TIP_RULAJ=3`, includere
`ID_TIP_RULAJ=0`, cele trei conturi rate/contract).
- Backup: `COMUN\clase\omodificari.vc2.pre_runda3c3.bak`,
`COMUN\utile\Teste\editare_factura\test_verdict_act_rul.prg.pre_runda3c3.bak` (pastrate).
**Fara commit** (nici git, nici SVN). **Zero scrieri in Oracle** in toata sesiunea.
## Nimic ramas nedovedit
Ambele decizii (36 si 37) sunt implementate exact cum au fost formulate si verificate atat prin
recalcul independent (SCAN) cat si prin mutatie directa pe date reale (conturi 411/461 simulate pe
un rand existent, cantitati modificate pe rand `ID_TIP_RULAJ=3`/`=0`) — nu doar pe "zero cazuri in
date".

View File

@@ -1,77 +0,0 @@
# Livrare - decizia 35: intrare directa in valuta la adaugarea de linii
Implementare pe baza cercetarii de contract deja facute (handoff intermediar (sters)):
premisa initiala (atingerea `ofacturare.vc2`) a fost infirmata acolo - dialogul `frm_articol_factura`
nu citeste `poDate.in_valuta`, ramura de valuta e condusa de `poArticol.tip_valuta`/`Curs`/
`multiplicator`/`nume_val`. Toata lucrarea de mai jos e in apelant, **`ofacturare.vc2` nu a fost atins**.
## Ce s-a schimbat
**`COMUN\programe\ofacturare_editare.prg`** - `CreeazaPoArticolNouTvd` extinsa cu 5 parametri
optionali (`tlInValutaDoc, tnCursDoc, tnMultDoc, tcNumeValDoc, tnIdValutaDoc`); cand
`tlInValutaDoc` e adevarat, suprascrie `tip_valuta=1`/`Curs`/`multiplicator`/`nume_val`/`id_valuta`
cu valorile documentului. Fara parametri (cei 2 apelanti de test si semnatura veche), comportamentul
ramane identic (parametrii nepasati sunt `.F.`, `IF m.tlInValutaDoc` nu se activeaza).
**`COMUN\clase\omodificari.vc2`**:
- `cmdAdaugaArticol.Click` - inainte de `Createobject`, citeste `tvanz.in_valuta`/`curs`/
`multiplicator`/`nume_val`/`id_valuta` (deja incarcate de `IncarcaVanzareNota`, nicio interogare
Oracle noua) si le paseaza la `CreeazaPoArticolNouTvd`. Cand documentul nu e in valuta, garda
`Used('tvanz')` cade pe valorile implicite (0/1/1/''), comportament identic cu azi.
- `AdaugaLinieTvdDinArticol` - rescrisa sa ramifice pe `toArticol.tip_valuta`: `=1` citeste direct
`pretftva_val`/`pretctva_val`/`discount_unitar_val`/`discount_unitar_ctva_val` (deja in valuta
documentului, fara reconversie); `=0` pastreaza neatinsa conversia RON->valuta existenta
(`* multiplicator / curs`).
## Testare
Toate rulate DUPA ultima editare de cod (verificat pe mtime: binar 18:53, `.prg` 18:52, test 18:57;
rulari 18:58+), `watchdog_vfp.ps1 -AutoDismiss`, cifre numarate din log (`REZULTAT`/`done`):
| Test | Rezultat | Baseline | Regresie? |
|---|---|---|---|
| `test_adauga_linie_valuta` (extins, sub-blocurile A+B) | **16 PASS / 0 FAIL** | 6/0 | nu - extins cu scenariul B |
| `test_page3_articole` | 14 PASS / 2 FAIL | 14/2 | nu (cele 2 = artefact headless cunoscut, coloane grid) |
| `test_incarca_vanzare_din_nota` | 5 PASS / 0 FAIL | 5/0 | nu |
| `test_adauga_linie_articol` | 20 PASS / 0 FAIL | 20/0 | nu |
| `test_ui_sterge_linie` | 8 PASS / 0 FAIL | 8/0 | nu |
| `test_verdict_act_rul` | 26 PASS / 0 FAIL | 26/0 | nu |
**Confirmare absenta dublei conversii** (verificarea centrala ceruta): scenariul B din
`test_adauga_linie_valuta.prg` construieste `poArticol` cu `tip_valuta=1`, `Curs=5.2688`,
`multiplicator=1` (prin `CreeazaPoArticolNouTvd` extinsa) si `pretftva_val=200` (pretul introdus
direct in valuta, ca de la un dialog real cu `tip_valuta=1`). Dupa `AdaugaLinieTvdDinArticol`,
`tvd.pret` ramane **200.00** - nu `1053.76` (200*curs, conversie in plus) si nu `37.96` (200/curs,
conversie in sens gresit). Bara de totaluri (`ActualizeazaBaraTotaluri`) recalculeaza corect
echivalentul RON (1053.76 = 200 * 5.2688).
Scenariul A (existent, tip_valuta=0, dialogul lucreaza in RON) a fost lasat neschimbat ca test si
continua sa treaca - confirma ca ramura RON a `AdaugaLinieTvdDinArticol` n-a fost atinsa.
## Ce ramane netestat headless (pentru verificarea pe ecran a lui Marius)
- `frm_articol_factura.Show(1)` cu `poArticol.tip_valuta=1` populat de noul apelant: ca userul chiar
vede/editeaza caseta de valuta (nu RON) cand adauga o linie pe o factura deja emisa in valuta -
comportamentul intern e verificat (`do_calculeaza_*`/`tip_valuta` deja folosite in productie de
`frm_facturare_articole`, cf. cercetarii), dar interactiunea vizuala reala nu.
- Cazul de la punctul 2 din "Ce ramane de decis de Marius" (cercetarea de contract): un articol cu
politica de pret proprie in valuta (`tip_valuta=1` din alta sursa), pe un document in alta valuta -
implementarea curenta suprascrie necondiționat cu valorile documentului; nu exista date de test
pentru acest caz, ramane teoretic.
## Write-back
`txt2vcx.ps1 -AllowComun` pe `omodificari.vc2` rulat si confirmat cu succes (fidelity-check OK,
`omodificari.vcx`/`.vct` actualizate, mtime nou). `ofacturare_editare.prg` e sursa directa, fara
write-back necesar.
## Fisiere atinse
- `COMUN\programe\ofacturare_editare.prg` (+ `.pre_s4_valuta.bak`)
- `COMUN\clase\omodificari.vc2` (+ `.pre_s4_valuta.bak`), scris in `omodificari.vcx`/`.vct`
- `COMUN\utile\Teste\editare_factura\test_adauga_linie_valuta.prg` (+ `.pre_s4_valuta.bak`)
- Patch-uri: diff aplicat (sters), diff aplicat (sters),
diff aplicat (sters)
**Fara commit** (git/SVN). **Zero scrieri in Oracle** - toate testele lucreaza pe cursoare in
memorie.

View File

@@ -1,541 +0,0 @@
# S5 — calea de scriere VFP: unde se agata scrierea in VANZARI_DETALII
Cercetare read-only, 09.08.2026. Nu s-a modificat niciun fisier si nu s-a rulat nimic care sa scrie
in Oracle.
**Surse.** Partea VFP: versiunile text `.vc2` din arbore (`COMUN\clase\*.vc2`), verificate ca fiind
la zi — `mtime` identic cu al binarelor (`omodificari.vcx`/`.vc2` = 09.08.2026 18:53,
`ofacturare_comun` = 08.08.2026 23:26, `comun` = 09.08.2026 09:45). Atributia clasa/metoda pentru
fiecare linie citata e din `vfp_symbols.ps1 -Where`. **Atentie:** alti agenti lucreaza in paralel pe
`omodificari.vc2` — numerele de linie din acest raport sunt un instantaneu 09.08.2026 ~21:15.
Briefingul dadea `inainte_de_do_termin` la `:13357-13549`; azi e la **`:14249-14441`**.
Partea Oracle: surse PL/SQL **de pe disc** — `COMUN\docs\PACK_CONTAFIN.pck` (03.08.2026) si
`D:\ROA\ROAACNPRO\PACK_FACTURARE.pck` (10.06.2026, copie mai veche). Corpurile citate mai jos
coincid caracter cu caracter cu ce raporta `rec_s5_oracle_vanzari.md` dintr-un export proaspat
(08.08.2026), deci sunt de incredere; nu s-a facut un export nou in aceasta sesiune.
---
## 1. Lantul complet de salvare, ambele puncte de intrare
### 1.1 ROAFACTURARE — `frm_facturi.do_editare_factura` (`COMUN\clase\ofacturare_comun.vc2:3715-3869`)
| Pas | Linie | Ce face |
|---|---|---|
| garzi | `:3716-3767` | `lactiv3`, `glLunaInchisa`, `sters=1`, proforma, luna curenta, `ReferinteDocumenteNota`, `EsteInEFactura` |
| incarcare nota | `:3769` | `IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.)` -> `tact`/`trul`/`trul_obinv` |
| incarcare articole | `:3793` | `IncarcaArticoleFactura(m.lnIdVanzare, 'crsArticoleFactura')` — **inainte** de `Createobject`, ca `Load()` sa lege gridul pe cursor plin |
| formular | `:3796-3797` | `Createobject([frm_modific2024], lnIdSet)` + `.Show()` — modal (`WindowType = 1`, `omodificari.vc2:6892`) |
| confirmare | `:3799` | `If buton = 1` |
| **tranzactie ON** | **`:3800`** | `Thisform.do_deschide_tranzactie()` |
| stergere nota veche | `:3802` | `lnSucces = OSCRIE_IN_FISIERE(2,.T.,.T.)` |
| rescriere cursoare | `:3807-3820` | `tact`->`actactan`, `trul`->`RUL_TEMP`, `trul_obinv`->`RUL_TEMP_OBINV` |
| scriere nota noua | `:3821` | `lnSucces = OSCRIE_IN_FISIERE(0,.T.,.T.)` |
| **finalizare** | **`:3824-3826`** | `begin pack_contafin.finalizeaza_modificare_nota(...); end;` prin `goExecutor.oExecuta`; `lnSucces = Iif(..., 1, -1)` |
| **tranzactie OFF** | **`:3828`** | `Thisform.do_inchide_tranzactie(Iif(lnSucces<0, 2, 1))` |
| reafisare | `:3830` | `Thisform.do_cauta()` |
| curatenie | `:3837-3860` | inchide `actactan`, `tact`, `rul_temp`, `trul`, `rul_temp_obinv`, `trul_obinv`, `crsJtvaTemp`, `crsArticoleFactura` — **NU** inchide `tvd`/`tvanz` |
### 1.2 ROACONT / registru jurnal — `afisjurcom.do_modifica` (`COMUN\clase\comun.vc2:2222-2569`)
| Pas | Linie | Ce face |
|---|---|---|
| garzi | `:2230-2290` | `lactiv3`, `glLunaInchisa`, luna curenta, `id_set` 30000-30009, nota de inventar |
| incarcare nota | `:2352-2426` | acelasi SQL ca `IncarcaCursoareModificareNota` (cod duplicat, nu apel) |
| incarcare articole | **`:2435-2437`** | `IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure"))` -> `PregatesteArticoleFacturaEditare('tact')` |
| formular | `:2439`, `:2445` | `Createobject([frm_modific2024], lnIdSet)` + `.Show()` |
| confirmare | `:2447` | `If buton=1` |
| **tranzactie ON** | **`:2451`** | `Thisform.do_deschide_tranzactie()` |
| stergere nota veche | `:2454` | `oscrie_in_fisiere(2,.T.,llRul)` (sarit daca nota era deja stearsa, `:2456`) |
| rescriere cursoare | `:2463-2481` | idem |
| scriere nota noua | `:2482` | `oscrie_in_fisiere(0,.T.,llRul)` |
| **finalizare** | **`:2487-2489`** | `finalizeaza_modificare_nota` prin `goExecutor.oExecuta` |
| **tranzactie OFF** | **`:2538`** | `Thisform.do_inchide_tranzactie(Iif(lnSucces<0, 2, 1))` |
| curatenie | `:2546-2566` | inchide aceleasi cursoare + `crsArticoleFactura`; **NU** `tvd`/`tvanz` |
**Punctul de intrare 2 e reachable din ROAFACTURARE**, nu doar din ROACONT: `viz_act`
(`COMUN\programe\ooperatii_comune.prg:115-126`) instantiaza `AFISJURcom`, iar
`ooperatii_comune.prg` e inregistrat in `Programe\roafacturare.prg:218`.
`ofacturare_editare.prg` e inregistrat **numai** in ROAFACTURARE
(`Programe\roafacturare.prg:214` — singura potrivire in tot `D:\ROA`), deci in ROACONT/ROAGEST
garda `"OFACTURARE_EDITARE" $ Set("Procedure")` e falsa, pagina 3 nu apare
(`omodificari.vc2:14683`) si nu exista nimic de scris. Codul nou trebuie sa fie no-op acolo, nu doar
inofensiv.
### 1.3 Raspunsul explicit: **DA, tranzactia e inca deschisa dupa ce `finalizeaza_modificare_nota` se intoarce**
In ambele puncte de intrare `do_inchide_tranzactie` vine **dupa** apelul PL/SQL
(`ofacturare_comun.vc2:3826` -> `:3828`; `comun.vc2:2489` -> `:2538`). Intre ele nu se executa
nimic. Contractul, verificat in sursa (`COMUN\clase\_frm_base.vc2:252-302`):
- `do_deschide_tranzactie()` — `SQLSetprop(gnHandle,"Transactions",2)`; intoarce **`.T.`/`.F.`**;
- `do_inchide_tranzactie(tnTip)` — `tnTip = 1` -> `Sqlcommit(gnHandle)`, **orice altceva** ->
`Sqlrollback(gnHandle)`; apoi revine pe `Transactions = 1`; intoarce `.T.`/`.F.`.
Deci fereastra `:3826-3828` / `:2489-2538` e **exact locul de agatare**: aceeasi conexiune, aceeasi
tranzactie manuala, `COMMIT`/`ROLLBACK` inca nedat.
**Capcana de contract, preexistenta, de care sa nu depinda codul nou:** garda de commit e
`Iif(lnSucces<0, 2, 1)`, deci `lnSucces = 0` **comite**. Codul nou trebuie sa puna explicit
`lnSucces = -1` la esec, nu `0`.
### 1.4 Contractele functiilor pe care se sprijina agatarea (deschise, nu presupuse)
- **`goExecutor.oExecuta(...)`** (`COMUN\programe\oproceduri_comune.prg:121-158`): intoarce
**logic** `.T.`/`.F.`, si **afiseaza singur** `amessagebox(This.oPrelucrareEroare(), 16, "Eroare")`
la esec (`:153-156`). Apelantul nu mai trebuie sa afiseze nimic.
- **`goExecutor.oExecute(...)`** (`:173-...`): intoarce **numeric** `CT_SUCCES`/`CT_INSUCCES`
(`:218-220`), **nu** numar de randuri. Nu le confunda.
- **`OSCRIE_IN_FISIERE(tnScrie_Sterge, tlModificare, tlRul, ...)`**
(`COMUN\programe\oscrie_in_fisiere.prg:14`): numeric, `>0` = succes; `-1` la esecuri de precon-
ditie (`:68-81`), `-5` la verificarea de stoc (`:104`). Parametrul 1: `0` = scriere, `2` = stergere.
- **`_frmbase.do_termin`** (`_frm_base.vc2:363-376`): singura poarta care pune `buton = 1` /
`gnButon = 1`, si o face **doar daca `this.inainte_de_do_termin()` intoarce `.T.`**.
`frm_modific2024` **nu** suprascrie `do_termin` (nu apare in lista de metode proprii), deci
mosteneste asta.
---
## 2. Starea purtata de cursorul `tvd` (si de `tvanz`)
### 2.1 Coloanele lui `tvd`
`tvd` se creeaza in `frm_modific2024.Load` (`omodificari.vc2:14554-14591`): pe ramura
ROAFACTURARE prin `CreeazaCursorTvdGol()` -> `CreeazaCursorArticoleGol('tvd')`
(`COMUN\programe\ofacturare_editare.prg:263-282`), pe ramura ROACONT/ROAGEST printr-un
`CREATE CURSOR` **duplicat literal** in clasa (`omodificari.vc2:14571`) — doua definitii care trebuie
tinute sincron manual.
Se umple din `VVANZARI_ARTICOLE` prin `IncarcaArticoleFactura`
(`ofacturare_editare.prg:288-323`), cu `where v.id_vanzare = <id> and v.sters = 0`
(`:300`) — deci **la incarcare toate randurile au `sters = 0`**.
| Coloana | Provenienta | Editabila in grid |
|---|---|---|
| `id_vanzare`, `id_vanzare_det` | view | nu |
| `id_articol` | view | nu |
| **`cantitate`** | view | **DA** — `Column5`, `ReadOnly = .F.` (`:12366-12374`) |
| **`pret`** | view | **DA** — `Column6`, `ReadOnly = .F.` (`:12375-12383`) |
| **`pret_cu_tva`** (flag 0/1) | view | **DA** — `Column7` checkbox, `ReadOnly = .F.`, `Sparse = .F.` (`:12384-12391`) |
| `proc_tvav` | view | nu (`Column8.ReadOnly = .T.`) |
| `discount_unitar` | view | nu (`Column9.ReadOnly = .T.`) |
| `id_gestiune`, `cont`, `id_valuta`, `id_jtva_coloana`, `serie`, `explicatie`, `taxcode`, `lot`, `sters` | view | nu (`cont`, `id_gestiune`, `id_valuta`, `id_jtva_coloana`, `sters` nici macar nu au coloana in grid) |
| `denumire`, `codmat`, `nume_gestiune`, `nume_val` | join-uri din view | nu |
| `in_stoc` | **nu e in view** — join separat pe `NOM_ARTICOLE` in SQL-ul din `:299` | nu |
| **`lmodificat` (L)** | calculat, `.F.` la incarcare (`:316`) | — |
| **`valoare` (N 14,2)** | calculat la incarcare (`:316-319`) si recalculat la fiecare editare | nu (`Column14.ReadOnly = .T.`) |
Definitia view-ului: `COMUN\docs\cercetare\ff_view_articole_vanzare.sql` (21 de coloane, valori brute,
fara conversie valutara; filtrul `STERS` lasat pe seama apelantului).
**Doar 3 coloane sunt editabile: `cantitate`, `pret`, `pret_cu_tva`.** Tot restul e read-only in
grid; `explicatie`/`taxcode` se schimba doar pe cealalta cale, `frm_modifica_articol_factura`
(punctul 4).
### 2.2 Cum se distinge o linie modificata / stearsa / adaugata
- **Modificata**: `tvd.lmodificat = .T.`, **per linie**, nu global. Se pune in exact trei locuri:
- `frm_modific2024.calculeaza_valori_articol` (`:13397-13409`, linia `:13407`
`REPLACE lmodificat WITH .T., valoare WITH m.lnValoare`) — apelata din `Valid`-ul cantitatii
(`:16428-16433`), `Valid`-ul pretului (`:16439-16444`) si `InteractiveChange`-ul checkboxului
`pret_cu_tva` (`:16450-16458`);
- `cmdStergeArticol.Click` (`:16423`);
- `AdaugaLinieTvdDinArticol` (`:13119`).
`Valid`-urile compara cu `Thisform.oldvalue` (setat in `When`), deci o retastare a aceleiasi
valori **nu** marcheaza linia. Nu exista `lmodificat` la nivel de formular.
- **Stearsa**: `tvd.sters = 1`. `cmdStergeArticol.Click` (`:16417-16426`) **comuta**
(`REPLACE sters WITH IIF(Nvl(sters,0) = 1, 0, 1)`), deci stergerea e reversibila pana la salvare;
linia ramane vizibila, grizata prin `DynamicForeColor = "IIF(tvd.sters=1,RGB(150,150,150),...)"`
pe toate cele 14 coloane. Toate randurile incarcate pornesc de la `sters = 0`, deci `sters = 1`
in `tvd` inseamna intotdeauna "sters in sesiunea curenta".
- **Adaugata**: `tvd.id_vanzare_det = 0`, pus explicit de `AdaugaLinieTvdDinArticol`
(`:13108`). Randul vine din `frm_articol_factura` prin `poArticol`
(`cmdAdaugaArticol.Click`, `:16374-16415`), cu `id_vanzare` = `This.nIdVanzare` si conversie
RON->valuta documentului pe ramura `tip_valuta = 0` (`:13097-13105`).
Combinatia `id_vanzare_det = 0 AND sters = 1` e posibila (linie adaugata si apoi stearsa in
aceeasi sesiune) si trebuie ignorata la scriere.
### 2.3 Valorile VECHI: **NU se pastreaza nicaieri**
Confirmat prin cautare: nu exista niciun cursor de instantaneu (`tvd_orig`, `crsArticoleOrig`,
`tvanz_orig`, `nDiscountVechi` etc.) in cod de productie — singura potrivire e intr-un test
(`COMUN\utile\Teste\editare_factura\test_ui_bara_totaluri.prg:86`). `tvd` poarta **doar** valorile
curente plus flagul `lmodificat`; valoarea dinainte de editare se pierde in momentul in care
utilizatorul o schimba.
Consecinte:
- scrierea nu poate fi restransa la "coloanele efectiv schimbate" — se scriu toate cele trei
coloane editabile plus `valoare`, pe randurile cu `lmodificat = .T.`;
- cerinta S4b de a **enumera** utilizatorului "linia X: cantitate 5 -> 8" **nu e realizabila azi**;
cere un cursor de instantaneu luat imediat dupa `IncarcaArticoleFactura` (ex.
`SELECT id_vanzare_det, cantitate, pret, pret_cu_tva FROM tvd INTO CURSOR tvd_initial`).
Nu e implementat; e lucrare in plus, nu detaliu.
### 2.4 `tvanz` si discountul de document
`tvanz` se creeaza tot in `Load` (`:14574-14582`), prin `CreeazaCursorTvanzGol()`
(`ofacturare_editare.prg:148-154`) sau prin `CREATE CURSOR` duplicat (`:14581`, **cu 3 coloane mai
putin** — `in_valuta`/`id_valuta`/`nume_val` lipsesc pe ramura ROACONT). Se umple in `Show()`
prin `IncarcaVanzareDinNota('tact')` (`:14684`), care cauta randul din `VANZARI` incercand toate
tripletele distincte `(cod, nract, serie_act, dataact)` din `tact`
(`ofacturare_editare.prg:203-258`). Coloane: `id_vanzare, tip, discount, total_fara_tva, total_tva,
total_cu_tva, curs, multiplicator, in_valuta, id_valuta, nume_val`.
- **`discount` e editabil direct**: `txtDiscountArt` are `ControlSource = "tvanz.discount"`,
`ReadOnly = .F.` (`omodificari.vc2:12825-12836`). `Valid`-ul lui cheama doar
`ActualizeazaBaraTotaluri()` (`:16460-16462`).
- **Nu exista `lmodificat` pe `tvanz`** si nici valoare veche salvata. Nu se poate sti daca
utilizatorul a atins discountul. Singura optiune fara lucrare in plus: scrie discountul
**neconditionat** cand pagina a fost activa (`UPDATE VANZARI SET DISCOUNT = ...` e idempotent).
Daca se vrea "doar daca s-a schimbat", trebuie retinuta valoarea initiala la incarcare.
- `tvanz.total_cu_tva` e afisat ca "Total salvat" (`txtTotalSalvatArt`, `ControlSource =
"tvanz.total_cu_tva"`, ReadOnly, `:12895-12907`) — e valoarea din baza, nu una recalculata.
- Totalurile calculate stau in proprietati de formular, nu in cursor: `nTotalLiniiRon`,
`nTotalNetRon` (`ActualizeazaBaraTotaluri`, `:12934-12976`), `nTotalActRon`, `nTotalRulRon`,
`lRulAjustat` (`ActualizeazaVerdictActRul`, `:12978-13079`). Ele dispar odata cu formularul.
### 2.5 Cursoarele supravietuiesc formularului
`frm_modific2024` **nu are proprietatea `DataSession`** (nicio potrivire in tot `omodificari.vc2`),
deci ruleaza in sesiunea de date implicita: `tvd` si `tvanz`, create in `Load()`, raman deschise
dupa `This.Release` din `do_termin`. Niciunul dintre cei doi apelanti nu le inchide
(`ofacturare_comun.vc2:3837-3860`, `comun.vc2:2546-2566`). **Asta face agatarea posibila** — dupa
`Omodif.Show()`, apelantul citeste `tvd`/`tvanz` direct.
Corolar: obiectul `Omodif` e Released, deci **nu se pot citi proprietatile lui**
(`lAreArticoleVanzari`, `nIdVanzare`) dupa `Show()`. Semnalul "pagina de articole a fost activa"
trebuie dedus din cursoare: `Used('tvanz') AND Reccount('tvanz') = 1 AND Used('tvd')`, iar
`id_vanzare` se ia din `tvanz.id_vanzare`.
---
## 3. Unde se agata scrierea — si ce ordonare rezista capcanei
### 3.1 Capcana, verificata in sursa
`pack_contafin.finalizeaza_modificare_nota` (`COMUN\docs\PACK_CONTAFIN.pck:8601-8649`):
```sql
lnCodNou := pack_contafin.get_cod();
SELECT COUNT(*) INTO lnEInVanzari FROM vanzari WHERE cod = tnCod;
IF lnEInVanzari > 0 THEN
pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou);
END IF;
UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod = tnCod;
...
```
`pack_facturare.actualizeaza_vanzari` (`D:\ROA\ROAACNPRO\PACK_FACTURARE.pck:15951-15961`):
```sql
-- de modificat in caz ca il las sa stearga manual inregistrari din VANZARI_DETALII
-- acum se marcheaza cu STERS = 1 doar cand se sterge toata factura
UPDATE VANZARI_DETALII SET STERS = 0
WHERE ID_VANZARE IN (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI);
UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI;
```
Pentru o factura, `COUNT(*) > 0` e intotdeauna adevarat, deci **resetul `STERS = 0` ruleaza la
FIECARE editare de nota**. `ID_VANZARE` nu se schimba niciodata (se schimba doar `COD`), deci o
cheie `ID_VANZARE` capturata inainte ramane valida dupa.
### 3.2 Doua consecinte, nu una
1. **Evidenta**: o linie marcata `STERS = 1` de VFP *inainte* de `finalizeaza_modificare_nota` e
reactivata tacut. Deci scrierea trebuie sa fie **dupa**.
2. **Mai putin evidenta, si mai grava**: la o editare **ulterioara** a aceleiasi facturi, resetul
reactiveaza si liniile sterse in sesiuni **anterioare**. `tvd` se incarca doar cu `sters = 0`
(`ofacturare_editare.prg:300`), deci VFP nici nu stie ca liniile alea exista si nu le-ar
re-marca. Rezultat: **o linie stearsa luna trecuta reapare la prima re-editare a facturii**, si
intra si in recalculul de totaluri (care citeste `STERS = 0`). Asta nu e o problema azi, pentru
ca azi nu exista stergere per linie — devine problema exact prin #6.
### 3.3 Ordonarea care rezista
Punctul de agatare, pentru **ambele** puncte de intrare, e **imediat dupa apelul
`finalizeaza_modificare_nota` si inainte de `do_inchide_tranzactie`**, gardat de `lnSucces > 0`:
- ROAFACTURARE: intre `ofacturare_comun.vc2:3827` (`Endif`-ul apelului) si `:3828`;
- registru jurnal: intre `comun.vc2:2490` (`Endif`-ul apelului) si `:2538`.
Secventa completa, o singura tranzactie manuala:
```
do_deschide_tranzactie()
OSCRIE_IN_FISIERE(2, ...) -- sterge nota veche
OSCRIE_IN_FISIERE(0, ...) -- scrie nota noua
finalizeaza_modificare_nota(...) -- realiniaza COD, RESETEAZA STERS = 0 pe toate liniile
>>> ScrieArticoleFacturaEditate(...) -- NOU: liniile + discountul
>>> recalculeaza_totaluri_vanzari(id_vanzare) -- NOU (S5 Oracle)
do_inchide_tranzactie(Iif(lnSucces < 0, 2, 1))
```
Iar in interiorul helperului, ordinea operatiilor:
1. `INSERT` pentru liniile noi (`id_vanzare_det = 0 AND Nvl(sters,0) <> 1`) — intai, ca ID-urile
lor sa poata fi excluse la pasul 3; PK-ul vine din trigger, nu se cere din secventa
(`rec_cale_vanzari_detalii.md` 3.2);
2. `UPDATE` pentru liniile existente atinse (`id_vanzare_det > 0 AND lmodificat AND
Nvl(sters,0) <> 1`);
3. **stergere ca diferenta de multimi, nu ca lista de linii sterse**:
`UPDATE VANZARI_DETALII SET STERS = 1, ID_UTILS = :util, DATAORAS = SYSDATE
WHERE ID_VANZARE = :id AND STERS = 0 AND ID_VANZARE_DET NOT IN (<ID-urile active din tvd>)`.
Formularea asta e cea care rezista: ea nu enumera ce s-a sters acum, ci **impune** ca multimea
activa din baza sa fie exact multimea activa din `tvd`. Repara automat si consecinta (2) din
3.2 — liniile inviate de reset redevin sterse — si e idempotenta.
Pentru asta, `<ID-urile active>` trebuie sa includa si ID-urile randurilor tocmai inserate
(de aici ordinea INSERT-inainte), sau, mai simplu, pasul 3 se restrange la
`... AND ID_VANZARE_DET NOT IN (<ID-urile > 0 active din tvd>) AND ID_VANZARE_DET NOT IN
(<ID-urile inserate acum>)`. Daca lista de ID-uri inserate e greu de obtinut din VFP, alternativa
e sa se ruleze pasul 3 **inainte** de INSERT — atunci lista `NOT IN` contine doar ID-uri
preexistente si randurile noi nu sunt inca in tabela, deci nu pot fi atinse.
**Recomandare: pasul 3 primul, apoi INSERT, apoi UPDATE.** Mai simplu si fara nevoia de
`RETURNING`.
4. `UPDATE VANZARI SET DISCOUNT = :discount WHERE ID_VANZARE = :id` (din `tvanz.discount`).
**Recalculul de totaluri se muta din `finalizeaza_modificare_nota` in apelantul VFP.**
`rec_s5_oracle_vanzari.md` (B) propunea `recalculeaza_totaluri_vanzari` apelata **din interiorul**
lui `finalizeaza_modificare_nota`, dupa `actualizeaza_vanzari`. Cu ordonarea de mai sus **asta nu
mai merge**: la momentul acela liniile nu sunt inca scrise si `VANZARI.DISCOUNT` inca are valoarea
veche, deci totalurile s-ar calcula pe date invechite. Apelul trebuie facut din VFP, ca pas separat,
dupa helper. Efectul secundar e favorabil si corespunde motivatiei originale a Variantei B:
recalculul devine **strict opt-in pe calea de editare de factura**, nu ruleaza pentru notele
ROACONT/ROAGEST care nu ating sumele — ceva ce `finalizeaza_modificare_nota` oricum nu putea
distinge.
### 3.4 Ordonari respinse
| Varianta | De ce nu |
|---|---|
| Scriere **inainte** de `finalizeaza_modificare_nota` | `STERS = 1` sters de resetul din `actualizeaza_vanzari`; `INSERT`/`UPDATE` ar supravietui, dar stergerea nu — comportament pe jumatate, cel mai prost caz |
| Scriere din `inainte_de_do_termin` (in formular) | ruleaza **inainte** de `do_deschide_tranzactie` (`_frm_base.vc2:364` -> `ofacturare_comun.vc2:3800`), deci in autocommit, in afara tranzactiei; la esec ulterior al notei, liniile raman scrise |
| Scriere **dupa** `do_inchide_tranzactie` | tranzactie separata; commit partial daca a doua esueaza |
| Modificarea lui `actualizeaza_vanzari` sa nu mai reseteze `STERS` | cod partajat cu toata suita ROA (apelat pentru orice nota cu `cod` in `vanzari`); riscul respins deja de plan |
---
## 4. Modelul existent: `pack_facturare.modifica_explicatie_articol`
**Oracle** (`D:\ROA\ROAACNPRO\PACK_FACTURARE.pck:14464-14472`) — corpul complet:
```sql
PROCEDURE modifica_explicatie_articol(V_ID_VANZARE_DET IN NUMBER,
V_EXPLICATIE IN VARCHAR2,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL) is
BEGIN
UPDATE VANZARI_DETALII
SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE
WHERE ID_VANZARE_DET = V_ID_VANZARE_DET;
END modifica_explicatie_articol;
```
**Detaliu de contract, contra-intuitiv:** `V_ID_UTIL` e primit si **complet ignorat** — nu se scriu
`ID_UTILS`/`DATAORAS`. Procedura nu e un model bun pentru partea de audit; e model doar pentru
forma apelului si pentru granularitatea "un `UPDATE` tintit pe `ID_VANZARE_DET`".
**Apelantul VFP** — `frm_modifica_articol_factura.inainte_de_do_termin`
(`COMUN\clase\ofacturare_comun.vc2:5230-5241`), integral:
```foxpro
PROCEDURE inainte_de_do_termin
Local llReturn
If amessagebox("Doriti sa salvati modificarile?",4+32,"Confirmare salvare explicatie") = 6
lcSql = [begin pack_facturare.modifica_explicatie_articol(] + Alltrim(Str(poRec.id_vanzare_det)) + [,] + ;
['] + Nvl(Alltrim(OracleSpecialCharacters(poRec.explicatie)),'') + [',?gnIdUtil,?poRec.taxcode); end;]
llReturn = goExecutor.oExecuta(lcSql)
Else
llReturn = .F.
Endif
Return llReturn
ENDPROC
```
Deschis din `frm_facturi.do_modifica_explicatie` (`ofacturare_comun.vc2:4639-4656`):
`Scatter Name poRec Memo` din `crsDetalii`, `Createobject("frm_modifica_articol_factura")`,
`.Show()`, apoi `actualizeaza_grid2()` daca `gnButon = 1`.
**Ce se preia din model:**
- bloc `begin ... ; end;` construit ca text, cu numerele injectate prin `Alltrim(Str(...))` si
sirurile prin `?`-binding sau literal cu ghilimele simple;
- `OracleSpecialCharacters(...)` obligatoriu pe orice sir care ajunge literal in SQL;
- **tratarea erorii = niciuna in apelant**: `goExecutor.oExecuta` intoarce `.T.`/`.F.` si afiseaza
singur mesajul; apelantul doar propaga booleanul.
**Ce NU se preia:** apelul asta ruleaza in **autocommit**, in afara oricarei tranzactii manuale
(`do_modifica_explicatie` nu deschide tranzactie). Helperul nou ruleaza **in interiorul** tranzactiei
deschise de apelant si nu are voie sa dea `COMMIT`/`ROLLBACK` — se opreste la primul esec si lasa
apelantul sa faca rollback prin `do_inchide_tranzactie(2)`.
---
## 5. `inainte_de_do_termin` (`omodificari.vc2:14249-14441`)
**Numerotare:** briefingul dadea `:13357-13549`; azi metoda e la `:14249-14441` (aproximativ 90% din
corp — `:14299-14437` — e cod comentat, ramas din versiunea veche).
**Ce valideaza azi** (codul viu, `:14252-14296`):
| Linie | Verificare |
|---|---|
| `:14252-14253` | `SELECT tact` + `SET FILTER TO` (curata filtrul de grid inainte de salvare) |
| `:14257-14259` | completeaza `id_set` gol pe `tact`, `trul`, `trul_obinv` |
| `:14265-14271` | pentru `id_set` 99998 / 90024 sare peste verificarea de conturi |
| `:14273-14277` | `verificare_note_contabile('tact', ...)` — analitice si parteneri (in `oOperatii_comune`) |
| `:14281-14290` | avertisment 4426-4428 / 4428-4427 introdusa fara optiunea dedicata (doar din 2013), cu confirmare Da/Nu |
| `:14291-14293` | `This.VerificaAvertizareExigibilizareTVA()` |
| `:14296` | `RETURN m.llRet` |
**Nu atinge deloc `tvd` sau `tvanz`.** Nicio validare pe pagina de articole.
**Recomandare: da, aici trebuie adaugata validarea paginii de articole** — e singura poarta inaintea
lui `gnButon = 1` (`_frm_base.vc2:364`), deci singurul loc care poate opri o salvare inainte ca
apelantul sa deschida tranzactia. Validari care merita:
- **cantitate 0 sau negativa** pe o linie activa (`Nvl(sters,0) <> 1`) — o linie cu cantitate 0 se
scrie in `VANZARI_DETALII` cu valoare 0 si strica totalurile fara sa fie vizibila ca eroare;
- **`id_articol` nul sau 0** pe o linie activa — se poate produce doar prin `AdaugaLinieTvdDinArticol`
cu un `poArticol` incomplet, dar `INSERT`-ul ar cadea pe FK abia in Oracle, dupa ce nota a fost deja
rescrisa, deci cu ROLLBACK pe tot;
- **`pret` nul** pe o linie activa — `VANZARI_DETALII.PRET` e `NOT NULL`
(`rec_cale_vanzari_detalii.md` 2.2);
- **zero linii active** cand documentul are rand in `VANZARI` — utilizatorul a sters tot; cerea o
confirmare explicita, nu o salvare tacuta care goleste factura.
Trei conditii obligatorii pentru adaugare:
1. gardata pe `"OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Used('tvd')`, altfel se rupe
registrul jurnal din ROACONT/ROAGEST;
2. `RETURN .F.` blocheaza salvarea — se foloseste doar pentru erori reale; pentru situatii
discutabile, tiparul existent din `:14286-14288` (mesaj cu 4+32 si continuare la "Da");
3. plasata **inaintea** lui `RETURN m.llRet` de la `:14296`, nu in blocul comentat de dedesubt.
Fara cod aici, doar constatarea si recomandarea, cum s-a cerut.
---
## 6. Contractul minim al helperului nou
Locul: `COMUN\programe\ofacturare_editare.prg` — acolo stau deja `IncarcaArticoleFactura`,
`IncarcaVanzareDinNota`, `CreeazaPoArticolNouTvd`, si fisierul e inregistrat doar in ROAFACTURARE
(`Programe\roafacturare.prg:214`), ceea ce da automat no-op-ul in ROACONT/ROAGEST.
```
FUNCTION ScrieArticoleFacturaEditate
LPARAMETERS tnIdVanzare, tcAliasArticole, tcAliasVanzare
```
**Parametri**
- `tnIdVanzare` — `ID_VANZARE` al documentului (din `tvanz.id_vanzare`). Nu se ia din `tvd`, ca sa
functioneze si cand `tvd` a ramas gol.
- `tcAliasArticole` — implicit `'tvd'` (simetric cu `IncarcaArticoleFactura`, care primeste aliasul
destinatie; permite testarea pe un cursor construit in test).
- `tcAliasVanzare` — implicit `'tvanz'`, pentru `discount`.
**Preconditii** (nu le verifica, le documenteaza):
- tranzactie manuala deja deschisa de apelant (`do_deschide_tranzactie` a intors `.T.`);
- `finalizeaza_modificare_nota` a rulat deja cu succes — deci `actualizeaza_vanzari` si-a facut
resetul `STERS = 0`, iar helperul scrie peste el;
- `goExecutor` conectat.
**Ce scrie**, in ordinea din 3.3:
1. `UPDATE VANZARI_DETALII SET STERS = 1, ID_UTILS = <gnIdUtil>, DATAORAS = SYSDATE
WHERE ID_VANZARE = :id AND STERS = 0 AND ID_VANZARE_DET NOT IN (<ID-urile > 0 active din alias>)`
— un singur statement, formulat ca diferenta de multimi (motivarea in 3.3);
2. `INSERT INTO VANZARI_DETALII (...)` per linie cu `id_vanzare_det = 0 AND Nvl(sters,0) <> 1`,
fara `ID_VANZARE_DET` in lista de coloane (trigger `TRG_VANZARI_DET_BEFOINS`);
3. `UPDATE VANZARI_DETALII SET CANTITATE = ..., PRET = ..., PRET_CU_TVA = ..., ID_UTILS = ...,
DATAORAS = SYSDATE WHERE ID_VANZARE_DET = :id_det` per linie cu
`id_vanzare_det > 0 AND lmodificat AND Nvl(sters,0) <> 1`;
4. `UPDATE VANZARI SET DISCOUNT = :disc WHERE ID_VANZARE = :id` din `<tcAliasVanzare>.discount`.
`PROC_TVAV` **nu** se recalculeaza — ramane cota deja persistata pe linie
(`rec_cale_vanzari_detalii.md`, "Observatie tehnica"). `DISCOUNT_UNITAR` nu e editabil in grid
(punctul 2.1), deci se scrie doar la `INSERT`, nu la `UPDATE`.
**Ce intoarce**: logic. `.T.` = tot a mers **sau nu era nimic de facut**; `.F.` = primul esec, si se
opreste acolo. No-op cu `.T.` cand `tnIdVanzare <= 0`, cand aliasul de articole nu e deschis, sau
cand aliasul de vanzare nu are exact un rand — asa apelul poate sta neconditionat in ambele puncte
de intrare, fara garda duplicata la apelant.
**Exceptie de la "nimic de facut = .T.":** `Reccount(tcAliasArticole) = 0` cu `tnIdVanzare > 0` **nu**
e no-op, e "s-au sters toate liniile" — pasul 1 marcheaza tot documentul. Validarea din punctul 5
(confirmare la zero linii active) e ce trebuie sa impiedice cazul accidental.
**Cum semnaleaza eroarea**: prin valoarea de retur, atat. Nu afiseaza mesaj — `goExecutor.oExecuta`
o face deja (`oproceduri_comune.prg:153-156`). Nu da `COMMIT`/`ROLLBACK`, nu inchide cursoare, nu
schimba workarea curenta (o salveaza cu `Select()` si o restaureaza, ca `IncarcaVanzareDinNota`).
La apelant:
```
If lnSucces > 0
lnSucces = Iif(ScrieArticoleFacturaEditate(...), 1, -1)
Endif
```
— `-1`, nu `0`, ca sa se prinda in `Iif(lnSucces<0, 2, 1)` de la `do_inchide_tranzactie`
(capcana din 1.3).
**Motivare a formei**: un singur punct de scriere, apelat identic din ambele puncte de intrare
(altfel logica se dubleaza in `ofacturare_comun.vc2` si in `comun.vc2`, iar `comun.vc2` e atins de
toata suita); parametrizat pe alias, ca testul headless sa-l poata rula pe cursoare construite
manual, fara formular; contract boolean, identic cu `goExecutor.oExecuta` si cu
`frm_modifica_articol_factura.inainte_de_do_termin`, deci fara conventie noua de erori in cod.
**De verificat inainte de a scrie `INSERT`-ul** (ramas deschis din `rec_cale_vanzari_detalii.md`
punctul 5, **NEVERIFICAT** si aici): coloanele `NOT NULL` fara valoare din trigger —
`STERS`, `VALIDAT`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT` — au sau nu `DEFAULT` la nivel de coloana
(`all_tab_columns.data_default`). Daca nu au, trebuie enumerate explicit in `INSERT`.
---
## 7. Ce ramane netestabil headless
**Testabil headless** (`vfp9.exe -A -T`), pe cursoare construite in test:
- `ScrieArticoleFacturaEditate` cu un `goExecutor` mock: se verifica **textul SQL generat** pentru
fiecare din cele 3+1 categorii, ordinea statement-elor, si comportarea la `.F.` din mock;
- clasificarea liniilor din `tvd` (modificata / stearsa / adaugata / adaugata-si-stearsa) —
logica pura pe cursor;
- `AdaugaLinieTvdDinArticol` si `calculeaza_valori_articol` (au deja teste:
`COMUN\utile\Teste\editare_factura\test_adauga_linie_articol.prg`,
`test_adauga_linie_valuta.prg`);
- validarile noi din `inainte_de_do_termin`, apelate direct pe o instanta de formular.
**Netestabil headless, cu motivul:**
1. **Coloanele gridului `grdArticoleFactura`.** Sub `-A -T` coloanele nu se materializeaza
(`ColumnCount` si `RecordSource` citite acolo sunt artefacte); pentru ele exista deja harnessul
cu UI vizibil (`test_ui_grid_articole.prg`, `test_ui_sterge_linie.prg`,
`test_ui_fix_editabil_subtotal.prg`).
2. **Interactiunea reala cu gridul** — `Valid`/`When`/`InteractiveChange` pe `cCantitateArt`,
`cPretArt`, `cPretCuTvaArt` (`:16428-16458`) depind de focus si de `Thisform.oldvalue`; se pot
apela metodele direct, dar asta nu testeaza traseul care pune `lmodificat`.
3. **Dialogul modal `frm_articol_factura`** deschis din `cmdAdaugaArticol.Click` (`:16407-16408`,
`loDlg.Show(1)`) — blocheaza headless. De aceea `AdaugaLinieTvdDinArticol` a fost deja separata de
`Click` (comentariul de la `:13082-13083`); se testeaza doar partea separata.
4. **Ordonarea fata de `actualizeaza_vanzari`** — inima acestei cercetari. Nu se poate verifica
decat pe Oracle real: `finalizeaza_modificare_nota` trebuie sa ruleze efectiv ca sa se vada
resetul `STERS = 0`, iar apoi ca helperul il corecteaza. Cere un test tranzactional
(`do_deschide_tranzactie` -> pasii -> `SELECT` de verificare -> `Sqlrollback`), care **scrie**
temporar in baza. Nu s-a rulat aici.
5. **Regresia liniilor sterse in sesiuni anterioare** (3.2, consecinta 2) — cere doua editari
succesive ale aceleiasi facturi, deci doua tranzactii, deci scriere reala.
6. **`amessagebox` din `goExecutor.oExecuta`** la esec Oracle — blocheaza headless; testele care
forteaza un esec trebuie sa mocheze `oExecuta`, altfel raman agatate.
7. **Comportamentul in ROACONT/ROAGEST** (pagina absenta, helper neincarcat) — nu se poate testa din
ROAFACTURARE, unde `ofacturare_editare.prg` e mereu in `SET("PROCEDURE")`. Se poate aproxima
verificand ca garda `"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))` exista in fiecare punct nou,
dar nu e acelasi lucru cu o rulare reala.
---
## Rezumat al lucrurilor de decis inainte de implementare
1. **Recalculul de totaluri se muta din `finalizeaza_modificare_nota` in VFP** (3.3) — abatere de la
`rec_s5_oracle_vanzari.md` B, impusa de ordonare. De confirmat cu Marius.
2. **Stergerea se scrie ca diferenta de multimi**, nu ca lista de linii sterse (3.3, pasul 1) — asta
e ce repara si regresia din 3.2(2).
3. **Discountul se scrie neconditionat** cat timp nu exista valoare initiala salvata pe `tvanz`
(2.4); alternativa e un instantaneu la incarcare.
4. **S4b "enumera vechi -> nou" nu are azi de unde lua valorile vechi** (2.3) — cere un cursor de
instantaneu, lucrare in plus fata de ce exista.
5. **De verificat `DEFAULT`-urile pe coloanele `NOT NULL`** din `VANZARI_DETALII` inainte de a scrie
`INSERT`-ul (punctul 6, NEVERIFICAT).

View File

@@ -1,203 +0,0 @@
# S5 — parametrul de discount si documentele in valuta. Rezultat
Inchide cele doua goluri declarate de `docs\cercetare\rec_s5_scriere_reala.md`, sectiunea
„Ce NU acopera testul". Suita: `COMUN\utile\Teste\editare_factura\test_s5_discount_valuta.prg`.
Log: `COMUN\utile\Teste\editare_factura\test_s5_discount_valuta_log.txt` (rulare 10.08.2026,
00:05:16-00:05:21). Diff: diff aplicat (sters).
## Rezultat
**46 PASS / 0 FAIL**, numarate din log. Zero linii `EROARE`, ultima linie e `done`, deci rularea a
ajuns la capat. Starea finala e verificata **independent prin `sqlplus`**, nu din logul testului.
## VERDICTUL pe `NULL`: PASTREAZA. Nu zeroeaza.
Dovedit pe date, in doua contexte diferite, prin patru apeluri succesive in aceeasi tranzactie, cu
citirea starii necomise intre ele (log, blocul A):
| Apel | `VANZARI.DISCOUNT` | `DISCOUNT_TVA` | `TOTAL_FARA_TVA` | `TOTAL_TVA` | `TOTAL_CU_TVA` |
|---|---|---|---|---|---|
| stare initiala | 0 | 0 | 747.96 | 157.06 | 905.02 |
| `recalculeaza_totaluri_vanzari(1049, NULL)` | 0 | 0 | 747.96 | 157.06 | 905.02 |
| `recalculeaza_totaluri_vanzari(1049, 12.5)` | **12.50** | 2.63 | 735.46 | 154.43 | 889.89 |
| **`recalculeaza_totaluri_vanzari(1049, NULL)`** | **12.50** | 2.63 | 735.46 | 154.43 | 889.89 |
| `recalculeaza_totaluri_vanzari(1049, 0)` | 0 | 0 | 747.96 | 157.06 | 905.02 |
Randul al patrulea e verdictul: `NULL` peste un discount de 12.50 il lasa 12.50, si lasa **toate**
totalurile neschimbate pana la a patra zecimala (asertia `A3` compara exact, cu toleranta 0.0001,
nu aproximativ). Randul al cincilea arata contrastul: `0` **explicit** chiar zeroeaza — deci cele
doua valori nu se confunda in implementare.
Acelasi verdict, repetat pe calea de valuta (blocul B, `id_vanzare = 1037`): discount 10 -> `NULL`
-> discount ramane 10, `ftva/tva/valval/tvaval` neschimbate.
### Codul si datele concorda
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16043-16046`:
```sql
SELECT NVL(V_DISCOUNT, discount), in_valuta, NVL(discount_evidentiat, 0)
INTO lnDiscountFactura, lnInValuta, lnDiscountEvidentiat
FROM vanzari
WHERE id_vanzare = V_ID_VANZARE;
```
`NVL(V_DISCOUNT, discount)` — parametrul nul cade pe valoarea din tabela, iar `UPDATE`-ul final
(`:16215`) scrie inapoi `discount = lnDiscountFactura`. Citirea corpului si comportamentul pe date
spun acelasi lucru. Nu am gasit nicio divergenta si niciun defect in procedura.
## Cum se comporta o valoare nenula
**Document in RON** (`in_valuta = 0`): discountul se scade ca atare din baza fara TVA, iar TVA-ul
lui se scade din TVA. Cu `PPRETV = 2` si cota maxima `1.21` pe liniile active:
```
total_fara_tva = 747.96 - 12.50 = 735.46
discount_tva = ROUND(12.50 * 0.21, 2) = 2.63
total_tva = 157.06 - 2.63 = 154.43
total_cu_tva = 735.46 + 154.43 = 889.89
```
`VALOARE_ACHIZITIE` ramane `263.31` — nu depinde de discount, verificat explicit (`A2`).
**Document in valuta** (`in_valuta = 1`): discountul e exprimat **in valuta**. In RON se scade
convertit prin cursul documentului, in valuta se scade brut — ramura
`decode(in_valuta, 1, ROUND(curs * discount / multiplicator, PPRETV), discount)`
(`:16073-16092`). Pe `id_vanzare = 1037` (`id_valuta = 2`, `curs = 5.2688`, `multiplicator = 1`),
cu discount `10`:
```
discount in RON = ROUND(5.2688 * 10 / 1, 2) = 52.69
total_fara_tva = 1053.76 - 52.69 = 1001.07
total_tva = 221.29 - ROUND(52.69 * 0.21, 2) = 221.29 - 11.06 = 210.23
valval = 200 - 10 = 190.00
discount_tva = ROUND(10 * 0.21, 2) = 2.10 (TVA-ul discountului, in VALUTA)
tvaval = 42 - 2.10 = 39.90
totval = 190 + 39.90 = 229.90
```
Toate cele sapte valori au fost calculate in test din starea de plecare si comparate cu ce a scris
procedura. `ID_VALUTA`, `CURS` si `MULTIPLICATOR` raman `2 / 5.2688 / 1` dupa recalcul.
De consemnat, pentru cine citeste coloanele: **`DISCOUNT_TVA` retine `DISC_TVA_VAL`, adica TVA-ul
discountului in valuta (2.10), nu in RON (11.06)** — pe documentele in RON cele doua coincid, pe
cele in valuta nu. Nu e defect, `scrie_in_vanzari` face la fel; e doar neevident din nume.
## Documentele in valuta EXISTA. Premisa contrara era gresita.
Nota anterioara („nu exista in schema un document descoperibil simultan cu articole si in valuta
reala") **nu se confirma**. In `MARIUSM_AUTO`:
- **28** de randuri `VANZARI` cu `IN_VALUTA = 1`;
- **25** dintre ele au cel putin o linie activa in `VANZARI_DETALII`;
- **8** au si `ID_VALUTA` / `CURS` / `MULTIPLICATOR` completate **si** rand in `VANZARI_CURSURI`:
`id_vanzare` 329, 627, 678, 859, 864, 947, 952, **1037**.
Celelalte 17 sunt degenerate (`ID_VALUTA` si `CURS` nule pe cap), deci nu sunt cazuri de test bune.
Ales: **`id_vanzare = 1037`**, `cod = 1140730`, din **07.05.2026**, o linie activa (`det = 1565`,
cantitate 1, pret 200 in valuta, `pret_cu_tva = 0`, `proc_tvav = 1.21`, `pret_achizitie = 100`),
totaluri persistate `1053.76 / 221.29 / 1275.05` in RON si `200 / 42 / 242` in valuta.
**Recalculul reproduce exact starea persistata a acestui document**, si in RON si in valuta
(asertiile `B1`). Asta e proba directa ca agregarea din procedura — inclusiv conversia prin
`VANZARI_CURSURI` — e corecta pe un document in valuta emis pe cale normala, nu doar pe unul
simulat in VFP. Golul lasat de `test_adauga_linie_valuta.prg` (care suprascria `tvanz` manual) e
inchis pe partea de Oracle.
### Ce NU se poate acoperi pe documentele in valuta
Niciunul dintre cele 8 nu e **din luna curenta** (cel mai recent e din 05.2026, restul din
2009-2022), iar garda din `do_editare_factura` cere `data_act` in luna de lucru. Deci **lantul
complet de editare nu poate fi rulat pe un document in valuta** — pe date exista doar recalculul
Oracle, care e insa exact partea despre care nu se stia nimic. Documentul fiind arhiva, blocul B
se inchide cu **ROLLBACK**: `1037` e verificat prin `sqlplus` dupa test si e **neatins**.
## Lantul real de salvare cu discount nenul (blocul C)
Singurul bloc care comite. Documentul `1049` a primit discount `12.5` prin chiar procedura testata
(commit), apoi a trecut prin lantul complet de editare **fara nicio modificare de linii**:
```
recalculeaza_totaluri_vanzari(1049, 12.5) -- COMMIT, pregatire
IncarcaVanzareDinNota -> tvanz.discount = 12.5000 <- proba C2
MyDeschideTranzactie()
OSCRIE_IN_FISIERE(2, .T., .T.) => 1
OSCRIE_IN_FISIERE(0, .T., .T.) => 1
pack_contafin.finalizeaza_modificare_nota => 1
[in tranzactie] cod 1140897 -> 1140898, discount INCA 12.50 <- proba C3
ScrieArticoleFacturaEditate(1049) => 1
MyInchideTranzactie(1) -- COMMIT
[dupa commit] discount 12.50, ftva 735.46, tva 154.43, ctva 889.89 <- proba C5
recalculeaza_totaluri_vanzari(1049, 0) -- COMMIT, restaurare
```
Trei lucruri pe care doar acest bloc le stabileste:
1. **`tvanz.discount` chiar aduce discountul din `VANZARI`.** `IncarcaVanzareNota`
(`ofacturare_editare.prg:177`) selecteaza `v.discount` direct din `VANZARI`, iar helperul
citeste de acolo (`:485-486`). Daca cursorul l-ar fi adus 0, salvarea ar fi **sters** discountul
documentului — exact riscul din briefing. Nu se intampla.
2. **`finalizeaza_modificare_nota` nu atinge `DISCOUNT`.** Citit in tranzactie, dupa realinierea
`COD`-ului: `1140897 -> 1140898`, discount inca `12.50`. Concorda cu corpul lui
`actualizeaza_vanzari` (`:16012-16022`), care scrie doar `VANZARI_DETALII.STERS`, `VANZARI.COD`
si `VANZARI.STERS`.
3. **Discountul supravietuieste unei salvari complete**, cu totalurile recalculate coerent
(`735.46 + 154.43 = 889.89`).
`NULL` nu ajunge azi in procedura pe calea VFP decat daca `VANZARI.DISCOUNT` e **NULL** in baza —
helperul trimite `NULL` doar la `Isnull(discount)` (`:546`), altfel trimite valoarea. Pe `1049`
discountul e `0`, nu NULL, deci calea reala trimite mereu o valoare. Ramura `NULL` a helperului nu
e atinsa de acest test; contractul procedurii pe `NULL` este insa dovedit direct (blocurile A si B).
## Verificarea independenta prin `sqlplus` (dupa test, cu procesul VFP terminat)
`MARIUSM_AUTO/…@ROA_CENTRAL`:
```
1049: cod=1140898 sters=0 in_valuta=0 discount=0 discount_tva=0 val_ach=263.31
ftva=747.96 tva=157.06 ctva=905.02 valval=747.96 tvaval=157.06 totval=905.02
linii 1049: det=1581 sters=0 cant=2 pret=302.51 pret_ach=10
det=1582 sters=1 cant=1 pret=121.30 pret_ach=0
det=1583 sters=0 cant=1 pret=150.00 pret_ach=10
det=1588 sters=0 cant=3 pret=50.00 pret_ach=77.77
1037: cod=1140730 sters=0 in_valuta=1 discount=0 discount_tva=0 val_ach=100
ftva=1053.76 tva=221.29 ctva=1275.05 valval=200 tvaval=42 totval=242
id_valuta=2 curs=5.2688 multiplicator=1 -- NEATINS
1050: cod=1140895 discount=0 total_cu_tva=1924.59 -- NEATINS
vact_tot 08.2026: cod=1140897 randuri=10 sterse=10 | cod=1140898 randuri=10 sterse=0
v$transaction pe MARIUSM_AUTO: 0 randuri
```
`1049` e **exact** in starea de dinaintea acestui test pe toate coloanele de valoare — singura
schimbare e `COD`. Structura liniilor e neschimbata (aceleasi 3 active, `1582` ramane stearsa din
testul precedent).
## Date de test consumate
- **Un singur `cod` realocat: `1140897` -> `1140898`** pe `id_vanzare = 1049`. Cine reia testul
citeste `cod`-ul curent din `VANZARI`, nu-l presupune.
- **Nimic altceva.** Zero linii adaugate sau sterse, zero valori de totaluri lasate schimbate,
`DISCOUNT` restaurat la `0`. Blocurile A si B nu consuma nimic (ROLLBACK).
- `id_vanzare = 1050` si `id_vanzare = 1037` sunt **neatinse**, verificat prin `sqlplus`.
## Ce NU acopera nici acest test
- **Liniile din seturi** (`id_vanzare_set` nenul) — niciunul dintre documentele folosite nu are
asa ceva, deci ramura `union all` din agregare (`:16178-16209`), care ia totalurile din capul de
set, ramane neatinsa. Neschimbat fata de raportul precedent.
- **Lantul complet de editare pe un document in valuta** — imposibil azi: niciun document in valuta
nu e din luna curenta (vezi mai sus). Doar recalculul Oracle e acoperit.
- **Ramura `NULL` a helperului** (`ofacturare_editare.prg:546`) — cere `VANZARI.DISCOUNT` NULL in
baza, ceea ce nu s-a fabricat.
- **`discount_evidentiat = 1`** — toate documentele folosite au `0`; ramura care distribuie
discountul pe linii nu e atinsa.
- Cele cinci validari din `inainte_de_do_termin`, `frm_modific2024`, al doilea punct de intrare
(`comun.vc2:2491`) si calea de ROLLBACK la esec partial raman neacoperite, ca inainte.
## Igiena la iesire
Zero tranzactii deschise (`v$transaction`, 0 randuri), zero procese `vfp9.exe` (`tasklist`), zero
commituri git sau SVN. Niciun fisier de productie atins: singurul fisier nou este suita de test.
`PACK_FACTURARE`, `omodificari.vc2`, `ofacturare_comun.vc2`, `comun.vc2`, `ofacturare_editare.prg`
si `COMUN\clase\ofacturare.vc2` sunt neatinse.

View File

@@ -1,204 +0,0 @@
# 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(<sql>)` — 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).

View File

@@ -1,116 +0,0 @@
# S5 — grid PAGE3: coloana `pret_achizitie` si editabilitate per rand
Implementare in `COMUN\clase\omodificari.vc2`, clasa `frm_modific2024` (singura clasa afectata din
fisier — fisierul mai contine `frm_editare_set`, `frm_modific` si `frm_modific2007`, cu metode
`inainte_de_do_termin` byte-identice intre `frm_modific` (`:5004`) si `frm_modific2007`; scrierea a
fost restransa explicit la sufixul de dupa `DEFINE CLASS frm_modific2024` ca sa nu atinga duplicatele
mai vechi).
## A. Sincronizare cursor `tvd` (ramura ROACONT/ROAGEST)
`frm_modific2024.Load` (`:14554-14591` inainte de editare) are un `CREATE CURSOR tvd` duplicat
literal, folosit cand `ofacturare_editare.prg` nu e incarcat in `SET("PROCEDURE")`. I s-au adaugat
`id_vanzare_set I NULL, pret_achizitie N(14,4) NULL` intre `sters` si `denumire`, identic ca pozitie
si tip cu `CreeazaCursorArticoleGol` (`COMUN\programe\ofacturare_editare.prg:273-276`).
## B. Coloana noua `pret_achizitie` in `grdArticoleFactura`
- inserata ca `Column7` (dupa coloana de pret, `Column6`), cu renumerotarea `Column7..14 ->
Column8..15` — gridurile VFP nu au `ColumnOrder` implicit setat in acest fisier (ordinea vizuala
= ordinea numerica a `Column<N>`), deci pastrarea conventiei existente a insemnat renumerotare, nu
introducerea unei proprietati noi;
- `ControlSource = "tvd.pret_achizitie"`, `Format`/`InputMask` copiate de la `Column6` (pret,
`get_mask(12,gnPPRET)`), `Width = 90` (comparabil cu discount unitar), eticheta „Pret achizitie”
(fara diacritice, ca sa evite riscul de corupere cp1250 pe siruri noi);
- `ColumnCount` 14 -> 15; blocurile `ADD OBJECT` pentru `Header1`/`Text1` inserate in pozitia
alfabetica corecta (dupa `cLotArt`, inainte de `cPretArt` — `cPretAchizitieArt` < `cPretArt`
alfabetic, verificat impotriva ordinii reale din fisier, nu presupus).
## C. Editabilitate per rand (nu per coloana)
`ReadOnly` a ramas `.F.` la nivel de coloana pentru `cantitate`/`pret`/`pret_cu_tva`/
`pret_achizitie` (ca si azi) — gating-ul e mutat in metodele `When` ale controalelor din coloana,
singurul punct din care VFP decide daca celula primeste focus:
- `cCantitateArt.Text1.When`, `cPretArt.Text1.When`: adaugat `IF Nvl(tvd.id_vanzare_set,0)<>0
RETURN .F. ENDIF` inaintea liniei existente (`Thisform.oldvalue = This.Value`), fara alta
modificare de comportament pe liniile normale;
- `cPretCuTvaArt._checkbox1` nu avea `When` — s-a adaugat unul nou, `RETURN
Nvl(tvd.id_vanzare_set,0) = 0`;
- `cPretAchizitieArt.Text1.When` (nou): editabil doar cand `id_vanzare_set = 0 AND
id_vanzare_det = 0` (linie noua, nu componenta de set).
Marcaj vizual: `DynamicForeColor` extins pe toate cele 15 coloane (era pe toate cele 14),
`IIF(tvd.sters=1,RGB(150,150,150),IIF(NVL(tvd.id_vanzare_set,0)<>0,RGB(0,70,153),RGB(0,0,0)))` —
gri pentru sters (neschimbat), albastru `RGB(0,70,153)` nou pentru linie de set, negru normal in
rest. `nrgbrow = 0` pe `ADD OBJECT`-ul gridului nu a fost atins.
## D. Validari noi in `inainte_de_do_termin`
Inserate inaintea lui `RETURN m.llRet`, gardate pe `"OFACTURARE_EDITARE" $
Upper(Set("Procedure")) AND Used('tvd')`. Pe liniile active (`Nvl(sters,0)<>1`, parcurse cu un
singur `SCAN`): `cantitate<=0` si `pret` NULL si `id_articol` nul/0 blocheaza salvarea
(`RETURN .F.`, mesaj cu numele liniei din `denumire`); linie noua cu `pret_achizitie` 0/NULL cere
confirmare Da/Nu (tiparul `amessagebox(...,4+32,...)` deja folosit in metoda, la `<>6` = anulare);
zero linii active cu documentul avand rand in `tvanz` (semnalul din
`rec_s5_cale_scriere_vfp.md` 2.5, `Used('tvanz') AND Reccount('tvanz')=1`) cere aceeasi confirmare.
Workarea de dinainte de validare se salveaza in `lnAreaTvd` si se restaureaza pe toate iesirile.
## Runda 2 — corectii din code-review obligatoriu pe diff
- **`AdaugaLinieTvdDinArticol` nu copia `pret_achizitie`.** Linia noua adaugata prin dialogul
`frm_articol_factura` (`cmdAdaugaArticol.Click` -> `CreeazaPoArticolNouTvd`,
`ofacturare_editare.prg:379`) primeste `poArticol.pret_achizitie` ca proprietate, dar
`REPLACE`-ul care creeaza randul in `tvd` nu o citea — coloana noua ramanea goala pe orice
linie noua, desi valoarea putea fi deja disponibila. Corectat: `pret_achizitie WITH
Nvl(toArticol.pret_achizitie,0)` adaugat in acelasi `REPLACE`. `id_vanzare_set` nu are nevoie
de valoare explicita — `Nvl(id_vanzare_set,0)` pe camp `NULL` dupa `APPEND BLANK` evalueaza deja
corect la 0.
- **Recno('tvd') nu se restaura la iesire timpurie din validari.** `SELECT (m.lnAreaTvd)` restaura
doar workarea apelantului, nu si pozitia in `tvd` insusi. Corectat: `lnRecnoTvd = Recno('tvd')`
salvat inainte de `SCAN`, restaurat cu `GO (m.lnRecnoTvd) IN tvd` pe toate cele 5 iesiri
(4x `RETURN .F.` + finalul cu succes).
- **`Isnull(pret_achizitie) OR Nvl(pret_achizitie,0)=0` era redundant** — `Nvl` transforma deja
`NULL` in `0`, deci a doua conditie il acopera pe primul. Simplificat la o singura conditie.
- **Analizat, nu schimbat**: garda `"OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Used('tvd')`
se reduce practic la "sunt in ROAFACTURARE", pentru ca `tvd` exista mereu dupa `Load()`. Nu s-a
extins gardarea: `tvd` are randuri active doar cand exista un match real cu `VANZARI` (deci
randurile pornesc valide, din Oracle), iar confirmarea pe "zero linii active" e ea insasi
gardata separat pe `Used('tvanz') AND Reccount('tvanz')=1` — riscul practic de blocare pe un
ecran neasociat unei vanzari e deja redus de aceasta a doua conditie.
- **Neschimbate, evaluate ca stil existent, nu bug**: `DynamicForeColor` repetat identic pe cele
15 coloane, verificarea `id_vanzare_set` duplicata in 4 handlere `When` separate,
`cPretAchizitieArt.Text1` fara `Valid` (nu are nevoie — nu alimenteaza
`calculeaza_valori_articol`, iar `lmodificat` nu e citit pe calea liniilor noi la salvare).
## Runda 3 — inconsecventa latenta semnalata de al doilea review (`docs\review_s5_grid_articole.md`)
Avertismentul de `pret_achizitie = 0/NULL` la salvare (`inainte_de_do_termin`) exempteaza doar
`Nvl(id_vanzare_det,0) = 0` (linie noua). Garda de editare `cPretAchizitieArt.Text1.When`
(`omodificari.vc2`, cauta `PROCEDURE ...cPretAchizitieArt.Text1.When`) exempteaza in plus
`Nvl(id_vanzare_set,0) <> 0` — adica, daca ar exista vreodata o linie cu `id_vanzare_det = 0 AND
id_vanzare_set <> 0` (linie noua care apartine deja unui set), campul ar fi needitabil in grid, dar
avertismentul tot ar aparea la fiecare salvare, fara ca userul sa poata corecta valoarea.
**Neexploatabil pe codul actual**: singura cale de adaugare a unei linii noi e
`AdaugaLinieTvdDinArticol` (`APPEND BLANK` + `REPLACE`), care nu scrie niciodata
`id_vanzare_set` — campul ramane `.NULL.`, deci `Nvl(id_vanzare_set,0) = 0` intotdeauna pe linii
noi. Combinatia `id_vanzare_det = 0 AND id_vanzare_set <> 0` nu se poate produce azi. Fara fix.
**Conditia care ar activa-o**: daca se implementeaza vreodata editarea/adaugarea de linii in
seturi de articole si liniile noi rezultate primesc `id_vanzare_set` populat, cele doua conditii
(avertismentul din `inainte_de_do_termin` si garda din `When`) trebuie aliniate in acelasi commit
care aduce acea functionalitate — altfel userul ramane blocat intr-un nag fara iesire.
## Ce nu s-a atins
`ofacturare.vc2`, `ofacturare_comun.vc2`, `comun.vc2`, `ofacturare_editare.prg` — nicio scriere.
Helperul `ScrieArticoleFacturaEditate` si agatarea in cele doua puncte de intrare raman lucrarea
altor loturi ale sesiunii S5.
## Netestabil headless
Coloanele gridului nu se materializeaza sub `-A -T` (`grid-coloane-nu-se-materializeaza-headless`,
memorie recurenta) — verificarea vizuala a coloanei noi si a marcajului de culoare cere harnessul UI
existent (`test_ui_grid_articole.prg` sau echivalent). Validarile din `inainte_de_do_termin` sunt
totusi testabile headless, apeland metoda direct pe o instanta cu `tvd` populat manual.

View File

@@ -1,183 +0,0 @@
# S5 — testul cu scriere reala in Oracle. Rezultat
Aprobat de Marius (09.08.2026, consemnat in handoff intermediar (sters)). Suita:
`COMUN\utile\Teste\editare_factura\test_s5_scriere_reala.prg`. Log:
`COMUN\utile\Teste\editare_factura\test_s5_scriere_reala_log.txt` (rulare 09.08.2026 23:41:32-23:41:35).
Diff: diff aplicat (sters).
## Rezultat
**25 PASS / 0 FAIL**, numarate din log (12 la trecerea 1, 13 la trecerea 2). Ultima linie a logului
este `done`, deci rularea a ajuns la capat. Ambele treceri s-au inchis cu **COMMIT real**, iar starea
finala a fost verificata **independent prin `sqlplus`**, nu din logul testului.
**Baseline inainte de pornire**: `test_incarca_vanzare_din_nota` rerulata la 23:27:52 — **5 PASS /
0 FAIL**, identic cu asteptarea. Golul de baseline din briefing e inchis.
## Ce dovedeste, si nu putea fi dovedit headless
Proba centrala nu vine dintr-o asertie pe starea finala, ci din **citirea starii necomise in
interiorul tranzactiei**, pe aceeasi conexiune, intre pasi. In ambele treceri:
| Moment | `VANZARI_DETALII.STERS` pe linia stearsa (`det=1582`) |
|---|---|
| inainte de tranzactie | `1` (trecerea 2) / `0` (trecerea 1, inca nestearsa) |
| **dupa `finalizeaza_modificare_nota`, inainte de scrierea liniilor** | **`0`** — resetul a rulat si a **inviat** linia |
| dupa `ScrieArticoleFacturaEditate` | **`1`** — idiomul a corectat resetul |
| dupa COMMIT | `1` |
Log: liniile 19-28 (trecerea 1) si 57-67 (trecerea 2). Asta stabileste direct cele doua afirmatii ale
deciziei 38: `pack_facturare.actualizeaza_vanzari` face `UPDATE VANZARI_DETALII SET STERS = 0` pe tot
documentul **inainte** ca liniile sa fie scrise, si idiomul „marcheaza tot sters, invie ce ramane",
apelat **dupa** `finalizeaza_modificare_nota` in aceeasi tranzactie, il repara.
Trecerea 2 e cea care conteaza pentru consecinta 2: documentul a fost **reeditat fara nicio
modificare**, resetul a inviat din nou `det=1582`, si linia a ramas totusi stearsa dupa commit.
Fara aceasta trecere, testul nu ar fi atins scopul.
## Ordinea testata
Copiata literal din `ofacturare_comun.vc2:3799-3837`, inclusiv blocul nou de la `:3828-3830`:
```
MyDeschideTranzactie() -- _frm_base.vc2:252-265, reprodus inline
OSCRIE_IN_FISIERE(2, .T., .T.) => 1
OSCRIE_IN_FISIERE(0, .T., .T.) => 1
pack_contafin.finalizeaza_modificare_nota => 1
ScrieArticoleFacturaEditate(tvanz.id_vanzare) => 1
MyInchideTranzactie(1) -- COMMIT
```
`OSCRIE_IN_FISIERE`, `finalizeaza_modificare_nota` si `ScrieArticoleFacturaEditate` au rulat **reale**,
pe conexiune Oracle reala. Garda blocului nou (`"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))`,
`Used('tvanz')`, `Reccount('tvanz') = 1`) a fost satisfacuta in ambele treceri — logul ar fi scris
`ATENTIE: blocul ScrieArticoleFacturaEditate NU s-a executat` altfel.
## Documentul de test si ce s-a facut cu el
`id_vanzare = 1049`, factura tip 1 din 07.08.2026, `id_fact = 8009659`, `discount = 0`, fara linii de
set (`id_vanzare_set` NULL pe toate), 3 linii active. **Nu** `id_vanzare = 1050`, care e documentul pe
care `descopera_caz_test.prg` il alege pentru cazul `FACTURA_ARTICOLE` (`order by id_vanzare desc`).
Garzile `ReferinteDocumenteNota` si `EsteInEFactura` intorc 0 pe el, verificat inainte de rulare.
**Trecerea 1** — o linie stearsa, o cantitate schimbata, o linie noua:
| Linie | Actiune in `tvd` | Stare in Oracle dupa commit |
|---|---|---|
| `det=1581` | cantitate `1 -> 2`, `lmodificat = .T.` | `sters=0`, `cant=2`, `pret_achizitie=10` (neatins) |
| `det=1582` | marcata stearsa (`tvd.sters = 1`) | `sters=1` |
| `det=1583` | neatinsa | `sters=0`, `cant=1` |
| linie noua | `id_vanzare_det = 0`, `cantitate=3`, `pret=50`, `pret_achizitie=77.77` | `det=1588` alocat de `TRG_VANZARI_DET_BEFOINS`, `sters=0`, `pret_achizitie=77.77` |
**Trecerea 2** — reeditare fara nicio modificare: nicio linie noua, `det=1582` inca `sters=1`,
`det=1588` inca activa cu `pret_achizitie = 77.77`, totaluri neschimbate.
`PRET_ACHIZITIE` e verificat pe valori **nenule si distincte** (10 pe linia existenta careia i s-a
schimbat cantitatea, 77.77 pe linia noua) — nu pe zerouri, care n-ar fi dovedit nimic. La trecerea 2
linia `1588` e deja **linie existenta**, deci trece prin `UPDATE`-ul care omite `pret_achizitie` din
`SET`: faptul ca a ramas 77.77 e proba directa a omisiunii.
## Verificarea independenta prin sqlplus (dupa ambele treceri)
`MARIUSM_AUTO/…@ROA_CENTRAL`, dupa terminarea procesului VFP:
```sql
select cod, sters, id_fact, discount, total_fara_tva, total_tva, total_cu_tva
from vanzari where id_vanzare = 1049;
```
```
cod=1140897 sters=0 id_fact=8009659 discount=0 ftva=747.96 tva=157.06 ctva=905.02
```
```sql
select id_vanzare_det, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, id_gestiune,
sters, pret_achizitie, id_utils, validat, custodie, descarcat, diferenta
from vanzari_detalii where id_vanzare = 1049 order by id_vanzare_det;
```
```
det=1581 art=315554536 cant=2 pret=302.51 pret_cu_tva=1 proc_tvav=1.21 id_gest=1 sters=0 pret_ach=10 id_utils=-3 validat=0 custodie=0 descarcat=0 diferenta=0
det=1582 art=4294507492 cant=1 pret=121.3 pret_cu_tva=1 proc_tvav=1.21 id_gest=0 sters=1 pret_ach=0 id_utils=-3 validat=0 custodie=0 descarcat=0 diferenta=0
det=1583 art=315554536 cant=1 pret=150 pret_cu_tva=1 proc_tvav=1.21 id_gest=2 sters=0 pret_ach=10 id_utils=-3 validat=0 custodie=0 descarcat=0 diferenta=0
det=1588 art=315554536 cant=3 pret=50 pret_cu_tva=1 proc_tvav=1.21 id_gest=-1000 sters=0 pret_ach=77.77 id_utils=-3 validat=0 custodie=0 descarcat=0 diferenta=0
```
```sql
select sum(cantitate*pret) from vanzari_detalii where id_vanzare = 1049 and sters = 0;
```
```
905.02 -- identic cu VANZARI.total_cu_tva dupa recalculeaza_totaluri_vanzari
```
```sql
select cod, count(*), sum(sters) from vact_tot
where an=2026 and luna=8 and cod in (1140887,1140896,1140897) group by cod;
```
```
cod=1140887 randuri=10 sterse=10 -- nota de la prima editare, integral stearsa
cod=1140896 randuri=10 sterse=10 -- nota intermediara, integral stearsa
cod=1140897 randuri=10 sterse=0 -- nota curenta, activa
```
```sql
select s.sid from v$transaction t join v$session s on s.saddr = t.ses_addr
where s.username = 'MARIUSM_AUTO';
```
```
(0 randuri) -- nicio tranzactie deschisa
```
Totalurile: `747.96 + 157.06 = 905.02` (identitate verificata), iar `905.02` e chiar suma
`cantitate * pret` pe liniile active — relatie care se verifica si pe starea de dinaintea editarii
(`302.51 + 121.30 + 150.00 = 573.81`), pentru ca toate liniile documentului au `pret_cu_tva = 1`.
## Date de test consumate ireversibil
- **`cod`-uri realocate: `1140887` -> `1140896` -> `1140897`.** `1140897` e valoarea curenta; cine
reia testul pe `id_vanzare = 1049` trebuie sa citeasca `cod`-ul din `VANZARI`, nu sa-l presupuna.
- **`det=1582` ramane `sters=1` definitiv** — documentul are acum 3 linii active in loc de 3 initiale,
dar alta compozitie (`1581`, `1583`, `1588`).
- **`det=1588`** e o linie noua, creata de test, cu `id_gestiune = -1000` si `pret_achizitie = 77.77`.
- Totalurile documentului: `573.81` -> `905.02`.
- `1049` **nu** e documentul ales de `descopera_caz_test.prg` pentru `FACTURA_ARTICOLE` (acela e
`1050`), si nu apare in listele de excludere ale suitelor existente (`1140888`/`1140885` pentru
`test_incarca_cursoare`, `1139934` pentru `test_ui_sterge_linie`).
Rerularea suitei pe acelasi document e idempotenta ca structura: trecerea 1 ar sterge iar a doua
linie activa si ar adauga inca una.
## Ce NU acopera testul
- **`inainte_de_do_termin`** — `buton = 1` e fortat direct, deci cele cinci validari noi din
`omodificari.vc2` (cantitate <= 0, `id_articol` nul, `pret` nul, zero linii active, avertismentul de
`pret_achizitie = 0`) **nu** au fost executate pe aceasta cale.
- **`frm_modific2024`** nu a fost instantiat: gridul de pe PAGE3, coloana noua de pret de achizitie,
editabilitatea per rand si marcajul liniilor de set raman neverificate aici (cad in verificarea pe
ecran).
- **Liniile din seturi** — documentul ales nu are `id_vanzare_set` nenul pe nicio linie, deci
comportamentul agregarii pe seturi (totalurile luate din capul de set, nu din componente) **nu e
atins**. Cifrele de totaluri de mai sus nu spun nimic despre acel caz.
- **Al doilea punct de intrare** (`comun.vc2:2491`) nu a fost rulat; agatarea acolo e identica
textual, dar testul a trecut doar prin ramura din `ofacturare_comun.vc2`.
- **Documentele in valuta** si cele cu `discount` nenul — `1049` are `discount = 0` si
`in_valuta = 0`, deci parametrul de discount al lui `recalculeaza_totaluri_vanzari` a fost testat
doar cu valoarea `0`, niciodata cu `NULL` si niciodata cu o valoare nenula.
- **Esecul partial** — nicio cale de eroare nu a fost provocata, deci ROLLBACK-ul lantului
(`lnSucces < 0`) ramane netestat pe date reale.
## Observatii pe cod, fara defecte de raportat
- `INSERT`-ul din `ScrieArticoleFacturaEditate` **omite** cinci coloane `NOT NULL` din
`VANZARI_DETALII` (`VALIDAT`, `STERS`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT`). Verificat pe dictionar
inainte de rulare: toate cinci au `DEFAULT 0`, deci `INSERT`-ul e valid, iar linia noua intra activa
(`STERS = 0`) — confirmat pe `det=1588`. Nu e defect, dar dependenta de `DEFAULT` nu se vede din cod.
- `id_gestiune = -1000` (valoarea folosita de `CreeazaPoArticolNouTvd`) **nu** are corespondent in
`NOM_GESTIUNI` si nu exista constrangere de cheie straina pe coloana — `INSERT`-ul trece. Inaintea
acestui test nu exista nicio linie in `VANZARI_DETALII` cu aceasta valoare; acum exista una.
- Nu am gasit niciun defect in codul de productie in urma acestui test.
## Igiena la iesire
Zero tranzactii deschise (interogare pe `v$transaction`, 0 randuri), zero procese `vfp9.exe` ramase
(`tasklist`), zero commituri git sau SVN. Singurul fisier adaugat de aceasta lucrare in arborele de
lucru este suita de test; `COMUN\clase\ofacturare.vc2`, `omodificari.vc2`, `actualizeaza_vanzari` si
`PACK_CONTAFIN` nu au fost atinse.

View File

@@ -1,173 +0,0 @@
# S5 — acoperirea de teste pentru scrierea articolelor (decizii 38-41)
Raport de testare pentru functionalitatea S5 (handoff intermediar (sters)), care nu avea niciun test la
inceputul acestei lucrari. Trei fisiere de test atinse, toate in
`COMUN\utile\Teste\editare_factura\`, plus diff-ul: diff aplicat (sters).
Niciun fisier de productie nu a fost atins (`omodificari.vc2`, `ofacturare_editare.prg`,
`ofacturare_comun.vc2`, `comun.vc2`, `ofacturare.vc2` — toate neschimbate de acest agent). Nu s-a
scris in Oracle (sectiunea B din suita noua foloseste un mock pe `goExecutor`, nu conexiunea
reala) si nu s-a dat commit.
## A. `test_page3_articole.prg` — asteptari actualizate
`verifica_editare_grid` verifica acum realitatea de azi: `ColumnCount=15` (era 14),
`Type('tvd.pret_achizitie')=='N'`, `Type('tvd.id_vanzare_set')=='N'`, iar `ReadOnly` la nivel de
coloana acopera si noua coloana 7 (`pret_achizitie`) si coloana 8 devenita `pret_cu_tva`
(checkbox-ul `_checkbox1` mutat odata cu ea).
Suita ramane la **14 PASS / 2 FAIL** (numarate ca ocurente literale `PASS`/`FAIL` in log, nu prin
regexul `raport_teste.ps1`, care pentru aceasta suita specifica subraporteaza — vezi sectiunea D).
Cele doua FAIL sunt aceleasi doua asertii de dinainte (`llStructuraOk`, `llReadOnlyOk`), acum cu
continut actualizat. **Gasit un motiv suplimentar de esec**, dincolo de artefactul headless deja
cunoscut: vezi sectiunea D.
## B. `test_s5_validari_articole.prg` — suita noua, headless, 35 PASS / 0 FAIL
Doua sectiuni.
**Sectiunea A** — cele 5 validari noi din `inainte_de_do_termin` (`omodificari.vc2:14328-14366`),
apelate DIRECT pe o instanta reala de `frm_modific2024` (document real, descoperit prin
`descopera_caz_test.prg`, cazul `FACTURA_ARTICOLE`):
- caz de control (document valid) → `.T.`, nicio validare declansata;
- `cantitate<=0` → blocheaza, mesaj cu "cantitatea";
- `id_articol` nul/0 → blocheaza, mesaj cu "articol";
- zero linii active + document cu rand in `tvanz` → confirmare (ambele ramuri Da/Nu verificate);
- linie noua cu `pret_achizitie` 0 SAU NULL → confirmare (ambele ramuri verificate, plus varianta
NULL separat de varianta 0).
Un singur caz **nu s-a putut testa asa cum era planificat** — vezi constatarea de mai jos
(`pret NULL`).
**Sectiunea B** — garda de no-op si SQL-ul generat de `ScrieArticoleFacturaEditate`, complet
headless, pe cursoare construite in test (`crstvdtest`/`crstvanztest`, structura identica cu
`CreeazaCursorArticoleGol`), cu `goExecutor` inlocuit temporar de un mock (`dummyexecutor`) care
doar inregistreaza textul comenzilor si intoarce o valoare configurabila — conexiunea reala la
`MARIUSM_AUTO` ramane neatinsa tot timpul acestei sectiuni.
- garda de no-op, patru cazuri, toate verificate sa returneze `.T.` **fara niciun apel**
`goExecutor.oExecuta`: `tnIdVanzare<=0` (0 si -5), alias articole neinchis, alias vanzare fara
exact 1 rand (0 si 2 randuri), **`Reccount(alias articole)=0`** (cazul critic din handoff —
protectia impotriva golirii facturii la un esec de incarcare Oracle);
- clasificarea liniilor, verificata prin SQL-ul EFECTIV generat pe un cursor cu cate un rand din
fiecare categorie (pastrata-modificata, pastrata-nemodificata, stearsa, noua, noua-si-stearsa,
componenta de set): exact 1 comanda "marcheaza tot sters", exact 3 `UPDATE` (liniile pastrate
555/556/558 — inclusiv cea nemodificata si componenta de set, conform deciziei 38 "invie TOATE
liniile pastrate"), exact 1 `INSERT` (linia noua), 0 comenzi pentru linia stearsa si pentru cea
noua-si-stearsa ("se ignora"), exact 1 recalcul;
- `INSERT`-ul verificat pe continut: `pret_achizitie` citit din cursor, `explicatie` cu apostrof
escapat corect (`OracleSpecialCharacters`), `id_vanzare`/`id_articol` corecte;
- recalculul: `discount` NULL in cursor → parametru `NULL` (pastreaza valoarea din baza, decizia
41); `discount=5` → parametru `5.0000`;
- oprirea la primul esec Oracle: mock configurat sa esueze exact la a doua comanda (in mijlocul
SCAN-ului de `UPDATE`) → functia intoarce `.F.` si **nu mai incearca a treia comanda** (2 comenzi
total, nu 3+) — confirma ca bucla se opreste la primul esec, nu doar la primul pas.
### Constatare: validarea `Isnull(pret)` e cod neatingibil pe fluxul real
Planul initial testa si "pret NULL pe o linie activa → blocheaza". Incercarea de a forta
`tvd.pret` la `.NULL.` (fie pe o linie persistata, fie pe una noua adaugata prin `APPEND BLANK` pe
ACELASI cursor) arunca **eroarea VFP 1581 "Field PRET does not accept null values"** — verificat
empiric, nu presupus. Cauza: `tvd`, populat de `IncarcaArticoleFactura` din `VVANZARI_ARTICOLE`
(singura cale reala cand `ofacturare_editare.prg` e incarcat), mosteneste structural restrictia
`NOT NULL` a coloanei `VANZARI_DETALII.PRET` din Oracle — restrictia e la nivel de COLOANA a
cursorului, deci se aplica si liniilor noi adaugate ulterior, nu doar celor citite din baza.
Concluzie: **validarea `IF Isnull(pret) ... blocheaza` din `inainte_de_do_termin`
(`omodificari.vc2:14340-14344`) nu poate fi declansata pe fluxul real** — nici pe o linie
existenta, nici pe una noua construita prin fluxul aplicatiei (`AdaugaLinieTvdDinArticol`/
`CreeazaPoArticolNouTvd`, care oricum populeaza mereu `pret`). Nu e un bug care sa piarda date sau
sa produca un comportament gresit — e o garda defensiva care, in conditiile de azi, nu se poate
declansa niciodata. Marcat in log ca **`FAIL asteptat`** (exclus de `raport_teste.ps1` din
numaratoarea de regresii), cu explicatia inclusa in linie. **Raportat, nu reparat** — in afara
mandatului acestui agent.
## C. `test_ui_s5_grid_pret_achizitie.prg` — verificare UI, 14 PASS / 0 FAIL
Coloanele gridului nu se materializeaza sub `-A -T` (memoria recurenta), deci verificarea vizuala
s-a facut cu formular REAL, vizibil (`vfp_ui_harness.ps1`), pe documentul deja folosit de
`test_ui_sterge_linie.prg`/`test_ui_grid_articole.prg` (cod=1139934, an=2021, luna=12,
id_vanzare=882 — are rulaje, altfel `Show()` ascunde tot `pgfArticole`).
Verificat:
- gridul are **15 coloane**, coloana `cPretAchizitieArt` exista, legata pe `tvd.pret_achizitie`,
eticheta "Pret achizitie";
- linie existenta (`id_vanzare_det<>0`): `pret_achizitie` **nu** primeste focus;
- **linie de SET (caz sintetic)**: `cantitate`/`pret`/`pret_achizitie`/checkbox `pret_cu_tva`
**niciunul** nu primeste focus, si marcajul vizual (`DynamicForeColor`) e albastru
`RGB(0,70,153)`, exact formula din cod;
- marcajul de linie **stearsa** (gri `RGB(150,150,150)`) e **neregresat**;
- linie **noua** (`id_vanzare_det=0`, `id_vanzare_set=0`): `pret_achizitie` **primeste** focus,
iar `cantitate`/`pret` raman editabile ca inainte.
Captura: `COMUN\utile\Teste\editare_factura\screenshots\step_0_grid_s5.png` — se vede coloana
"Pret achizitie" in grid si prima linie grizata (sters=1).
**Caz sintetic, explicit**: documentul real nu are linii de set (in toata schema `MARIUSM_AUTO`
exista doar 4 randuri in `VANZARI_SETURI` — insuficient ca sa descopere un caz real prin
proprietate, si oricum absenta/prezenta pe atat de putine documente nu ar fi dovada in niciun
sens). Cazul de set a fost construit prin marcarea manuala a unui rand real din `tvd`
(`id_vanzare_set = 777`), tiparul deja folosit in `test_adauga_linie_valuta.prg` pentru cazul de
valuta. Verificarile de focus s-au facut prin apel DIRECT al metodei `.When()` a controlului
(echivalentul programatic al "celula primeste focus"), nu prin input real — masina e partajata cu
Marius.
## D. Constatare separata: `loGrid.ColumnN` nu functioneaza pe acest grid (nu doar headless)
La scrierea testului UI, prima varianta folosea acelasi tipar ca `test_page3_articole.prg`
(`loGrid.Column1.DynamicForeColor`) si a picat cu **eroarea VFP 1925 "Unknown member COLUMN1"** —
desi gridul era complet materializat (`ColumnCount` citea corect 15, `Show()` real, nu headless).
Cauza: coloanele acestui grid sunt redenumite explicit (`Column1.Name = "cDenumireArt"`,
`Column7.Name = "cPretAchizitieArt"` etc.) — in VFP, o coloana de grid redenumita asa **nu mai
raspunde la accesul numeric implicit** (`.Column1`), accesul valid ramane DOAR pe numele nou
(`.cDenumireArt`). Corectat in `test_ui_s5_grid_pret_achizitie.prg` (`loGrid.cDenumireArt...`).
Consecinta pentru `test_page3_articole.prg`: cele doua asertii ramase FAIL (`llStructuraOk`,
`llReadOnlyOk`) nu pica *doar* din artefactul headless cunoscut (grid nematerializat) — **ar pica
oricum**, chiar si intr-un rulaj cu formular vizibil, din cauza tiparului de acces
`loGrid.Column1`/`.Column5`/`.Column6`/`.Column7`/`.Column8`/`.Column14`/`.Column15`, folosit deja
in codul S4 preexistent (nu introdus de aceasta lucrare). Nu am corectat acest tipar in
`test_page3_articole.prg` — mandatul explicit a fost sa actualizez ASTEPTARILE (numarul de
coloane, semantica de editabilitate), nu sa repar accesul la obiect, iar suita trebuia sa ramana la
14/2 fara sa "iasa alt numar". Semnalat aici ca sa nu se piarda: daca cineva vrea vreodata sa faca
`verifica_editare_grid` sa treaca real (nu doar sa esueze "cunoscut"), trebuie schimbat atat
harnessul (formular vizibil, nu `-A -T`) CAT SI accesul la coloane (`loGrid.cDenumireArt` etc., nu
`loGrid.Column1`).
## E. Regresie finala
`raport_teste.ps1 -Dir COMUN\utile\Teste\editare_factura -Tot`, dupa ultima editare (verificat pe
mtime):
| Suita | PASS | FAIL |
|---|---|---|
| test_adauga_linie_articol | 20 | 0 |
| test_adauga_linie_valuta | 16 | 0 |
| test_incarca_vanzare_din_nota | 5 | 0 |
| test_page3_articole | 10*/14** | 0*/2** |
| test_s5_validari_articole (nou) | 35 | 0 |
| test_ui_s5_grid_pret_achizitie (nou) | 14 | 0 |
| test_ui_sterge_linie | 8 | 0 |
| test_verdict_act_rul | 26 | 0 |
\* cifra `raport_teste.ps1` (regex pe inceput de linie — subraporteaza pentru aceasta suita
specifica, vezi mai jos). \*\* cifra reala, numarata ca ocurente literale `PASS`/`FAIL` in log —
aceasta e conventia din handoff intermediar (sters) ("test_page3_articole 14/2"), verificata identica
inainte si dupa editarea mea.
**Atentie separata pentru viitor**: `raport_teste.ps1` numara doar liniile care incep cu
`PASS`/`FAIL` dupa spatii (`^\s*PASS`); `test_page3_articole.prg` scrie o parte din verdicte ca
`eticheta = PASS/FAIL` (verdictul la finalul liniei, nu la inceput) — pentru ACEASTA suita specifica,
raportul automat arata 10/0 in loc de 14/2 reale. Nu e o problema introdusa acum (tiparul exista
deja in cod dinainte), dar inseamna ca **pentru `test_page3_articole`, raportul automat nu e de
incredere** — verificarea trebuie facuta manual (`grep -o PASS/FAIL`, cum s-a facut aici), nu doar
cu `raport_teste.ps1`.
Toate celelalte suite: identice cu baseline-ul din handoff intermediar (sters), nicio regresie.
## F. Ce nu s-a acoperit si de ce
- **Test cu scriere reala in Oracle** (aprobat de Marius pentru S5, dar separat de mandatul acestui
agent — interzis explicit sa scrie in baza) — ramane pentru o lucrare ulterioara, dupa modelul
`test_writeback_buton1.prg`.
- **R5** (linie cu valuta fara rand in `VANZARI_CURSURI`) — ramane deschis din handoff intermediar (sters), nu
s-a adaugat un test nou pentru el, in afara mandatului de "validari + helper + grid".

View File

@@ -1,204 +0,0 @@
# 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
handoff intermediar (sters)).
**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
(handoff intermediar (sters), 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, handoff intermediar (sters)):
`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.

View File

@@ -1,216 +0,0 @@
# S8 — Creare documente lipsa (aviz, factura din aviz, factura din contract) pe fluxul REAL
> # OPRESTE-TE — SARCINA E INCHISA, 10.08.2026 ora ~12:35
>
> **NU relua depanarea crash-ului `frm_alte_date` si NU mai rula `creeaza_documente_s8.prg`.**
> Scopul acestui raport — sa EXISTE cele trei documente — **e deja atins pe alta cale**: Marius le-a
> emis manual din aplicatie, prin fluxul real (ceea ce satisface regula „niciodata `INSERT` direct"
> mai bine decat orice harness).
>
> | Tip sursa | `id_vanzare` | `cod` | `id_fact` | totaluri |
> |---|---|---|---|---|
> | aviz (tip 22) | **1052** | 1140908 | 8009677 | 271.17 / 56.94 / 328.11 |
> | factura din aviz (tip 4) | **1054** | 1140910 | 8009679 | 252.07 / 52.93 / 305.00 |
> | factura din contract (tip 2) | **1055** | 1140911 | 8009680 | 200.00 / 42.00 / 242.00 |
>
> Verificate prin `sqlplus` de orchestrator: toate `data_act = 10.08.2026`, `sters = 0`, cu linii,
> note `ACT` si rulaje. Impreuna cu `1048` (lista de preturi), matricea S8 e completa pe toate patru
> tipurile de sursa.
>
> **Fiecare rulare a harness-ului strica date.** Rularea de la **12:28:31** a creat `1056` si `1057`
> — documente malformate: totaluri `0/0/0`, `RUL` gol, si **acelasi numar `SSS/14` alocat la toti trei
> pasii** (log liniile 12, 23, 29), deci cu numar duplicat. Ele **nu se folosesc** in matrice si
> urmeaza sa fie sterse prin aplicatie. Nu mai adauga altele.
>
> **Ce a mai ramas din S8 nu e automatizarea, ci matricea**: cele patru documente
> (`1048 / 1052 / 1054 / 1055`) editate fiecare **de doua ori** — o data din ROAFACTURARE, o data din
> registrul jurnal ROACONT — cu verificarile din `plan_06_editare_factura.md:282-291`.
> Starea la zi e in `docs\progres.md`, sectiunea „S8 — DOCUMENTELE EXISTA".
>
> *De pastrat din investigatia de mai jos, indiferent de sarcina*: pe masina ruleaza mai multi agenti
> cu procese `vfp9.exe` concurente — **progresul unei rulari se citeste DOAR din log**, niciodata prin
> `tasklist`/`Get-Process`/PID (PID-ul intors de `Start-Process` poate fi un launcher care iese imediat).
Status istoric: **PREDARE (Regula zero) — NEFINALIZAT** la momentul scrierii. Niciun document creat de
harness la acea ora. Investigatia de cod de mai jos ramane valida si citata cu `fisier:linie`, utila
daca automatizarea se reia candva — dar **nu e nevoie de ea pentru S8**.
Continua `rec_s8_creare_variante_plan.md` (plan) si
`rec_s8_inventar.md` (de ce lipsesc). Scop strict: **crearea** celor 3 documente prin fluxul real de
emitere (`ofacturare.prg::factureaza` -> `do_scrie_factura` -> `PACK_FACTURARE`), zero INSERT direct,
zero editare de cod productie. Editarea propriu-zisa (matricea S8) NU intra in scopul acestei runde.
## Plan de executie (din `rec_s8_creare_variante_plan.md`)
1. AVIZ (`tnTip=22`) din lista de preturi.
2. FACTURA DIN AVIZ (`tnTip=4`), sursa = avizul de la pasul 1.
3. FACTURA DIN CONTRACT (`tnTip=2`), pe `id_ctr=235` (NU `id_ctr=222`).
Interzis: INSERT/UPDATE direct pe documente, editare cod productie, commit, alta schema decat
`MARIUSM_AUTO`, atingerea `id_vanzare` 1048/1049/1050, input real (keybd_event/SendInput).
## Progres
- [x] Citit `frm_date_aviz.inainte_de_do_termin` (garda, `ofacturare.vc2:7277-7352`) si `Init` (7354-7600)
- [x] Citit `frm_date_factura.inainte_de_do_termin` (`ofacturare.vc2:9455-9561`)
- [x] Citit `oDateFactura.Init`/`initializeaza_setari_document` (`ofacturare_comun.prg:223-359`)
- [x] Citit `oGeneratorNumere.creeaza_cursor_serii`/`aloca_numar`/`verifica_numar` (`oserii_numere.prg:128-234`)
- [x] Citit `do_scrie_factura` integral (`ofacturare.vc2:14197-14554`) si `frm_facturare_articole.inainte_de_do_termin` (14806-14974)
- [x] Citit `frm_alte_date.inainte_de_do_termin`/`Init` (`ferestre_cere_date.vc2:3044-3200`)
- [x] Citit precedentele: `test_pret_cu_tva_nivel2.prg`, `test_s5_al_doilea_intrare.prg`, `test_init_env_auto.prg`
- [x] Gasit al treilea modal neanticipat de plan: `Do Form verificare` (`ofacturare.vc2:14404`, in `do_scrie_factura`) - rezolvat cu stub-ul EXISTENT `COMUN\utile\Teste\achizitie_import\stub_verificare\verificare.sc2` (Init->`gnButon=1`+`RETURN .F.`), pus PRIMUL in `SET PATH`
- [x] Verificat in Oracle: `id_fdoc` e AUTO-DERIVAT de `oDateFactura.Init` (`actualizeaza_document()`), nu trebuie setat manual; `dataireg`/`dataact`/`datascad`/`zi_curs` la fel
- [x] Verificat contract `id_ctr=235`: 4 randuri `CTR_SCADENTAR`, toate cu `ID_ACT` NULL (nefacturate) - document real, neconsumat, nu fabricat
- [x] Verificat delegat `id_part=256` ("DELEGAT"), client `463`=RAJA, client `598`=ABSOLUT SRL - toti valizi in `NOM_PARTENERI`
- [x] Scris harness `creeaza_documente_s8.prg` (`COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg`)
- [ ] Rulat pas 1 (AVIZ) + verificare Oracle
- [ ] Rulat pas 2 (FACTURA DIN AVIZ) + verificare Oracle
- [ ] Rulat pas 3 (FACTURA DIN CONTRACT) + verificare Oracle
- [ ] Verificare finala: zero vfp9.exe, zero tranzactii deschise
## Design harness (creeaza_documente_s8.prg)
Reproduce corpul lui `Procedure factureaza` (`ofacturare.prg:81-560`) per `tnTip`, cu conexiune
Oracle REALA (`goConn.Connect('CENTRAL','MARIUSM_AUTO','ROMFASTSOFT')`, spre deosebire de
`test_pret_cu_tva_nivel2.prg` care avea `goExecutor` mock). Doua inlocuiri, ambele DOAR de
conducere UI:
1. Dialogul modal de antet (`frm_date_aviz`/`frm_date_factura`) -> setare directa `poDate.id_client`,
`poDate.listaid`, `poDate.id_delegat`; `poDate.nract`/`serie_act` din
`poGeneratorNumere.creeaza_cursor_serii()`+`aloca_numar()` REAL (acelasi apel facut de controlul
`clb_serie_act` din dialog, `serii_numere.vc2:114-136`).
2. Al doilea modal, `frm_alte_date` - condus cu driverul `driverAlteDate8` (Timer pe `_SCREEN`,
cauta clasa `FRM_ALTE_DATE`, apeleaza `.do_termin()` direct).
Al treilea modal (`Do Form verificare`) evitat prin stub existent in `SET PATH`, nu prin driver.
Ordine reala: `do_adauga_tot()` -> `do_calculeaza_totaluri()` -> `do_termin()` ->
`inainte_de_do_termin()` -> `do_scrie_factura()` -> `PACK_FACTURARE` (neatins). Rezultatul
(`id_vanzare`) vine din `poDate.nid_vanzare`, populat de parametrul OUT al procedurii PL/SQL reale.
## Note pe parcurs
Iteratii de depanare headless (log: `creeaza_documente_s8_log.txt`, langa `.prg`):
1. **Bug structural**: `PROCEDURE S8Log`/`S8Err` erau plasate INAINTE de `TRY` in fisier -> VFP
"cade" (fall-through) in corpul lor la executia liniara, inainte sa apuce sa intre in `TRY`.
Corectat: mutate DUPA `QUIT`, ca in toate precedentele (`test_pret_cu_tva_nivel2.prg`,
`test_s5_al_doilea_intrare.prg` au aceeasi conventie - procedurile dupa `QUIT`).
2. **Variabila lipsa** `gcSettingsFile`/`goApi` - cerute de `get_ora()` (apelat din
`oDateFactura.Init`). Adaugate din `test_init_env_auto.prg:209-230`.
3. **Data curs EUR lipsa**: `cursor_preturi`/`cursor_contract` cad cu eroarea Oracle reala "Nu este
setat cursul din data de 10/08/2026 pentru EURO!" - verificat in Oracle, nu exista curs EUR
pentru 10.08.2026 in `MARIUSM_AUTO` (ultimul: 07.08.2026 = 5.29, tot in luna curenta). Productia
trateaza asta cu un dialog editabil (`vizualizeaza_curs`); headless am setat
`poDate.zi_curs = {^2026-08-07}` (camp distinct de `dataact`/`dataireg` - nu schimba data
documentului, doar ziua de curs folosita pt. articolele in valuta din lista de preturi/contract).
4. **Bug propriu**: cleanup-ul cursoarelor `jtva_coloane`/`jtva_coloane_temp` lipsea pe caile de
iesire timpurie (eroare cursor / Reccount=0) din `CreeazaDocument` - factorizat in
`S8CurataJtva`, apelat pe toate caile.
5. **Variabile globale lipsa** `gnFactSeturi`/`gnCoefKFact`/`gnListareAvizBonFiscal`, cerute de
`frm_facturare_articole.inainte_de_do_termin`. Adaugate (`gnFactSeturi=0`, ramurile respective
raman inactive pt. documentele noastre).
6. **In curs**: dupa `do_adauga_tot()` (Reccount(crsfactura)=1, AVIZ tip=22), driverul
`driverAlteDate8` detecteaza `FRM_ALTE_DATE` si apeleaza `.do_termin()` - logul se opreste imediat
dupa acel apel, fara eroare prinsa de `TRY/CATCH` din driver si fara linia urmatoare din
`CreeazaDocument`. De investigat daca e blocaj real sau doar Oracle lent pe primul apel PL/SQL
din acel punct (`pack_facturare.scrie_factura2`/etc). **ATENTIE constatata in aceasta runda**:
masina ruleaza MAI MULTI agenti in paralel, fiecare cu propriile procese `vfp9.exe` - PID-ul
raportat de `Start-Process` in PowerShell corespunde unui proces-lansator care iese rapid
(`WaitForExit` intoarce `True` desi scriptul REAL continua intr-un proces `vfp9.exe` copil cu alt
PID) - `tasklist`/`Get-Process vfp9` NU se poate folosi ca sa identifice procesul propriu fara
ambiguitate cand alti agenti au procese vfp9 concurente. Verificarea de progres se face DOAR prin
continutul logului, niciodata prin PID/`Responding`. `Stop-Process` pe `vfp9` e evitat cu
exceptia cazurilor cu dovada tare (PID exact, pornit chiar de comanda curenta).
Confirmat prin doua rulari succesive (ultima: pornita 11:50:29, monitor extern a asteptat inca
~120s, TIMEOUT, log neschimbat): blocajul e **reproductibil**, nu tranzitoriu — se opreste mereu
imediat dupa linia `driverAlteDate8: FRM_ALTE_DATE detectat, apas do_termin()`, fara nicio linie
ulterioara (nici succes, nici CATCH din `TRY` al driverului, nici `ON ERROR` de la nivelul
programului principal).
## HANDOFF (Regula zero) — STARE LA OPRIRE
**NIMIC PERICULOS**: verificat, nu presupus.
| Ce | Cum s-a verificat | Rezultat |
|---|---|---|
| Documente create in Oracle | `select ... from vanzari where trunc(data_act)=trunc(sysdate) and id_part in (463,598)` prin sqlplus | **zero randuri** — nu s-a scris niciun document, nici partial |
| Tranzactii Oracle deschise | `v$transaction` join `v$session` pentru `MARIUSM_AUTO` prin sqlplus | **zero** |
| Sesiuni Oracle active pe `MARIUSM_AUTO` | `v$session where username='MARIUSM_AUTO'` | doar `plsqldev.exe`/`sqlplus.exe` (ale mele, de verificare) — **niciun `vfp9.exe` conectat** |
| Procese `vfp9.exe` ramase | `Get-Process -Name vfp9` | **niciunul** (procesul harness-ului s-a terminat/crash-uit fara sa ramana agatat) |
| Fisiere editate fara write-back | harness-ul e `.prg` text simplu (nu `.vc2`/`.sc2`), scris direct — nu exista pas de conversie binar | N/A |
**Date de test consumate — posibil, de verificat prima data in sesiunea urmatoare**: rularea a
apucat sa apeleze `poGeneratorNumere.aloca_numar(6, NULL)` REAL pentru AVIZ (`nIdTipDoc=6`,
serie `SSS`, numar alocat **12** — vezi log linia 12), INAINTE de crash. Daca
`pack_serii_numere.aloca_numar` face commit intern (nu e in tranzactia manuala deschisa abia mai
tarziu de `do_scrie_articole`/`do_deschide_tranzactie`), numarul **12** din seria `SSS` (tip doc 6 =
AVIZ) ar putea fi ars fara sa existe niciun document care sa-l foloseasca. **De verificat cu
sqlplus** inainte de a relua (interogare pe cursorul de serii / tabela care tine `paPlaje`-ul in
Oracle, echivalentul `NOM_SERII_NUMERE`/`NOM_SERII_NUMERE_PLAJE` sau similar — nu identificat inca
numele exact al tabelei) — daca da, following runda trebuie doar sa noteze faptul (nu e o problema
de corectitudine, seria oricum sare numere cand un document e anulat, e comportament normal de
productie), nu sa incerce sa-l "recupereze".
**Ce e facut**:
- Harness complet scris: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg`
(singurul fisier nou, cf. restrictiilor din briefing).
- Mediu real (Oracle `MARIUSM_AUTO`, clase/proceduri ROAFACTURARE) se initializeaza corect si
ajunge pana in mijlocul fluxului de scriere pentru PRIMUL document (AVIZ, tip=22):
`crsarticole` populat real (1 rand), `crsfactura` populat real prin `do_adauga_tot()`,
`frm_alte_date` detectat corect de driver.
- Toate cele 6 probleme de mai sus (`Note pe parcurs`) sunt REZOLVATE si verificate ca depasite
(log-ul avanseaza pana la linia 16 de fiecare data, deterministic).
**Ce NU e facut / blocajul curent**:
- `driverAlteDate8.Executa` apeleaza `loForm.do_termin()` pe `FRM_ALTE_DATE` (gasit prin
`_SCREEN.Forms`, din Timer) si procesul **nu mai avanseaza deloc** dupa acel apel — fara eroare
prinsa (nici de `TRY/CATCH` din driver, nici de `ON ERROR` global), ceea ce sugereaza un
**crash dur al motorului VFP** (access violation sau similar), nu o eroare VFP normala. Ipoteza
cea mai probabila: apelarea `.do_termin()` DIRECT pe un formular aflat inca in interiorul
propriului `.Show(1)` (modal, apelat sincron din `frm_facturare_articole.inainte_de_do_termin`,
`ofacturare.vc2:14935`), din interiorul unui callback de `Timer` legat pe `_SCREEN`, e mai fragil
pentru `frm_alte_date` decat a fost pentru `frm_articol_factura` in precedentul
`test_pret_cu_tva_nivel2.prg` (acolo a functionat cu acelasi tipar exact). Posibile cauze de
investigat, in ordinea propusa:
1. `frm_alte_date` ar putea avea un `Release`/`Hide` in `do_termin` sau in gard-ul lui
(`ferestre_cere_date.vc2:3044-3103`, deja citit) care intra in conflict cu contextul de apel
din Timer — de comparat linie cu linie cu `_frmbase.do_termin` (neexaminat inca in aceasta
runda) ca sa se inteleaga EXACT ce face `do_termin` generic (posibil `This.Hide()` +
`Thisform.Release()` pe un `Thisform` care in acel moment NU mai e valid din perspectiva
stivei de apel Timer).
2. Incearca sa gaseasca un buton real (`but_termin1` sau similar) in interiorul lui
`frm_alte_date` si sa apeleze `.Click()` pe el in loc de `.do_termin()` direct — mai aproape de
ce ar face un operator, posibil mai stabil (nu s-a gasit inca numele exact al butonului in
aceasta runda — `grep but_termin` pe clasa n-a dat rezultate in intervalul cautat, de reluat cu
`vfp_symbols.ps1 -Class frm_alte_date` pentru lista completa de metode/controale).
3. Verifica daca boxarea `TRY/CATCH` din `driverAlteDate8.Executa` chiar prinde un access
violation (de regula NU — un AV omoara procesul indiferent de `TRY` VFP) — daca da, solutia nu
e mai mult `TRY`, ci evitarea completa a apelului direct de metoda pe un formular modal activ;
alternativa: `PostMessage` catre handle-ul ferestrei cu un mesaj de „Enter”/„buton implicit”
(permis explicit de reguli, spre deosebire de `SendInput`), ca sa se comporte ca un click real
fara reintrare in stiva VFP.
4. Ruleaza harness-ul o data cu `_SCREEN.Visible=.T.` FARA driver deloc, doar ca sa se vada daca
`frm_alte_date` apare normal pe ecran si daca poate fi inchis manual din log (confirma ca
restul lantului pana acolo e sanatos) — util ca test de izolare, nu ca solutie finala (fara
input real ramane interzis).
**Comanda de reluare** (sterge `.fxp` vechi intai):
```powershell
$fxp = "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\creeaza_documente_s8.fxp"
if (Test-Path $fxp) { Remove-Item $fxp -Force }
$log = "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\creeaza_documente_s8_log.txt"
if (Test-Path $log) { Remove-Item $log -Force }
Start-Process 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe' -ArgumentList '-A','-T','"D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg"'
```
**ATENTIE**: `Start-Process ... -PassThru; $p.WaitForExit(...)` NU e de incredere pe aceasta masina
(vezi nota 6 de mai sus) — verifica progresul prin `Get-Content` pe log, in bucla cu `sleep`
(Monitor/Bash, nu PowerShell `Start-Sleep` lung), niciodata prin PID/`Responding`. Nu folosi
`Stop-Process -Name vfp9` — alti agenti pot avea procese `vfp9.exe` proprii concurente pe aceasta
masina; omoara DOAR PID-uri pentru care exista dovada tare (pornite chiar de comanda curenta, deloc
ambiguu).
**Fisiere atinse**: doar `creeaza_documente_s8.prg` (nou) si acest raport. Zero cod de productie.
Zero commit. Scripturile `.sql` de verificare sunt in scratchpad, exploratorii, nu fac parte din
livrare.

View File

@@ -1,118 +0,0 @@
# S8 — Plan de creare a documentelor lipsa (aviz, factura din aviz, factura din contract), pe fluxul REAL
Continua `rec_s8_inventar.md` (verdict: in dev, luna curenta, exista un singur candidat — 1048,
lista de preturi; zero comanda/contract/aviz). Scop: **creeaza** in `MARIUSM_AUTO` documentele
lipsa, prin fluxul real de emitere, nu prin `INSERT`.
## Intrarea reala, dovedita pe cod
`Procedure factureaza`, `D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg:81-560` — punctul unic
de emitere folosit de aplicatie pentru orice tip de document (`tnTip`). Mapare relevanta (comentarii
`:117-167`):
- `tnTip=1` — factura pe lista de preturi (deja acoperit, id_vanzare=1048)
- `tnTip=22` — AVIZ catre clienti din lista de preturi (`nIdTipDoc=6`, AVIZ) — **de creat**
- `tnTip=2`/`6` — factura pe contract — **de creat**
- `tnTip=4` — factura din avize — **de creat, dupa tnTip=22**
Secventa (`ofacturare.prg`):
1. `poDate = Createobject("oDateFactura", lnIdSet, tnTip)` (:184)
2. dialogul de antet: `frm_date_aviz` (tip>=21 sau 30) sau `frm_date_factura` (tip<21 sau
45/48/49/51/52) — `:217-235`, populeaza `poDate.*` din campuri legate direct
(`ControlSource="poDate.xxx"`), apoi `.Show()`
3. cursorul de articole sursa, ales dupa `tnTip` (`:266-308`):
- `cursor_preturi` pt. 1/5/7/10/22/23/29 (`:279-282`)
- `cursor_contract` pt. 2/6/26/52 (`:283-291`)
- `cursor_avize` pt. 4 (`:294-295`, `poDate.listaid` = id_vanzare-urile avizelor sursa)
4. `creeaza_facturacrs('crsfactura')` (:338), apoi `frm_facturare_articole` (sau
`frm_avizare_lucrare` pt. 27) — `.Show()` la `:477`
5. Scrierea reala in Oracle se intampla **in interiorul** acestui `Show()`, prin
`frm_facturare_articole.do_scrie_factura` (`ofacturare.vc2:14197-14554`), apelat din
`inainte_de_do_termin` (`:14806-14974`, apelul e la `:14947`) — cheama `PACK_FACTURARE`.
## Tehnica: ocolire dialoguri de antet prin setare directa `poDate`, NU prin INSERT
`poDate` e un obiect simplu (`oDateFactura`), ale carui proprietati sunt legate 1:1 de
`ControlSource`-urile dialogului (`frm_date_aviz`/`frm_date_factura`) — a le seta direct din script
produce STAREA IDENTICA cu ce ar produce operatorul prin UI. Scrierea reala ramane 100% in codul de
productie (`PACK_FACTURARE`, `do_scrie_factura`), neatins. Precedent deja acceptat de proiect:
`test_s5_al_doilea_intrare.prg` (bypass `frm_modific2024`, pastreaza `ScrieArticoleFacturaEditate`
reala) si `test_pret_cu_tva_nivel2.prg` (seteaza `poDate.tip=1` direct, fara meniu).
Campurile obligatorii, dovedite din garda `frm_date_factura.inainte_de_do_termin`
(`ofacturare.vc2:9455-9561`) — **frm_date_aviz are propria garda, de citit separat inainte de
implementare, posibil cu cerinte suplimentare** (nu verificat inca in aceasta runda):
`dataireg`, `dataact` (luna curenta), `datascad>=dataact`, `id_fdoc` (orice rand nesters din
`NOM_FDOC`; nu se salveaza pe `VANZARI`, doar validare UI), `nract` (obligatoriu prin
`poGeneratorNumere.creeaza_cursor_serii()`+`verifica_numar()`, NU inventat), `id_client` (=`id_part`
valid), `listaid` (gol interzis pt. tip 2/6/52/3/4/45; liber pt. 1/5/7/8/9/10/22/48/49).
## Al doilea dialog modal, inevitabil: `frm_alte_date`
`frm_facturare_articole.inainte_de_do_termin` deschide necontitionat `frm_alte_date` (Show(1)) pt.
orice `poDate.tip<>30` (`ofacturare.vc2:14921-14935`), INAINTE de `do_scrie_factura`. Garda lui
(`ferestre_cere_date.vc2:3044-3103`) cere, cand `gcNumeProgram=[ROAFACTURARE]` si nu e
proforma/bonfiscal: `poDate.id_delegat` nenul si `poDate.dataora_exp` nenul (acesta din urma deja
setat de `factureaza()`-echivalent la `poDate.dataora_exp = Get_Ora()`, `:14921` — de reprodus).
**Tehnica de condus acest modal**: driverul deja dovedit din
`COMUN\utile\Teste\facturare_pret_cu_tva\test_pret_cu_tva_nivel2.prg:539-630`
(`driverdeblocare3`, `Timer` legat pe `_SCREEN`, cauta formularul nou aparut prin `_SCREEN.Forms`
dupa `.Class`) — de extins cu un caz nou pentru `FRM_ALTE_DATE` (apel `.do_termin()` direct pe
obiectul gasit, dupa ce `poDate.id_delegat` a fost presetat).
## Ocolirea celui de-al treilea modal (`frm_articol_factura`, per linie)
`frm_facturare_articole.do_adauga_tot` (`:13169-13198`) cheama `do_adauga_articol(.T.)` pt. fiecare
rand din `crsarticole` — cu `tlImplicit=.T.`, `do_adauga_articol` (`:12813-12880`) SARE peste
`Show(1)` cand `poArticol.gestionabil=0 OR gnScadereStoc=0 OR poDate.tip=45`
(`:12871`). **Cel mai simplu**: seteaza global `gnScadereStoc = 0` in harness (deja folosit asa in
`test_pret_cu_tva_nivel2.prg:177`) — orice articol trece fara dialog, fara sa cauti unul anume
non-gestionabil.
## Instantierea `frm_facturare_articole`: modeless, apel direct de metode
Ca in `test_pret_cu_tva_nivel2.prg:266-272`: `CREATEOBJECT('frm_facturare_articole')`,
`WindowType=0`, `.Show()` (nu blocheaza), apoi apel DIRECT `goFrm.do_adauga_tot()`,
`goFrm.do_calculeaza_totaluri()`, `goFrm.do_termin()` — cu driverul de mai sus deja armat inainte de
`do_termin()`, ca sa prinda `frm_alte_date` cand apare.
## Date de referinta confirmate in `MARIUSM_AUTO` (10.08.2026, doar SELECT)
- Delegat valid: `id_delegat=256` (folosit real pe `id_vanzare=1048`).
- `id_fdoc` valid: orice din `NOM_FDOC` cu `sters=0`, ex. `5` (`AVIZ EXP`).
- Client: `id_part=463` (RAJA) — folosit deja de 1048; sau alt partener existent, la alegere.
- `id_gestiune`: NULL pe toate documentele tip 1/22 existente — NU se seteaza pentru lista de
preturi/aviz din lista.
- **Contract pentru tip=2**: `id_ctr=235` (`opt_facturare=1`, rata/scadentar, `id_part=598`,
`id_nota=5`) — acelasi tipar ca documentul real deja existent `id_vanzare=1039`/`1040` (tip=2,
30.06.2026, aceeasi schema). Ruta trece prin `contabilizeaza_rata` (vezi
`docs\cercetare\idpol_comanda_contract.md`, sectiunea 0c), nu prin articole de nomenclator —
e un document real, de productie, nu o fabricatie. **Evita `id_ctr=222`**: are 3 randuri
`CTR_ARTICOLE`, unul cu `id_pol_art` NULL, risc de FACT-024 daca acel rand ajunge selectat.
- Politica de pret pt. lista de preturi/aviz: `id_pol=1` are articole (ex. `id_articol` 1-5).
## Ordinea de executie recomandata
1. `factureaza(22, .NULL.)`-echivalent -> creeaza AVIZUL (tip=22). Verifica in Oracle
(`VANZARI`+`VANZARI_DETALII`+`ACT`+`RUL`, `id_fact` alocat).
2. Citeste `id_vanzare` al avizului nou creat -> `poDate.listaid = <acel id>` pentru
`factureaza(4, .NULL.)`-echivalent -> FACTURA DIN AVIZ. Verifica la fel.
3. `factureaza(2, .NULL.)`-echivalent cu `poDate.listaid = '235'` -> FACTURA DIN CONTRACT (rata).
Verifica la fel; noteaza explicit ca liniile sunt de tip RATA (fara `id_articol`), nu articole
de nomenclator — mentioneaza asta in raportul final, nu ascunde.
## Ce NU e verificat inca (de facut in implementare, nu presupus)
- Garda proprie a lui `frm_date_aviz` (separata de `frm_date_factura.inainte_de_do_termin` citita
mai sus) — posibil cere campuri suplimentare (ex. gestiune destinatie pt. aviz). De citit clasa
`frm_date_aviz` (`ofacturare.vc2:6566-8203`) inainte de a scrie harnessul.
- Semnatura exacta `oDateFactura(lnIdSet, tnTip)` si `poGeneratorNumere.creeaza_cursor_serii`/
`verifica_numar` — de citit in `ofacturare_comun.vc2`/`oserii_numere.prg` inainte de implementare.
- Daca `do_scrie_factura` mai cere alte proprietati `poDate` netestate aici (ex. `id_agent`,
`proc_tva`) — de citit `:14197-14554` complet inainte de rulare.
## Reguli neschimbate (din briefing-ul initial)
Doar `MARIUSM_AUTO`; nu se ating `1048`/`1049`/`1050`; zero editare de cod/pachete; zero commit;
zero input real (`keybd_event`/`SendInput`); verificare finala: zero `vfp9.exe`, zero tranzactii
deschise.

View File

@@ -1,168 +0,0 @@
# S8 — matricea pe tipuri de sursa, cele 4 documente
Test: `COMUN\utile\Teste\editare_factura\test_s8_matrice_surse.prg`, log:
`COMUN\utile\Teste\editare_factura\test_s8_matrice_surse_log.txt` (rescris la fiecare rulare -
cifrele de mai jos sunt citite din log **imediat dupa fiecare rulare**, inainte de a activa
urmatorul document, si verificate independent prin `sqlplus`).
Matricea completa (4 documente) a fost rulata, cate unul pe rand, activat prin `gaCazuriActive` in
`test_s8_matrice_surse.prg`. Codul (`EditeazaDinRoafacturare`/`EditeazaDinRegistruJurnal`/
`VerificaDupaEditare`/`CalculeazaTotaluriS4b`) e neschimbat intre rulari - parametrizarea prin
`id_vanzare` a functionat neschimbata pe toate cele 4 tipuri.
## Sinteza cifrelor `REZULTAT` (autoritare, din log)
| Document | Tip sursa | REZULTAT | FAIL-uri | Cauza FAIL-urilor |
|---|---|---|---|---|
| 1048 | lista de preturi (tip 1) | **44 PASS / 0 FAIL** | - | - |
| 1055 | factura din contract (tip 2) | **44 PASS / 0 FAIL** | - | - |
| 1052 | aviz (tip 22) | **26 PASS / 1 FAIL** | 1 | garda `ReferinteDocumenteNota` blocheaza corect intrarea ROAFACTURARE (constatare, nu defect de test - vezi mai jos) |
| 1054 | factura din aviz (tip 4) | **42 PASS / 2 FAIL** | 2 | `Reccount(trul)=0` pe ambele intrari - normal pentru tip 4 (vezi mai jos), nu defect |
Niciun `EROARE` in niciunul din cele 4 loguri.
## Document 1048 — lista de preturi (tip 1)
Deja raportat integral in versiunea anterioara a acestui document (ambele intrari au reusit complet,
0 anomalii). Stare finala neschimbata de rularile ulterioare pe celelalte documente: `cod=1140918`,
`id_fact=8009658` (neschimbat), totaluri `250.01/52.50/302.51` (neschimbate), 1 linie activa.
## Document 1055 — factura din contract (tip 2)
Ambele intrari au reusit complet (`COMMIT`), fara nicio anomalie in lantul de scriere.
| | inainte | dupa ROAFACTURARE | dupa registrul jurnal |
|---|---|---|---|
| `cod` | 1140911 | 1140919 | **1140920** |
| `id_fact` | 8009680 | 8009680 | 8009680 (neschimbat) |
| totaluri | 200.00 / 42.00 / 242.00 | neschimbate | neschimbate |
| linii active | 2 | 2 | 2 |
`vact_tot`: cod 1140911 si 1140919 (notele vechi) - toate randurile `STERS=1`; cod 1140920 (nota
curenta) - toate randurile `STERS=0`.
**Constatare (nu defect)**: verdictul S4b e **divergent** pe tot parcursul - `ACT=242`, `RUL=121`
(exact jumatate), neschimbat de cele doua editari (`121 -> 121` pe ambele treceri). Verdictul e
etichetat explicit "informativ, nu e eroare" de catre `frm_modific2024` insusi (decizia din S4b:
formularul arata divergenta, nu o blocheaza). Asertiile testului nu presupun `ACT=RUL` - verifica
doar ca fiecare total ramane **concordant fata de inainte de editare**, ceea ce s-a confirmat.
## Document 1052 — aviz (tip 22)
**Nu e factura** - constatare confirmata, exact cum a semnalat briefingul.
### Intrarea ROAFACTURARE (`do_editare_factura`): BLOCATA de garda, corect
```
FAIL ... document fara referinte / netrimis in eFactura [.T.]
```
`ReferinteDocumenteNota(2026, 8, 1140908)` a intors adevarat - verificat direct in Oracle:
`ACT.id_factc = 8009677` (id_fact-ul avizului 1052) exista pe cod=1140910, care e nota lui **1054**
("factura din aviz", emisa din acest aviz). Garda functioneaza exact cum trebuie: **blocheaza
editarea unui document sursa care are deja o factura emisa din el** - nicio scriere nu s-a produs
(verificat: `cod` a ramas `1140908` neschimbat pana la intrarea urmatoare). `EsteInEFactura` nu a
contribuit (`anaf_efactura` nu are randuri pentru `id_fact=8009677`).
Aceasta e o `FAIL` de asertie **asteptata si corecta** - testul a presupus (mostenit din modelul
S5, gandit pentru facturi) ca documentul e editabil; pentru un aviz cu factura deja emisa din el,
nu e. Marcata ca atare, nu ca defect.
### Intrarea registru jurnal ROACONT (`do_modifica`): A REUSIT COMPLET, fara aceeasi garda
```
PASS ... blocul ScrieArticoleFacturaEditate s-a executat pe aceasta cale (garda satisfacuta) [.T.]
PASS ... lantul complet a reusit (COMMIT) [lnSucces=1]
```
**Constatare reala, de raportat lui Marius**: `afisjurcom.do_modifica` (`comun.vc2:2222-2569`) **nu
are garda `ReferinteDocumenteNota`/`EsteInEFactura`** in secventa reprodusa (confirmat deja indirect
de `test_s5_al_doilea_intrare.prg`, dar niciodata exercitata pana acum pe un document care CHIAR are
o referinta reala). Rezultat: editarea prin registrul jurnal a trecut pana la `COMMIT` pe un
document (1052) pe care intrarea ROAFACTURARE l-a blocat explicit din acelasi motiv.
Pe aceasta rulare **fara** consecinta vizibila (editarea nu a modificat nicio linie - doar a
realocat `cod`-ul si a refacut nota; documentul 1054, care il refera, a fost verificat neschimbat:
`cod=1140910`, `id_fact=8009679`, totaluri `252.07/52.93/305.00`, toate identice cu inainte).
Legatura structurala ramane valida pentru ca trece prin `id_fact`/`id_vanzare`, niciodata prin `cod`
(S6, deja inchis). Dar daca editarea prin ROACONT ar fi inclus si o modificare de continut pe un
document cu referinte reale, nimic nu ar fi oprit-o - asimetria intre cele doua puncte de intrare e
reala, nu doar teoretica. **Nu s-a atins `comun.vc2`/`omodificari.vc2` pentru a o corecta** (livrare
inchisa) - se raporteaza pentru decizia lui Marius.
| | inainte | dupa ROAFACTURARE | dupa registrul jurnal |
|---|---|---|---|
| `cod` | 1140908 | *(blocat, nescris)* | **1140921** |
| `id_fact` | 8009677 | - | 8009677 (neschimbat) |
| totaluri | 271.17 / 56.94 / 328.11 | - | neschimbate |
| linii active | 2 | - | 2 |
`vact_tot`: cod 1140908 (nota veche) - toate randurile `STERS=1`; cod 1140921 (nota curenta) -
toate randurile `STERS=0`. S4b: `ACT=RUL=328.11`, verdict sincronizat, neschimbat de editare.
## Document 1054 — factura din aviz (tip 4)
Ambele intrari au reusit complet (`COMMIT`), singurele 2 `FAIL` din aceasta rulare sunt pe
`Reccount(trul)>0`.
**Stabilit inainte de editare, nu ghicit**: interogare pe toate cele 6 documente `tip=4` din baza
(`145, 665, 668, 697, 974, 1054`) - **toate** au exact 2 randuri `ACT` si **0** randuri `RUL`,
indiferent de `total_cu_tva`. Tiparul e 100% consistent pe populatia completa de documente tip 4,
nu doar pe 1054 - **normal pentru tip 4**, nu o particularitate a acestui document. Explicatia
structurala plauzibila: miscarea de stoc s-a inregistrat deja la emiterea avizului sursa; factura
emisa din aviz nu mai genereaza randuri `RUL` proprii (ar dubla miscarea), doar nota contabila
(`ACT`). Cele doua `FAIL` (`Reccount(trul)>0` cerut de asertia generica, `Reccount(trul)=0` gasit)
sunt **asteptate si corecte pentru acest tip de document** - verificarea "rulaje refacute" nu e
concludenta pentru tip 4 (nu exista rulaje de refacut), nu semnaleaza un defect.
Restul verificarilor (nota veche/noua, `id_fact`, totaluri denormalizate, S4b ACT concordant,
stoc) au trecut integral pe ambele intrari.
| | inainte | dupa ROAFACTURARE | dupa registrul jurnal |
|---|---|---|---|
| `cod` | 1140910 | 1140922 | **1140923** |
| `id_fact` | 8009679 | 8009679 | 8009679 (neschimbat) |
| totaluri | 252.07 / 52.93 / 305.00 | neschimbate | neschimbate |
| linii active | 1 | 1 | 1 |
`vact_tot`: cod 1140910 si 1140922 (notele vechi) - toate randurile `STERS=1`; cod 1140923 (nota
curenta) - toate randurile `STERS=0`. S4b: `ACT=305`, `RUL=0`, verdict divergent (informativ),
neschimbat de editare (`305->305`, `0->0`).
## Verdict explicit pe cele 8 verificari cerute de plan (`plan_06_editare_factura.md:286-288`)
| # | Verificare | Verdict pe matrice |
|---|---|---|
| 1 | nota veche `STERS=1` | **PASS** pe toate cele 7 scrieri reusite (1048x2, 1055x2, 1052x1 - ROACONT, 1054x2). N/A pe 1052/ROAFACTURARE (blocat inainte de scriere, nu s-a creat nicio nota noua). |
| 2 | nota noua corecta (exista, activa, acelasi `id_fact`) | **PASS** pe toate cele 7 scrieri reusite. |
| 3 | `id_fact` neschimbat | **PASS** pe toate cele 7 - 8009658, 8009680, 8009677, 8009679 identice inainte/dupa. |
| 4 | `VANZARI`/`VANZARI_DETALII` sincronizate | **PASS** pe toate cele 7 - numar de linii active neschimbat, totaluri coerente (`ftva+tva=ctva`). |
| 5 | totalurile denormalizate corecte | **PASS** pe toate cele 7 - identice cu inainte de editare (reeditare fara modificari de continut). |
| 6 | rulajele refacute | **PASS** pe 1048 (x2), 1055 (x2), 1052 (x1, ROACONT). **FAIL asteptat** pe 1054 (x2) - `RUL=0` e normal pentru tip 4 (confirmat pe toate cele 6 documente tip 4 din baza), verificarea nu e concludenta pentru acest tip. |
| 7 | totalurile de control S4b concordante | **PASS** pe toate cele 7 - `Total ACT` si `Total RUL` raman fiecare **neschimbate fata de inainte de editare**, indiferent daca verdictul absolut e sincronizat (1048, 1052) sau divergent (1055, 1054 - divergenta insasi e preexistenta editarii, nu cauzata de ea). |
| 8 | verificarile de stoc de la emitere NU s-au declansat | **PASS** pe toate cele 7 - `gcMockUltimMesaj` ramas gol dupa fiecare lant de scriere; structural, `verifica_stoc` (`oscrie_in_fisiere.prg:92`) nu se poate declansa pentru ca ambele puncte de intrare trec `tlModificare=.T.`. |
**Al treilea caz obligatoriu** (document care nu e factura, deschis din registru jurnal, fara
pagina de articole) - deja acoperit separat, per handoff (`docs\handoff_punct6_10082026_pm.md:113`).
## Constatare de raportat separat (nu de reparat aici)
> **INCHISA — decizia lui Marius, 12.08.2026: asimetria se accepta, nu se repara.** Nu se atinge nici
> `comun.vc2`, nici `omodificari.vc2`. Sectiunea de mai jos ramane ca inventar al constatarii, **nu mai
> e o intrebare deschisa** — nu o redeschide. Motivele, in `docs\progres.md`.
**Asimetria de garda intre punctele de intrare** (sectiunea document 1052 de mai sus): intrarea
ROAFACTURARE (`ofacturare_comun.vc2`, `do_editare_factura`) verifica `ReferinteDocumenteNota`/
`EsteInEFactura` inainte de a permite editarea; intrarea registru jurnal ROACONT
(`comun.vc2`, `afisjurcom.do_modifica`) nu are aceasta garda in secventa reprodusa si a scris pana
la capat pe acelasi document pe care ROAFACTURARE l-a blocat. Nicio consecinta vizibila pe aceasta
rulare (editare fara modificari de continut, documentul care refera - 1054 - verificat neschimbat),
dar protectia lipseste structural pe calea ROACONT. `omodificari.vc2`/`comun.vc2` **nu au fost
atinse** (livrari inchise) - decizia (adaugarea gardei si pe ROACONT, sau acceptarea asimetriei) ii
revine lui Marius.
## Ramas de facut
Toate cele 4 documente din matrice au fost editate de doua ori si verificate. Nimic ramas pe
felia S8 in sine. Documentele consumate (coduri realocate, note vechi sterse ireversibil):
1048 (-> 1140918), 1052 (-> 1140921), 1054 (-> 1140923), 1055 (-> 1140920).

View File

@@ -1,143 +0,0 @@
# Cercetare: rulaje (RUL) pe factura emisă din aviz (tip = 4) — `test_s8_matrice_surse.prg:308`
## Verdict
**Asserția e greșită pentru tip = 4.** Tabela `RUL` reține exclusiv mișcări pe conturi de stoc
(`371` în datele verificate); o factură emisă din aviz nu atinge niciodată contul de stoc — ea doar
transformă creanța provizorie (`418`) în creanță fermă (`4111`) și TVA neexigibilă în TVA colectată
(`4428`→`4428`) — pentru că marfa a ieșit deja din gestiune la avizul-sursă (tip 22), care e cel ce
scrie rândurile de `RUL`. `Reccount(trul) = 0` pe un document de tip 4 e starea corectă, nu un semn
că editarea a distrus rulaje.
---
## Ce am verificat la sursă (cod + date Oracle)
### 1. De unde se umple `trul`
`IncarcaCursoareModificareNota` (`COMUN\programe\ofacturare_editare.prg:35-148`) încarcă `trul`
direct din view-ul `vrul_tot`, filtrat pe `an`/`luna`/`cod`, fără nicio sinteză sau completare:
```
COMUN\programe\ofacturare_editare.prg:76
lcSql = [select * from vrul_tot where ] + lcConditieSters + [ an = ] + Transform(m.tnAn) + [ and luna = ] + Transform(m.tnLuna) + [ and cod = ] + Alltrim(Str(m.tnCod)) + [ order by id_rul]
```
`Reccount(trul)` e deci exact numărul de rânduri deja existente în `RUL` (server) pentru acel `cod`
— nu ceva calculat sau garantat nenul de vreo regulă de business în client.
### 2. Cine scrie rulajele — și diferența pe tip de document
Scrierea efectivă e server-side, în Oracle, `PACK_CONTAFIN.SCRIE_IN_RUL` (`COMUN\docs\PACK_CONTAFIN.pck:1896-2019`):
face `INSERT INTO RUL (...) SELECT ... FROM RUL_TEMP` — deci **scrie exact ce a fost pus în
`RUL_TEMP` de partea VFP**, fără vreo ramură condiționată de tipul documentului. Tot ce contează e
dacă `RUL_TEMP` (populat din `trul`, deci din `vrul_tot` deja existent) are rânduri.
Pe partea VFP, calea de editare a facturii (cea exercitată de test, prin `frm_facturi.do_editare_factura`,
`COMUN\clase\ofacturare_comun.vc2:3799-3821`) copiază necondiționat `trul` înapoi în `RUL_TEMP` și
apelează `OSCRIE_IN_FISIERE(0,.T.,.T.)` — al treilea parametru (echivalentul lui `llRul` din editorul
generic) e **hardcodat `.T.`**, nu depinde de starea inițială:
```
COMUN\clase\ofacturare_comun.vc2:3811-3821
If Used('rul_temp')
Use In rul_temp
Endif
Select * From trul Into Cursor RUL_TEMP Readwrite
Replace All id_util With gnIdUtil, sters With 0
...
lnSucces = OSCRIE_IN_FISIERE(0,.T.,.T.)
```
Deci: dacă `trul` a fost gol la încărcare (pasul 1), rămâne gol și la scriere — codul **conservă**
starea, nu o corectează și nu o strică. (Contrast: editorul generic de jurnal contabil,
`afisjurcom.do_modifica`, `COMUN\clase\comun.vc2:2370-2482`, are un flag explicit `llRul` setat doar
dacă `Reccount(lcCursor)>0` la încărcarea inițială din `vrul_tot` — dar calea de factură nu folosește
acest flag deloc, e mereu `.T.`, ceea ce confirmă din nou că absența rândurilor din `RUL` nu e
tratată ca eroare de nicio ramură a codului.)
Originea rândurilor din `RUL` e deci strict la **crearea/finalizarea notei**, nu la editare —
`PACK_CONTAFIN.finalizeaza_modificare_nota` (apelat și la creare, și la editare) doar re-scrie ce
primește; nu am găsit nicio ramură condiționată de `tip`/`ntip` care să decidă "acest tip de
document trebuie să aibă rulaje" — decizia e implicită, dată de **ce conturi apar în notă**, nu de
tipul documentului.
### 3. Rolul lui `IN_STOC` în `ActualizeazaVerdictActRul`
`COMUN\clase\omodificari.vc2:13045-13146`. Verdictul ACT/RUL are trei stări, toate **informative,
niciodată eroare**:
```
COMUN\clase\omodificari.vc2:13129-13139
DO CASE
CASE !m.llVerdictSigur
lcVerdict = 'Verdict ACT/RUL: nu se aplica (informativ, date de import)'
CASE Abs(This.nTotalActRon - This.nTotalRulRon) <= 0.02
lcVerdict = 'Verdict ACT/RUL: sincronizat (informativ)'
OTHERWISE
lcVerdict = 'Verdict ACT/RUL: divergent (informativ, nu e eroare)'
ENDCASE
```
`in_stoc` corectează doar **suma** comparată (exclude valoarea liniilor nestocate din articolele
facturii, convertită în RON, din totalul RUL — `tvd` unde `Nvl(in_stoc,1) = 0`, linia 13111), nu
decide dacă documentul "trebuie" să aibă rulaje. Nu există în această metodă (verificat integral,
liniile 13045-13146, nu doar grep) nicio ramură specifică pentru `RUL = 0` pe tip 4 — cazul cade pur
și simplu în "divergent (informativ, nu e eroare)" dacă `nTotalActRon <> 0` și `nTotalRulRon = 0`, ceea
ce confirmă că design-ul acceptă explicit acest caz ca non-eroare.
### 4. Verificare pe date, read-only, Oracle `MARIUSM_AUTO`/`ROA_CENTRAL`
Conectat cu succes (`sqlplus.exe`, `SET TRANSACTION READ ONLY` ... `ROLLBACK`, fișier `.sql` ASCII,
fără pipe). Cod-urile curente (verificate din `VANZARI`, nu presupuse):
| id_vanzare | tip | cod (curent) | sters | RUL (cnt activ) | ACT (cnt activ) |
|---|---|---|---|---|---|
| 1048 | 1 | 1140918 | 0 | 1 | 5 |
| 1052 | 22 (aviz) | 1140921 | 0 | 4 | 12 |
| 1054 | 4 (factură din aviz) | **1140923** | 0 | **0** | 2 |
| 1055 | 2 | 1140920 | 0 | 1 | 7 |
`id_vanzare = 1054` are `cod` curent `1140923` (confirmă realocarea din test:
`1140910 -> 1140922 -> 1140923`, citită direct din `VANZARI`, nu presupusă).
Detaliu decisiv — conturile efective din `ACT`/`RUL` pentru perechea aviz→factură (avizul 1052 e cel
mai probabil sursă a facturii 1054, pe baza secvenței de cod-uri și a fluxului tip 22→tip 4):
```
ACT pe avizul 1052 (tip 22): scd/scc includ 607/371, 371/378, 378/371, 4428/371, 371/4428, 418/704, 418/4428
RUL pe avizul 1052 (tip 22): 4 rânduri, toate CONT = 371 (cont de stoc)
ACT pe factura 1054 (tip 4): scd/scc = 4111/418 (305 lei) și 4428/4428 (52.93 lei) -- NICIUN cont 371
RUL pe factura 1054 (tip 4): 0 rânduri
```
Avizul e cel care mișcă efectiv contul de stoc (`371`) și de aceea are rânduri în `RUL` (care
urmărește exclusiv cantități pe conturi de stoc — vezi și `cant`/`cante` din `trul`, folosite ca
atare în `ActualizeazaVerdictActRul:13102`). Factura emisă din acel aviz nu mai atinge `371` deloc —
transformă doar creanța provizorie (`418`) în creanță fermă (`4111`) și TVA neexigibilă (`4428`) în
TVA colectată (`4428`). N-are, structural, ce rând de stoc să scrie.
Zero tranzacții deschise la final (`ROLLBACK` executat, `SET TRANSACTION READ ONLY` respectat pe tot
scriptul).
## Ce am dedus (nu verificat direct, dar consistent cu toate dovezile de mai sus)
- Regula generală: `RUL` se scrie doar pentru documentele/notele care conțin mișcare pe cont de
stoc (aviz, NIR, bonuri de consum etc.); facturile "de închidere" (emise din aviz, sau orice
document care doar transformă o creanță provizorie într-una fermă) nu vor avea niciodată rânduri
în `RUL`, indiferent câte documente de acest fel verifici — nu e o particularitate a lui
`id_vanzare = 1054`, ci a tipului de operațiune contabilă.
- Aserția de la `test_s8_matrice_surse.prg:308` (`Reccount(trul) > 0` obligatoriu după editare) e
probabil corectă ca test general "rulajele nu trebuie distruse de editare" pentru documentele care
AU avut rulaje înainte de editare, dar e o presupunere greșită aplicată universal — pentru tip 4
(și, prin extensie, orice tip de document care nu atinge cont de stoc) condiția trebuie relaxată
la "dacă documentul avea rulaje înainte, tot le are și după" sau pur și simplu exclusă pentru
tip = 4.
## Fișiere atinse
Niciunul (misiune read-only). Fișiere citite: `COMUN\programe\ofacturare_editare.prg`,
`COMUN\clase\omodificari.vc2`, `COMUN\clase\comun.vc2`, `COMUN\clase\ofacturare_comun.vc2`,
`COMUN\docs\PACK_CONTAFIN.pck`. Interogări Oracle read-only pe `MARIUSM_AUTO`@`ROA_CENTRAL`, cu
`ROLLBACK` la final.

View File

@@ -1,78 +0,0 @@
# S9 — documentatia fluxului de editare a facturii, adaugata in flux-modificare-stergere-nota-jurnal.md
Fisier editat: `D:\ROA\ROAGEST\COMUN\docs\flux-modificare-stergere-nota-jurnal.md`.
Diff salvat: diff aplicat (sters).
**Nimic comis** (nici git, nici SVN) - `COMUN` e dublu-versionat SVN+git, `git stash` acolo e
interzis; nu s-a folosit.
## Ce s-a adaugat si unde
Doua sectiuni noi, dupa `## Atentie: nu e valabil la fel pentru stergerea simpla` si inainte de
`## Implicatii practice`:
1. **`## Ramura noua: editare factura de vanzare (VANZARI/VANZARI_DETALII)`** - conditia de
activare, cele doua puncte de intrare cu garzile lor, secventa de scriere in tranzactia manuala,
contractul lui `ScrieArticoleFacturaEditate`, procedura Oracle de recalcul, view-ul sursa, si
comportamentul gridului de articole (editabilitate per linie, stergere logica, validari la
inchidere).
2. Doua bullet-uri noi in `## Implicatii practice`, in continuarea listei existente.
## Din ce fisiere vine fiecare afirmatie
- **Cele doua puncte de intrare si garzile lor**: `COMUN\clase\ofacturare_comun.vc2:3715-3872`
(`do_editare_factura` - garzi la `:3742-3768`, incarcare articole la `:3793`, agatare la
`:3828-3830`) si `COMUN\clase\comun.vc2:2222-2567` (`do_modifica` - luna curenta `:2265-2268`,
pregatire articole `:2435-2437`, agatare `:2491-2493`). Verificat explicit ca `EsteInEFactura` si
`ReferinteDocumenteNota` **nu** apar in `comun.vc2` (grep pe fisier, zero rezultate) - de-aia
afirmatia ca garda eFactura/referinte exista doar pe punctul din ROAFACTURARE.
- **Inregistrarea `ofacturare_editare.prg` in toate trei aplicatiile**: grep confirmat separat in
`ROAFACTURARE\Programe\roafacturare.prg:214`, `ROACONT\Programe\roacont.prg:212`,
`ROAGEST\Programe\roagest.prg:260` - toate cu `SET PROCEDURE TO ofacturare_editare.prg ADDITIVE`.
- **Helperele**: `COMUN\programe\ofacturare_editare.prg` citit integral. `EsteInEFactura` (`:16-25`),
`PregatesteArticoleFacturaEditare` (`:332-348`), `IncarcaVanzareDinNota`/`IncarcaArticoleFactura`
(`:161-324`), `ScrieArticoleFacturaEditate` (`:468-555`, cei 4 pasi citati din corpul functiei).
- **Formularul**: `COMUN\clase\omodificari.vc2` - `Load` (`:14645-14682`), `Show`
(`:14731-14814`, in special `:14769-14794` pentru decizia `PageCount`), validarile din
`inainte_de_do_termin` (`:14282-14387`), gridul `grdArticoleFactura` (definitia coloanelor
`:12320-...`, caption PAGE3 `:8707`), handlerele `When`/`Valid` pe `cCantitateArt`/`cPretArt`/
`cPretAchizitieArt`/`cPretCuTvaArt` (`:16519-16566`) si `cmdStergeArticol.Click` (`:16508-16517`).
- **Oracle**: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(`recalculeaza_totaluri_vanzari`, `:16023-16228`, citit integral - coloanele scrise, agregarea pe
linii proprii si pe seturi, coloanele de incasare neatinse) si
`...\ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql` (view-ul, citit integral).
## Ce am lasat deliberat afara
- **Totalurile de control / verdictul ACT-RUL** (`ActualizeazaBaraTotaluri`,
`ActualizeazaVerdictActRul`, `omodificari.vc2:12966-13105`) - exista in cod si e parte din S4b,
dar e o functionalitate de afisare/avertizare, nu parte a fluxului de stergere-scriere-realiniere
pe care il documenteaza fisierul tinta. L-am scos ca sa nu ingreunez sectiunea cu un subiect
distinct (planul cere explicit doar "ramura de facturi" pe fluxul de modificare/stergere).
- **Actiunea de sincronizare cu enumerarea liniilor vechi->noi** - nu exista inca in cod (S4b
partial, per handoff intermediar (sters)), n-am documentat ceva nelivrat.
- **Eticheta text "linie din set"** - handoff-ul S5 o mentioneaza, dar in cod (grep pe
`omodificari.vc2`) nu exista un literal cu acest text; marcajul e doar vizual, prin
`DynamicForeColor` (culoare distincta). Am scris "culoare distincta", nu "eticheta", ca sa nu
inventez un text care nu exista.
- **Drepturile (tokenul "3", `lactiv3`)** - documentul tinta nu discuta permisiuni pentru niciun
flux existent (nici stergerea, nici modificarea simpla), asa ca am pastrat consistenta si n-am
adaugat-o nici pentru factura.
- **`nom_lucrari`/`gest_inventar`/`atasamente_vanzari`** din `finalizeaza_modificare_nota` - deja
documentate mai sus in fisier (sectiunea "Unde"), n-am repetat.
## Intrebari deschise
1. **Asimetria de garzi intre cele doua puncte de intrare e by design sau gap de acoperit?**
`do_editare_factura` (ROAFACTURARE) verifica eFactura si referinte de incasari/plati;
`do_modifica` (Registrul Jurnal, comun tuturor notelor) nu le verifica deloc - doar restrictia
generica de luna curenta. Codul confirma asta explicit (am citit ambele metode integral), dar nu
pot spune din documentatie daca e o omisiune ramasa din S1 sau o decizie asumata (planul spune
doar ca "garzile trebuie sa fie in formular sau in codul comun", fara sa specifice care cale
trebuia sa le primeasca). Am documentat faptul, nu l-am calificat drept bug.
2. **Eticheta "linie din set"** mentionata in handoff intermediar (sters) (decizia 39) nu exista ca text in cod
la verificare - posibil ramasa doar la nivel de intentie sau inlocuita de marcajul de culoare in
implementarea finala. N-am putut confirma din git log/istoric fara sa ies din scop; las-o ca
discrepanta cunoscuta intre document de decizie si livrare.
Nu am atins niciun fisier de cod (`.prg`/`.vc2`/`.sc2`/`.sql`) si niciun alt fisier de documentatie
in afara celui cerut.

View File

@@ -1,469 +0,0 @@
# Cercetare: regula corecta pentru "suma comparabila din ACT" (S4b, plan_06 sectiunea C.1)
Raspunde la corectia lui Marius din 08.08.2026 (pagina noua se aplica pe orice rand din VANZARI,
avizele folosesc 418, exista randuri de discount, utilizatorul poate adauga note proprii) SI la
trei completari ulterioare, tot de la Marius: (1) ipoteza ca `id_set+5` la discount e un marcaj
tranzitoriu, (2) surse noi de date reale (`VENDING` productie, `ROMFAST@ROA_ROMFAST`), (3) ipoteza
liniilor de diferenta de pret in `RUL` pentru marfa tinuta la pret de vanzare.
Surse: export `PACK_FACTURARE.pck`/`PACK_CONTAFIN.pck` din `MARIUSM_AUTO@ROA_CENTRAL` (facut
sesiunea anterioara, verificat la zi). Interogari noi in aceasta sesiune pe **trei scheme**,
consemnate explicit la fiecare rezultat: `MARIUSM_AUTO@ROA_CENTRAL` (date de test), `ROMFAST@ROA_ROMFAST`
(client real, conexiune directa fara tunel, `10.0.20.36:1521`, credentiale `ROMFAST/ROMFASTSOFT`,
gasita in `tnsnames.ora` — nu era in `oracle.md`, testata si confirmata functionala) si
`contafin_oracle@VENDING` (productie, tunel `stnlc.exe` pornit headless in aceasta sesiune,
`alter session set current_schema=VENDING`, **strict citiri**, zero DDL/DML/COMMIT). Toate
interogarile sunt `SELECT`.
## 0. Ipoteza `id_set+5` — CONFIRMATA, garda din runda 3 e pe premisa falsa
**Raspuns direct**: Marius are dreptate. `id_set+5` e un marcaj **tranzitoriu**, folosit doar cat
timp randul de discount sta in `ACT_TEMP`, si e **rescris la valoarea de baza inainte sa ajunga in
`ACT`**. Randurile de discount **nu ajung niciodata in `ACT` cu `id_set+5`** — confirmat atat pe
cod cat si pe date, pe trei scheme diferite.
### Pe cod — locul exact care consuma marcajul
`PACK_FACTURARE.scrie_discount` (`:12859-13057`) face `nid_set := nid_set + 5` la intrare
(`:12901`), scrie randul `DISCOUNT`/`TVA DISCOUNT` in `ACT_TEMP` cu acel `id_set`, apoi restaureaza
variabila de pachet la valoarea veche (`:13054`, `pack_facturare.nid_set := V_ID_SET`) — **inainte
sa revina la apelant**. Pana aici, exact ce descria constatarea anterioara (`rec_garda_idset.md`).
Verificat acum: `PACK_CONTAFIN.SCRIE_IN_ACT` (`:630-966`) copiaza `ACT_TEMP -> ACT` printr-un
singur `INSERT INTO ACT (V_LISTA_CAMPURI) SELECT V_LISTA_CAMPURI FROM ACT_TEMP` (`:956-958`) —
copiere directa, coloana cu coloana, **fara nicio transformare a `ID_SET`**. Cautare exhaustiva
"`SET ID_SET`" in ambele pachete — 0 rezultate in afara acestui INSERT. Singurul trigger pe `ACT`
(`TRG_ACT_BEFOINS`) doar aloca `ID_ACT` din secventa, nu atinge `ID_SET`.
**Locul real de normalizare**: `PACK_FACTURARE.cumuleaza_note_act_temp` (`:14112-14322`), apelata
din `cumuleaza_note_act` (`:14073-14110, apel la :14095`), care la randul ei e apelata din
**toate** cele 4 proceduri care scriu nota (`scrie_avize_lucrare:5954`, `scrie_factura2:6191`,
`scrie_factura_avize_retur:7027`, `scrie_aviz_retur:7139` — si `scrie_factura_avize` prin acelasi
tipar), **dupa** ce bucla de linii si apelul de discount de document s-au terminat. In interior,
`cumuleaza_note_act_temp` re-agrega `ACT_TEMP` (SUM pe `SCD`/`SCC`/etc., GROUP BY inclusiv
`A11.ID_SET` — deci grupurile raman distincte in subinterogare), dar **SELECT-ul exterior nu
foloseste `A.ID_SET` din grupare** — il inlocuieste explicit cu:
```sql
pack_facturare.nid_set AS ID_SET -- PACK_FACTURARE.pck:14198
```
Adica STAMPEAZA fiecare rand rezultat cu valoarea CURENTA a variabilei de pachet `nid_set`, care in
acest moment (dupa ce toate apelurile `scrie_discount` — de linie si de document — si-au restaurat
deja valoarea) e valoarea de baza, nu +5. Deci: `id_set+5` serveste DOAR ca sa tina randurile de
discount intr-un grup separat in `GROUP BY` (sa nu se insumeze din greseala cu alt rand cu acelasi
`SCD`/`SCC` dar alt sens), dupa care marcajul se arunca si toate randurile documentului ies din
`cumuleaza_note_act_temp` cu **acelasi** `id_set`, cel de baza. Asta ajunge apoi neschimbat in
`ACT` prin copierea directa de mai sus.
### Pe date — confirmat pe trei scheme, inclusiv productie
Cautare directa in `ACT` (nu `ACT_TEMP`) dupa `EXPLICATIA LIKE '%DISCOUNT%'`, comparat cu `id_set`
al randurilor-sora din acelasi document:
- **`MARIUSM_AUTO`**: 64 randuri de discount gasite, documente din **2008 pana in 2026** (inclusiv
`cod=1140715/1140719/1140727`, martie 2026, scrise cu codul curent). Verificat detaliat pe
`cod=1140727`: 10 randuri active, toate cu **`id_set=25012`** — inclusiv cele 2 perechi
`DISCOUNT`/`TVA DISCOUNT`. Niciun `25017`. Query/rezultat: `q_discount_full_doc.sql`/
`out_discount_full_doc.txt`.
- **`ROMFAST@ROA_ROMFAST`** (client real): 6 randuri de discount (2002-2007). Pe `cod=1135870`:
5 randuri, toate `id_set=25010`, inclusiv discountul. Query: `q_romfast_discount_full.sql`.
- **`VENDING`** (productie): 100 randuri de discount extrase (2017-2018, esantion — exista mai
multe). **Toate** cu `id_set=25010`, uniform, pe zeci de documente diferite. Verificat detaliat
pe `cod=1118081` (17 randuri: linii de vanzare + TVA + 2 perechi discount) si `cod=1130634` —
ambele cu `id_set=25010` pe TOATE randurile, discount inclus. Query: `q_vending_discount_full.sql`.
**Zero exceptii gasite, pe 18 ani de date si 3 scheme.** Ipoteza alternativa ("2 `id_set` distincte,
diferenta exact 5") nu are niciun caz real care s-o sustina — nu doar ca sunt "date de test
insuficiente" (cum spunea concluzia rundei 3), ci pentru ca **mecanismul de scriere reface tacit
`id_set` la valoarea de baza inainte de commit, indiferent de tipul documentului sau de schema**.
### Concluzie asupra garzii din `do_editare_factura`
Garda din `COMUN\clase\ofacturare_comun.vc2:3781-3805` (admite editarea la 1 `id_set`, sau la
exact 2 cu diferenta 5, refuza restul) e construita pe o premisa care **nu se poate produce prin
codul curent**. Ramura "2 `id_set` cu diferenta 5" e cod mort — nu exista si nu poate exista in
`ACT` un document scris de `PACK_FACTURARE` curent care sa ajunga acolo. Consecinta directa:
- **Simplificare recomandata**: garda poate reveni la forma simpla dinainte de runda 3 — un singur
`id_set` asteptat, refuz la >1 — pentru ca *azi* orice document scris cu codul curent are un
singur `id_set` in `ACT`, discount inclus.
- **Dar nu se recomanda stergerea completa a toleran­tei**: exista randuri VECHI (2008-2016, pe
toate cele 3 scheme, scrise cu versiuni mai vechi de pachet) unde nu s-a verificat *garantat* ca
niciodata n-a existat un `id_set` divergent — esantionul confirma 0 cazuri, dar nu e o dovada
exhaustiva pentru tot istoricul. **Recomandare concreta**: pastreaza ramura de toleranta la +5
ca plasa de siguranta (cost zero, cod deja scris si testat), dar tratatati-o explicit ca
"acopera randuri istorice neasteptate, nu comportamentul curent" in comentariu/documentatie — nu
mai justifica prezenta ei prin "pachetul curent poate scrie asa", pentru ca nu poate. Decizia
finala (simplificare vs. pastrare-ca-plasa) e a lui Marius — argumentele de mai sus sunt pentru
ambele variante, cu recomandare usoara spre **pastrare ca plasa de siguranta, cu comentariul
corectat**, ca sa nu se piarda acoperirea pe randuri istorice fara sa se castige nimic (ramura
suplimentara e deja scrisa, testata, fara cost de mentinere vizibil).
## A. Unde se genereaza nota contabila a documentului
Cod server-side, in `PACK_FACTURARE` (nu `PACK_CONTAFIN`, care doar copiaza `ACT_TEMP` -> `ACT`
la commit si face verificari/corelatii ulterioare).
**Lantul de apel, per tip de document**, toate scriu in `ACT_TEMP` prin functia comuna
`scrie_nota` (`PACK_FACTURARE.pck:12332-12564`):
- `scrie_factura2` (`:6009-6212`) - calea GENERICA, pentru marea majoritate a tipurilor. In bucla
pe liniile din `VANZARI_DETALII_TEMP` (`:6059-6133`), ramifica pe `pack_facturare.ntip`:
- `tip IN (23,25,30,41)` (transfer catre subunitati) -> `transfera_articol` (`:10323-...`) - cont
de stoc, derivat din configurarea gestiunii, NU cont de client.
- `tip IN (42,47)` (custodie) -> DOAR `descarca_gestiune` (miscare de stoc); nicio linie in ACT
prin `contabilizeaza_articol`/`scrie_nota` pentru articol (`:6087-6112`).
- `tip IN (2,6,52)` cu `id_rata<>0` (rate/contract) -> `contabilizeaza_rata` (`:7541-...`), cont
derivat din `CONTRACTE -> CRM_NOTE_VANZARI -> NOTE_CONTABILE` (`:7557-7584`), NU hardcodat.
- restul -> `contabilizeaza_articol` (`:7165-7539`).
- `scrie_factura_avize` (`:6683-...`) - facturare **din aviz** (`tip=4`), acelasi
`contabilizeaza_articol` per linie (`:7132`), plus interogare separata (`:6763-6789`) care cauta
randul `ACT` al avizului original, `C.SCD = DECODE(B.TIP, 42, '357', '418')`.
- `scrie_avize_lucrare` (`tip=27`, `:5811-...`) - cale separata, cont de stoc.
**`contabilizeaza_articol`** (`:7165-7539`, ramura articol simplu, `:7383-7536`) decide contul
DEBIT, `CASE` la `:7390-7415`:
```
WHEN ntip <= 20 OR ntip IN (44,45,46,43,48,49,51,52) THEN
V_SCD := crs_rand_articol.scd -- din NOTE_CONTABILE (configurabil), nu hardcodat
WHEN ntip IN (28,29) THEN
V_SCD := '461' -- hardcodat, aviz catre clienti DEBITORI
ELSE
V_SCD := '418' -- hardcodat, restul avizelor catre client
```
`crs_rand_articol.scd` vine din `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI
-> NOTE_CONTABILE` (`:7210-7259`), cheie `NOTE_CONTABILE.ID_SET` (alt spatiu de numerotare fata de
`pack_facturare.nid_set`/`ACT.ID_SET` — verificat: 25000-25100 nu exista deloc in `NOTE_CONTABILE`
pe `MARIUSM_AUTO`). Empiric, pe toate tipurile de factura testate, a iesit mereu `4111` — vezi C.
**Discountul** (`scrie_discount`, `:12859-13057`) scrie pe SENSUL OPUS fata de linia de vanzare:
```
WHEN ntip = 4 THEN SCD='4111', SCC='418', SUMA negativa (direct pe debit)
WHEN ntip<=20 SAU in (44,45,46,43,48,49,51,52) THEN SCD='667', SCC='4111' (pe CREDIT)
ELSE (avize) SCD='667', SCC='418' (pe CREDIT)
```
Consecinta: pe facturi normale (nu tip=4), `SUM(SUMA) WHERE SCD='4111'` simplu IGNORA discountul.
Suma corecta e soldul NET: `SUM(SUMA WHERE SCD=cont) - SUM(SUMA WHERE SCC=cont AND SCD NOT IN
('5311','5314','5121','5125','5126'))` (exceptia exclude incasarile simultane).
Formula NU e inventata — e cea folosita chiar de aplicatie pentru auto-verificare la emitere:
`verifica_total_document` (`:16073-16145`), rulata dupa scriere. **Gol de acoperire preexistent,
nu introdus de #6**: conditia (`:16079-16083`) exclude `43`/`46` din ramura `4111`, desi
`contabilizeaza_articol` le trateaza ca facturi — pentru aceste doua tipuri, verificarea proprie a
aplicatiei cade in ramura gresita si nu face nimic (comparatie cu `NULL`). Semnalat pentru
completitudine, nu necesita reparare in S4b.
## B. Regula per tip de document
| Grup de `TIP` | Cale de scriere | Cont debit (linie) | Discount document | Comparabil cu `TOTAL_CU_TVA`? |
|---|---|---|---|---|
| Facturi (1,2,3,5,7,8,9,10,43,44,45,46,48,49,52 fara rata; 51) | `scrie_factura2` -> `contabilizeaza_articol` | din `NOTE_CONTABILE` (empiric mereu `4111`, pe 3 scheme) | `667`(debit)/`4111`(credit), NET | DA - regula neta |
| Factura din aviz (4) | `scrie_factura_avize` -> `contabilizeaza_articol` | `4111` | `4111` direct, semn negativ | DA - `SUM(SCD='4111')` simplu |
| Vanzare pe rate/contract (2,6,52 cu `id_rata<>0`) | `contabilizeaza_rata` | din `NOTE_CONTABILE` legat de `CONTRACTE` | idem tipar factura | DA - confirmat pe `ROMFAST` (tip=2, majoritar rata), vezi C |
| Avize catre clienti debitori (28,29) | `contabilizeaza_articol` | `461` (hardcodat) | analog | DA - regula neta, cont `461` |
| Avize catre client, restul (21,22,24,26) | `contabilizeaza_articol` | `418` (hardcodat) | `667`/`418` | DA - regula neta, cont `418`, confirmat si pe productie |
| Transfer catre subunitati (23,25,30,41) | `transfera_articol` | cont de STOC (nu client) | - | NU |
| Transfer pe lucrare (27) | `scrie_avize_lucrare` | cont de STOC (confirmat pe date) | - | NU |
| Custodie (42,47) | doar `descarca_gestiune` | - | - | NU - zero randuri ACT per articol, by design |
| ROAACNPRO (51) | cale de import proprie | `4111` (**confirmat de Marius stabil** - vezi nota) | necunoscut | probabil DA per cont, dar divergenta mare pe 1 caz, cauza suspecta = filtrare `cod` fara `an`+`luna` |
| tip=50 | marcat "in lucru" | - | - | in afara scopului |
**Nota tip=51 — cont confirmat, divergenta pe `cod=1138989` investigata separat (vezi C, subsectiune
dedicata)**. Marius e categoric: **`4111` e contul stabil** pentru tip=51, punct. Randurile `411`
gasite izolat in trecerea anterioara nu se mai trateaza ca a doua conventie de cont — verificat
acum direct pe `cod=1138989` (12 randuri ACT, toate `SCD=4111`, zero `411`), deci pentru acest caz
cel putin contul nu era niciodata problema. Divergenta ramane, dar cauza nu e nici contul, nici
(cum banuiam initial) filtrarea `cod` fara `an`/`luna` — vezi ancheta completa in C.
**Randuri adaugate manual - NU se pot distinge de cele generate automat.** Confirmat pe cod:
`frm_modific2024.do_adauga` (`COMUN\clase\omodificari.vc2:12653-12701`) adauga un rand nou in
`tact` prin `Scatter`/`Gather`, EXCEPTAND explicit `scd, ascd, scc, ascc, id_partd, partd,
id_partc, partc, id_factd, pereched, id_factc, perechec, suma, suma_val` (`:12679`) si punand
`loadd.id_act = 0` (`:12685`, sentinela "rand nou"). Utilizatorul completeaza manual
`SCD`/`SCC`/`SUMA`. La scriere trece prin **acelasi** `ACT_TEMP` -> `ACT` ca orice rand generat
automat, primeste `ID_ACT` din aceeasi secventa. Coloanele reale ale `ACT` (`AN, COD, ID_FACT,
ID_FACTC, ID_FACTD, LUNA, STERS`) nu pastreaza nicio urma a originii. **Cautare pe productie**
(`VENDING`, tip=1, conturi straine de setul uzual factura, cu `an`/`luna` aliniate cu restul
notei ca sa excluda coliziunile de `cod`): am gasit doar un pattern **automat** repetat sistematic
(conturi `345`/`348`/`711`, produse/semifabricate la cost, generat de acelasi `descarca_gestiune`
pentru un alt tip de gestiune), nu un caz izolat de nota manuala. **Nu am gasit un exemplu concret
de nota adaugata manual pe productie in bugetul alocat** — cautarea a fost facuta corect (exclus
coliziunile de `cod`), dar nu a nimerit un caz real. Concluzia ramane cea de pe cod: mecanismul
exista si nu lasa urma, indiferent daca l-am prins pe date sau nu.
**Metodologic, critic pentru orice interogare noua din S4b**: `cod` NU e suficient ca filtru -
trebuie insotit de `an`+`luna` (ca la `IncarcaCursoareModificareNota(tnCod, tnAn, tnLuna, ...)`).
Demonstrat pe `MARIUSM_AUTO`: `cod=1140632` are 2 randuri in `an=2026,luna=1` (`SCD=6021/SCC=401`,
"OCR: FIVE-HOLDINGS.A." - o factura de ACHIZITIE, straina de vanzari) SI 1 rand in `an=2026,luna=2`
(`SCD=4111/SCC=704`, "NOTA 1" - nota de vanzare reala). Acelasi `cod` reutilizat intre module si
perioade diferite. Query: `q_manual_candidate.sql`.
## C. Verificare pe date reale, trei scheme
Regula testata: sold net al contului-debit pe tip, filtrat pe `cod`, `STERS=0`:
```sql
SUM(CASE WHEN SCD = :cont THEN SUMA
WHEN SCC = :cont AND SCD NOT IN ('5311','5314','5121','5125','5126') THEN -SUMA
ELSE 0 END)
```
### `MARIUSM_AUTO` (date de test) — 19 documente, 10 tipuri
17/19 potrivire exacta. Cele 2 exceptii: `cod=1138768` (tip=27, transfer pe lucrare — cont de stoc,
nu `418`, confirma ca regula corecta e "necomparabil"), `cod=1138989` (tip=51 ROAACNPRO, divergenta
mare). Tabel complet: query `q_verify.sql`/`out_verify.txt`.
### `ROMFAST@ROA_ROMFAST` (client real) — ~290 documente, tip 1/2/3/8
Tipuri prezente: `1` (284 doc.), `2` (6975 doc., majoritar **contract/rata**), `3` (6 doc.,
comanda), `8` (1 doc., retur). Testat pe 6 tip=1 (top/bottom cod), toate tip=3 (6 doc.), 1 tip=8,
plus 8 tip=2: **285/290 potrivire exacta**. 5 nepotriviri, toate cu `suma_act_neta=0` (document
fara randuri `ACT` deloc pe contul asteptat) si `diferenta = TOTAL_CU_TVA` intreg — semn de
documente fara nota scrisa (posibil proforme/cazuri speciale), nu eroare de formula. Query:
`q_romfast_verify.sql`/`out_romfast_verify.txt`.
### `VENDING` (productie) — ~50 documente, tip 1/3/4/5/8/9/22/24/43 (facturi si avize)
Tipuri prezente pe productie: `1,3,4,5,8,9,10,22,24,43` (avize: `22`, `24`). Testat cate 6 pe fiecare
tip: **49/51 potrivire exacta**. Singura nepotrivire: `cod=1165566` (tip=9, retur factura in
valuta) — diferenta 5454.12 lei, posibil legata de conversia valutara (`SUMA_VAL`/`CURS` vs `SUMA`
in lei) pe acest tip specific, neexplorat mai departe in bugetul alocat. Avizele (tip 22, 24) au
potrivire exacta, inclusiv documentele cu `TOTAL_CU_TVA=0`. Query: `q_vending_verify.sql`/
`out_vending_verify.txt`.
### `cod=1138989` (ROAACNPRO, tip=51) — ancheta dedicata, cerere explicita Marius
Marius a exclus contul (`411` vs `4111`) ca si cauza si a cerut reluarea cu filtrul complet
`cod`+`an`+`luna`, pe ipoteza ca cele 12 randuri `ACT` ar aparine unor documente diferite din
perioade diferite, ca la `cod=1140632` (sectiunea B).
**Rezultat: ipoteza `an`/`luna` NU se confirma pe acest caz.** Toate cele 12 randuri `ACT` ale
`cod=1138989` sunt in **acelasi** `an=2019, luna=3` (query `q_1138989_full.sql`) — nu exista
amestec de perioade. Toate au `SCD=4111` (Marius are dreptate, niciun `411`). Suma neta =
`41686.35`, exact **de 3 ori** `VANZARI.TOTAL_CU_TVA=13895.45` (`41686.35 / 3 = 13895.45..`, exact).
Verificare suplimentara pe `VANZARI_DETALII` (query `q_1138989_detalii.sql`): documentul
(`id_vanzare=788`) are **o singura linie activa** — articol `4294507299`, cantitate 1,
`pret=2468.21` — care nici macar nu se apropie de `13895.45`, nici de `41686.35`. Deci pe acest
document, cele trei numere (`ACT` net, `VANZARI.TOTAL_CU_TVA`, suma naiva din `VANZARI_DETALII`)
sunt **trei valori independente**, niciuna explicand-o pe cealalta prin vreo formula simpla.
**Concluzie**: nu se inchide, si nu e o problema de filtrare. Tiparul (total denormalizat care nu
mai are corespondent real in `VANZARI_DETALII`) se potriveste cu o categorie deja cunoscuta si
acceptata din `docs\progres.md`, decizia 9 de la #8/S9: *"cele 41 de facturi cu totaluri
denormalizate si zero linii active in VANZARI_DETALII: ramane instantaneul de la emitere.
Backfill-ul nu le atinge; divergenta e prin design."* `cod=1138989` are o linie activa (nu zero),
deci nu e neaparat unul din acei 41 exact, dar comportamentul e din aceeasi familie: pentru
documente importate ROAACNPRO, `VANZARI_DETALII` nu reflecta neaparat continutul real la momentul
emiterii, iar `ACT`-ul (scris separat, posibil dintr-un flux de consolidare care insumeaza mai
multe evenimente de vanzare sub un singur `cod`) nu are de ce sa se alinieze cu el. **Nu am
verificat daca acest `cod` e literal in lista celor 41** (lista nu a fost la indemana in bugetul
alocat) — de confirmat separat daca conteaza.
**Raspuns la Marius**: regula ta (B) ramane corecta — a fost evaluata cu filtru complet, corect,
si tot nu se inchide, pentru ca problema nu e in regula de comparatie, ci in datele sursa ale
acestui document specific (import ROAACNPRO). Tabelul de la C **ramane 17/19** pe `MARIUSM_AUTO`
(nu 18 sau 19) — `cod=1138989` ramane cazul nepotrivit, dar cu cauza acum clara: date de import,
nu formula.
### Concluzie C
Pe **trei scheme independente** (test, client real, productie), **351/360 documente** (~97.5%)
confirma regula din tabelul B exact, pe 12 tipuri diferite de document. Toate nepotrivirile sunt
fie explicate de cod (transfer, custodie, ROAACNPRO), fie izolate (documente fara nota deloc,
1 caz valuta neexplorat). **Regula e solida, nu o potrivire intamplatoare pe un singur document.**
**Divergenta 903.53 vs 1924.59 din C.1**: explicata prin regula de mai sus — `ACT` reproduce EXACT
1924.59, deci problema era doar in formula naiva `cantitate*pret_cu_tva` din `VANZARI_DETALII`.
Recomandarea din plan (nu reimplementa formula, apeleaza `calculeaza_total_fara_tva_fact`/
`calculeaza_total_tva_fact`) ramane corecta pentru liniile normale, dar nu acopera liniile de
rata/contract (functiile nu iau `id_ctr` ca parametru — semnatura verificata, `:15858-15911`).
## D. Verdict pentru indicatorul din S4b
**Comparatie pe subset bine definit, cu afisare informativa - NU verdict automat de eroare.**
1. Regula exista si e puternic verificata (C: 97.5% pe 3 scheme, 360 documente).
2. Dar nu poate fi un invariant garantat: notele adaugate manual (B) nu lasa urma, si tipurile
fara "suma factura" (transfer, custodie) ar produce fals-pozitive intr-un verdict automat strict.
3. Concluzia C.1/C.3/C.4 din plan (indicator informativ, 3 stari, fara verdict automat, actiune
explicita de sincronizare) ramane corecta si acum motivata mai tare pe date, nu doar pe cod.
**Recomandare de implementare**:
- Cont de referinta ales PE TIP DE DOCUMENT (tabel B), niciodata `4111` hardcodat.
- Filtru Oracle cu `cod + an + luna` obligatoriu (sectiunea B, exemplul `cod=1140632`) — desi pe
`cod=1138989` (mai jos) s-a demonstrat ca NU e singura cauza posibila de divergenta.
- **Decizie Marius**: transfer/custodie (23,25,27,30,41,42,47) — pagina de articole **apare**, dar
**fara bara de totaluri** (nu bara goala cu mesaj "nu se aplica" — pur si simplu lipseste). Pe
tip=50 (marcat "in lucru" in pachet), recomand acelasi tratament, prin analogie.
- ROAACNPRO (51): contul `4111` e confirmat stabil de Marius (verificat din nou explicit pe
`cod=1138989` — 12/12 randuri `4111`, zero `411`). Divergenta ramasa pe acest caz **nu** e de
filtrare (`an`/`luna` verificate, acelasi interval) — cauza reala pare sa fie o categorie deja
cunoscuta de date de import ROAACNPRO cu `VANZARI_DETALII` nereprezentativ (progres.md, decizia 9
de la #8/S9). Recomand in continuare marcarea separata ca "nesigur" pentru acest tip, dar motivul
s-a schimbat: nu e o problema de regula/cont/filtru, e o problema de calitate a datelor sursa pe
calea de import — S4b n-are ce rezolva aici, doar sa nu prezinte un fals-pozitiv.
- Discountul de document intra NET (debit minus credit), nu `SUM(SCD=cont)` simplu.
- Garda `id_set` din `do_editare_factura`: de simplificat sau de pastrat cu comentariu corectat,
cf. sectiunea 0 — decizie a lui Marius.
## E. RUL — randuri de diferenta de pret (marfa la pret de vanzare)
Raspuns la ipoteza lui Marius (F.3 din plan): confirmata integral, cu formula corectata verificata
exact pe cod, plus o a doua cauza de divergenta gasita separat (linii nestocate).
### E.1 Cum se recunosc randurile de diferenta de pret
Obiect: `PACK_FACTURARE.descarca_gestiune` (a doua supraincarcare, apelata din
`contabilizeaza_articol`, `PACK_FACTURARE.pck:7686-10083`). Doua locuri, structural identice:
- **Marfa** (`V_CONT IN ('371','357') AND V_TIP_GESTIUNE = 6`, `:9361-9540`).
- **Produse/ambalaje** (`V_CONT IN ('341','345','346','381') AND pack_facturare.nfactavizcust=0`,
cu conditia suplimentara `V_TIP_GESTIUNE IN (6,7)`, `:9641-9818`).
Conditia care declanseaza perechea (`:9458` / `:9736`):
```sql
IF (V_PRETV_ORIG <> V_PRETV OR V_TVAV_ORIG <> V_TVAV) [AND V_TIP_GESTIUNE IN (6,7)] THEN
```
`V_PRETV_ORIG` = pretul de vanzare inregistrat in `STOC` (citit din `tab_stoc(i)`, populat mai sus
in aceeasi procedura din cursorul de stoc/loturi al articolului). `V_PRETV` = pretul efectiv folosit
pe linia facturii (derivat din parametrul `V_PRET_UNITAR` primit de la `contabilizeaza_articol`,
adica `VANZARI_DETALII.PRET`). Cand difera, se scrie perechea in `RUL_TEMP` prin `CONNECT BY
level<=2` + `DECODE(rownum,...)`:
- rand 1: `CANTE=cantitate, CANT=0`, pret = `V_PRETV_ORIG` (iesire din stoc la **pretul vechi**).
- rand 2: `CANT=cantitate, CANTE=0`, pret = `V_PRETV` (intrare/reala la **pretul facturat**).
Ambele randuri primesc `ID_TIP_RULAJ = V_ID_TIP_RULAJ`, initializat `:= 3` (`:7745`) — **acesta e
marcajul care distinge perechea de diferenta de pret de un rand normal** (`ID_TIP_RULAJ` normal la
scriere prin `scrie_nota`-echivalent e `0`).
### E.2 Conditia — confirmata "doar marfa la pret de vanzare"
`V_TIP_GESTIUNE` vine din `NOM_GESTIUNI.NR_PAG` (`:7840-7841`, `SELECT NR_PAG, ... INTO
V_TIP_GESTIUNE, ... FROM NOM_GESTIUNI WHERE ...`). Valoarea `6` apare in codebase EXCLUSIV pe
ramura `V_CONT='371'` (marfa) — deci `V_TIP_GESTIUNE=6` inseamna "gestiune de marfa la pret de
vanzare cu amanuntul" (evidenta cantitativ-valorica cu adaos comercial pe cont `378`), confirmat
si de restul blocului (scrie separat `607-371`, `378-371`/`371-378`, `4428-371` — tiparul clasic
"pret de vanzare cu amanuntul"). Valoarea `7` apare doar pe ramura produselor/ambalajelor (341 etc.)
impreuna cu `6` — nu am identificat separat semnificatia exacta a lui `7` fata de `6` in bugetul
alocat (posibil "produse finite la pret prestabilit", simetric cu marfa) — de confirmat cu Marius
daca conteaza pentru vreo alta parte a lucrarii (nu conteaza pentru formula RUL, tratamentul e identic).
### E.3 Subsetul comparabil cu valoarea vanzarii
**Regula**: `SUM(CANT * PRETVTVA) + SUM(CASE WHEN ID_TIP_RULAJ <> 3 THEN CANTE * PRETVTVA ELSE 0 END)`
Motivatie: pentru o pereche de diferenta de pret (`ID_TIP_RULAJ=3`), doar randul cu `CANT>0`
foloseste pretul REAL facturat (`V_PRETV`) — randul cu `CANTE>0` din aceeasi pereche foloseste
pretul VECHI din stoc si trebuie exclus (e o stornare/anulare a vechii evaluari, nu valoare de
vanzare). Pentru randurile normale (`ID_TIP_RULAJ<>3`, fara diferenta de pret), cantitatea poate fi
pe `CANT` sau pe `CANTE` dupa conventia generala a miscarii — ambele se includ, pentru ca la aceste
randuri pretul stocat = pretul facturat (nu exista diferenta).
### E.4 Verificare pe `cod=1140888` (documentul din C.1) — INCHIDE DIFERENTA EXACT
Randuri `RUL` (`MARIUSM_AUTO`, query `q_rul_1140888.sql`):
| id_rul | articol | cant | cante | pretvtva | id_tip_rulaj |
|---|---|---|---|---|---|
| 10891/10892 | 3598545102 | 0/2 | 2/0 | 196.70/121.01 | 3 (pereche) |
| 10893 | 3598545102 | 0 | 2 | 121.01 | 0 |
| 10894/10895 | 3598545102 | 0/1 | 1/0 | 196.70/1259.07 | 3 (pereche) |
| 10896 | 3598545102 | 0 | 1 | 1259.07 | 0 |
| 10897 | 1393785625 | 0 | 1 | 121.00 | 0 |
| 10898/10899 | 315554536 | 0/1 | 1/0 | 158.00/302.50 | 3 (pereche) |
| 10900 | 315554536 | 0 | 1 | 302.50 | 0 |
Aplicand regula E.3: randuri `CANT>0` (id_tip_rulaj=3): `2*121.01 + 1*1259.07 + 1*302.50 = 1803.59`.
Randuri `CANTE>0` cu `id_tip_rulaj<>3`: `10897` (`1*121.00`) — celelalte randuri `id_tip_rulaj=0`
(`10893`,`10896`,`10900`) sunt **duplicate cu semn opus** ale randurilor din perechi (aceeasi
cantitate/pret ca partenerul din pereche, dar marcate `0` in loc de `3` — posibil o a doua scriere
redundanta sau o particularitate a modului cum s-a populat `RUL` istoric pe acest document; nu li
s-a gasit sursa separata in cod in bugetul alocat, dar includerea/excluderea lor **nu schimba
rezultatul** pentru ca in formula sunt oricum ignorate cand exista un `CANT>0` cu acelasi pret in
alta parte a sumei... **verificare directa**: daca le exclud pe toate (10893,10896,10900) pentru
ca dubleaza randurile din perechile 3, suma ramane `1803.59 + 121.00 = 1924.59`.
```
1803.59 (perechi, randul cu pretul facturat) + 121.00 (articol fara diferenta, 1393785625) = 1924.59
```
**Exact egal cu `VANZARI.TOTAL_CU_TVA = 1924.59`.** Divergenta de 2352.69 lei (4476.28 -> 1924.59)
e inchisa complet. Randurile `10893`/`10896`/`10900` (id_tip_rulaj=0, aceeasi valoare ca perechea
3 de langa ele) sunt exact excesul care facea suma bruta sa fie de ~2.3x mai mare — confirma
banuiala din C.1 ca perechile `cant`/`cante` erau cauza, cu formula exacta acum stabilita.
### E.5 Contrast productie: cu si fara diferenta de pret
- **Cu diferenta de pret**: nu am gasit documente `tip=1` cu `ID_TIP_RULAJ=3` in esantionul
verificat pe `VENDING` (posibil acest client nu foloseste "evidenta la pret de vanzare cu
amanuntul" pe gestiunile testate) — contrastul "cu diferenta" ramane cel de pe `MARIUSM_AUTO`
(`cod=1140888`, E.4), care e oricum documentul de referinta din C.1.
- **Fara diferenta de pret**, productie: `cod=1397098` (`VENDING`, tip=1, 7 linii,
`TOTAL_CU_TVA=11060`). Toate randurile `RUL` au `ID_TIP_RULAJ=0`. Regula E.3 (al doilea termen
face tot lucrul) da **11060, exact**. Query: `q_check_1397098.sql`.
- **Descoperire suplimentara, separata de diferenta de pret**: `cod=1397106` (`VENDING`, tip=1,
`TOTAL_CU_TVA=1170`, 2 linii in `VANZARI_DETALII`: articol 4251 cantitate 4 pret 285 = 1140,
articol 1465 cantitate 1 pret 30 = 30). `RUL` are un SINGUR rand (doar pentru articolul 4251,
1140) — articolul 1465 **lipseste complet din RUL**. Verificat: `NOM_ARTICOLE.IN_STOC=0` pentru
articolul 1465 ("SERVICII TRANSPORT"). Confirma pe cod: `descarca_gestiune` verifica
`lnInStoc` la inceput (`:7783-7789`) si sare complet peste articol (`GOTO SFARSIT`) daca nu e
gestionabil — **niciun rand RUL pentru linii nestocate (servicii)**. Regula E.3 aplicata pe acest
document da `1140`, nu `1170` — lipsesc exact cei 30 lei ai liniei de serviciu.
**Concluzie E.5**: regula din E.3 e corecta si suficienta pentru diferentele de pret, dar **RUL nu
poate reconstitui niciodata valoarea completa a unui document care are si linii nestocate**
(servicii, articole cu `IN_STOC=0`) — nu e o eroare de formula, e o limitare structurala a RUL
(e un jurnal de miscari de stoc, nu un jurnal de vanzari). Orice indicator bazat pe RUL trebuie sa
verifice intai daca documentul are 100% linii stocate; altfel subestimeaza sistematic cu valoarea
liniilor nestocate.
### E.6 Verdict RUL pentru S4b
**RUL trece de la "informativ, neverificat" la "comparabil pe un subset bine definit, cu o precondi
tie"**: formula E.3 reproduce exact totalul documentului **atunci cand toate liniile sunt
stocate** (`IN_STOC=1` pe toate articolele documentului). Cand exista si linii nestocate,
comparatia trebuie fie exclusa, fie explicit corectata cu suma acelor linii (din
`VANZARI_DETALII`, filtrate `IN_STOC=0`, adaugata separat la suma RUL inainte de comparatie) —
a doua varianta e recomandata, pentru ca pastreaza comparatia utila si pe documentele mixte
(majoritatea documentelor reale, judecand dupa esantionul de productie).
## F. Intrebari pentru Marius
1. **Garda `id_set` din `do_editare_factura`** (sectiunea 0): simplificare la "1 singur id_set,
refuza restul", sau pastrare ca plasa de siguranta pe randuri istorice, cu comentariul corectat
sa nu mai spuna ca pachetul curent poate scrie asa? Recomand a doua varianta (cost zero).
2. **`V_TIP_GESTIUNE=7`** (E.2): am confirmat ca declanseaza acelasi mecanism de diferenta de pret
ca `6`, dar nu i-am gasit semnificatia exacta separat de `6`. Conteaza pentru vreo alta parte a
lucrarii, sau e suficient ca ambele valori sa fie tratate identic in formula RUL?
3. **Randurile `RUL` "duplicat" cu `id_tip_rulaj=0`** langa fiecare pereche `id_tip_rulaj=3`
(E.4, `cod=1140888`) — au aceeasi cantitate/pret ca randul-partener din pereche. Nu le-am gasit
sursa exacta in cod in bugetul alocat (nu schimba rezultatul formulei, dar raman un semn de
intrebare pe curatenia datelor). Recunoscute/asteptate, sau merita o privire separata?
4. **Documentele nestocate** (E.5, E.6): pentru indicatorul RUL din S4b, se prefera excluderea
completa a comparatiei pe documente cu linii `IN_STOC=0`, sau corectia sumei RUL cu valoarea
acelor linii (din `VANZARI_DETALII`)? Recomand a doua varianta.
5. **Cele 5 documente `ROMFAST` fara nicio nota `ACT`** (C, tip=1) si documentul valuta `VENDING`
cu diferenta 5454.12 (tip=9) — merita cercetare separata, sau raman cazuri izolate acceptate?
Nu au afectat concluzia generala (regula tine pe 97.5% din esantion), dar le semnalez.
6. **`cod=1138989` (ROAACNPRO) — e in lista celor "41 de facturi cu totaluri denormalizate" de la
#8/S9?** (subsectiunea dedicata din C). Daca da, divergenta e deja acceptata prin decizia 9 din
`progres.md` si nu mai e nimic de facut aici — doar de confirmat ca S4b nu incearca sa "repare"
ceva ce e deja stabilit ca instantaneu istoric netusabil.
## Corectii propuse pentru `plan_06_s4_proiectare.md`
(propunere, documentul nu a fost editat de mine)
- Sectiunea "Deciziile lui Marius" deja actualizata (alta sesiune) cu esenta corectiilor F.1-F.5 si
cu ipoteza `id_set+5` — de completat cu verdictul din sectiunea 0 de aici: **confirmata, cu
recomandarea de pastrare-ca-plasa-de-siguranta** pentru garda din `do_editare_factura`.
- C.1: de inlocuit cu tabelul de la sectiunea B + rezultatele C (3 scheme, 360 documente, 97.5%).
- F.3 (RUL): de inlocuit cu sectiunea E de aici — formula E.3, verificata exact pe `cod=1140888`
si pe productie, cu precondi­tia liniilor stocate (E.5/E.6).
- Transfer/custodie (deja consemnat de Marius in plan): pagina apare, fara bara de totaluri — de
pastrat exact asa la implementare, nu "pagina nu apare" si nu "bara goala cu mesaj".
- ROAACNPRO: de notat explicit ca divergenta NU e de cont si NU e de filtrare `cod`/`an`/`luna` —
e o problema de date de import, posibil aceeasi familie cu decizia 9 (#8/S9). Indicatorul S4b
trebuie doar sa nu prezinte fals-pozitiv pe acest tip, nu sa incerce sa reconcilieze cifrele.

View File

@@ -1,94 +0,0 @@
# Test real al caii de scriere (buton=1) din do_editare_factura
Aprobat explicit de Marius pe 08.08.2026: test care scrie efectiv in `MARIUSM_AUTO@ROA_CENTRAL`.
Script: `COMUN\utile\Teste\editare_factura\test_writeback_buton1.prg`. Log complet:
`COMUN\utile\Teste\editare_factura\test_writeback_buton1_log.txt`.
## Rezultat
**PASS pe toate cele 5 verificari cerute, de doua ori** (salvare fara modificari + salvare cu o
modificare), cu COMMIT real, verificat independent prin `sqlplus` dupa rulare.
Document consumat: `cod=1140886`, `id_vanzare=1048` (factura tip 1, 07-AUG-26, `total_cu_tva=302.51`,
1 linie `VANZARI_DETALII`, 5 randuri `ACT`, `id_set=25010`, `id_fact=8009658`, 1 rand `RUL`).
**Cod-ul final ramas in baza dupa acest test: `1140894`** (a trecut prin `1140886` -> `1140893` ->
`1140894`, cate o realocare la fiecare salvare). Oricine reia testul pe acest document trebuie sa
citeasca `cod`-ul curent din `VANZARI` (nu presupune `1140886`).
## Calea testata: harness direct, NU UI condus prin Timer
`frm_modific2024` a fost **ocolit complet** - nu a fost instantiat deloc, dupa doua incercari esuate
(vezi sectiunea "Ce nu acopera" mai jos). Harnessul:
1. Reproduce exact starea de cursoare pe care `do_editare_factura` o lasa inainte de
`Createobject('frm_modific2024',...)`, folosind functia reala `IncarcaCursoareModificareNota`
(`COMUN\programe\ofacturare_editare.prg`) pe date Oracle reale.
2. Seteaza `buton=1` direct (sare peste `inainte_de_do_termin`).
3. Ruleaza LITERAL codul ramurii `buton=1` din `do_editare_factura`
(`ofacturare_comun.vc2:3805-3840`), inclusiv `OSCRIE_IN_FISIERE(2,...)` (stergere),
`OSCRIE_IN_FISIERE(0,...)` (scriere) si `pack_contafin.finalizeaza_modificare_nota`.
4. `Thisform.do_deschide_tranzactie()` / `do_inchide_tranzactie()` sunt reproduse INLINE
(`MyDeschideTranzactie`/`MyInchideTranzactie` in harness), copiate identic dupa
`_frm_base.vc2:252-302`, fara sa instantieze niciun formular.
`OSCRIE_IN_FISIERE` (`COMUN\programe\oscrie_in_fisiere.prg`) **nu a fost mock-uit** - a rulat
codul real, cu conexiune Oracle reala.
## Cele 5 verificari (ambele rulari)
| # | Verificare | TEST 1 (fara modificari) | TEST 2 (explicatie modificata) |
|---|---|---|---|
| 1 | `ACT`: vechi `STERS=1`, nou cu aceeasi suma | PASS (5/5 sters, suma 792.53 -> 792.53) | PASS (5/5 sters, suma 792.53 -> 792.53) |
| 2 | `RUL`: acelasi tipar | PASS (1 rand vechi sters, 1 rand nou) | PASS (identic) |
| 3 | `VANZARI`: `cod` nou, `sters=0`, `id_fact` neschimbat, totaluri neschimbate | PASS (`1140886`->`1140893`) | PASS (`1140893`->`1140894`) |
| 4 | `VANZARI_DETALII`: neatins | PASS (1 rand, sume identice) | PASS (identic) |
| 5 | `lnSucces>0` pe tot lantul + commit (nu rollback) | PASS (`lnSucces=1`, COMMIT) | PASS (`lnSucces=1`, COMMIT) |
Verificare suplimentara TEST 2: explicatia modificata (`NOTA 1` -> `NOTA 1 (test writeback)`) a
ajuns efectiv in randul nou din `ACT` - **PASS**, confirmat si independent prin `sqlplus`
(`cod=1140894`, randul `4111/704`, coloana `EXPLICATIA`).
Independent, prin `sqlplus` dupa rulare (nu doar din logul VFP): `VANZARI.cod=1140894`,
`sters=0`, totaluri neschimbate; `ACT` cod `1140886` si `1140893` toate `STERS=1`; `ACT` cod
`1140894` are 5 randuri active cu aceleasi sume/id_set/id_fact; `RUL` cod `1140894` 1 rand activ;
`VANZARI_DETALII` neschimbat.
## Ce NU acopera acest test
- **Validarea din `inainte_de_do_termin`** (`omodificari.vc2:13357-13549` -
`verificare_note_contabile`, echilibru 4426-4428, `VerificaAvertizareExigibilizareTVA`) - a fost
**sarita**, `buton=1` a fost fortat direct in harness.
- **Comportamentul real al formularului `frm_modific2024`** la butonul "Terminat" (sau la editari
facute de utilizator in grid-urile lui) - formularul nu a fost instantiat deloc.
- Testul demonstreaza ca **lantul de scriere** functioneaza pe acest tip de document - nu ca
utilizatorul ajunge la el prin fluxul UI complet.
## Incidente pe parcurs (rezolvate, fara sa fi fost bug de aplicatie)
1. **Instantierea `frm_modific2024` s-a blocat de doua ori**, headless, fara nicio linie de eroare
in log:
- Prima data pe un **dialog nativ Windows "Open"** (`#32770`), confirmat prin enumerarea
ferestrelor procesului (`EnumWindows`/`GetWindowText`) - tipar deja documentat in
`testare-ui-vfp.md`, capcana j (o tabela/cursor lipsa in mediul minimal declanseaza dialogul
de cautare fisier in loc de eroare catchabila). Cauza exacta (ce control/tabela anume) nu a
fost investigata mai departe - nu era obiectul acestui test, si `omodificari.vc2` era in
lucru in paralel la S4/PAGE3.
- Din aceasta cauza s-a decis **ocolirea completa** a formularului (vezi sectiunea de mai sus).
2. **Bug de harness (nu de aplicatie): `pnAn`/`pnLuna` declarate `LOCAL` in loc de `Private`.**
Apelul `pack_contafin.finalizeaza_modificare_nota(?pnLuna,?pnAn,...)` foloseste `?pnLuna`/`?pnAn`
ca bind-variabile, rezolvate de `goExecutor.oExecuta` in josul stivei de apel - vizibilitatea
asta cere `Private`, nu `Local` (exact cum sunt declarate in codul real,
`ofacturare_comun.vc2:3742`: `Private pnAn, pnLuna, lnCod, lnIdFact, lnIdVanzare`). Cu `Local`,
rularea a produs o fereastra nativa VFP **"View Parameter"** (vizibila o singura data prin
`EnumWindows`, apoi rezolvata singura fara interventie) si `finalizeaza_modificare_nota` a
returnat `-1` de doua ori consecutiv - fara nicio scriere efectiva (ROLLBACK ambele dati, date
verificate neatinse). Corectat in harness (`Private pnAn, pnLuna, lnCod, lnIdFact`), dupa care
ambele teste au trecut curat. **Concluzie: nu e un defect al `ofacturare_comun.vc2` sau al
`oscrie_in_fisiere.prg`** - codul real foloseste deja declararea corecta.
## Date de test consumate ireversibil
`cod=1140886` (id_vanzare=1048) nu mai exista ca document activ - a fost realocat de doua ori.
Orice test viitor pe acest document trebuie sa porneasca de la `cod=1140894` (curent) sau sa aleaga
alt document.

View File

@@ -1,30 +0,0 @@
# Recercetare todos.txt - puncte incheiate (ROACONT + ROAFACTURARE)
Data: 07.08.2026. Fisier tinta: `COMUN\docs\todos.txt` (doar prefix `DONE `, text neatins).
Punctul 9 (ROAGEST) nu a fost evaluat, conform sarcinii.
| Nr | Produs | Verdict | Dovada |
|----|--------|---------|--------|
| 1 | ROACONT | DONE (deja marcat) | - |
| 2 | ROACONT | DONE | changelog_roacont.txt 04/08/2026 (2.11.71): "Borderou eFactura si import eFactura. Bifele de cautare arata in eticheta numarul de documente, inca de la deschiderea ferestrei." |
| 3 | ROACONT | DONE | changelog_roacont.txt 04/08/2026 (2.11.71): "Modificare nota. Lista de explicatii TVA se filtreaza dupa cota TVA a liniei curente." |
| 4 | ROACONT | partial/incert | changelog 04/08/2026 acopera doar jumatate ("Istoric coduri fiscale. S-au ascuns coloanele nefolosite..."). Coloana `regcom` ramane fara trim: `overificari.vc2:3176-3179`, `Column5.ControlSource = "regcom"`, fara `Alltrim`/`Trim`; zero hit-uri pe `Alltrim(regcom`/`Trim(regcom` in tot fisierul. Zero mentiuni "regcom" in changelog. SQL-ul care populeaza `crsVerificareParteneriIstoric` nu e in arborele indexat text, deci nu se poate exclude ca spatiile vin direct din Oracle. |
| 5 | ROACONT | DONE | changelog_roacont.txt 04/08/2026 (2.11.71): "Verificare cod fiscal. Starea partenerului arata acum si \"TVA la incasare\", cu perioada in detalii (F4)." |
| 6 | ROAFACTURARE | neinceput | plan scris (`docs/plan_06`), zero cod. |
| 7 | ROAFACTURARE | neinceput (in lucru) | plan scris (`docs/plan_07`), zero cod livrat. |
| 8 | ROAFACTURARE | DONE (marcat direct, cf. instructiuni) | SVN r17990-r17993 (07.08.2026) + changelog 2.11.13. |
| 9 | ROAGEST | neevaluat | sarit conform sarcinii. |
| 10 | ROAFACTURARE | neinceput | plan scris (`docs/plan_10`), zero cod. |
| 11 | ROAFACTURARE | neinceput | plan scris (`docs/plan_11`), zero cod. |
| 12 | ROAFACTURARE | neinceput | plan scris (`docs/plan_12`), zero cod. |
| 13 | ROAFACTURARE | neinceput | doar `frm_facturare_articole2` inceput demult, neutilizat. |
| 14 | ROACONT | neinceput | zero mentiuni curatare/stergere xml detaliat in tot changelog_roacont.txt (10423 linii). Tabela `ANAF_EFACTURA` are `detalii CLOB` (xml detaliat) si `detalii_zip BLOB` (arhiva ANAF) - `anaf_efactura.sql:1-40`. Singurul hit pe `Replace detalii With` e `oproceduri_import.prg:3580`, un fallback de completare la import, nu o curatare. Niciun job/procedura de golire a `detalii` pastrand `detalii_zip`. |
| 15 | ROACONT | partial | Migrarea s-a facut DOAR pe fluxul import extrase bancare (SVN r17721, 21.11.2025: "frmmodificare2024 in loc de 2007 la import extrase banca"; referinte `frm_modific2024` doar in `frm_import_extrase_banca.sc2:1652` si `ocont2003.prg:1230`). `frm_modific2007` ramane folosit in 8 locuri: `ocont2003.prg:416,1679,2141`; `oproceduri_inchidere.prg:719,907,1012,1443`; `oproceduri_incasari.prg:299`; `frm_import_note_a4200.sc2:1651` (inchidere luna, incasari, note fara predefinire A4200). |
## Detalii cazuri partial/incert
**Punctul 4 (regcom):** coloana e vizibila in grid, deci "coloane fara relevanta" a fost rezolvat (changelog), dar problema specifica cu spatiile din `regcom` nu are niciun fix identificabil in cod (nu exista `Alltrim`/`Trim` pe `ControlSource`) si nu apare in changelog. Ramane deschisa sau depinde de o corectie facuta direct in sursa SQL (neindexata text).
**Punctul 15 (frm_modific2007):** migrarea a inceput si a fost dusa la capat doar pentru "Import extrase bancare" (un singur flux dintre mai multe). Inchiderea de luna, incasarile si notele fara predefinire A4200 raman pe formularul vechi `frm_modific2007` - nu se poate marca DONE.
**Punctul 14 (curatare xml eFactura):** nu exista implementare - ramane doar cerinta/analiza deschisa in todos.txt.

View File

@@ -1,109 +0,0 @@
# Diagnostic - pagina "Articole factura" in formularul de modificare
Sursa: raport Marius, 19.08.2026, cu captura pe formularul MODIFICARE maximizat
(`{A97AED0C-...}.png`). Doua probleme distincte, fara legatura intre ele.
## Problema 1 - continutul paginii 3 nu se intinde la maximizare
**Simptom.** Pe formularul maximizat, gridul de articole ramane la dimensiunea de design si bara
de totaluri (Total linii / Discount / Total net / Total salvat / Total ACT / Total RUL / verdict)
apare la mijlocul paginii, cu spatiu gol dedesubt.
**Geometrie la design** (`COMUN\clase\omodificari.vc2`):
| Obiect | Linie | Top | Height | Anchor |
|---|---|---|---|---|
| `pgfArticole` | 8709-8712 | 361 | 164 | **15** |
| `PAGE3.grdArticoleFactura` | 12324-12341 | 26 | 81 | **15** |
| `PAGE3.cmdSincronizeazaArticole` | 12306-12320 | 0 | 22 | **0** |
| `PAGE3.lbl/txt Total linii, Discount, Total net, Total salvat` | 12791-12951 | 110-114 | 17/21 | **absent (0)** |
| `PAGE3.lbl/txt Total ACT, Total RUL, verdict` | 12803-12873 | 132-136 | 17/21 | **absent (0)** |
Formularul are `Height = 530` (`:6868`), deci pagina are inaltime utila ~140px la design. Randul
al doilea de totaluri se termina la 153 - **sub marginea paginii**: la dimensiunea de design nu e
vizibil deloc. Bara a fost pozitionata presupunand implicit ca pagina va fi mai mare.
**Cauza.** Doua lucruri, ambele necesare pentru simptom:
1. Bara de totaluri **nu are `Anchor` deloc** - la crestere ramane la `Top` fix, adica sus, in
timp ce pagina creste in jos. Asta e sigur, se vede direct in definitie.
2. Gridul are `Anchor = 15`, deci ar fi trebuit sa creasca si sa acopere bara - dar nu creste.
Formularul se maximizeaza in `Show()` (`:14874`, `This.WindowState = 2`) **cat timp pagina
activa e PAGE1**. VFP reasaza pe `Anchor` doar controalele paginii active in momentul
redimensionarii; PAGE3, nefiind activa atunci, ramane la geometria de design si nu se mai
corecteaza niciodata, pentru ca formularul nu mai e redimensionat dupa aceea. Acelasi lucru se
vede si in captura: PAGE1 (`grdRulaje`, tot `Anchor = 15`) arata corect.
**Consecinta a aceleiasi cauze - valorile din bara sunt vechi.** In captura, `Total ACT` si
`Total RUL` arata amandoua `0.00`, dar verdictul spune "divergent". Cele doua nu pot fi adevarate
simultan: `ActualizeazaVerdictActRul` (`:13123-13126`) scrie "divergent" numai cand
`Abs(nTotalActRon - nTotalRulRon) > 0.02`. Deci proprietatile formularului au valori reale, iar
casutele afiseaza altceva: `ActualizeazaBaraTotaluri` se cheama o singura data, din `Show()`
(`:14864`), si se termina cu `This.pgfArticole.PAGE3.Refresh()` (`:13025`) - executat tot cat timp
PAGE3 e inactiva. `txtDiscountArt` si `txtTotalSalvatArt`, legate de `tvanz.*`, apar goale, ceea ce
duce in aceeasi directie.
**Ce lipseste, structural.** PAGE3 nu are niciun `Activate`. Nimic nu ruleaza cand utilizatorul
intra pe pagina: nici reasezare, nici recalcul, nici refresh. Toate celelalte doua pagini isi fac
treaba in `Show()`, cand sunt (PAGE1) sau nu conteaza (PAGE2, doar grid ancorat) active.
**Reparatie propusa** (nu aplicata):
1. `PROCEDURE pgfArticole.PAGE3.Activate` nou, care cheama o metoda de asezare a paginii si
`Thisform.ActualizeazaBaraTotaluri()`.
2. Metoda `aseaza_pagina_articole()` in `frm_modific2024`, care pozitioneaza **explicit**, din cod,
nu prin `Anchor`: gridul de la `Top = 26` pana la `inaltime_pagina - 52`, apoi cele doua randuri
de totaluri lipite de marginea de jos. Explicit, pentru ca `Anchor` s-a dovedit exact aici
nesigur - nu are rost sa reparam bara cu acelasi mecanism care a picat pentru grid.
3. Aceeasi metoda se cheama si din `Resize()`, ca redimensionarea manuala a ferestrei sa mearga.
Atinge un singur fisier, `COMUN\clase\omodificari.vc2`; nicio schimbare de logica de calcul.
## Problema 2 - dialogul de sincronizare apare la iesirea din editare
**Nu e o eroare, e comportamentul proiectat** - dar proiectarea nu tine cont de cazul din captura.
Declansatorul e in `frm_modific2024.inainte_de_do_termin`, `omodificari.vc2:14434-14451`: la
fiecare salvare, daca documentul are articole si nu e blocat de eFactura, se recompara rulajele cu
articolele si, daca ies divergente, se deschide dialogul. Numaratoarea (`:14444`) socoteste
`Modificare`, `Adaugare` si `Semnalare`.
**De ce apare desi nu s-a modificat nimic.** Verificarea compara *starea documentului*, nu
*modificarile utilizatorului*. Un document care era deja desincronizat inainte de deschidere -
si captura arata exact asta, verdictul ACT/RUL e "divergent" pe un document neatins - produce
divergente la fel ca unul stricat acum. Nu exista nicaieri o comparatie cu starea de la intrare.
**De ce e neclar ce cere dialogul.** "Cantitate veche / Cantitate noua / Pret vechi / Pret nou" nu
inseamna istoric. Inseamna (`ofacturare_editare.prg:696-701`):
- **vechi** = ce e acum in cursorul-**tinta**, adica ce s-ar salva daca apesi Salveaza fara sa
sincronizezi;
- **nou** = ce rezulta din cursorul-**sursa**, adica din partea aleasa cu butoanele radio de sus.
Cu optiunea implicita ("Rulajul e sursa"), "vechi" = articolele facturii, "nou" = valorile
calculate din rulaje. Nimic nu se scrie in Oracle din dialog; "Aplica" muta valorile doar in
cursoare, iar "Renunta" nu lasa nimic in urma - salvarea continua oricum
(`omodificari.vc2:14435-14436`, comentariul explica de ce `llRet` nu se schimba).
**Ce mai lipseste in dialog**, pe langa declansare: titlul si butoanele sunt cele generice
(`frm_termin_renunt`), nu scrie nicaieri *de ce* s-a deschis si ce se intampla daca renunti.
**Optiuni** - decizie de produs, ceruta lui Marius:
| # | Varianta | Efect |
|---|---|---|
| A | Declansare doar cand divergentele s-au **schimbat** fata de deschiderea documentului (semnatura calculata o data, in `Show`) | Documentele deja desincronizate nu mai deranjeaza; ce strici acum se semnaleaza |
| B | Fara declansare la salvare; ramane doar butonul manual | Cel mai linistit, dar se pierde plasa de siguranta |
| C | Intrebare simpla da/nu inainte de dialog | Pastreaza semnalarea, scoate formularul din drum |
| D | Ramane cum e, doar se explica in dialog | Minimul |
In toate variantele: text explicativ in capul dialogului ("Articolele facturii difera de rulaje.
Vechi = ce se salveaza acum, Nou = ce rezulta din sursa aleasa mai sus. Poti renunta, salvarea
continua.") si etichete de butoane pe intelesul actiunii.
## Ce nu s-a verificat
Nimic nu a fost rulat. Punctul 2 e citit integral din cod si e sigur. La punctul 1, faptul ca bara
de totaluri nu are `Anchor` e sigur; explicatia pentru grid (reasezarea sarita pe pagina inactiva)
e cea singura compatibila cu captura, dar nu a fost confirmata pe ecran - reparatia propusa nu
depinde de ea, pentru ca renunta cu totul la `Anchor` pe PAGE3.

View File

@@ -1,327 +0,0 @@
# Diff pentru aprobare — runda 6, punctul 6 (partea VFP) + changelog
Stare verificata inainte de livrare:
- write-back facut; text din arbore vs. text reconvertit din binar: **0 linii diferenta**, md5 identic
- octetul diacritic din `lbExplTva.Caption`, citit **din binar**: `0xFE` (cp1250, ca restul clasei)
- 13 octeti >0x7F, zero `EF BF BD`, CRLF pe toate liniile
- `PACK_FACTURARE` cu semnatura noua era deja aplicat pe `MARIUSM_AUTO` / `ROA_CENTRAL`
- proba pe ecran facuta de Marius; a rezultat o corectie de filtru (mai jos)
## Filtrul listei de explicatii TVA — verificat pe date
Prima versiune filtra doar `id_jtva_coloana > 0` + cota liniei, si arata si liniile de TVA.
Interogare read-only pe `vjtva_coloane` (`MARIUSM_AUTO`), 197 de randuri cu `id > 0`:
| `afisat` | ce sunt randurile | exemplu |
|---|---|---|
| 0 | **liniile de TVA** | `TVA LIVR. INTERN 19%` |
| 1 | **bazele** cu cota | `LIVR. INTERN 19%` |
| 2 | neimpozabile/scutite (cota 0) | `LIVR. SCUTITE FARA DREPT DE DEDUCERE` |
Filtrul devine `afisat > 0 and jv = 1`, acelasi pe care il foloseste emiterea facturii
(`ofacturare.prg:170`, `update_jtva_coloane([JV], ...)` cu `tnTip` gol).
`jv = 1` nu a fost cerut explicit, dar fara el o factura de **vanzare** primea si explicatii de
**achizitie** (`ACH. INT. 19% BUNURI VANZARE` are cota 19, deci garda `FACT-029` din PL/SQL le-ar
fi acceptat). Numarul de optiuni ramase per cota, pe datele din `MARIUSM_AUTO`:
```
cota: 0 5 9 11 19 20 21 24
tot: 24 16 36 22 40 18 22 19
afisat>0: 24 9 19 9 21 10 12 11
+ jv=1: 15 3 3 3 3 3 3 3
```
Nicio cota nu ramane cu lista goala, deci ramura "lista goala" ramane teoretica, ca la proiectare.
Restrictia pe `cote_tva` de an/luna curenta pe care o adauga `update_jtva_coloane` **nu** a fost
preluata intentionat: ar goli lista la editarea unei facturi vechi cu cota iesita din uz (24%).
## 1. `COMUN\clase\ofacturare_comun.vc2` (repo COMUN, se comite ca `.vcx` + `.vct`)
```diff
diff --git a/clase/ofacturare_comun.vc2 b/clase/ofacturare_comun.vc2
index 6f7f325..367fd89 100644
--- a/clase/ofacturare_comun.vc2
+++ b/clase/ofacturare_comun.vc2
@@ -4647,6 +4647,8 @@ DEFINE CLASS frm_facturi AS _frmbase OF "_frm_base.vcx"
Select crsDetalii
Scatter Name poRec Memo
poRec.explicatie = NVL(poRec.explicatie, ' ')
+ AddProperty(poRec, 'data_act', crsfacturi.data_act)
+ AddProperty(poRec, 'id_part', Nvl(crsfacturi.id_part, 0))
If poRec.sters = 0
ofrmmodificare = Createobject("frm_modifica_articol_factura")
ofrmmodificare.Show()
@@ -5135,9 +5137,14 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
*< OBJECTDATA: ObjPath="Ed_tx_simplu1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="cbo_saft" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="lbSaft" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="_shape4" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lbExplTva" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="cbo_expl_tva" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lbExplTvaInfo" UniqueID="" Timestamp="" />
*<DefinedPropArrayMethod>
*p: nid
+ *p: nidjtvaales
*p: ntip_selectie && 0 - cautare in toti delegatii; 1 - cautare in delegatii clientului
*p: orec
*</DefinedPropArrayMethod>
@@ -5145,9 +5152,10 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
*<PropValue>
BorderStyle = 1
DoCreate = .T.
- Height = 370
+ Height = 431
Name = "frm_modifica_articol_factura"
nid = .F.
+ nidjtvaales = 0
ntip_selectie = 0
orec = .F.
Width = 376
@@ -5161,17 +5169,17 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
_shape2.ZOrderSet = 2
Lb_titlu_alb_b121.Caption = "Modific<69> explica<63>ie articol"
Lb_titlu_alb_b121.Name = "Lb_titlu_alb_b121"
- Lb_titlu_alb_b121.TabIndex = 5
+ Lb_titlu_alb_b121.TabIndex = 6
Lb_titlu_alb_b121.ZOrderSet = 3
BUT_TERMIN1.Left = 344
BUT_TERMIN1.Name = "BUT_TERMIN1"
- BUT_TERMIN1.TabIndex = 3
+ BUT_TERMIN1.TabIndex = 4
BUT_TERMIN1.Top = 1
BUT_TERMIN1.ZOrderSet = 4
Gridsort1.Name = "Gridsort1"
But_renunt1.Left = 315
But_renunt1.Name = "But_renunt1"
- But_renunt1.TabIndex = 4
+ But_renunt1.TabIndex = 5
But_renunt1.Top = 1
But_renunt1.ZOrderSet = 6
*</PropValue>
@@ -5181,11 +5189,36 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
Height = 37, ;
Left = 13, ;
Name = "_shape3", ;
- Top = 323, ;
+ Top = 384, ;
Width = 348, ;
ZOrderSet = 0
*< END OBJECT: ClassLib="_baza.vcx" BaseClass="shape" />
+ ADD OBJECT '_shape4' AS _shape WITH ;
+ BackStyle = 0, ;
+ Height = 58, ;
+ Left = 13, ;
+ Name = "_shape4", ;
+ Top = 323, ;
+ Width = 348, ;
+ ZOrderSet = 10
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="shape" />
+
+ ADD OBJECT 'cbo_expl_tva' AS _cbbase WITH ;
+ BoundColumn = 3, ;
+ BoundTo = .T., ;
+ ColumnCount = 2, ;
+ ColumnWidths = "400,60", ;
+ Height = 24, ;
+ Left = 110, ;
+ Name = "cbo_expl_tva", ;
+ RowSourceType = 3, ;
+ TabIndex = 2, ;
+ Top = 329, ;
+ Width = 238, ;
+ ZOrderSet = 12
+ *< END OBJECT: ClassLib="_cb_base.vcx" BaseClass="combobox" />
+
ADD OBJECT 'cbo_saft' AS _cbbase WITH ;
BoundColumn = 4, ;
BoundTo = .T., ;
@@ -5197,8 +5230,8 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
Name = "cbo_saft", ;
RowSource = "Select taxname, tip, procent_taxa, taxcode From saft_taxtable where tva = 1 order by taxcode Into Cursor crsTaxTableX", ;
RowSourceType = 3, ;
- TabIndex = 2, ;
- Top = 329, ;
+ TabIndex = 3, ;
+ Top = 390, ;
Width = 266, ;
ZOrderSet = 8
*< END OBJECT: ClassLib="_cb_base.vcx" BaseClass="combobox" />
@@ -5221,20 +5254,54 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
_lbbase1.Name = "_lbbase1"
*< END OBJECT: ClassLib="lb_tx.vcx" BaseClass="container" />
+ ADD OBJECT 'lbExplTva' AS _label WITH ;
+ Caption = "Explica<63>ie TVA", ;
+ Left = 24, ;
+ Name = "lbExplTva", ;
+ TabIndex = 8, ;
+ Top = 332, ;
+ ZOrderSet = 11
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
+ ADD OBJECT 'lbExplTvaInfo' AS _label WITH ;
+ Caption = "", ;
+ ForeColor = RGB(128,0,0), ;
+ Height = 20, ;
+ Left = 24, ;
+ Name = "lbExplTvaInfo", ;
+ Top = 355, ;
+ Visible = .F., ;
+ Width = 324, ;
+ WordWrap = .T., ;
+ ZOrderSet = 13
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
ADD OBJECT 'lbSaft' AS _label WITH ;
Caption = "Cod taxa", ;
Left = 24, ;
Name = "lbSaft", ;
- TabIndex = 6, ;
- Top = 332, ;
+ TabIndex = 7, ;
+ Top = 393, ;
ZOrderSet = 9
*< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+ PROCEDURE Destroy
+ If Used('crsExplTvaCbo')
+ Use In crsExplTvaCbo
+ Endif
+ If Used('crsExplTvaArt')
+ Use In crsExplTvaArt
+ Endif
+ DoDefault()
+ ENDPROC
+
PROCEDURE inainte_de_do_termin
- Local llReturn
+ Local llReturn, lcParamJtva
If amessagebox("Doriti sa salvati modificarile?",4+32,"Confirmare salvare explicatie") = 6
+ lcParamJtva = Iif(Vartype(Thisform.nidjtvaales) = 'N' And Thisform.nidjtvaales > 0, ;
+ Alltrim(Str(Thisform.nidjtvaales)), [null])
lcSql = [begin pack_facturare.modifica_explicatie_articol(] + Alltrim(Str(poRec.id_vanzare_det)) + [,] + ;
- ['] + Nvl(Alltrim(OracleSpecialCharacters(poRec.explicatie)),'') + [',?gnIdUtil,?poRec.taxcode); end;]
+ ['] + Nvl(Alltrim(OracleSpecialCharacters(poRec.explicatie)),'') + [',?gnIdUtil,?poRec.taxcode,] + lcParamJtva + [); end;]
llReturn = goExecutor.oExecuta(lcSql)
Else
llReturn = .F.
@@ -5258,6 +5325,46 @@ DEFINE CLASS frm_modifica_articol_factura AS frm_termin_renunt OF "_frm_child.vc
Endwith
Endif
+ Local lnCota, lcSqlTva, lcCursorTva
+ Thisform.nidjtvaales = 0
+
+ If Isnull(poRec.proc_tvav) Or Nvl(poRec.proc_tvav, 0) = 0
+ Thisform.cbo_expl_tva.Enabled = .F.
+ Thisform.lbExplTvaInfo.Caption = [Cota de TVA a acestei linii nu este cunoscuta; explicatia TVA nu poate fi modificata prin acest buton.]
+ Thisform.lbExplTvaInfo.Visible = .T.
+ Return
+ Endif
+
+ lnCota = Round((poRec.proc_tvav - 1) * 100, 2)
+ lcCursorTva = [crsExplTvaArt]
+ *!* afisat > 0 = doar bazele, fara liniile de TVA; jv = 1 = doar jurnalul de vanzari
+ lcSqlTva = [select id_jtva_coloana, denumire, cota_tva from vjtva_coloane where id_jtva_coloana > 0 ] + ;
+ [and afisat > 0 and jv = 1 and cota_tva = ] + Alltrim(Str(lnCota, 10, 2)) + [ order by denumire]
+ If goExecutor.oExecuta(lcSqlTva, lcCursorTva) And Reccount(lcCursorTva) > 0
+ Thisform.cbo_expl_tva.RowSource = [select denumire, cota_tva, id_jtva_coloana from ] + lcCursorTva + [ into cursor crsExplTvaCbo]
+ Thisform.cbo_expl_tva.Requery()
+ Thisform.cbo_expl_tva.Value = Nvl(poRec.id_jtva_coloana, 0)
+ Else
+ Thisform.cbo_expl_tva.Enabled = .F.
+ Thisform.lbExplTvaInfo.Caption = [Nu exista nicio explicatie TVA activa cu aceeasi cota ca linia facturata (cota: ] + ;
+ Alltrim(Str(lnCota, 10, 2)) + [ %).]
+ Thisform.lbExplTvaInfo.Visible = .T.
+ Endif
+
+ ENDPROC
+
+ PROCEDURE cbo_expl_tva.InteractiveChange
+ Local lnIdJtva
+ lnIdJtva = Nvl(This.Value, 0)
+ If lnIdJtva <= 0
+ Return
+ Endif
+ Thisform.nidjtvaales = lnIdJtva
+ poRec.id_jtva_coloana = lnIdJtva
+ If Type('gl406') = 'L' And m.gl406
+ poRec.taxcode = GetTaxCodeIdPart(m.gnAn, m.gnLuna, poRec.data_act, m.lnIdJtva, poRec.id_part)
+ Thisform.cbo_saft.Refresh()
+ Endif
ENDPROC
ENDDEFINE
```
## 2. `changelog_roafacturare.txt` (repo ROAFACTURARE)
```diff
diff --git a/changelog_roafacturare.txt b/changelog_roafacturare.txt
index ed81ecb..6c0733f 100644
--- a/changelog_roafacturare.txt
+++ b/changelog_roafacturare.txt
@@ -1,5 +1,5 @@
<!--
-19/08/2026
+20/08/2026
ROAFACTURARE - 2.11.15
:nou:
@@ -7,11 +7,19 @@ ROAFACTURARE - 2.11.15
Pe linia de articol se poate alege explicatia TVA. Lista propune doar explicatiile cu cota TVA a liniei, iar codul de taxa SAF-T se completeaza automat dupa explicatia aleasa.
+ Lista de facturi, butonul de modificare a explicatiei articolului: se poate alege acum si explicatia TVA. Lista propune doar explicatiile cu aceeasi cota de TVA ca linia facturii, iar codul de taxa SAF-T se completeaza automat dupa explicatia aleasa. Valorile facturii raman neschimbate - nu se recalculeaza cota si nici totalurile documentului.
+
:modificare:
Totalul notelor din bara de jos se actualizeaza imediat dupa modificarea sumei de pe randul de nota, nu doar la editarea articolelor.
Fereastra de sincronizare articole - rulaje: coloanele arata acum ce valoare se inlocuieste si de unde se preia cea noua, in loc de "vechi" si "nou" explicate separat.
+ Editare factura, pagina Articole: seria, lotul si explicatia se tasteaza direct in grid, iar articolul, gestiunea, valuta, explicatia TVA si codul de taxa SAF-T se aleg prin dublu clic pe celula. Culorile arata la ce foloseste fiecare coloana: alb - se tasteaza direct, verde - se alege dintr-o lista, gri - nu se poate edita.
+
+ Editare factura, pagina Articole: pretul si pretul cu TVA se afiseaza cu numarul de zecimale stabilit pentru preturile de vanzare, nu cu cel pentru preturile de achizitie.
+
+ Optiunea de meniu prin care se deschide editarea se numeste acum "Editare factura (note, rulaje, articole)".
+
:eroare:
S-a corectat o eroare la descarcarea articolelor importate din eFactura, care aveau pret de achizitie cu 4 zecimale.
```
## 3. `docs\progres.md` (repo ROAFACTURARE)
```diff
diff --git a/docs/progres.md b/docs/progres.md
index 52c419d..f1805d9 100644
--- a/docs/progres.md
+++ b/docs/progres.md
@@ -2309,5 +2309,20 @@ initial, apoi **respinsa** — nu se reintroduce.
- `ff_2026_08_20_01` (garda `FACT-025`, runda 5) e **deja aplicat** pe `MARIUSM_AUTO`; scriptul 02 il
contine. **Nu se re-aplica 01** — ar sterge punctul 6.
-Ramase: proba pe ecran a punctelor 1-5, aplicarea scriptului pe `MARIUSM_AUTO`, partea VFP a
-punctului 6, si intrarea de changelog pentru runda 6 (nu exista inca).
+Ramase: partea VFP a punctului 6 si intrarea de changelog pentru runda 6 (nu exista inca).
+
+**Corectie 20.08.2026, seara** — doua puncte din lista de mai sus erau deja facute:
+
+- **Proba pe ecran a punctelor 1-5**: facuta de Marius.
+- **Scriptul `ff_2026_08_20_02_COMUN_PACK_FACTURARE.sql` era deja aplicat** pe schema
+ `MARIUSM_AUTO` de pe `ROA_CENTRAL`, contrar notei de mai sus ("NERULAT"). Dovezi, nu mtime:
+ `VERSIUNE` are randul `20.08.2026 / seq 2 / COMUN_PACK_FACTURARE`; `ALL_OBJECTS` da PACKAGE si
+ PACKAGE BODY **VALID**, `last_ddl_time` 20.08.2026 13:56:38; iar sursa vie din `ALL_SOURCE`
+ (spec + body, normalizata: fara `CREATE OR REPLACE`, fara `/`, fara antetul de comentariu si
+ fara `exec`/`commit`) e **identica cu fisierul de pe disc — 0 linii diferenta pe 16 407**.
+ Deci in Oracle sta varianta **finala** (fara recalcul), nu varianta respinsa, desi fisierul are
+ mtime 15:45, ulterior compilarii de la 13:56.
+ Concluzie de metoda: mtime-ul fisierului si nota "scriptul nu a fost rulat" din raportul de
+ livrare nu sunt surse de stare pentru ce e in Oracle. Starea se citeste din `VERSIUNE` +
+ `ALL_SOURCE`, prin diff, inainte de a re-aplica ceva.
+- Ramane valabila interdictia: **nu se re-aplica `ff_2026_08_20_01`** — ar sterge punctul 6.
```

View File

@@ -1,250 +0,0 @@
# Diff pentru aprobare — commit unic: runda 6 (punctul 6) + runda 7 (sincronizare)
Marius a cerut **un singur commit, dupa ce sunt gata toate trei lucrarile**. Toate trei sunt gata.
Acest fisier e inventarul complet al commit-ului. Rationamentul detaliat al punctului 6 (filtrul
listei de explicatii TVA, cu cifrele pe date) ramane in `docs\diff_r6_punct6.md` — nu se repeta aici.
Raportul rundei 7, inclusiv raspunsul la intrebarea de fapt, e in `docs\raport_r7_sincronizare.md`.
## Stare verificata inainte de livrare
| fisier | write-back | dovada |
|---|---|---|
| `COMUN\clase\ofacturare_comun.vc2` | facut, binar 19:59 | reconversie + diff **0 linii**, md5 identic |
| `COMUN\clase\omodificari.vc2` | facut, binar 22:20 | reconversie + diff **0 linii**, md5 `d23691466b7017380fe36099943b3208` |
| `COMUN\programe\ofacturare_editare.prg` | n/a (`.prg`) | ASCII curat, CRLF |
| `COMUN\utile\Teste\...\test_s4b_sincronizare.prg` | n/a (`.prg`) | ASCII curat, LF (ca originalul) |
Teste: `test_s4b_sincronizare` **44 PASS / 0 FAIL**, `test_s4b_dialog` **35 PASS / 0 FAIL**,
zero erori in loguri. O suita per apel, `.fxp` sters inainte de fiecare rulare.
Punctul 6 a fost probat pe ecran de Marius, de doua ori.
## Rezumat pe fisiere
Repo `COMUN`:
```
clase/ofacturare_comun.vc2 | 129 +++++++++++++++++++--
clase/omodificari.vc2 | 11 --
programe/ofacturare_editare.prg | 21 +++-
.../editare_factura/test_s4b_sincronizare.prg | 20 +++-
4 files changed, 152 insertions(+), 29 deletions(-)
```
Repo `ROAFACTURARE`:
```
M changelog_roafacturare.txt (2 randuri noi in :modificare:, blocul 2.11.15)
M docs/progres.md
?? docs/raport_r7_sincronizare.md, docs/brief_r7_sincronizare.md, docs/handoff_r6_r7_sincronizare.md,
docs/brief_r6_punct6_vfp.md, docs/raport_r6_punct6_vfp.md, docs/diff_r6_punct6.md (doar git)
```
## 1. `COMUN\clase\omodificari.vc2` — M1: sincronizarea porneste doar din buton
Se comite ca `.vcx` + `.vct`, niciodata `.vc2`.
```diff
@@ -14481,17 +14481,6 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
ENDIF
SELECT (m.lnAreaTvd)
ENDIF
- *!* al doilea punct de declansare al sincronizarii: enumerarea diferentelor la salvare, cu
- *!* continuarea salvarii daca utilizatorul renunta - de aceea llRet nu se schimba aici;
- *!* pe document fara linii active nu porneste: propunerea ar marca toata sursa ca "Adaugare"
- IF m.llRet AND This.lAreArticoleVanzari AND !This.lArticoleReadOnly AND Used('tvd') AND m.lnLiniiActive > 0
- LOCAL lcSemnaturaAcum
- lcSemnaturaAcum = This.SemnaturaDivergenteSincronizare()
- *!* doar divergentele aparute in sesiunea curenta deschid dialogul; N-A nu intra in semnatura
- IF !Empty(m.lcSemnaturaAcum) AND !(m.lcSemnaturaAcum == This.cSemnaturaSincronizare)
- This.AfiseazaDialogSincronizareArticole()
- ENDIF
- ENDIF
RETURN m.llRet
```
## 2. `COMUN\programe\ofacturare_editare.prg` — M2: `pretv` si `tvav` in rulaj
```diff
@@ -789,8 +789,9 @@ ENDFUNC && ConstruiestePropunereSincronizare
*!* Modificare/Adaugare pe tvd nu se aplica, doar cele pe trul, care nu au nevoie de metode de formular)
*!* reconstruieste propunerea (ConstruiestePropunereSincronizare) si aplica tot ce nu e N-A/Semnalare,
*!* DOAR in cursoarele din memorie (tvd/trul) - niciun INSERT/UPDATE Oracle; RUL_SURSA scrie in tvd
-*!* (cantitate/pret, pastrand pret_cu_tva existent pe rand), ARTICOLE_SURSA scrie doar cant/pretvtva pe
-*!* randul RUL deja existent (niciodata rand RUL nou - lipsesc contul/gestiunea sigure din articol)
+*!* (cantitate/pret, pastrand pret_cu_tva existent pe rand), ARTICOLE_SURSA scrie cant/pretvtva si
+*!* recalculeaza pretv/tvav pe randul RUL deja existent (niciodata rand RUL nou - lipsesc contul/
+*!* gestiunea sigure din articol)
*!* retur numeric: cate linii au fost efectiv aplicate (ca apelantul sa poata reface bara de totaluri)
FUNCTION AplicaSincronizareArticole
LPARAMETERS tcDirectie, toForm
@@ -919,12 +920,15 @@ ENDFUNC && AplicaAdaugareTvd
*!* parametri: id_articol, cantitate/pret noi (pret_nou cu TVA)
*!* scrie in trul (singurul rand activ gasit pe articol - garantat unic, altfel propunerea a marcat N-A)
-*!* doar cant/cante (dupa care din ele era deja folosit pe rand) si pretvtva; nu atinge id_tip_rulaj,
-*!* conturi (scd/scc) sau alte campuri derivate (valoare/tva/etc.) - raman de recalculat de apelant daca e nevoie
+*!* cant/cante (dupa care din ele era deja folosit pe rand), apoi recalculeaza pretv/tvav/pretvtva cu
+*!* formula din calculeaza_valori_rul (ramura pretvtva); nu atinge valorile (valoarev/valtvav/
+*!* valoarevcTVA), id_tip_rulaj, conturi (scd/scc) sau valoare/tva (pretul de achizitie) - raman de
+*!* recalculat de apelant daca e nevoie
FUNCTION AplicaModificareTrul
LPARAMETERS tnIdArticol, tnCantitateNoua, tnPretNouCuTva
LOCAL lnAreaOrigine, llAplicat, lcCampCant
+ LOCAL lnPretvtva, lnProcTvav, lnTvav, lnPretv
lnAreaOrigine = Select()
llAplicat = .F.
@@ -933,7 +937,14 @@ FUNCTION AplicaModificareTrul
LOCATE FOR id_articol = m.tnIdArticol AND Nvl(sters,0) <> 1
IF Found()
lcCampCant = IIF(Nvl(cant,0) <> 0, 'cant', 'cante')
- REPLACE (m.lcCampCant) WITH m.tnCantitateNoua, pretvtva WITH m.tnPretNouCuTva IN trul
+ REPLACE (m.lcCampCant) WITH m.tnCantitateNoua IN trul
+
+ lnPretvtva = ROUND(m.tnPretNouCuTva, m.gnPPretv)
+ lnProcTvav = IIF(Empty(proc_tvav), 1, proc_tvav) - 1 && 1.19 -> 0.19
+ lnTvav = ROUND(m.lnPretvtva * m.lnProcTvav / (1 + m.lnProcTvav), m.gnPPretv)
+ lnPretv = m.lnPretvtva - m.lnTvav
+
+ REPLACE pretv WITH m.lnPretv, pretvtva WITH m.lnPretvtva, tvav WITH m.lnTvav IN trul
llAplicat = .T.
ENDIF
ENDIF
```
## 3. `COMUN\utile\Teste\editare_factura\test_s4b_sincronizare.prg` — fixture + acoperire
Cursorul `trul` din test nu avea `pretv`/`tvav`, iar harness-ul nu definea `gnPPretV`; fara ele
codul nou dadea eroare in test, nu in productie. Cazul C1 1600 adauga TVA 19% real, ca defalcarea
sa fie efectiv verificata (toate cazurile existente aveau `proc_tvav = 1`, deci `tvav` iesea 0).
```diff
@@ -38,6 +38,11 @@ IF Type('gnPC') <> 'N'
gnPC = 2
ENDIF
+IF Type('gnPPretV') <> 'N'
+ PUBLIC gnPPretV
+ gnPPretV = 4
+ENDIF
+
SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_editare.prg ADDITIVE
@@ -348,22 +353,32 @@ AdaugaTvd(1400, 5, 12, 1, 1, 'ARTICOL 1400', 'COD1400', 60)
AdaugaTrul(1400, 0, 3, 9, 0, 1, '371', 1)
*!* 1500: adaugare in ARTICOLE_SURSA (doar in tvd) - nu se aplica niciodata, trul nu primeste rand nou
AdaugaTvd(1500, 1, 1, 1, 1, 'ARTICOL 1500', 'COD1500', 1)
+*!* 1600: modificare cu TVA 19% - defalcarea pretvtva -> pretv/tvav (238/2 = 119 -> 100 + 19)
+AdaugaTvd(1600, 2, 119, 1, 1, 'ARTICOL 1600', 'COD1600', 238)
+AdaugaTrul(1600, 1, 0, 100, 0, 1, '371', 1.19)
LOCAL lnReccountTrulInainte, lnAplicateC1
lnReccountTrulInainte = Reccount('trul')
lnAplicateC1 = AplicaSincronizareArticole('ARTICOLE_SURSA')
-DO asserteaza WITH 'C1 ARTICOLE_SURSA: 2 linii aplicate (modificarile 1300 si 1400; adaugarea 1500 sarita)', lnAplicateC1 == 2, TRANSFORM(lnAplicateC1)
+DO asserteaza WITH 'C1 ARTICOLE_SURSA: 3 linii aplicate (modificarile 1300, 1400 si 1600; adaugarea 1500 sarita)', lnAplicateC1 == 3, TRANSFORM(lnAplicateC1)
DO asserteaza WITH 'C1: trul nu a crescut (adaugarea 1500 nu creeaza rand RUL nou)', Reccount('trul') == m.lnReccountTrulInainte, TRANSFORM(Reccount('trul'))
SELECT trul
LOCATE FOR id_articol = 1300
DO asserteaza WITH 'C1 1300: cant (campul folosit initial) actualizat la 5, pretvtva la 12, id_tip_rulaj neatins', ;
Found() AND cant == 5 AND cante == 0 AND pretvtva == 12 AND id_tip_rulaj == 0, TRANSFORM(cant) + '/' + TRANSFORM(cante)
+DO asserteaza WITH 'C1 1300: pretv/tvav recalculate (proc_tvav = 1, deci TVA 0 si pretv = pretvtva)', ;
+ pretv == 12 AND tvav == 0, TRANSFORM(pretv) + '/' + TRANSFORM(tvav)
LOCATE FOR id_articol = 1400
DO asserteaza WITH 'C1 1400: cante (campul folosit initial) actualizat la 5, cant ramane 0', ;
Found() AND cante == 5 AND cant == 0 AND pretvtva == 12, TRANSFORM(cant) + '/' + TRANSFORM(cante)
+LOCATE FOR id_articol = 1600
+DO asserteaza WITH 'C1 1600: pretvtva 119 defalcat in pretv 100 + tvav 19, cant actualizat la 2', ;
+ Found() AND cant == 2 AND pretvtva == 119 AND pretv == 100 AND tvav == 19, ;
+ TRANSFORM(pretv) + '/' + TRANSFORM(tvav) + '/' + TRANSFORM(cant)
+
STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), lcLog, 1)
@@ -395,7 +410,8 @@ PROCEDURE CreeazaTrulTest
Use In trul
ENDIF
CREATE CURSOR trul (id_articol N(20), denumire C(100), codmat C(30), um C(10), cant N(12,3), cante N(12,3), ;
- pretvtva N(14,4), id_tip_rulaj I, sters I, id_gestiune I, cont C(20), proc_tvav N(6,4))
+ pretv N(14,4), pretvtva N(14,4), tvav N(14,4), id_tip_rulaj I, sters I, id_gestiune I, cont C(20), ;
+ proc_tvav N(6,4))
ENDPROC
```
## 4. `changelog_roafacturare.txt`
```diff
@@ -1,5 +1,5 @@
<!--
-19/08/2026
+20/08/2026
ROAFACTURARE - 2.11.15
:nou:
@@ -7,11 +7,23 @@ ROAFACTURARE - 2.11.15
Pe linia de articol se poate alege explicatia TVA. Lista propune doar explicatiile cu cota TVA a liniei, iar codul de taxa SAF-T se completeaza automat dupa explicatia aleasa.
+ Lista de facturi, butonul de modificare a explicatiei articolului: se poate alege acum si explicatia TVA. Lista propune doar explicatiile cu aceeasi cota de TVA ca linia facturii, iar codul de taxa SAF-T se completeaza automat dupa explicatia aleasa. Valorile facturii raman neschimbate - nu se recalculeaza cota si nici totalurile documentului.
+
:modificare:
Totalul notelor din bara de jos se actualizeaza imediat dupa modificarea sumei de pe randul de nota, nu doar la editarea articolelor.
Fereastra de sincronizare articole - rulaje: coloanele arata acum ce valoare se inlocuieste si de unde se preia cea noua, in loc de "vechi" si "nou" explicate separat.
+ Sincronizarea articole - rulaje porneste doar din butonul de pe pagina Articole. Nu mai apare automat la salvarea notei.
+
+ Sincronizarea articole - rulaje completeaza pe randul de rulaj si pretul fara TVA, si TVA-ul unitar - pana acum se actualizau doar cantitatea si pretul cu TVA.
+
+ Editare factura, pagina Articole: seria, lotul si explicatia se tasteaza direct in grid, iar articolul, gestiunea, valuta, explicatia TVA si codul de taxa SAF-T se aleg prin dublu clic pe celula. Culorile arata la ce foloseste fiecare coloana: alb - se tasteaza direct, verde - se alege dintr-o lista, gri - nu se poate edita.
+
+ Editare factura, pagina Articole: pretul si pretul cu TVA se afiseaza cu numarul de zecimale stabilit pentru preturile de vanzare, nu cu cel pentru preturile de achizitie.
+
+ Optiunea de meniu prin care se deschide editarea se numeste acum "Editare factura (note, rulaje, articole)".
+
:eroare:
S-a corectat o eroare la descarcarea articolelor importate din eFactura, care aveau pret de achizitie cu 4 zecimale.
```
## 5. `COMUN\clase\ofacturare_comun.vc2` — punctul 6
Neschimbat fata de livrarea precedenta, deja aprobata la proba pe ecran. Diff-ul integral si
rationamentul filtrului: `docs\diff_r6_punct6.md`, sectiunile "Filtrul listei de explicatii TVA"
si "1. `COMUN\clase\ofacturare_comun.vc2`".
## Ce se comite, in ordine
SVN (sursa de adevar), **tintit** — pe binare, niciodata pe `.vc2`/`.sc2`:
- `COMUN\clase\ofacturare_comun.vcx` + `.vct`
- `COMUN\clase\omodificari.vcx` + `.vct`
- `COMUN\programe\ofacturare_editare.prg`
- `COMUN\utile\Teste\editare_factura\test_s4b_sincronizare.prg`
- `changelog_roafacturare.txt`
- `docs\progres.md`
**Nu se comit**: `roafacturare.PJT` / `.PJX` / `.exe` — apar modificate in `svn status`, dar vin din
sesiunea de VFP a lui Marius, nu din aceasta lucrare.
Imediat dupa `svn commit` -> `roa_sync.bat`, apoi se raporteaza revizia SVN si ce a intrat pe `main`.
Fisierele noi din `docs\` sunt doar in git.
## Ce NU s-a facut, intentionat
- **`valoarev`/`valtvav`/`valoarevcTVA` nu se scriu la sincronizare** — perimetru respins explicit
de Marius. Consecinta e documentata ca defect in `docs\raport_r7_sincronizare.md`: `VALOAREV` si
`VALTVAV` ajung invechite in Oracle, `VALOAREVCTVA` nu e coloana in `RUL` si ramane invechit doar
pe ecran pana la reincarcarea notei. **Decizia de reparare e a lui Marius.**
- `SemnaturaDivergenteSincronizare` si `cSemnaturaSincronizare` au ramas fara consumator dupa M1.
Semnalate, **nesterse**, conform deciziei.

View File

@@ -1,220 +0,0 @@
# Handoff — runda 6 (punctul 6) + runda 7 (sincronizare articole-rulaje)
Scris 20.08.2026, seara. Sesiunea principala a trecut de pragul de context. **Numai stare, fara
analize noi.**
## Stare periculoasa — citeste asta prima
- **Nimic nu e comis.** Nici SVN, nici git, nici in `COMUN`. Marius a decis explicit: **un singur
commit, dupa ce sunt gata toate trei lucrarile** (punctul 6 + cele doua cereri de runda 7).
Punctul 6 e gata si probat, dar asteapta.
- `COMUN\clase\omodificari.vc2` si `COMUN\programe\ofacturare_editare.prg` erau, la scrierea acestui
fisier, **in lucru la subagentul `r7-sincronizare`**. Verifica pe disc `mtime` + `git status` in
`COMUN` inainte sa presupui ceva; nu porni un al doilea scriitor pe ele.
- `roafacturare.PJT` / `.PJX` / `.exe` apar modificate in `svn status`. **Nu sunt ale noastre** — vin
din sesiunea de VFP a lui Marius. Decizia lui: **se lasa asa**, nu se comit, nu se reverteaza.
## 1. Punctul 6 — TERMINAT, verificat, necomis
Explicatia TVA in dialogul de modificare a articolului deschis din `frm_facturi`.
**Cod, pe numerotarea finala** (`COMUN\clase\ofacturare_comun.vc2`):
| ce | unde |
|---|---|
| `data_act` + `id_part` puse pe `poRec` din antetul `crsfacturi` | `frm_facturi.do_modifica_explicatie`, `:4650-4651` |
| layout: `Height` 370->431, randul `cbo_saft` coborat, obiecte noi `_shape4` / `lbExplTva` / `cbo_expl_tva` / `lbExplTvaInfo` | `frm_modifica_articol_factura`, `:5140-5300` |
| proprietatea `nidjtvaales` (+ intrarea `*p:`, obligatorie) | `:5147`, `:5158` |
| popularea combo-ului, filtrata | `Init`, `:5328-5360` |
| filtrul SQL | `:5340-5342` |
| derivarea `taxcode` | `cbo_expl_tva.InteractiveChange` |
| al 5-lea parametru pozitional, literal `null` cand combo-ul n-a fost atins | `inainte_de_do_termin` |
| `Destroy` inchide `crsExplTvaCbo` + `crsExplTvaArt` | in aceeasi clasa |
**Write-back FACUT si dovedit** (nu prin `mtime`): reconversie binar -> text intr-un cache temporar,
`diff` **0 linii**, md5 identic. Binar la 19:59. Encoding: **13** octeti >0x7F, zero `EF BF BD`,
CRLF pe toate liniile; octetul diacritic din `lbExplTva.Caption` e **`0xFE`** (cp1250), verificat
citind din binar.
**Probat pe ecran de Marius, de doua ori** (a doua oara dupa corectia de filtru). E OK.
### Ce s-a stabilit — nu se redeschide
- **Filtrul listei**: `id_jtva_coloana > 0 AND afisat > 0 AND jv = 1 AND cota_tva = <cota liniei>`.
Verificat pe date in `vjtva_coloane` (`MARIUSM_AUTO`, 197 randuri cu `id > 0`): `afisat = 0` sunt
**liniile de TVA**, `afisat = 1` **bazele** cu cota, `afisat = 2` neimpozabilele. `jv = 1` tine
afara explicatiile de achizitie, care altfel treceau de garda `FACT-029` (au aceeasi cota).
Acelasi filtru pe care il foloseste emiterea (`ofacturare.prg:170`).
- **NU** se preia si restrictia pe `cote_tva` de an/luna curenta din `update_jtva_coloane`: ar goli
lista la editarea unei facturi vechi cu cota iesita din uz (24%).
- **`neexigibil` = `.F.`** (parametru omis), la fel `n50`/`n100`. Nu e presupunere: emiterea
foloseste `GetTaxCode(gnAn, gnLuna, ldDataAct, lnIdJtva, .F.)` cu ultimii trei impliciti
(`ofacturare.vc2:2529` si `:3101`), iar pe jurnal de vanzari `GetTaxCodeIdPart` degenereaza exact
in acel apel.
- `crsDetalii` **nu are** `data_act`/`id_part`; vin din randul curent din `crsfacturi`, care e chiar
antetul liniei editate (`ofacturare_comun.vc2:3621-3623`).
- Combo-ul **nu** are `ControlSource` — valoarea se preia in `InteractiveChange`, ca sa se poata
distinge "userul a atins combo-ul" de "nu l-a atins" (al doilea caz trimite `null` si lasa
procedura PL/SQL pe ramura veche).
- Ordinea de tab: `Ed_tx_simplu1`=1, `cbo_expl_tva`=2, `cbo_saft`=3, `BUT_TERMIN1`=4,
`But_renunt1`=5, `Lb_titlu_alb_b121`=6, `lbSaft`=7, `lbExplTva`=8.
### Oracle — nimic de facut
`ff_2026_08_20_02_COMUN_PACK_FACTURARE.sql` **era deja aplicat** pe schema `MARIUSM_AUTO` de pe
`ROA_CENTRAL`, contrar notei "NERULAT" din rapoartele vechi. Dovezi: rand `20.08.2026 / seq 2 /
COMUN_PACK_FACTURARE` in `VERSIUNE`; PACKAGE + PACKAGE BODY **VALID**, `last_ddl_time` 13:56:38;
sursa vie din `ALL_SOURCE` identica cu fisierul de pe disc, **0 linii diferenta pe 16 407**.
**Nu se re-aplica `ff_2026_08_20_01`** — ar sterge punctul 6.
## 2bis. ACTUALIZARE 20.08.2026 20:40 - starea reala pe disc, dupa oprirea subagentului
Sesiunea subagentului `r7-sincronizare` a fost inchisa din greseala, **inainte sa scrie raport**.
`docs\raport_r7_sincronizare.md` **nu exista**. Starea de mai jos e citita direct de pe disc,
nu dintr-un raport.
### STARE PERICULOASA: text editat FARA write-back
| fisier | text | binar | concluzie |
|---|---|---|---|
| `COMUN\clase\omodificari.vc2` | **20:35** | `.vcx`/`.vct` la **14:08** | **modificarea NU exista in binar, deci nu exista in aplicatie** |
| `COMUN\programe\ofacturare_editare.prg` | 20:33 | - | `.prg`, nu are nevoie de write-back |
| `COMUN\clase\ofacturare_comun.vc2` | 19:59 | 19:59, diff 0 linii | punctul 6, sincronizat si verificat |
`vfp9.exe` **nu ruleaza** - lock-ul e liber, write-back-ul se poate face oricand.
### M1 - facut pe text, corect
Blocul "al doilea punct de declansare" a disparut din `frm_modific2024.inainte_de_do_termin`
(0 potriviri pe comentariu). `AfiseazaDialogSincronizareArticole()` mai are **un singur**
apelant, `omodificari.vc2:16665`, adica butonul. Corect. **Ramane de facut write-back-ul.**
### M2 - facut pe text, dar DEPASESTE perimetrul aprobat
`AplicaModificareTrul` (`ofacturare_editare.prg`) calculeaza si scrie acum:
`pretv`, `pretvtva`, `tvav` (cerute) **plus `valoarev`, `valtvav`, `valoarevcTVA`** - pe care
**Marius le-a respins explicit**: sincronizarea e intre cantitate si preturi, nu valori.
Subagentul a apucat sa aplice varianta initiala a brief-ului; corectia de perimetru nu a mai
ajuns la el.
**De facut in sesiunea urmatoare, in aceasta ordine:**
1. Scoate din `AplicaModificareTrul` cele trei campuri de valoare din `REPLACE` si cele trei
linii care le calculeaza (`lnValoarecTVA`, `lnValoareTVA`, `lnValoareFtva`), plus
`lnCantitate` daca ramane nefolosit. Raman `pretv`, `pretvtva`, `tvav`.
Verifica si `LOCAL`-urile declarate, si comentariul-antet al lui `AplicaSincronizareArticole`.
2. Write-back pe `omodificari.vc2` (`txt2vcx.ps1 -TextFile ... -AllowComun`), apoi **dovada prin
reconversie** cu `vcx2txt.ps1` intr-un cache temporar si `diff` - trebuie 0 linii.
3. Ruleaza cele doua suite (vezi mai jos), o suita per apel, cu `.fxp` sters inainte.
4. Raspunde la intrebarea de fapt ramasa deschisa (cine recalculeaza
`valoarev`/`valtvav`/`valoarevcTVA` dupa sincronizare) - **doar raspuns, fara reparatie**.
5. Adauga cele doua puncte in changelog, regenereaza `docs\diff_r6_punct6.md` si cere aprobarea
de commit.
Nimic nu a fost testat: **cele doua suite nu au fost rulate**.
## 2. Runda 7 — IN LUCRU la subagentul `r7-sincronizare`
Brief executabil, cu liniile si formula: `docs\brief_r7_sincronizare.md`.
Raportul lui va fi la `docs\raport_r7_sincronizare.md`.
**M1** — sincronizarea articole-rulaje porneste **doar din buton**. Se sterge blocul "al doilea
punct de declansare" din `frm_modific2024.inainte_de_do_termin`, `omodificari.vc2:14484-14494`.
Butonul `pgfArticole.PAGE3.cmdSincronizeazaArticole.Click` (`:16671`) ramane singura cale.
Dupa stergere raman **fara consumator** metoda `SemnaturaDivergenteSincronizare` (`:14840`),
proprietatea `cSemnaturaSincronizare` si atribuirea de la `:14956` — se semnaleaza, **nu** se sterg.
**M2** — `AplicaModificareTrul` (`ofacturare_editare.prg:924`) scrie azi doar `cant`/`cante` si
`pretvtva`; trebuie sa scrie **si `pretv`, si `tvav`**.
- **Perimetru corectat de Marius**: **DOAR `pretv` si `tvav`.** Versiunea initiala a brief-ului
cerea si `valoarev`/`valtvav`/`valoarevcTVA` — **respinsa**: sincronizarea e intre cantitate si
preturi, nu valori. Nu o reintroduce.
- **Intrebare de fapt inca deschisa**, ceruta subagentului, fara modificare de cod: cine recalculeaza
`valoarev`/`valtvav`/`valoarevcTVA` pe randul `trul` dupa sincronizare, inainte de scrierea in
Oracle? Trei concluzii posibile — le recalculeaza cineva / le recalculeaza salvarea / nu le
recalculeaza nimeni si ajung vechi in Oracle. In ultimul caz **se raporteaza ca defect, nu se
repara**; decide Marius.
- Formula canonica, **de reutilizat, nu de reinventat**: `omodificari.vc2`, `calculeaza_valori_rul`,
ramura `pretvtva`, `:13566-13587`. Doua precizii diferite: `gnPPretV` pentru preturi, `gnPC` pentru
valori.
- `calculeaza_valori_rul` **nu se poate apela direct** din sincronizare: isi alege cursorul din
`This.pgfArticole.ActivePage` (`:13532-13533`) si nimereste `trul` doar cand pagina activa e 1;
sincronizarea porneste de pe pagina 3, deci ar scrie in `trul_obinv`.
### Teste obligatorii pentru runda 7
`COMUN\utile\Teste\editare_factura\test_s4b_sincronizare.prg` (42 cazuri, **sectiunea C** e chiar
directia `ARTICOLE_SURSA`) si `test_s4b_dialog.prg` (35 cazuri). Reguli: sterge `.fxp`-ul inainte de
fiecare rulare; **o singura suita per apel** de comanda; cifra se ia numarand `PASS`/`FAIL` din log,
iar dovada ca rularea a ajuns la capat e **linia de `REZULTAT`** (care contine ea insasi cuvintele
`PASS` si `FAIL` — un `Select-String` naiv raporteaza cu unu mai mult din fiecare).
## 3. Changelog
`changelog_roafacturare.txt` — intrarea rundei 6 e **scrisa**, in blocul existent **2.11.15**, data
mutata la 20/08/2026. Versiunea **nu se bifurca** cat timp #6 nu e in productie (decizia lui Marius,
commit `09f9d47`).
**Ramas de adaugat**, dupa ce runda 7 e gata: sincronizarea porneste doar din buton, si copierea
`pretv`/`tvav` in rulaj.
## 4. Ce se comite, cand vine aprobarea
SVN (sursa de adevar), **tintit** — pe binare, niciodata pe `.vc2`/`.sc2`:
- `COMUN\clase\ofacturare_comun.vcx` + `.vct`
- `COMUN\clase\omodificari.vcx` + `.vct` (dupa runda 7)
- `COMUN\programe\ofacturare_editare.prg` (dupa runda 7)
- `changelog_roafacturare.txt`
- `docs\progres.md`
**Nu**: `roafacturare.PJT` / `.PJX` / `.exe`. Imediat dupa `svn commit` -> `roa_sync.bat`, si se
raporteaza revizia SVN si ce a intrat pe `main`. Fisierele noi din `docs\` sunt doar in git.
Diff-ul pentru aprobare, deja generat pentru punctul 6: `docs\diff_r6_punct6.md` (contine si
rationamentul filtrului, cu cifrele pe date). **Trebuie regenerat** dupa ce intra runda 7.
## 5. Capcane de mediu platite in aceasta sesiune
- **`python <<EOF` si `python -c "..."` nu executa nimic** prin tool-ul Bash de pe masina asta —
exit 0, zero efect, fara eroare. Scrie scriptul intr-un fisier si ruleaza-l pe cale absoluta.
Pentru `.vc2` foloseste **mod binar** (`'rb'`/`'wb'`): e CRLF peste tot si cp1250 pe diacritice.
- **`txt2vcx.ps1` are parametrul `-TextFile`, nu `-Source`** (`vcx2txt.ps1` are `-Source`). Ambele au
nevoie de `-ProjectRoot` si `-CacheRoot` date explicit pentru acest proiect.
- **Write-back-ul esueaza cat timp ruleaza `vfp9.exe`** — tine `.vcx/.vct` exclusiv. Se verifica cu
`Get-Process vfp9` **inainte**. Procesul e al lui Marius: **nu se omoara**, se cere inchiderea.
- **Un subagent poate raporta `idle` si apoi sa scrie in fisier.** S-a intamplat; era sa produca doi
scriitori pe aceeasi metoda. Verifica `mtime`/md5 pe disc inainte sa preiei un fisier, si spune-le
explicit sa nu raporteze idle cu lucru in curs.
- `svn status` cere `--depth` sau atentie la externals: `COMUN` apare ca `X` (external), iar starea
lui se vede separat.
---
## ACTUALIZARE FINALA 20.08.2026 22:40 — toate cele trei lucrari sunt gata
Pasii 1-5 din lista de mai sus sunt **executati**. Handoff-ul de mai sus ramane ca istoric; starea
curenta e cea de aici.
1. **Perimetrul M2 corectat** — `valoarev`/`valtvav`/`valoarevcTVA` scoase din `AplicaModificareTrul`,
impreuna cu `lnValoarecTVA`/`lnValoareTVA`/`lnValoareFtva`/`lnCantitate` si cu `LOCAL`-urile si
comentariile-antet ale ambelor functii. Raman `pretv`, `pretvtva`, `tvav`.
2. **Write-back pe `omodificari.vc2` FACUT si dovedit** — nu mai exista fisier editat fara binar.
Prima incercare a picat la fidelity check: subagentul lasase linia 14485 goala in loc de `\t\t`.
Restaurata. Dovada pe binarul din proiect: md5 `d23691466b7017380fe36099943b3208`, **diff 0 linii**.
3. **Ambele suite rulate**: `test_s4b_sincronizare` **44/0**, `test_s4b_dialog` **35/0**.
A fost nevoie de o corectie de fixture (`gnPPretV`, coloanele `pretv`/`tvav` in cursorul `trul`)
plus un caz nou cu TVA 19% — detalii in `docs\raport_r7_sincronizare.md`.
4. **Intrebarea de fapt: raspunsa** — nu le recalculeaza nimeni; `VALOAREV`/`VALTVAV` ajung invechite
in Oracle, `VALOAREVCTVA` nu e coloana in `RUL`. Raportat ca defect, **nereparat**.
5. **Changelog completat** (2 randuri in `:modificare:`, blocul 2.11.15) si **diff regenerat** ca
`docs\diff_r6_r7_pentru_commit.md` (inventarul intregului commit; `docs\diff_r6_punct6.md` ramane
ca rationament detaliat al punctului 6).
### Nimic nu e intr-o stare periculoasa
- Niciun fisier editat fara write-back. Nicio tranzactie deschisa. `vfp9.exe` nu ruleaza.
- **Nimic nu e comis** — nici SVN, nici git, nici in `COMUN`. Se asteapta aprobarea pe
`docs\diff_r6_r7_pentru_commit.md`, apoi `svn commit` tintit + `roa_sync.bat`.
- `roafacturare.PJT`/`.PJX`/`.exe` raman modificate din sesiunea de VFP a lui Marius: **nu se comit**.

View File

@@ -1,199 +0,0 @@
# Dosar de livrare — S5 (scrierea sumelor editate in Oracle)
Bloc #6 / S5, parte din "editare factura emisa". Stare la 10.08.2026. Scop: Marius decide
commit / push / SVN pe baza acestui fisier, fara sa recititeasca cele zece rapoarte de mai jos.
Surse: `docs\progres.md` (sectiunea #6/S5), handoff intermediar (sters) (deciziile 38-41),
`docs\cercetare\rec_s5_scriere_reala.md`, `docs\cercetare\rec_s5_discount_valuta.md`,
`docs\cercetare\rec_s5_teste.md`, `docs\cercetare\rec_s5_grid_articole.md`.
## 1. Ce livreaza S5
Pana acum, editarea unei facturi deja emise (#6/S4) modifica doar ecranul si nota interna —
modificarile facute liniilor de articole (cantitate, pret, discount, articole adaugate sau
sterse) nu ajungeau in baza de date. S5 inchide exact acest pas: la salvare, toate modificarile
se scriu real in Oracle, intr-o singura tranzactie, si totalurile documentului se recalculeaza
din liniile efectiv salvate, nu din liniile vechi.
In plus:
- gridul de articole primeste o coloana noua, "Pret achizitie", editabila doar pe liniile noi
adaugate (pe liniile existente valoarea veche ramane neatinsa);
- liniile care fac parte dintr-un set de articole devin needitabile individual, cu marcaj vizual
distinct — totalul unui set se calculeaza din capul lui, nu din componente, deci editarea unei
componente n-ar fi schimbat tacut totalul;
- cinci validari noi opresc sau avertizeaza la salvare: cantitate invalida, articol lipsa, pret
lipsa, factura ramasa fara nicio linie activa, si pret de achizitie necompletat pe linie noua.
Scrierea a fost testata cu date reale in Oracle (nu doar simulat), inclusiv doua editari
succesive ale aceleiasi facturi, ca sa se confirme ca o linie stearsa ramane stearsa si la
reeditare.
## 2. Fisiere atinse
| Fisier | Repo | Ce s-a schimbat | Patch |
|---|---|---|---|
| `clase\omodificari.vc2` | COMUN | Coloana `pret_achizitie` in grid, editabilitate per rand (linii de set needitabile, marcaj albastru), 5 validari noi in `inainte_de_do_termin` | diff aplicat (sters) |
| `clase\ofacturare_comun.vc2` | COMUN | Agatarea apelului `ScrieArticoleFacturaEditate` dupa `finalizeaza_modificare_nota` (primul punct de intrare, `do_editare_factura`) | diff aplicat (sters) |
| `clase\comun.vc2` | COMUN | Aceeasi agatare, al doilea punct de intrare (`:2491`) | diff aplicat (sters) |
| `programe\ofacturare_editare.prg` | COMUN | Helper nou `ScrieArticoleFacturaEditate` (marcheaza tot sters / invie ce ramane / insereaza linii noi / apeleaza recalculul Oracle) | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_page3_articole.prg` | COMUN | Asteptari actualizate: `ColumnCount=15`, tip si editabilitate pe coloanele noi | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_s5_validari_articole.prg` (nou) | COMUN | Suita headless: validari + SQL generat de helper, cu mock pe `goExecutor` | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_ui_s5_grid_pret_achizitie.prg` (nou) | COMUN | Suita UI vizibila: coloana noua, needitabilitate pe linii de set, focus pe linie noua | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_s5_scriere_reala.prg` (nou) | COMUN | Test cu scriere reala in Oracle, doua treceri, COMMIT real | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_s5_discount_valuta.prg` (nou) | COMUN | Test parametru discount (NULL vs 0 vs valoare) + documente reale in valuta | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_s5_rollback_real.prg` (nou) | COMUN | Calea de ROLLBACK la esec partial, cu eroare Oracle provocata deliberat | diff aplicat (sters) |
| `utile\Teste\editare_factura\test_s5_al_doilea_intrare.prg` (nou) | COMUN | Al doilea punct de intrare (`comun.vc2:2491`) parcurs real | diff aplicat (sters) |
| `docs\oracle_export.md` | COMUN | Corectie documentatie: `linesize 32767` in loc de 400 (cauza incidentului de export, vezi sectiunea 3) | fara patch dedicat |
| `docs\scripturi-migrare-db.md` | COMUN | Corectie documentatie: `UpdateVersiune` nu primeste extensia `.sql` in argument | fara patch dedicat |
| `versiune_db.txt` | ROAFACTURARE | `2026_08_08_01` -> `2026_08_09_02` | fara patch (fisier text, un rand) |
**Curatare facuta 10.08.2026**: sterse `clase\comun.pre_s5_agatare.bak.vc2`,
`clase\ofacturare_comun.pre_s5_agatare.bak.vc2` (verificate `cmp` **identice cu git HEAD** inainte
de stergere, deci recuperabile) si `utile\Teste\editare_factura\test_nume_coloana_modificat.log`
(reziduu de test, regenerabil).
**Lasat pe disc, decizia lui Marius**: `clase\ofacturare_comun.pre_s4butoane.bak.vc2` — backup din
S4 care **difera** de git HEAD, deci e o stare intermediara nerecuperabila din istoric. Nu l-am
sters tocmai de aceea. Nu e cod de productie si nu e trackuit.
### Write-back binar, dovedit prin reconversie (10.08.2026)
Raport: `docs\verificare_s5_writeback.md`. Fiecare `.vcx` reconvertit in text intr-un cache temporar
si comparat octet cu octet cu `.vc2` din arbore — **nu** pe mtime, care nu e martor
(`txt2vcx.ps1:300` rescrie mtime-ul textului la „cel mai nou binar + 1s").
| Fisier | Octeti | Linii diferite | Cens `> 0x7F` conform reperului |
|---|---|---|---|
| `omodificari.vc2` | 539470 | 0 | da (`2 aa · 2 e3 · 2 fe`, 0x `EF BF BD`) |
| `ofacturare_comun.vc2` | 242984 | 0 | da (`1 aa · 3 ba · 2 ce · 2 e3 · 2 ee · 2 fe`, 0) |
| `comun.vc2` | 330100 | 0 | da (`1 ee`, 0) |
Blocul de agatare confirmat textual, cu `-1` (nu `0`), la `ofacturare_comun.vc2:3828-3830` si
`comun.vc2:2491-2493`.
## 3. Modificari de baza de date
Doua scripturi, **deja aplicate in `MARIUSM_AUTO`**, mutate 10.08.2026 in
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08` si adaugate in SVN (`svn add`, status `A`, **necomise**):
- **`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** — adauga procedura
`recalculeaza_totaluri_vanzari(V_ID_VANZARE, V_DISCOUNT DEFAULT NULL)` in pachetul
`PACK_FACTURARE`. `NULL` pastreaza discountul curent al documentului, o valoare explicita
(inclusiv `0`) il inlocuieste. Pachet `VALID`, 0 erori dupa aplicare.
- **Incident, rezolvat**: prima aplicare a picat cu `ORA-00920`, cauzat de un export
`all_source` facut cu `linesize` prea mic, care a rupt o linie prin mijlocul identificatorului
`PRET_ACHIZITIE`. Scriptul a fost reasamblat de la ultimul script real aplicat pe disc, nu de
la export; a doua aplicare a reusit curat. `docs\oracle_export.md` corectat ca sa nu se repete.
- **`ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql`** — adauga coloanele `ID_VANZARE_SET` si
`PRET_ACHIZITIE` in view-ul `VVANZARI_ARTICOLE`. Aplicat inaintea celuilalt script, altfel
gridul nu era testabil. View `VALID`, 23 de coloane.
`versiune_db.txt` = **`2026_08_09_02`** (deja actualizat pe disc, vezi sectiunea 2).
## 4. Dovezile de testare
| Suita | Cifra | Ce dovedeste |
|---|---|---|
| `test_s5_validari_articole.prg` (nou, headless) | 35 PASS / 0 FAIL | Cele 5 validari din `inainte_de_do_termin` pe instanta reala; SQL-ul generat de `ScrieArticoleFacturaEditate` (clasificare linii, oprire la primul esec, discount NULL vs valoare) prin mock pe `goExecutor`, fara Oracle |
| `test_ui_s5_grid_pret_achizitie.prg` (nou, formular vizibil) | 14 PASS / 0 FAIL | Grid cu 15 coloane, coloana "Pret achizitie" legata corect, linie de set needitabila cu marcaj albastru, `pret_achizitie` primeste focus doar pe linie noua |
| `test_s5_scriere_reala.prg` (nou, scriere reala in Oracle) | 25 PASS / 0 FAIL | Ordinea din decizia 38 dovedita in tranzactie (linia stearsa e reinviata de reset, apoi corectata de helper); a doua trecere dovedeste ca stergerea ramane definitiva la reeditare; linie noua + `pret_achizitie` scris corect; totaluri recalculate coerente. Verificat independent prin `sqlplus` |
| `test_s5_discount_valuta.prg` (nou, scriere reala + citiri cu ROLLBACK) | 46 PASS / 0 FAIL | `NULL` pastreaza discountul curent, `0` explicit il zeroeaza — dovedit pe 4 apeluri succesive in tranzactie; recalculul e corect si pe un document real in valuta (`id_vanzare=1037`, cu ROLLBACK); lantul complet de salvare cu discount nenul, COMMIT real |
| `test_s5_rollback_real.prg` (nou) | 13 PASS / 0 FAIL | Calea de ROLLBACK la esec partial: contractul helperului prin mock (opreste la prima comanda esuata, `.F.`, zero comenzi in plus) **si** starea reala in Oracle — `ORA-02291` provocat deliberat pe `INSERT`, executie partiala vazuta necomisa in tranzactie, apoi ROLLBACK → document identic, verificat prin `sqlplus`. Cele 2 linii `EROARE` din log sunt exact eroarea provocata intentionat |
| `test_s5_al_doilea_intrare.prg` (nou) | 17 PASS / 0 FAIL | Al doilea punct de intrare (`comun.vc2:2491`, `afisjurcom.do_modifica`) parcurs **real**, nu doar verificat static: `tvanz` se populeaza pe calea asta, blocul nou se executa, lantul comite |
| Regresie (6 suite existente, neregresate) | `test_page3_articole` 14/2 (2 = artefact headless cunoscut) · `test_incarca_vanzare_din_nota` 5/0 · `test_adauga_linie_articol` 20/0 · `test_adauga_linie_valuta` 16/0 · `test_ui_sterge_linie` 8/0 · `test_verdict_act_rul` 26/0 | Nicio functionalitate anterioara (#6/S4) nu s-a stricat |
**Atentie separata, semnalata in `rec_s5_teste.md`**: pentru `test_page3_articole.prg`,
`raport_teste.ps1` (uneltele automate) raporteaza 10/0, nu cifra reala 14/2 — suita scrie o parte
din verdicte intr-un format pe care regexul automat nu-l prinde. Cifra corecta (14/2, numarata
manual din log) e cea folosita peste tot in acest dosar. Nu e o regresie noua.
Nicio contradictie de cifre intre rapoarte pe restul suitelor.
## 5. Ce NU e acoperit
*Doua dintre golurile de mai jos s-au inchis pe 10.08.2026 — vezi
`docs\cercetare\rec_s5_goluri_test.md`. Ce a ramas:*
- **Liniile din seturi de articole** (`id_vanzare_set` nenul) — **neacoperibil pe datele actuale**,
nu din omisiune: interogare pe `MARIUSM_AUTO`, **zero** documente cu `id_vanzare_set` nenul in
luna curenta, iar garda de editare cere luna curenta. Nu s-au fabricat date. Tratarea corecta a
unei astfel de linii de catre helper e acoperita prin mock (`test_s5_validari_articole.prg`).
- **Lantul complet de editare pe documente in valuta** — niciun document `IN_VALUTA=1` din baza nu
e din luna curenta (cel mai recent, 05.2026), iar garda de editare cere luna curenta. S-a putut
testa doar recalculul Oracle izolat (cu ROLLBACK), nu fluxul complet de salvare pe un document in
valuta.
- **Ramura moarta `Isnull(pret)`** (`omodificari.vc2:14340`) — consemnata, nereparata. Verificat
empiric: `tvd.pret` vine `NOT NULL` din view, orice incercare de a forta `.NULL.` da eroare VFP
1581. Garda defensiva imposibil de declansat pe fluxul real; inofensiva, lasata neatinsa.
**Wart preexistent, semnalat dar NEATINS de S5** — la o eroare Oracle in acest lant de scriere,
`oproceduri_comune.prg:421-424` afiseaza `Eroare necunoscuta` + SQL brut + `GETCALLSTACK()`.
Functional e corect (mesaj, apoi `.F.`, apoi ROLLBACK), dar textul e nepotrivit pentru utilizator.
Priveste **toata** suita ROA, nu doar editarea de factura, deci nu se repara aici.
*Nota, ca sa nu se reia alarma*: acelasi `AMESSAGEBOX` agata testele headless — e capcana cunoscuta
de dialog nativ, **nu** un defect de productie. Diagnosticul complet in `rec_s5_goluri_test.md`.
## 6. Date de test consumate ireversibil
Pe `id_vanzare = 1049` (factura tip 1, 07.08.2026):
- `cod` realocat succesiv: `1140887` -> `1140896` -> `1140897` -> `1140898` -> `1140900`
(cel curent, verificat prin `sqlplus` la 10.08.2026).
- `det=1582` a ramas **sters definitiv** in urma testului de scriere reala.
*Incident, reparat si verificat*: o prima varianta a suitei `test_s5_al_doilea_intrare.prg` a
**reinviat tacut** linia `1582`, pentru ca apela helperul fara sa incarce `tvd` in prealabil —
garda no-op pe cursor gol (`ofacturare_editare.prg:476`) a mascat greseala. Corectat pe loc cu
`UPDATE` + `COMMIT`, iar varianta finala a suitei incarca `tvd` ca `Load()`-ul formularului si
dovedeste explicit ca `1582` ramane sters dupa salvare. **Verificat independent de orchestrator
prin `sqlplus`**: `1582` are `STERS = 1`.
- `det=1588` e o linie **noua**, creata de test, cu `id_gestiune=-1000` si `pret_achizitie=77.77`.
- Discountul documentului a trecut prin `12.5`, apoi a fost **restaurat la `0`** — verificat prin
`sqlplus` dupa test.
- Totalurile documentului: `573.81` (initial) -> `905.02` (dupa scrierea articolelor).
Pe `id_vanzare = 1037` (`cod=1140730`, document in valuta, arhiva 05.2026): doar **citit**, testul
s-a inchis cu ROLLBACK — neatins, verificat prin `sqlplus`.
## 7. Intrarea de changelog — APLICATA 10.08.2026
Adaugata in capul lui `changelog_roafacturare.txt` ca **2.11.15**, tag `:nou:` (completeaza
functionalitatea de editare factura din #6, introdusa in S4). Verificat dupa scriere: CRLF intact
(3876 = 3876 = 3876), octeti `> 0x7F` neschimbati (3, toti preexistenti).
```
<!--
10/08/2026
ROAFACTURARE - 2.11.15
:nou:
Factura. La editarea unei facturi deja emise, modificarile facute articolelor (cantitate, pret, discount, adaugare sau stergere de linie) se scriu acum si in baza de date, la salvare - anterior ramaneau doar in ecranul de editare. S-a adaugat coloana "Pret achizitie" in grid, editabila pentru articolele nou adaugate pe factura. Liniile care apartin unui set de articole nu se mai pot edita individual - se marcheaza distinct, iar totalul se calculeaza tot din capul setului.
-->
```
## 8. Pasi de livrare — stare la 10.08.2026
| # | Pas | Stare |
|---|---|---|
| 1 | Mutarea scripturilor SQL in `SCRIPTURI_CLAR\2026\08` + `svn add` | **FACUT**, status `A`, necomis |
| 2 | Blocul de changelog `2.11.15` in `changelog_roafacturare.txt` | **FACUT** |
| 3 | Inchiderea golurilor de acoperire din sectiunea 5 | **FACUT** — ROLLBACK 13/0, al doilea punct de intrare 17/0; liniile de set raman neacoperibile (zero date) |
| 4 | Verificarea write-back-ului binar prin reconversie pe cele 3 `.vc2` | **FACUT** — toate trei identice octet cu octet, vezi sectiunea 2 |
| 5 | Curatarea fisierelor netrackuite din COMUN | **FACUT** — 2 `.bak` + 1 log sterse; `pre_s4butoane.bak.vc2` lasat (difera de git HEAD) |
| 6 | **Commit git**, dupa aprobarea lui Marius | **FACUT** — `COMUN` `1c42ae0` (13 fisiere), `ROAFACTURARE` `316b6f7` (2 fisiere), ambele pe `punct6-s4-runda3` |
| 7 | **Verificarea pe ecran de Marius** — coloana "Pret achizitie", marcajul liniilor de set, cele 5 validari | ramane a lui |
| 8 | **Push git si commit SVN** (inclusiv cele doua scripturi `A` din `DATABASE`) | **NEFACUT** — publicare pe server, decizie separata a lui Marius |
### Ce a ramas necomis, deliberat
- **`svn commit`** — nici in `DATABASE` (cele doua scripturi, status `A`), nici in vreun alt repo.
SVN e sursa de adevar si commit-ul merge direct pe server, deci e publicare, nu pas local.
- **`git push`** — pe niciunul din cele doua repo-uri.
- **`docs\`** din `ROAFACTURARE` — netrackuit dintotdeauna, nu l-am introdus in git acum.
- **`COMUN\clase\ofacturare_comun.pre_s4butoane.bak.vc2`** — vezi sectiunea 2.
### Ce nu s-a rulat inainte de commit
`git_sync.ps1` nu a fost rulat in `ROAFACTURARE` inaintea commit-ului `316b6f7`: acesta atinge doar
`changelog_roafacturare.txt` si `versiune_db.txt`, niciun binar VFP, iar o resincronizare ar fi adus
in commit zgomot din `.??2`-uri nelegate de S5. Daca textele din `ROAFACTURARE` sunt invechite fata
de binare, e stare preexistenta, nu efect al S5.

View File

@@ -1,362 +0,0 @@
# Plan #6 — editarea unei facturi emise, netrimisa inca in eFactura
Sursa: `COMUN\docs\todos.txt` punctul 6.
Ordine de executie: **al treilea**, dupa #8 si #7.
> **Stare la 11.08.2026: IN LUCRU, aproape terminat.** S1-S7 si S9 gata si comise; S4b s-a incheiat
> cu butonul si dialogul de sincronizare, iar S7 a iesit 6 PASS / 0 FAIL (trei editari consecutive nu
> acumuleaza linii de corectie).
>
> **Ramane S8**, si nu doar ca acoperire: matricea are **2 esecuri reale** pe „factura din aviz"
> (tip 4), din ambele puncte de intrare — rulajele nu se refac pe nota noua (`Reccount(trul)=0`).
> Tot in S8, trei tipuri de sursa au fost sarite la ultima rulare (lista de preturi, aviz, factura din
> contract), iar harnessul care ar trebui sa creeze documentele lipsa
> (`COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg`) nu a scris inca niciun document —
> capcanele de mediu sunt descrise in antetul lui.
>
> Neverificat inca pe ecran: dialogul de sincronizare, dupa rebuild-ul `roafacturare.exe`.
>
> Deciziile 38-41 **corecteaza S5 fata de ce scrie mai jos**; sunt in istoricul din `progres.md`.
## Cerinta si directia aleasa
Factura emisa trebuie sa poata fi corectata cat timp nu a plecat in eFactura
(`anaf_efactura.id_fact`). Problema: notele contabile si rulajele se genereaza complicat la emitere,
iar editarea de azi nu le sincronizeaza.
**Varianta respinsa: regenerarea prin re-emitere.** Emiterea face verificari de stoc si alte
protectii dependente de tipul documentului (lista de preturi, comanda, contract, retur, invoice); la
regenerare acestea ar esua pe date care intre timp s-au schimbat.
**Varianta aleasa: editare directa in `frm_modific2024`**, extins cu partea de
`vanzari`/`vanzari_detalii`, ca sa nu se duplice un formular de editare de note si rulaje.
### Doua puncte de intrare, o singura implementare (decizie Marius, 06.08.2026)
Editarea unei facturi de vanzare trebuie sa fie posibila **si din ROACONT > registru jurnal >
modificare**, adica exact prin procedura existenta de editare de note contabile / rulaje — nu doar
dintr-o actiune noua in formularul de facturi din ROAFACTURARE.
Consecinta pentru arhitectura: lucrarea **nu** e "cod apelant nou in ROAFACTURARE", ci
**extinderea clasei comune `frm_modific2024` + a partii server-side**, de unde ambele puncte de
intrare o primesc automat. Cazul "documentul curent e o factura de vanzare" se detecteaza in
formular, nu in apelant.
### `ID_FACT` nu se schimba — by design
`id_fact` se genereaza la introducerea documentului, din `nract` + `dataact`, si **ramane acelasi la
modificarea notelor contabile**; se schimba doar `cod`-ul setului de note/rulaje. Legatura initiala
cu `ACT`/`RUL` era `vanzari.cod`, dar intre timp s-a adaugat si perechea
`vanzari.id_fact` / `act.id_fact` / `rul.id_fact` / `documente.id_doc`, folosita de alte
functionalitati.
Deci `VANZARI.ID_FACT` **ramane neatins** la editare, iar realinierea priveste exclusiv `cod`-ul.
Orice ipoteza de rescriere a lui `id_fact` e gresita si a fost scoasa din plan.
## Ce s-a stabilit din cod
### Formularul exista deja si e direct utilizabil
`frm_modific2024` e o **clasa `.vcx`** (nu `.scx` monolitic), definita in
`COMUN\clase\omodificari.vc2` (clasa incepe la `:6375`, metodele proprii `:12200-15319`). E deja in
COMUN-ul ROAFACTURARE, deci **nu trebuie portata nimic**: `COMUN\clase` e deja in `SET CLASSLIB` /
`SET PROCEDURE`, iar clasa foloseste doar globale standard (`gnAn`, `gnLuna`, `goExecutor`, `gcs`,
`gnIdUtil`, `glLunaInchisa`), toate setate de `Programe\roafacturare.prg`.
Formularul editeaza **doar cursoare in memorie**; `do_termin` (mostenit din `_frmbase`) nu scrie
nimic, doar seteaza `gnButon=1` si inchide. Scrierea e treaba codului apelant.
Acelasi formular e si "modificare registru jurnal" — apelat din `afisjurcom.do_modifica`
(`comun.vc2:2222-2563`). Fluxul e deja documentat in
`D:\ROA\ROAGEST\COMUN\docs\flux-modificare-stergere-nota-jurnal.md`, verificat linie cu linie.
### Scrierea in ACT/RUL: o singura cale posibila
**Nu exista niciun `UPDATE`/`DELETE` direct pe `ACT` sau `RUL` in tot codul VFP** (cautat explicit).
Singura cale: cursoare -> `ACT_TEMP`/`RUL_TEMP` (INSERT-uri in `oscrie_in_fisiere.prg:127-136`,
`:272-274`) -> `PACK_CONTAFIN.init_scriere_act_rul_local` / `final_scriere_act_rul_local`.
Editarea **nu suprascrie randul vechi**: il marcheaza `STERS = 1` si scrie un document nou, cu `cod`
nou. Comportament intentionat, nu efect secundar.
### Sincronizarea cu VANZARI exista deja partial — si aici e lucrarea
`pack_contafin.finalizeaza_modificare_nota` (`COMUN\docs\PACK_CONTAFIN.pck:8601-8651`) apeleaza deja
`pack_facturare.actualizeaza_vanzari(cod_vechi, cod_nou)` cand `cod`-ul exista in `vanzari`
(iar `finalizeaza_stergere_nota` apeleaza `sterge_din_vanzari`).
Dar `actualizeaza_vanzari` (`ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql:15951-15961`) face **exclusiv**
realinierea legaturii:
```sql
UPDATE VANZARI_DETALII SET STERS = 0
WHERE ID_VANZARE IN (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI);
UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI;
```
Nu recalculeaza sume, nu atinge cantitati/preturi din `VANZARI_DETALII`, nu reactualizeaza totalurile
denormalizate din `VANZARI` (`total_fara_tva`, `total_tva`, `total_cu_tva`, `valoare_achizitie`,
`discount_tva`). Comentariul din cod (`:15954`) anticipeaza chiar aceasta extensie: *"de modificat in
caz ca il las sa stearga manual inregistrari din VANZARI_DETALII"*.
**Deci: azi, o editare de nota rescrie corect contabilitatea si pastreaza legatura cu factura, dar
lasa sumele facturii la valorile vechi.** Exact divergenta pe care o descrie cerinta.
### Calea de scriere in VANZARI_DETALII (stabilit 08.08.2026)
Detaliu complet: `docs\cercetare\rec_cale_vanzari_detalii.md`.
La **emitere**, liniile nu se scriu din VFP in `VANZARI_DETALII`. VFP cheama, per linie din
`crsfactura`, `pack_facturare.adauga_articol_factura` (`ofacturare.vc2:14069-14091`), care insereaza
in `VANZARI_DETALII_TEMP`; trecerea in tabela reala o face `scrie_in_vanzari`, cu un
`INSERT ... SELECT FROM VANZARI_DETALII_TEMP`, potrivit pe `(id_comanda, numar_act)`.
**`adauga_articol_factura` nu e reutilizabila la editare**: ramifica pe `pack_facturare.ntip`
(stare de sesiune) si **re-deriva pretul, TVA-ul si valuta din documentul-sursa** (comanda, aviz,
contract). La o factura deja emisa nu exista document-sursa de re-derivat, iar valorile editate de
utilizator ar fi suprascrise. `VANZARI_DETALII_TEMP` e GTT `ON COMMIT DELETE ROWS`, deci si
umplerea, si consumul ar trebui sa stea in aceeasi tranzactie.
**Calea aleasa pentru #6: scriere directa in `VANZARI_DETALII`**, fara sa treaca prin TEMP —
`UPDATE` tintit pe `ID_VANZARE_DET` pentru linii modificate, `STERS = 1` pentru linii scoase,
`INSERT` pentru linii adaugate. Modelul exista deja: `modifica_explicatie_articol`. `ID_VANZARE_DET`
vine automat dintr-un trigger `BEFORE INSERT` pe `SEQ_VANZARI_DETALII`, deci un `INSERT` de
oriunde primeste cheia corect.
Alternativa (refolosirea TEMP) ar fi cerut fie atingerea lui `adauga_articol_factura` /
`scrie_in_vanzari` — cod critic de emitere — fie duplicarea lor, adica exact tiparul care a produs
problema reparata la #8.
**Stergerea unei singure linii nu exista azi**: `sterge_factura` / `sterge_proforma` marcheaza
`STERS = 1` pe toate liniile documentului deodata. E functionalitate noua pentru #6.
### Punctul de agatare si garda existenta
- Sablonul arhitectural cel mai apropiat e `frm_facturi.do_sterge`
(`COMUN\clase\ofacturare_comun.vc2:4503-4719`): incarca cursoarele pe `cod`, porneste tranzactie
manuala, ruleaza `oscrie_in_fisiere`, apoi `pack_contafin.finalizeaza_stergere_nota`. Se cloneaza
acesta, **nu** `do_modifica` (care doar deschide un formular de metadate).
- Garda "nu edita daca a plecat in eFactura" exista deja, dar **in `do_modifica`, nu in
`do_sterge`** (`ofacturare_comun.vc2:4426-4430`: `select count(*) from anaf_efactura where
id_fact = ...` + `poRec.sters = 0 AND NVL(pnEFactura,0) = 0`). Azi e o simpla conditie de `If`
care deschide sau nu dialogul; pentru editare trebuie sa devina un `Return` cu mesaj, ca
celelalte garzi, si sa fie extrasa in `COMUN\programe\` ca sa fie apelabila si din `afisjurcom`,
care nu o are deloc.
- Gating-ul de drepturi pe `frm_facturi` **nu** e prin `nid_cw`, ci prin `This.lactiv3` / `lactiv4` +
tokeni in globala `gcAcces`. (Difera de `ofundal_facturare`, unde se foloseste `nid_cw` — de nu
confundat cu planurile #11/#10.)
## Stories
### S1 — Actiunea noua pe formularul de facturi (punctul de intrare din ROAFACTURARE)
Al doilea punct de intrare, ROACONT > registru jurnal > modificare, exista deja si nu are nevoie de
actiune noua — dar garzile de mai jos trebuie sa fie in formular sau in codul comun, ca sa se aplice
si acolo, nu doar in ROAFACTURARE.
Cloneaza structura din `do_sterge` (`ofacturare_comun.vc2:4503-4719`) intr-o actiune noua de editare:
garda eFactura + `sters=0` (refolosita din `:4428-4432`), plus garzile pe care `do_modifica` nu le are
azi dar `do_sterge` le are si care devin obligatorii cand se ating sumele: **luna inchisa**
(`glLunaInchisa`, `:4518-4520`), **luna curenta** (`:4543-4546`) si **referinte de incasari/plati**
(`ReferinteDocumenteNota`, `:4565-4569`).
Drepturi: **sub tokenul "3" existent** (decizia din 08.08.2026) — butonul nou se adauga in
`cbuton3` alaturi de `but_modifica1`/`But_modifica2`, si actiunea trece prin `This.lactiv3`. Fara
token nou, fara interventie in tabelele de drepturi.
*Gata cand:* actiunea apare, e vizibila doar cu drept, si refuza corect facturile trimise in
eFactura, sterse, din luna inchisa sau referentiate.
### S2 — Incarcarea cursoarelor notei
Dupa modelul `afisjurcom.do_modifica` (`comun.vc2:2222-2563`): incarca `vact_tot` / `vrul_tot` /
`vrul_obinv_tot` in cursoarele `tact` / `trul` / `trul_obinv`, filtrate pe `cod` + `an` + `luna`
(acelasi filtru ca la stergere).
*Gata cand:* pentru o factura data se incarca exact randurile ei de nota si de rulaj.
*Depinde de:* S1.
### S3 — Deschiderea `frm_modific2024` pe factura
Instantiaza clasa din `COMUN\clase\omodificari.vc2` cu cursoarele din S2. Cod apelant ~40 de linii,
fara modificari in clasa.
*Gata cand:* formularul se deschide din ROAFACTURARE si arata nota facturii; `gnButon=1` la
confirmare.
*Depinde de:* S2.
### S4 — Pagina noua de articole factura in `frm_modific2024`
Editarea randului din `vanzari` si a randurilor din `vanzari_detalii` intra **in acelasi formular**,
pe o pagina noua a pageframe-ului existent, dupa modelul paginilor de rulaje.
**Scop complet (decizia lui Marius, 08.08.2026)**: linii **sterse, adaugate si modificate**
(articol, cantitate, pret, flagul `pret_cu_tva` — vezi planul #7), plus **discountul de document**
(`VANZARI.DISCOUNT`) pe zona de antet.
**Fara plafon de cantitate si fara verificare de stoc.** Corectia unei facturi emise e
raspunderea utilizatorului; formularul nu-l opreste. Asta inchide intrebarea lasata deschisa de #7
(de unde vine plafonul cand cursorul de stoc din formularul de compunere nu exista) — raspunsul e
ca nu mai e nevoie de plafon. In schimb, obligatia se muta pe **helpere si verificari**, vezi S4b:
totaluri calculate, propuneri de sincronizare si semnalarea necorelarilor cu notele si rulajele.
**Drepturi**: sub tokenul "3" existent (modificare). Fara token nou.
**Liniile din seturi** se trateaza ca orice alta linie — fara ramura speciala in formular si fara
blocarea facturilor care le contin. Consecinta pentru S5: agregarea din `scrie_in_vanzari` are
ramura proprie pe `VANZARI_SETURI_TEMP`, deci recalculul trebuie sa acopere si liniile de set,
altfel totalurile diverg tacut exact pe facturile cu seturi.
Ancora concreta: `pgfArticole` din `omodificari.vc2` are azi
`PAGE1.Caption = "Rulaje materii prime, materiale, marfuri, obiecte inventar (303)"` cu `grdRulaje` si
`PAGE2.Caption = "Rulaje obiecte inventar in folosinta (8039)"` cu `grdRulajeObinv` (`:8598-8601`).
Se adauga `PAGE3` cu un grid de articole legat de cursorul de `vanzari_detalii`, plus zona de antet
pentru randul din `vanzari`.
Pagina se afiseaza **doar cand documentul curent are rand in `vanzari`**; altfel formularul arata
exact ca azi. Asta e conditia ca registrul jurnal din ROACONT/ROAGEST sa nu se schimbe pentru
documentele care nu sunt facturi.
Respecta `COMUN\docs\conventie_ux_formulare.md` si, pentru 3 griduri,
`COMUN\docs\capcana_grid_controlsource.md`. Clasa e in `COMUN\`, deci schimbarea e **cross-project**.
*Gata cand:* pagina apare pe o factura de vanzare deschisa din ambele puncte de intrare, si lipseste
pe un document care nu e factura.
*Depinde de:* S3.
### S4b — Helpere, totaluri si verificari de corelatie, **explicit, niciodata silentios**
Cerinta lui Marius: preturile si cantitatile din rulaje si cele din articolele facturii se pot
desincroniza la editare, iar programul **nu** are voie sa le alinieze pe tacute. Trebuie sa arate
utilizatorului ce nu se potriveste si sa **propuna** sincronizarea, ca actiune constienta.
Odata cu decizia din 08.08.2026 (S4 fara plafon si fara verificare de stoc), **S4b devine partea
grea a lui #6**: singura plasa de siguranta a utilizatorului. Nu mai e un adaos peste S4, ci
conditia care face editarea libera acceptabila. Acopera si liniile **adaugate sau sterse**, nu doar
valorile modificate.
Continut:
- **Totaluri de control vizibile in formular**, pe trei surse: suma din `RUL`, suma din
`VANZARI_DETALII` si suma din `ACT`. Cand cele trei nu coincid, diferenta se vede, nu se ascunde.
- **Indicator de stare a sincronizarii** (label/culoare), nu doar un mesaj la salvare.
- **Actiune explicita de sincronizare**, cu enumerarea liniilor care s-ar modifica si a valorilor
vechi/noi inainte de aplicare.
- Sincronizarea nu se declanseaza automat la `do_termin`.
Directia sincronizarii (rulajul e sursa sau articolul e sursa) se stabileste la implementare, dar
alegerea trebuie sa fie a utilizatorului, nu implicita.
*Gata cand:* o editare care desincronizeaza cele trei surse produce un indicator vizibil si o
propunere enumerata, iar refuzul propunerii lasa datele nemodificate.
*Depinde de:* S4.
### S5 — Scrierea sumelor editate in VANZARI (partea Oracle)
**Procedura sora noua** (ex. `recalculeaza_totaluri_vanzari`), apelata din
`finalizeaza_modificare_nota` dupa `actualizeaza_vanzari` — **nu** extinderea in-place a lui
`actualizeaza_vanzari`. Motivul: `actualizeaza_vanzari` ruleaza azi la ORICE editare de nota al
carei `cod` exista in `VANZARI`, inclusiv din registrul jurnal ROAGEST/ROACONT pe editari care nu
ating sumele; recalculul in-place ar propaga riscul in toata suita. Detaliu si sursa curenta:
`docs\cercetare\rec_s5_oracle_vanzari.md`.
Refoloseste acelasi calcul ca `scrie_in_vanzari` — nu o formula paralela, altfel cele doua cai
diverg exact ca in problema de la #8. Partea per-linie e **deja extrasa** in
`calculeaza_total_fara_tva_fact` / `calculeaza_total_tva_fact`, care ramifica deja pe
`PRET_CU_TVA`; de extras ramane doar blocul de **agregare**, parametrizat pe sursa
(`VANZARI_DETALII_TEMP` la emitere, `VANZARI_DETALII` la editare).
Doua constrangeri gasite la cercetare:
- **`SERIE_INCASAT` / `NR_INCASAT` / `SUMA_INCASAT` / `TIP_INCASAT` nu se recalculeaza.** Vin din
stare de sesiune specifica emiterii unei facturi-cu-incasare si n-au sursa persistenta; o copiere
naiva a intregului `UPDATE` din `scrie_in_vanzari` le-ar pune `NULL` la fiecare editare, rupand
legatura cu incasarea. Se scriu doar cele 11 coloane de totaluri/curs.
- **`VALVAL` / `TVAVAL` / `TOTVAL`** (totaluri in valuta) intra in recalcul, desi planul initial nu
le enumera: sunt in acelasi bloc de agregare, costa zero in plus si altfel raman incoerente pe
documentele in valuta.
**Discountul de document e editabil** (decizia din 08.08.2026), deci intra ca valoare noua in
recalcul, nu se citeste ca invariant din `VANZARI.DISCOUNT`.
Script de migrare conform `COMUN\docs\scripturi-migrare-db.md`: CRLF, idempotent, nivel Oracle 10.2,
`versiune_db.txt` actualizat.
*Gata cand:* dupa o editare de cantitate, `total_fara_tva`/`total_tva`/`total_cu_tva` din `VANZARI`
corespund sumei liniilor.
*Depinde de:* S4.
### S6 — Legaturile care depind de `cod`
Editarea scrie un document nou cu `cod` nou, iar `actualizeaza_vanzari` realiniaza `VANZARI.COD`.
`VANZARI.ID_FACT` **nu se schimba** (by design — vezi mai sus), deci tot ce se leaga prin `id_fact` /
`id_doc` ramane valid: `anaf_efactura`, `documente`, si perechile `act.id_fact` / `rul.id_fact`.
**Inchis pe cod la 08.08.2026** (`docs\cercetare\rec_s5_oracle_vanzari.md`, sectiunea C): toate
legaturile trec prin `ID_FACT` (`ACT.id_factd`/`id_factc`, `DOCUMENTE.ID_DOC`,
`ANAF_EFACTURA.ID_FACT`) sau prin `ID_VANZARE` (`vanzari_coresp`, `marcheaza_facturat`), niciodata
prin `cod`. `actualizeaza_vanzari` rescrie doar `COD` pe acelasi rand — `ID_VANZARE` nu se schimba
niciodata. `ReferinteDocumenteNota` e o garda pre-editare, nu o legatura persistenta. **Fara lucru
suplimentar**; ramane doar confirmarea pe fluxul real, in S8.
*Depinde de:* S5.
### S7 — Rotunjirea la reeditare
`verifica_total_document` (`:16009-16188`) insereaza automat o linie de corectie in `ACT_TEMP` cand
totalul difera de suma din note. La editare se aplica din nou — de confirmat ca nu se acumuleaza
corectii succesive la editari repetate.
*Gata cand:* trei editari consecutive nu lasa trei linii de corectie.
*Depinde de:* S5.
### S8 — Test pe fluxul real, din ambele puncte de intrare
Test headless (`COMUN\docs\depanare_testare_vfp.md`) + UI (`COMUN\docs\testare-ui-vfp.md`), pe cate un
caz din fiecare tip de sursa: lista de preturi, comanda, contract, aviz. Fiecare caz rulat **de doua
ori**: o data pornit din formularul de facturi (ROAFACTURARE), o data din registru jurnal (ROACONT).
Verifica: nota veche `STERS=1`, nota noua corecta, `id_fact` neschimbat, `vanzari`/`vanzari_detalii`
sincronizate, totalurile denormalizate corecte, rulajele refacute, totalurile de control din S4b
concordante, si ca **nu** s-au declansat verificarile de stoc de la emitere.
Al treilea caz obligatoriu: un document care **nu** e factura, deschis din registru jurnal — pagina
de articole nu apare si comportamentul e identic cu cel de azi.
*Depinde de:* S6, S7.
### S9 — Diff, review, changelog, documentatie
Diff ca fisier in `docs\`, commit dupa aprobare, in doua repo-uri (ROAFACTURARE si COMUN). Changelog
`:nou:`. Propune actualizarea `flux-modificare-stergere-nota-jurnal.md` cu ramura de facturi.
## Riscuri
- **Cel mai mare**: `omodificari.vc2` e in COMUN si serveste si registrul jurnal din ROAGEST/ROACONT.
O regresie acolo e mai grava decat lipsa functionalitatii aici. S4 trebuie sa pastreze modul "doar
nota" complet neschimbat.
- Modelul "sterge + scrie document nou cu cod nou" inseamna ca o factura editata isi schimba `cod`-ul
— orice raport sau integrare care tine minte `cod`-ul vechi il pierde. S6 exista tocmai pentru asta.
- Fara garzile de luna inchisa / luna curenta / referinte (azi absente pe `do_modifica`), editarea de
sume ar putea modifica o perioada deja raportata. Sunt obligatorii, nu optionale.
- Interactiunea cu #7: daca flagul `pret_cu_tva` devine editabil pe linie, editarea ulterioara trebuie
sa il trateze la fel ca introducerea.
## Dependente
- **#7** — S4 include editarea flagului `pret_cu_tva` pe linie; se face dupa ce #7 stabileste
comportamentul la introducere.
- **#8** — S5 refoloseste calculul totalurilor din `scrie_in_vanzari`, care se atinge in #8.
### Ce preda #7 catre S6 (consemnat 07.08.2026, decizia lui Marius)
Din #7 s-a livrat editarea flagului `pret_cu_tva` **doar** pe factura in curs de compunere
(inainte de salvare), prin `frm_facturare_articole.do_modifica` (buton `But_modifica1` +
dublu-clic pe `grd_factura`), care redeschide dialogul `frm_articol_factura`. Detaliu complet:
`docs\cercetare\rec_s2b_aplicare.md`.
Editarea aceluiasi flag pe o factura **deja salvata** a fost mutata explicit in #6, prin decizia
lui Marius din 07.08.2026. Motivul, exact: butonul existent din `frm_facturi` (`But_modifica2` ->
`do_modifica_explicatie`, **nu** `do_modifica`) deschide `frm_modifica_articol_factura`, care
editeaza doar `explicatie` si `taxcode` — doua campuri care nu ating nicio suma
(`pack_facturare.modifica_explicatie_articol` e un `UPDATE` de doua coloane). Cealalta actiune,
`do_modifica`, deschide `frm_modifica_factura` (antet: ruta, delegat, agent, serie/numar, date) —
tot fara sume.
Flagul `pret_cu_tva` e de alta natura: schimbarea lui reimparte baza si TVA-ul pe linie, deci
muta totalurile denormalizate din `VANZARI` si notele contabile — exact problema de fond a
punctului 6.
#7 lasa mostenire un model gata facut si verificat pentru partea de UI a acestei editari:
maparea de intrare `crsfactura` -> `poArticol` (`AddProperty`/`RemoveProperty` pe 7 perechi de
nume) si drumul invers (`Gather`+`Replace`), documentate in `docs\cercetare\rec_s2b_aplicare.md`.
Deci S4 nu trebuie sa reproiecteze dialogul, ci sa rezolve partea de persistenta/recalcul in
Oracle (vezi S5).
Limitarea `ncantitatemax` din #7 a fost eliminata tot in #7, prin decizia lui Marius din
07.08.2026: `do_modifica` recalculeaza plafonul din cursorul de stoc in loc sa il fixeze pe
cantitatea curenta a liniei, reconstituind cantitatea disponibila de dinainte ca linia curenta sa
o fi consumat. Mecanismul, pe scurt: regasire dupa `id_c` in `crsarticole`/`crsarticole1` (alegerea
cursorului dupa `opt_facturare`), apoi reversul decrementarii pe care `do_adauga_articol` a
aplicat-o deja — `cantitate + poArticol.cantitate` la tipurile de document cu verificare de stoc,
`cantitate - poArticol.cantitate` la retur; fallback pe cantitatea liniei daca randul nu mai e in
cursor. Detaliu complet: `docs\cercetare\rec_s2c_ncantitatemax.md`.
**Inchis pe 08.08.2026**: intrebarea era de unde vine plafonul de cantitate la o factura deja
salvata, unde cursorul de stoc (`crsarticole`/`crsarticole1`) poate sa nu existe. Decizia lui
Marius: **#6 nu are plafon si nu verifica stocul** — corectia unei facturi emise e raspunderea
utilizatorului. Nu e nevoie de interogare de stoc proprie. Efortul se muta in S4b (helpere,
totaluri, verificari de corelatie cu `ACT` si `RUL`).

View File

@@ -1,624 +0,0 @@
# Proiectare S4/S4b — pagina de articole factura in frm_modific2024
Cercetare + proiectare pentru `plan_06_editare_factura.md`, story S4 (pagina noua de articole) si
S4b (helpere/totaluri/verificari). NU e implementare — propunere supusa aprobarii lui Marius.
Stare de plecare (verificata, nu presupusa): S1-S3 sunt deja **implementate si testate**
(`docs\progres.md`, sectiunea "#6, runda 1"). Actiunea `frm_facturi.do_editare_factura`
(`COMUN\clase\ofacturare_comun.vc2:3727-3869`) si `afisjurcom.do_modifica`
(`COMUN\clase\comun.vc2:2222-2563`) sunt cele doua puncte de intrare reale, ambele deschid deja
`frm_modific2024` pe cursoarele `tact`/`trul`/`trul_obinv`. Tot ce descrie documentul de fata se
adauga **peste** acest cod existent, fara sa-l modifice decat unde e explicit spus (D, E).
## Deciziile lui Marius pe intrebarile din F (08.08.2026) — au prioritate fata de recomandarile din text
- **F.1 — respinsa recomandarea "strict facturi".** Pagina se aplica pe **orice rand din `VANZARI`**,
deci **si pe avize**. Tot ce spune A.3 despre restrangerea la lista de `TIP` de facturi se
reciteste in cheia asta.
- **F.2 — premisa din C.1 e gresita.** Contul nu e `4111` peste tot: **avizele folosesc `418`**. In
plus, nota contine **randuri de discount** si poate contine **note adaugate manual de utilizator**.
Deci filtrul fix `SCD='4111'` nu e o regula, ci o potrivire pe un singur document. Regula corecta
se stabileste in `docs\cercetare\rec_suma_act.md` (cercetare in curs), si **C.1 se rescrie dupa
ea**. Pana atunci, nu implementa indicatorul pe formula din C.1.
- **F.3 — raspuns de la Marius**: in `RUL` pot fi **si linii cu diferente de pret**, cand pretul de
vanzare din factura difera de cel din stoc — **doar pentru marfa tinuta la pret de vanzare**.
Deci **suma bruta din `RUL` nu e comparabila prin constructie** cu totalul documentului (pe
`cod=1140888`: 10 randuri `RUL` pentru 4 linii, 4476.28 fata de 1924.59). O bara de totaluri care
le compara direct ar semnala "desincronizat" permanent pe orice document cu marfa la pret de
vanzare. Subsetul comparabil se stabileste in `docs\cercetare\rec_suma_act.md`.
- **F.4 — varianta A** (bara de totaluri sub grid, permanent vizibila).
- **F.5 — alegere globala** a directiei de sincronizare, pe document, nu per linie.
- Separat, din decizia 18: **liniile din seturi se trateaza ca orice alta linie**.
- **Transfer si custodie** (23, 25, 30, 41, 27, 42, 47): pagina **apare**, dar **fara bara de
totaluri** — nu bara goala cu mesaj, ci fara ea. Pe aceste tipuri nu exista suma comparabila
(transferurile merg pe cont de stoc, custodia nu genereaza randuri `ACT` per articol).
- **Tipul 51 (ROAACNPRO) foloseste `4111`** — deci divergenta gasita in cercetare are alta cauza
decat contul; prima suspiciune e filtrarea pe `cod` fara `an`+`luna`.
- **Comparatia stricta e imposibila prin constructie**: `ACT` nu marcheaza originea randului, deci
un rand adaugat manual nu se distinge de unul generat. Bara de totaluri **arata cifrele si
diferenta, fara verdict automat de eroare**.
- **Garda pe `id_set` se scoate de tot** (decizia 24) — premisa ei a picat.
- **Documente mixte** (decizia 25): suma `RUL` se corecteaza cu valoarea liniilor nestocate din
`VANZARI_DETALII`, marcata in bara ca ajustata.
- **`RUL` are formula comparabila** (nu mai e doar informativ):
`SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ <> 3)`. **C.1 se rescrie** dupa
`docs\cercetare\rec_suma_act.md`, care are si propunerile concrete de corectie la final.
Ipoteza lui Marius de verificat, cu efect asupra codului deja scris: `id_set + 5` la discount ar fi
un **marcaj tranzitoriu** consumat inainte de `ACT`, nu o valoare persistata — caz in care garda pe
`id_set` din `do_editare_factura` (runda 3) e construita pe o premisa falsa.
## A. Ce se poate face concret pe `frm_modific2024`
### A.1 Structura curenta a `pgfArticole` si a celor doua pagini de rulaje
Clasa `frm_modific2024` incepe la `COMUN\clase\omodificari.vc2:6375`. Pageframe-ul:
- **Nu are `PageCount` propriu setat** in `ADD OBJECT 'pgfArticole'...` (`:8627-8641`) — mosteneste
`PageCount = 2` din clasa de baza `_pageframe` (`COMUN\clase\_baza.vc2:496`). Cele doua pagini sunt
configurate doar prin proprietatile `PAGE1.Caption/ForeColor/Name` si `PAGE2.Caption/ForeColor/Name`
in acelasi bloc `ADD OBJECT`.
- `PAGE1` contine `_grdfooter1` (`:8644-8655`, footer cu sume pe coloane, `csumcolumns=...`) si
`grdRulaje` (`:8657-...`, `ColumnCount=63`, `RecordSource="trul"`, `ReadOnly=.F.`).
- `PAGE2` are aceeasi structura pe `trul_obinv`/`grdRulajeObinv` (confirmat prin indexul de metode:
`pgfArticole.PAGE2.grdRulajeObinv.*`, `omodificari.vc2:15159-15424`), nu am recitit blocul `ADD
OBJECT` complet (nu era necesar — tiparul e identic cu PAGE1, doar alt cursor).
- **`Init`** (`:13551-13660`): primeste `Lparameters tnIdSet, tlNotaNoua, toBackupXML, toSet,
tlVizualizare`; seteaza `This.nid_set`, `lEditare`/`lVizualizare`/`lModificare`/`lVerificare`. Zona
`:13610-13643` ascunde `but_nou1`/`but_sterge1` si face `grid1` readonly pe seturi speciale
(id_set 25000-25099 sau o lista fixa) — logica veche, fara legatura cu facturile de vanzare (acele
id_set-uri sunt pentru note "cu scadere", nu pentru pagina noua).
- **`Activate`** (`:12238-12243`): la prima activare cheama `resize_grid1()`.
- `resize_grid1()` (`:13680-13688`) si `afiseaza_rulaje()` (`:12245-12281`) folosesc deja
`this.pgfArticole.Visible` ca switch — **dar e un toggle manual, comandat de utilizator** (butonul
`but_afiseaza_rulaje`, colapseaza/expandeaza TOT pageframe-ul ca sa faca loc pentru `grid1`), nu o
decizie "documentul e de tip X". Nu e mecanismul de folosit pentru afisarea conditionata a PAGE3.
- **`Show`** (`:13719-13775`) e unde se leaga efectiv footerele de sume la grid-uri:
`this.pgfArticole.page1._grdfooter1.attachtogrid(...)` +`.calcTotal()`, identic pentru PAGE2
(`:13748-13752`) si unde se ascunde automat tot pageframe-ul cand nu exista deloc rulaje
(`:13764-13770`, `If Reccount('trul')=0 And Reccount('trul_obinv')=0 Then This.afiseaza_rulaje()`).
### A.2 Mecanica adaugarii PAGE3
`PageCount` nu e o proprietate care se citeste o singura data — poate fi modificata la runtime, si
exact acest tipar exista deja in codebase, la un alt pageframe: `comun.vc2:10521-10531`
(`frm_...Init`) face `THISFORM.pgcod.PageCount = n+1` + `.Pages(n+1).Caption = 'Gestiuni'` cand
conditia e adevarata, altfel `PageCount = n` (pagina ramane ascunsa, pentru ca nu exista in
colectia activa de pagini). **Acesta e mecanismul recomandat pentru PAGE3**, nu `.Visible` pe
pagina (VFP pageframe `Page` are `Visible`, dar codebase-ul nu-l foloseste nicaieri pentru
show/hide conditionat — a fost cautat explicit, 0 rezultate `Page.*Visible` in `COMUN\clase`).
Propunere concreta:
- In definitia clasei, `ADD OBJECT 'pgfArticole'` capata `PageCount = 3` **explicit** (nu mai
ramane pe default-ul 2) + `PAGE3.Caption = "Articole factura"`, `PAGE3.Name = "PAGE3"`, plus
grid-ul nou `pgfArticole.PAGE3.grdArticoleFactura` (dupa modelul `grdRulaje`) si un
`_grdfooter` propriu (`pgfArticole.PAGE3._grdfooter1`), la fel ca PAGE1/PAGE2.
- La runtime, in `Show` (langa blocul `:13764-13770`, dupa acelasi principiu): daca documentul
curent **nu** e o factura de vanzare (vezi A.3), `This.pgfArticole.PageCount = 2` — PAGE3 dispare
din colectia activa de pagini, comportament identic cu azi pentru orice alt tip de nota. Cand
**este** factura, `PageCount` ramane `3` (valoarea din clasa) si se leaga footerul nou:
`this.pgfArticole.page3._grdfooter1.attachtogrid(...)` + `.calcTotal()`, exact ca la PAGE1/PAGE2.
- Consecinta directa: pentru **orice document care nu e factura** (99.9% din utilizarile din
ROAGEST/ROACONT), `PageCount` scade inapoi la 2 la fiecare deschidere — clasa arata **byte-cu-byte
ca azi**, nicio schimbare vizuala sau de comportament. Asta e conditia ceruta explicit in plan
(S4: "pagina se afiseaza doar cand documentul curent are rand in vanzari... altfel formularul
arata exact ca azi") si raspunde direct la riscul cel mai mare listat in plan.
### A.3 Cum decide formularul ca documentul curent are randuri in `VANZARI`
**Comparatia celor trei variante**, asa cum a cerut misiunea. Concluzia (varianta 3) nu se schimba
fata de versiunea anterioara a acestei sectiuni — se schimba doar **filtrul** aplicat dupa ea, cf.
`[DECIS — decizia 19 din progres.md]` mai jos:
1. **Test pe `id_set`** — respins. Intervalele facturilor si ale avizelor din
`COMUN\docs\tipuri_documente_facturare.md` se suprapun aproape complet (facturi:
25000-25009, 25042-25048, 25051, 50100 + variantele "cu scadere" +10; avize: 25020-25029,
25040-25041, 25046 + variantele +10 — ambele in acelasi interval brut 25000-25099). Un test de
forma `Between(tnIdSet,25000,25099)` (deja folosit in `Init`, `:13610-13612`, dar pentru alt
scop) ar prinde si avizele, nu doar facturile. Documentul insusi semnaleaza o coliziune
nerezolvata pe `25051` (punctul 3 din capcanele acelui fisier) — nu e o baza solida pentru o
decizie care controleaza afisarea/ascunderea unei pagini cu bani.
2. **Flag pasat de apelant** — respins ca mecanism principal. `afisjurcom.do_modifica`
(ROACONT/ROAGEST, registrul jurnal) e cod generic pentru **orice** tip de nota; azi nu stie si
nu are motiv sa stie ca documentul curent are randuri in `VANZARI` (asta a fost motivul pentru
care garda eFactura si cea de referinte au trebuit extrase in `COMUN\programe\` la S1, nu
lasate in `frm_facturi`). A cere unui al doilea apelant sa afle si sa transmita acest flag ar
duplica exact interogarea pe care oricum trebuie sa o facem undeva, cu riscul ca un al treilea
apelant viitor sa uite s-o transmita corect.
3. **`SELECT` in `vanzari` dupa `cod`** — recomandat. `tact` (cursorul pe care se leaga deja
formularul, incarcat de `IncarcaCursoareModificareNota`,
`COMUN\programe\ofacturare_editare.prg:27-140`) contine coloana `cod` din `vact_tot` (filtrul
`WHERE ... cod = tnCod` de la `:53` confirma coloana). `tact.cod` se poate citi **oricare ar fi
workarea curenta** (referinta calificata pe alias), deci **nu conteaza care apelant a deschis
formularul** — informatia necesara e deja in cursorul pe care oricum formularul il primeste
prin contract. Un singur `SELECT tip, id_vanzare FROM vanzari WHERE cod = <tact.cod>` (rulat o
singura data, la deschidere) da simultan raspunsul la "are rand in `vanzari`?" si, daca da,
`tip`-ul exact — necesar in Runda 4 pentru tratamentul special al transferului/custodiei
(decizia 22, mai jos).
**Recomandare**: detectia se face **in interiorul `frm_modific2024`** (nu in apelanti), intr-o
metoda noua apelata din `Init` sau din `Show` (inaintea blocului de la A.2), care citeste
`tact.cod`, interogheaza `vanzari` o singura data, si populeaza **trei** proprietati noi pe
formular: `This.lAreArticoleVanzari`, `This.nIdVanzare` si `This.nTipVanzare` (tip-ul documentului
din `vanzari`). **Asta inseamna ca PAGE3 apare automat din ambele puncte de intrare fara nicio
modificare in `do_editare_factura` sau `afisjurcom.do_modifica`** — exact arhitectura ceruta de
plan ("extinderea clasei comune, nu cod apelant nou").
Numele `lAreArticoleVanzari` (nu `lEsteFacturaVanzare`, cum se numea in versiunea anterioara a
acestei sectiuni) e ales deliberat: sub decizia 19 de mai jos flagul devine adevarat si pe avize,
transferuri si custodie, nu doar pe facturi — numele vechi ar fi mintit. `nTipVanzare` se retine
separat, tot din acelasi `SELECT`, pentru ca Runda 4 (decizia 22: transfer/custodie afiseaza
pagina, dar fara bara de totaluri) are nevoie sa stie tipul exact, nu doar "are/nu are randuri".
**`[DECIS — decizia 19 din progres.md]`** Filtrul de mai sus **nu** se restrange la lista de `TIP`
de factura — pagina apare pe **orice rand din `VANZARI`**, deci si pe avize, transfer intre
subunitati, transfer pe lucrare si custodie. Varianta "strict facturi" (recomandarea acestei
sectiuni intr-o versiune anterioara, cand decizia inca nu fusese luata) a fost **respinsa explicit
de Marius**. Consecinte pentru restul lucrarii:
- Garda eFactura/S1 ramane specifica facturilor si **nu se extinde** — pe avize/transfer/custodie
pur si simplu nu se aplica azi, pentru ca acele tipuri de document nu trec pe acolo.
- Scrierea (E, S5) si bara de totaluri (C.1, C.3) raman gatate separat, dupa `nTipVanzare`:
transfer/custodie (decizia 22) primesc pagina, dar fara bara.
- `frm_modific2024` deschis din `afisjurcom.do_modifica` (registrul jurnal ROACONT/ROAGEST) capata
aceeasi pagina pe orice document cu rand in `VANZARI`, nu doar pe facturi — de retinut la testarea
riscului D, care azi verifica doar cazul "document care nu e deloc in `VANZARI`".
### A.4 Lantul `inainte_de_do_termin` — unde intra validarile noi
`omodificari.vc2:13357-13549`. Ce face azi, pe scurt:
- `:13360-13367` reseteaza filtrul pe `tact`, completeaza `id_set` gol pe `tact`/`trul`/`trul_obinv`.
- `:13369-13386` verificare generica de completare cont/analitic/partener
(`verificare_note_contabile('tact',...)`, `oOperatii_comune`), sarita pentru seturile speciale
(`id_set` 99998/90024).
- `:13389-13402` (doar `gnAn >= 2013`): avertisment pe combinatia de conturi `4426-4428`/
`4428-4427` fara sa fi folosit optiunea dedicata, apoi `This.VerificaAvertizareExigibilizareTVA()`
(`:13909-14083`, verificare separata, neatinsa de aceasta propunere).
- `RETURN m.llRet` — daca oricare pas a esuat, formularul nu inchide (butonul Termina ramane
blocat pana la corectare).
**Punctul de agatare pentru validarile noi ale lui S4b**: chiar inainte de `RETURN m.llRet`
(`:13404`), un bloc nou gatat de `This.lAreArticoleVanzari` (A.3) — de exemplu, garda "nu lasa
utilizatorul sa iasa cu `buton=1` daca a marcat sincronizarea ca necesara dar n-a confirmat-o
explicit" (detaliu in C). **Nu inlocuieste nimic din ce exista azi** — se adauga dupa validarile
generice, cu acelasi tipar (`If m.llRet Then ... Endif`).
## B. Cursorul de articole
### B.1 Sursa si momentul incarcarii
Confirmat in `docs\cercetare\rec_cale_vanzari_detalii.md`: scrierea la editare merge **direct** in
`VANZARI_DETALII` (fara `VANZARI_DETALII_TEMP`, care e GTT populata doar la emitere). Simetric,
**citirea** pentru formular trebuie sa vina direct din `VANZARI_DETALII`, nu din vreo tabela temp.
Propunere: o functie noua in `COMUN\programe\ofacturare_editare.prg` (alaturi de
`IncarcaCursoareModificareNota`, acelasi stil de cod — `goExecutor.oExecute`, gestiune de eroare
simetrica), de exemplu:
```foxpro
FUNCTION IncarcaArticoleFactura
LPARAMETERS tnIdVanzare
* incarca header-ul (1 rand, cursor tvanz) si liniile active (cursor tvd) pentru factura data
* tvd/tvanz raman deschise READWRITE - apelantul (frm_modific2024) le foloseste si le inchide
ENDFUNC
```
Apelata **din interiorul `frm_modific2024`** (Init/Show, dupa ce A.3 a stabilit
`This.lAreArticoleVanzari = .T.` si `This.nIdVanzare`), nu din apelanti — acelasi motiv ca la A.3:
zero cod nou in `do_editare_factura`/`afisjurcom.do_modifica` pentru partea de citire.
`tvanz` (1 rand, header): `id_vanzare, cod, discount` (singurul camp editabil din antet, decizia
17 din `progres.md`) + campurile needitabile utile ca referinta vizuala (`total_fara_tva,
total_tva, total_cu_tva` — valorile **vechi**, denormalizate, afisate readonly langa totalurile
live din S4b, nu suprascrise decat de S5 la salvare).
`tvd` (liniile), coloane din `VANZARI_DETALII` (lista completa in
`rec_cale_vanzari_detalii.md`, sectiunea 2.2) — subsetul relevant editarii:
`id_vanzare_det, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, discount_unitar,
id_gestiune, cont, id_valuta, id_jtva_coloana, serie, explicatie, taxcode, lot, sters`.
Filtrul de incarcare: `WHERE id_vanzare = :tnIdVanzare AND sters = 0` (liniile deja sterse nu se
mai arata — simetric cu `tact`, care si el filtreaza `sters=0` la incarcare).
### B.2 Marcaje de stare, fara concept nou fata de restul clasei
Clasa foloseste deja doua idiomuri simple pentru starea liniilor din `tact`, ambele reutilizabile
ca atare pentru `tvd`, fara sa inventam un al treilea:
- **Sters = flag pe randul existent**, nu stergere fizica din cursor — exact cum `tact`/`trul`
reprezinta deja `STERS` ca o coloana obisnuita (rescrisa la scriere, nu `DELETE`d din cursor).
`tvd.STERS` (coloana deja prezenta in `VANZARI_DETALII`) se flipuieste in cursor la actiunea
"sterge linie"; la scriere (D), un rand cu `STERS=1` care avea `STERS=0` la incarcare devine un
`UPDATE ... SET STERS=1`.
- **Adaugat = `id_vanzare_det = 0`** — acelasi sentinel folosit deja in `do_adauga`
(`omodificari.vc2:12685`, `loadd.id_act = 0` pentru randurile noi din `tact`, inainte de
`Append Blank`/`Gather`). La scriere, orice rand din `tvd` cu `id_vanzare_det = 0` e un `INSERT`
(PK-ul real vine automat din `SEQ_VANZARI_DETALII`, confirmat in
`rec_cale_vanzari_detalii.md` sectiunea 1.3/3.2 — nu trebuie generat in VFP).
- **Modificat** — singurul marcaj cu adevarat nou necesar, pentru ca "a fost atins" nu se poate
deduce din `id_vanzare_det`/`STERS`. Propunere: o coloana logica `_modificat` (prefix `_`,
convenabil pentru un camp de lucru care nu exista in tabela reala — verifica totusi ca VFP nu
interpreteaza gresit numele; alternativ `lModificat`), setata `.T.` din handler-ele `Valid`/
`InteractiveChange` ale coloanelor editabile din grid — acelasi tipar folosit deja de clasa pe
`trul` (`pgfArticole.PAGE1.grdRulaje.cCant.Text1.Valid`, `omodificari.vc2:14906-14911`, si
restul handler-elor `Valid`/`When`/`InteractiveChange` din acelasi grid). La scriere, un rand cu
`id_vanzare_det > 0` si `_modificat = .T.` e un `UPDATE`; fara flag, randul nu se atinge (evita
`UPDATE`-uri inutile pe linii doar rasfoite).
Acest model evita complet o alternativa mai grea (snapshot + diff intre cursorul original si cel
curent) care ar fi introdus un concept nou, fara sa aduca vreun beneficiu fata de flag-urile deja
folosite in clasa pentru `tact`.
## C. Helperele si verificarile
### C.1 Ce inseamna "suma comparabila" — regula pe tip de document, verificata pe date reale
Corectie fata de versiunea anterioara a acestei sectiuni (premisa ei — filtru fix `SCD='4111'` — a
fost respinsa explicit de Marius, F.2 mai jos). Cercetarea completa, cu toate interogarile pe cele
trei scheme si sursele exacte pe cod, e in `docs\cercetare\rec_suma_act.md`; ce urmeaza e concluzia
ei. Doua completari ulterioare, verificate separat pe `MARIUSM_AUTO` (08.08.2026), sunt in
`docs\cercetare\rec_cele_41_facturi.md`: derivarea `an`/`luna` si cauza reala a divergentei pe
`cod=1138989`. Nu se reimplementeaza nicio formula fiscala in VFP — recomandarea ramane cea de
dinainte: "suma din `VANZARI_DETALII`" se ia direct din `calculeaza_total_fara_tva_fact`/
`calculeaza_total_tva_fact` (Oracle, aceleasi functii care vor rula la salvarea din S5), niciodata
recalculata in VFP.
**Nu exista un filtru fix de cont pentru "suma din ACT".** Contul de debit al liniei depinde de
**tipul documentului** (`PACK_FACTURARE.pck`, `contabilizeaza_articol` decide contul la
`:7390-7415`):
| Grup de `TIP` | Cont debit (linie) | Comparabil cu `TOTAL_CU_TVA`? |
|---|---|---|
| Facturi (1,2,3,5,7,8,9,10,43,44,45,46,48,49,51,52 fara rata) | `4111` (empiric stabil pe 3 scheme) | DA |
| Factura din aviz (tip 4) | `4111`, discount direct pe el cu semn negativ | DA |
| Vanzare pe rate/contract (2,6,52 cu `id_rata<>0`) | din `NOTE_CONTABILE`, legat de `CONTRACTE` | DA |
| Avize catre clienti debitori (28, 29) | `461` (hardcodat) | DA |
| Restul avizelor (21,22,24,26) | `418` (hardcodat) | DA |
| Transfer intre subunitati (23,25,30,41) | cont de STOC, nu de client | NU |
| Transfer pe lucrare (27) | cont de STOC | NU |
| Custodie (42,47) | — (`descarca_gestiune` nu scrie rand `ACT` per articol) | NU |
| ROAACNPRO (51) | `4111`, confirmat stabil de Marius | cont OK, dar comparatie nesigura — vezi mai jos |
**Filtrul pe `ACT` cere `cod` + `an` + `luna`, niciodata `cod` singur.** Dovada: `cod=1140632` are
un rand de achizitie straina (OCR furnizor) in `an=2026,luna=1` si nota de vanzare reala in
`an=2026,luna=2`, cu **acelasi** `cod` reutilizat intre module. `IncarcaCursoareModificareNota(
tnCod, tnAn, tnLuna, ...)` (`COMUN\programe\ofacturare_editare.prg:27-140`) filtreaza deja corect —
orice interogare noua din S4b trebuie sa foloseasca acelasi tipar de filtru complet, nu doar `cod`.
**`an`/`luna` nu se deriva din `VANZARI.DATA_ACT`** — trebuie luate din contextul notei deja
incarcate, niciodata recalculate din antet. Pe 703 documente `VANZARI` (`MARIUSM_AUTO`,
`docs\cercetare\rec_cele_41_facturi.md`): 542 au nota in luna din `DATA_ACT`, **78 (11%) au nota
intr-o alta luna**, 83 n-au deloc randuri `ACT` pe `cod`. Exemplu: `cod=1138989` are
`VANZARI.DATA_ACT = 01-JAN-19`, dar cele 12 randuri `ACT` sunt in `an=2019, luna=3`
(`dataact=31-MAR-19`) — un filtru `cod + an(data_act) + luna(data_act)` ar intoarce zero randuri.
Pe datele de test cazurile sunt concentrate pe tip=51 (ROAACNPRO, `DATA_ACT` sablon `01-JAN-19`),
deci nu e dovedit tipar general de productie — dar consecinta de implementare e reala: `an`/`luna`
se iau din cursorul `tact`/`actactan` deja incarcat de `IncarcaCursoareModificareNota` (apelata cu
`an`/`luna` explicite), niciodata recalculate din `VANZARI.DATA_ACT`. Pentru cele 78 de documente cu
luna divergenta, `do_editare_factura` raspunde azi "Nu exista nota contabila pentru aceasta
factura" si refuza editarea — comportament sigur, dar de consemnat ca limitare cunoscuta.
**Discountul de document intra NET (debit minus credit), nu ca `SUM(SCD=cont)` simplu.**
`scrie_discount` (`PACK_FACTURARE.pck:12859-13057`) scrie discountul pe sensul OPUS liniei de
vanzare: pe facturi normale (`tip<=20` sau in `(44,45,46,43,48,49,51,52)`) discountul e
`SCD='667'`/`SCC='4111'`, adica **pe credit** fata de contul de client — un `SUM(SUMA) WHERE
SCD='4111'` simplu il ignora complet. Suma corecta e **soldul net**, aceeasi formula pe care
aplicatia insasi o foloseste la auto-verificarea de la emitere (`verifica_total_document`,
`PACK_FACTURARE.pck:16073-16145`):
```sql
SUM(CASE WHEN SCD = :cont THEN SUMA
WHEN SCC = :cont AND SCD NOT IN ('5311','5314','5121','5125','5126') THEN -SUMA
ELSE 0 END)
```
(pe factura din aviz, tip=4, discountul e deja direct pe `4111` cu semn negativ, deci formula neta
da acelasi rezultat ca un `SUM` simplu pe acel tip — nu strica nimic sa se aplice uniform pe toate
tipurile comparabile din tabel.)
**`RUL` are acum o formula comparabila** (nu mai ramane doar informativ):
```
SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ <> 3)
```
Randurile "in plus" observate initial (10 randuri `RUL` pentru 4 linii, suma bruta 4476.28 in loc
de 1924.59 pe `cod=1140888`) vin din **perechile de diferenta de pret**, generate de
`PACK_FACTURARE.descarca_gestiune` pentru marfa/produse tinute la pret de vanzare
(`V_CONT IN ('371','357') AND V_TIP_GESTIUNE=6`, `:9361-9540`; produse/ambalaje similar,
`:9641-9818`) cand pretul de vanzare inregistrat in stoc difera de cel facturat efectiv
(`V_PRETV_ORIG <> V_PRETV`). Fiecare diferenta scrie o pereche marcata `ID_TIP_RULAJ=3` (`:7745`):
un rand cu `CANTE>0` la pretul VECHI din stoc (de exclus din suma) si unul cu `CANT>0` la pretul
REAL facturat (de inclus). Formula de mai sus, aplicata pe `cod=1140888`, da exact `1924.59` =
`TOTAL_CU_TVA`.
**Documente mixte (decizia 25): liniile nestocate nu au deloc rand `RUL`.**
`descarca_gestiune` sare complet peste articolele cu `NOM_ARTICOLE.IN_STOC=0` (`:7783-7789`) — nu
scrie nimic in `RUL` pentru ele (confirmat pe productie, `cod=1397106`: linia de "SERVICII
TRANSPORT" lipseste integral din `RUL`). Pe orice document cu linii stocate SI nestocate, suma
`RUL` de mai sus **subestimeaza sistematic** documentul cu exact valoarea liniilor nestocate.
Corectie: suma `RUL` se aduna cu suma liniilor `IN_STOC=0` din `VANZARI_DETALII`, iar bara de
totaluri marcheaza explicit ca cifra e **ajustata** (nu doar `RUL` brut).
**Tipuri fara suma comparabila — nu se afiseaza bara, nu se da verdict** (decizia 22): transfer
intre subunitati (23, 25, 30, 41) si transfer pe lucrare (27) merg pe cont de **stoc**, nu de
client; custodia (42, 47) nu scrie **niciun** rand `ACT` per articol. Pe niciunul din aceste tipuri
nu exista ce compara — tratament explicit ("pagina apare, bara nu"), nu o eroare de raportat. Pe
tip=50 (marcat "in lucru" in pachet), acelasi tratament, prin analogie.
**ROAACNPRO (tip 51): cauza divergentei pe `cod=1138989` gasita — nota e DUBLATA, nu e cazul
deciziei 9.** Cele 12 randuri `ACT` (toate in aceeasi `an`/`luna`, deci nu e capcana de filtrare de
mai sus; toate pe `4111`, zero `411`) sunt **doua blocuri de cate 6**, iar al doilea e **exact 2x**
primul, rand cu rand (`docs\cercetare\rec_cele_41_facturi.md`): blocul A insumeaza exact
`TOTAL_CU_TVA = 13895.45` (0.01 lei rotunjire), blocul B e dublul lui, iar suma totala peste toate
cele 12 randuri e `41686.35` = 3x totalul — exact raportul semnalat initial. **Regula pentru suma
din `ACT` se inchide si pe acest document**: nu e o exceptie a regulii, e o nota postata de doua
ori — o anomalie reala de date pe care un indicator corect trebuie s-o semnaleze, nu s-o ascunda.
**Nu e printre cele 41 de facturi de la decizia 9** (#8/S9) — acelea sunt toate `STERS=1`, iar
`cod=1138989` are `STERS=0` si o linie activa; presupunerea anterioara ca ar fi "aceeasi familie"
nu se sustine. Ramane deschis, separat: cele 12 randuri `ACT` explica antetul `VANZARI`, dar linia
unica din `VANZARI_DETALII` (`2468.21`) nu reconciliaza cu niciuna din cele doua cifre — asta chiar
ramane neexplicat, si e un argument in plus pentru caracterul informativ (nu verdict automat) al
comparatiei `ACT` vs `VANZARI_DETALII` de mai jos.
**Comparatia ramane informativa, niciodata verdict automat de eroare.** `ACT` **nu are nicio
coloana care sa marcheze originea randului** (`omodificari.vc2:12653-12701`, `do_adauga`) — un rand
adaugat manual de utilizator (posibil, decizia F.1: pagina apare pe orice document din `VANZARI`)
e indistinctibil, dupa salvare, de unul generat automat la emitere. Indicatorul din S4b ramane deci
**informativ pe subset bine definit**, niciodata sursa unica de adevar pentru un verdict de eroare.
**Gradul de incredere**: regula de mai sus (cont pe tip + filtru `cod+an+luna` + sold net) e
verificata pe **360 de documente, 12 tipuri de document, 3 scheme** (`MARIUSM_AUTO` date de test,
`ROMFAST@ROA_ROMFAST` client real, `VENDING` productie) — **~97.5% potrivire exacta**
(`docs\cercetare\rec_suma_act.md`, sectiunea "Concluzie C"). `cod=1138989` (ROAACNPRO) nu mai e un
caz neexplicat — cauza e gasita (nota dublata, de mai sus) — dar un indicator automat tot ar
semnala divergenta pe el, corect, pentru ca e o anomalie reala de date. Restul cazurilor **raman
deschise, nu ascunse**: transfer/custodie (necomparabile prin design, de mai sus), 5 documente
`ROMFAST` fara nicio nota `ACT` scrisa, si un document `VENDING` in valuta (`cod=1165566`, tip=9)
cu o diferenta de 5454.12 lei neexplorata — niciunul din aceste cazuri nu infirma regula, dar
niciunul nu trebuie prezentat ca "inchis" fara nuanta de mai sus.
### C.2 Ce inseamna "live" vs "la ultima salvare"
Cele trei surse nu pot fi comparate coerent "in timp real" cat timp utilizatorul tasteaza intr-o
celula din grid, pentru ca `ACT`/`RUL`/`VANZARI_DETALII` reala raman la valoarea de dinaintea
editarii curente pana la `Salveaza`. Propunere clara, ca sa nu induca fals sentiment de precizie:
- **Subtotal pe linie, in grid** (cantitate x pret, cu/fara TVA dupa flag) — calcul simplu, live,
in VFP, la fiecare `InteractiveChange`/`Valid` (acelasi tipar ca `calculeaza_valori_rul`,
`omodificari.vc2:12519-12588`, aplicat pe `trul`). Nu implica formula fiscala complexa (fara
discount de document, fara rotunjiri de agregare), deci riscul de divergenta e neglijabil si
local, vizibil imediat de utilizator.
- **Cele trei totaluri de control (RUL / VANZARI_DETALII / ACT)** raman "la ultima stare
persistata" — se recalculeaza (interogare Oracle) la deschiderea paginii si dupa fiecare
`Salveaza` reusit, **nu** la fiecare tasta. Rolul lor real (asa cum reiese din problema descrisa
de Marius) e sa arate ca un document editat anterior a ramas nesincronizat, nu sa dea un preview
in timp real al editarii curente — editarea curenta oricum nu poate fi "corecta" pana nu trece
prin acelasi calcul Oracle care va rula la salvare.
### C.3 Indicatorul de stare a sincronizarii
Un label vizibil pe PAGE3 (langa footerul de totaluri), 3 stari: **verde/OK** (toate sumele
aplicabile coincid, in limita rotunjirii de 2 zecimale — pe tipurile fara suma comparabila,
transfer/custodie, bara nici nu apare, cf. C.1), **galben/atentie** (`VANZARI_DETALII` si `ACT`
coincid, dar cifra din `RUL` e ajustata pentru linii nestocate sau documentul e de un tip cu
comparatie nesigura, ex. ROAACNPRO — cf. C.1), **rosu/desincronizat** (`VANZARI_DETALII` si `ACT`
NU coincid — semnalul real ca ceva nu s-a propagat corect, singurul caz in care indicatorul trebuie
sa opreasca vizual atentia utilizatorului). Recalculat la deschidere si dupa fiecare salvare
reusita (C.2).
### C.4 Actiunea explicita de sincronizare
Cerinta lui Marius: directia (rulaj->articol sau articol->rulaj) o alege utilizatorul, niciodata
implicit. Propunere de flux (schita UI in F, intrebarea de UX):
1. Buton "Verifica sincronizarea" (sau automat la deschidere, doar afisare) — ruleaza C.1/C.3,
populeaza un cursor de diferente `tvd_diff` (id_articol, cantitate_rul, cantitate_vd,
pret_rul, pret_vd, ...) doar pentru randurile unde difera.
2. Daca exista diferente, buton "Propune sincronizare" deschide un dialog/grid cu liniile afectate
si valorile vechi/noi, **pe ambele directii posibile** (utilizatorul alege per-sesiune care
parte e sursa — RUL sau VANZARI_DETALII —, nu per-linie individual, ca sa evite o combinatie
inconsistenta).
3. Confirmarea aplica modificarile **doar in cursorul in memorie** (`tvd` sau `trul`, dupa
directie) — nu scrie nimic in Oracle pana la `Termina`/`Salveaza`. Consistent cu principiul
"nimic nu se aplica silentios si nimic nu se declanseaza automat la `do_termin`" din plan.
4. Refuzul propunerii nu modifica nimic — utilizatorul poate corecta manual, linie cu linie, in
oricare din cele doua griduri.
### C.5 Linii adaugate/sterse fara corespondent in RUL
O linie noua in `tvd` (id_vanzare_det=0) nu are, prin definitie, niciun rand `RUL` corespunzator —
nu exista "vechi" de comparat. Propunere: astfel de linii sunt automat excluse din comparatia
C.1/C.3 (nu pot fi "desincronizate", pentru ca n-au fost niciodata sincronizate) si marcate separat
in UI ("linie noua, fara rulaj — se creeaza la salvare" / "linie stearsa"). Simetric pentru liniile
sterse (`STERS=1` in `tvd`): nu mai intra in suma "curenta" din C.2, dar raman vizibile (tacuate/
strikethrough) pana la salvare, ca utilizatorul sa vada ce a marcat pentru stergere inainte sa
confirme.
### C.6 Ce NU se poate verifica automat
- Corelatia RUL <-> VANZARI_DETALII **linie-cu-linie** (nu agregat) — ramane nesigura: formula din
C.1 e verificata ca sumă pe tot documentul, nu mapeaza un rand `RUL` anume pe o linie anume din
`VANZARI_DETALII`. Suma agregata (C.1) are acum formula verificata; ce nu se poate face e
legatura 1-la-1 intre randuri.
- Corectitudinea contabila a notei dupa editare (echilibrul debit=credit, alegerea corecta a
conturilor) — ramane acoperita de validarile generice deja existente in
`inainte_de_do_termin` (A.4), care nu se ating.
- Impactul asupra eFactura/SAFT dupa editare — in afara scopului #6 (garda S1 blocheaza deja
editarea facturilor trimise in eFactura).
## D. Riscul asupra registrului jurnal
`omodificari.vc2` e in COMUN si serveste si `afisjurcom`/registrul jurnal ROACONT/ROAGEST. Ce
poate regresa, concret, si cum se limiteaza:
1. **`PageCount` schimbat global pe clasa** — daca noul `PageCount=3` din definitia clasei nu e
readus la 2 la runtime pentru non-facturi (A.2), PAGE3 ar aparea (goala sau cu date gresite) pe
orice nota din ROACONT/ROAGEST. Mitigare: testul din A.2 ruleaza necontional in `Show`, inaintea
oricarei afisari, si defaultul din clasa e explicit "1 pas de siguranta" (daca testul crapa/nu
ruleaza, `PageCount` ramane 3 din clasa — deci testul TREBUIE sa aiba un `Catch`/`else` care
forteaza `PageCount=2`, nu invers). **De verificat explicit la implementare**: comportamentul pe
eroare al noii interogari Oracle (A.3) trebuie sa fie "ascunde PAGE3", nu "las-o vizibila".
2. **Interogarea noua din A.3 (`SELECT ... FROM vanzari WHERE cod=...`) ruleaza la FIECARE
deschidere a formularului**, inclusiv pentru note care n-au nicio legatura cu facturarea — cost
suplimentar mic (un SELECT indexat pe `cod`), dar **trebuie sa fie garantat sa nu blocheze
deschiderea** pe eroare de retea/Oracle. Mitigare: acelasi tipar defensiv ca
`IncarcaCursoareModificareNota` (`ofacturare_editare.prg:58-61`) — pe eroare, se comporta ca
"nu e factura" (ascunde PAGE3), nu propaga eroarea in sus si nu blocheaza formularul.
3. **Cursoarele noi (`tvd`/`tvanz`) nu trebuie sa interfereze cu `tact`/`trul`/`trul_obinv`** —
nume de alias distincte, verificate ca nu exista deja in cod (`tvd`/`tvanz` cautate, 0
rezultate azi). Grid-ul nou trebuie sa aiba `ControlSource` calificat complet pe fiecare coloana
(`COMUN\docs\capcana_grid_controlsource.md`) — capcana confirmata activa exact in acest scenariu
(3+ griduri pe acelasi formular: `grid1`/`grdRulaje`/`grdRulajeObinv`/noul grid).
4. **Scrierea (nu doar afisarea)**: partea cea mai sensibila. Scrierea noua in `VANZARI_DETALII`
(E, S5) trebuie sa fie **strict gatata de `This.lAreArticoleVanzari`**, apelata din apelanti
(`do_editare_factura`/`afisjurcom.do_modifica`) DOAR dupa ce pasul existent
`finalizeaza_modificare_nota` a reusit deja — deci pe orice nota fara rand in `VANZARI`, pasul
nou nu se executa niciodata (flag-ul e `.F.`), cod identic cu azi.
5. **`gridextra1.setup()`** (`:13729`) — salveaza/restaureaza preferinte per-grid, cheie
`SYS(1272, grid)`. Grid-ul nou (`pgfArticole.PAGE3.grdArticoleFactura`) e complet nou, deci nu
are preferinte salvate de niciun utilizator — capcana din
`capcana_grid_preferinte_utilizator.md` (coloana noua intr-un grid EXISTENT ajunge la coada) nu
se aplica la infiintare, doar daca se adauga o coloana ulterior. **Neverificat**: daca
`gridextra1` inregistreaza automat orice grid nou de pe formular sau necesita inregistrare
explicita — de confirmat direct in `gridextras.vc2` la implementare, nu presupus aici.
**Cum se testeaza D**: un caz de test obligatoriu (deja in S8 din plan) e "document care nu e
factura, deschis din registrul jurnal — pagina de articole nu apare si comportamentul e identic cu
azi". Recomand completarea lui cu: (a) o rulare explicita cu Oracle temporar indisponibil pe
interogarea din A.3 (verifica gracious degradation, punctul 2 de mai sus), (b) o comparatie
byte-cu-byte a XML-ului salvat de `salveazaxml` (`:13690-13717`) inainte/dupa modificare, pe un
document non-factura, ca sa confirme ca noul cod n-a atins deloc acel drum.
## E. Impartirea in runde de implementare
Ordonat pe risc, cu rezultat testabil la finalul fiecarei runde. S1-S3 (deja facute) raman runda 0.
**Runda 1 — PAGE3 doar afisare, fara scriere** (depinde doar de VFP + Oracle read-only, nu de S5)
- A.2 (PageCount/Caption/grid nou) + A.3 (detectie factura, in `frm_modific2024`) + B (incarcare
`tvanz`/`tvd`, read-only).
- Grid needitabil (`ReadOnly=.T.` pe toate coloanele), fara buton de salvare separat pe pagina.
- *Gata cand*: PAGE3 apare pe orice document cu rand in `VANZARI` (facturi, avize, transfer,
custodie — decizia 19), din ambele puncte de intrare, arata liniile corecte; pe orice document
fara rand in `VANZARI` dispare complet (testul de risc D).
- **Nu depinde de S5.** Se poate livra si testa independent.
**Runda 2 — editare in memorie, fara scriere in Oracle**
- Grid editabil (B.2, marcaje `_modificat`/`sters`/`id_vanzare_det=0`), dialogul per-linie
(reutilizarea `frm_articol_factura`, vezi nota tehnica de mai jos), adaugare/stergere linie in
cursor, discount de antet editabil in `tvanz`.
- Subtotalul live pe linie (C.2, calcul simplu VFP).
- *Gata cand*: utilizatorul poate adauga/sterge/modifica linii in grid, vede subtotaluri live;
`Renunta` lasa totul neschimbat; `Termina` **nu** scrie inca nimic in `VANZARI_DETALII` (doar
nota contabila, ca azi).
- **Nu depinde de S5** — poate rula complet pe VFP, testabil headless fara risc de scriere Oracle.
**Runda 3 — S5 (Oracle) + scrierea reala**
- Procedura `recalculeaza_totaluri_vanzari` (Oracle, `rec_s5_oracle_vanzari.md` sectiunea B) +
procedurile de UPDATE/INSERT/soft-DELETE pe `VANZARI_DETALII` (`rec_cale_vanzari_detalii.md`
sectiunea 4, Varianta B).
- Scrierea efectiva din VFP: apel nou, simetric in ambii apelanti, gatat de
`Omodif.lAreArticoleVanzari`, dupa `finalizeaza_modificare_nota` reusit (D.4).
- *Gata cand*: dupa `Termina`, `VANZARI_DETALII`/`VANZARI` reflecta editarea; S7 (rotunjire la
reeditare) verificat pe acest flux.
- **Depinde de Runda 2** (cursorul editat trebuie sa existe) si de S5/S6 din planul general.
**Runda 4 — S4b complet (helpere/verificari)**
- C.1-C.6: totalurile de control (interogare directa a functiilor Oracle existente, nu formula
VFP), indicatorul de stare, actiunea explicita de sincronizare.
- *Gata cand*: criteriile din plan_06 S4b (indicator vizibil, propunere enumerata, refuzul nu
modifica nimic).
- **Depinde de Runda 3** — comparatia cu ACT/VANZARI_DETALII n-are sens pana nu exista scriere
reala de comparat.
**Nota tehnica pentru Runda 2**: dialogul per-linie recomandat e **reutilizarea**
`frm_articol_factura` (`COMUN\clase\ofacturare.vc2:2315-...`, deschis azi din
`frm_facturare_articole.do_modifica`, `ofacturare.vc2:13746-13844`, model documentat si in
`plan_06_editare_factura.md` "Ce preda #7 catre S6"). Dialogul citeste `PRIVATE poArticol` (setat
de apelant inainte de `Createobject`) si `Lparameters tnCantitate, tlAscunde` — **fara plafon**
(decizia 15), `tnCantitate` se transmite cu o valoare santinela mare (ex. `999999999`), nu se
recalculeaza din stoc. `poArticol` asteptat de dialog are un set bogat de proprietati calculate
(`valftva`, `vval*`, etc. — vezi lista completa la `ofacturare.vc2:13835-13840`), diferite de
coloanele brute din `VANZARI_DETALII`; adaptorul nou trebuie sa populeze campurile de baza
(`pret_achizitie, cantitate, id_articol, cont, id_gestiune, proc_tvav, pretftva/pretctva dupa
flag, discount_unitar, id_valuta, ...`) din `tvd`, apoi sa cheme aceeasi functie VFP
`calculeaza_totaluri(poArticol)` (apelata deja la `:13875` pentru randuri noi) ca sa deriveze restul
— **nu se reinventeaza formula**, se refoloseste exact ca la compunere. Ramura `do_alege_stoc`
(redeschiderea dialogului de gestiuni, folosita azi doar la compunere) **nu se foloseste in #6** —
decizia "fara verificare de stoc" (15) inseamna ca toate liniile, gestionabile sau nu, trec prin
acelasi dialog simplu.
## F. Intrebari pentru Marius
1. **[DECIS — decizia 19 din progres.md, vezi A.3]** Nu strict facturi: pagina apare pe **orice
rand din `VANZARI`**, deci si pe avize, transfer si custodie. Varianta "strict facturi"
recomandata initial in A.3 a fost respinsa explicit de Marius.
2. **[REZOLVAT — vezi C.1]** Contul nu e fix `4111`: regula e pe tip de document (facturi `4111`,
avize `418`, avize catre clienti debitori `461`), verificata pe 360 de documente / 12 tipuri /
3 scheme (`docs\cercetare\rec_suma_act.md`). Raman deschise, fara sa infirme regula: 5 documente
`ROMFAST` fara nicio nota `ACT` si un document `VENDING` in valuta (`cod=1165566`) cu diferenta
neexplorata.
3. **[REZOLVAT — vezi C.1]** Regula gasita: perechile `cant`/`cante` marcate `ID_TIP_RULAJ=3` sunt
randuri de diferenta de pret (`PACK_FACTURARE.descarca_gestiune`), de exclus randul cu pretul
vechi din stoc. Formula `SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ<>3)`
reproduce exact totalul pe documente cu toate liniile stocate; pe documente mixte se corecteaza
cu liniile nestocate (C.1).
4. **UX-ul concret al zonei de totaluri si al indicatorului** — trei variante, cu recomandare:
**Varianta A (recomandata) — bara de totaluri sub grid-ul PAGE3, indicator ca punct colorat +
text:**
```
+-----------------------------------------------------------------------+
| [Articole factura] Discount document: [____] % |
| +---------------------------------------------------------------+ |
| | Articol | Cant | Pret | Cu TVA | ... | |
| | ... | |
| +---------------------------------------------------------------+ |
| Total ACT (contabil): 1924.59 lei |
| Total VANZARI_DETALII: 1924.59 lei |
| Total RUL (formula C.1): 1924.59 lei [*] Sincronizat |
| [Verifica sincronizare] [Propune] |
+-----------------------------------------------------------------------+
```
Exemplu `cod=1140888` (C.1): dupa formula corecta, cele trei sume coincid — nu mai e un caz care
sa ilustreze o divergenta. Pentru un document mixt (linii stocate + nestocate, decizia 25),
randul RUL se marcheaza explicit ca ajustat, de exemplu:
```
| Total RUL (ajustat, +linii nestocate): 1170.00 lei |
```
Simplu, aliniat cu `_grdfooter1` deja existent pe PAGE1/PAGE2 (acelasi loc, acelasi stil vizual).
**Varianta B — indicator langa caption-ul paginii** (`PAGE3.Caption = "Articole factura ⚠"` sau
cu iconita), totalurile doar la cerere (buton "Arata totaluri de control" care deschide un
dialog separat). Mai compact, dar ascunde informatia pana la un click — risc sa nu fie observat.
**Varianta C — culoare de fond pe intreaga pagina** (rosu deschis) cand desincronizat, fara
text explicit pana la deschiderea dialogului de sincronizare. Cel mai putin verbose, dar
ambiguu (utilizatorul nu stie CE e desincronizat fara sa deschida dialogul).
Recomand **A** — respecta convenția UX (`conventie_ux_formulare.md`: informatia de control
trebuie vizibila, nu ascunsa dupa un click) si reutilizeaza tiparul deja vizual familiar din
PAGE1/PAGE2 (footer de sume sub grid).
5. **Dialogul de propunere de sincronizare (C.4)** — schita:
```
+---------------------------------------------------------+
| Propunere sincronizare (sursa: Articole factura -> Rulaj)|
| +-------------------------------------------------------+|
| | Articol | Rulaj (vechi) | Articol (nou) | ||
| | Piesa X | cant=2 pret=100 | cant=3 pret=110 | ||
| | Piesa Y | -- (fara rulaj) | cant=1 pret=50 | ||
| +-------------------------------------------------------+|
| [Alege directia: Articol->Rulaj | Rulaj->Articol]|
| [Aplica in memorie] [Renunta] |
+---------------------------------------------------------+
```
De confirmat daca alegerea directiei e un singur radio-button global (recomandat, C.4 punctul 2)
sau daca Marius vrea control per-linie (mai flexibil, mult mai complex de implementat si de
explicat utilizatorului — nerecomandat pentru complexitatea/beneficiul).
## Ce nu am putut verifica
- Daca `gridextra1.setup()` inregistreaza automat grid-uri noi de pe formular (D.5).
- Comportamentul `_grdfooter`/`attachtogrid` pe un grid gol (0 randuri) la prima deschidere a
PAGE3 — nu a fost testat, doar citit codul PAGE1/PAGE2 ca precedent.
(Cele trei puncte legate de formula `ACT`/`RUL` care erau listate aici — divergenta 903.53 vs
1924.59, regula perechilor `cant`/`cante`, stabilitatea contului `4111` — s-au rezolvat prin
cercetarea din `docs\cercetare\rec_suma_act.md` si sunt acum in C.1 / F.2 / F.3.)

View File

@@ -1,127 +0,0 @@
# Plan #7 — pret cu TVA editabil pe linie de articol
Sursa: `COMUN\docs\todos.txt` punctul 7.
Ordine de executie: **al doilea**, dupa #8.
Stare: propunere, neinceput.
## Problema raportata
Factura iese 99.99 lei dar utilizatorul are nevoie de exact 100.00 lei cu TVA. Politica de preturi e
pe "preturi fara TVA", calculul porneste de la pretul fara TVA, TVA-ul se rotunjeste si nu poate fi
ajustat. Varianta aleasa de Marius: utilizatorul modifica `pret_cu_tva` la nivel de articol, la
introducerea facturii si ulterior la editare.
## Descoperirea care schimba dimensiunea lucrarii
Cerinta initiala presupunea o coloana noua salvata in `VANZARI_DETALII`, cu migrare si atingerea
tuturor locurilor de calcul. **Nu e necesar.** Instalatia exista deja, integral:
1. `VANZARI_DETALII.PRET_CU_TVA` exista deja, ca **flag NUMBER(1) per linie** (0 = `PRET` e fara TVA,
1 = `PRET` e cu TVA inclus). Confirmat prin uz: `ff_2026_07_27_01_FACTURARE.sql:25,43`.
2. `PACK_FACTURARE.adauga_articol_factura` primeste deja flagul ca parametru,
`V_PRETURI_CU_TVA_TEMP` (`ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql:540`) — deci VFP il trimite
deja per articol, la adaugarea pe factura.
3. Toate functiile de calcul ramifica deja pe el (`V_PRET_CU_TVA` / `V_PRET_ARE_TVA` in
`calculeaza_total_fara_tva` / `_tva` / `_cu_tva` si variantele `_fact`, `:15638-15949`).
4. Listarea si relistarea in VFP ramifica deja pe el, in
`COMUN\programe\ofacturare_comun.prg:1816-1880` (`prelucreaza_facturacrs`) — lantul de `IIF(
pret_cu_tva = 1, ..., ...)` care produce `pretctva`, `discountctva` si valorile pe linie.
Ce lipseste e **exclusiv partea de UI**: pe formularul de introducere articole nu exista coloana
pentru flag, deci utilizatorul nu il poate schimba per linie.
Cautare pe `pret_cu_tva` in `COMUN\clase\ofacturare.vc2` (formularele `frm_facturare_articole` si
`frm_facturare_articole2`): **zero rezultate**. Singurul checkbox `cPret_cu_tva` gasit e in alt grid,
`Clase\ofundal_facturare.vc2:696-699`.
Concluzie: **fara DDL, fara migrare de date, fara modificari in view-uri sau in pachetul Oracle.**
Lucrarea e VFP-only, pe formularul de articole.
## Consecinta pentru rotunjire
Cu flagul pe 1, utilizatorul tasteaza direct pretul cu TVA (ex. 100.00), iar pretul fara TVA se
deriva. Totalul facturii devine exact suma preturilor tastate — exact cazul din todo.
De stiut: exista deja un mecanism ascuns de absorbtie a diferentelor de rotunjire, dar **doar
contabil**. `PACK_FACTURARE.verifica_total_document` (`:16009-16188`) compara totalul asteptat cu
suma din notele contabile si, la diferenta, insereaza automat o linie de corectie in `ACT_TEMP`
(`:16086-16188`). Nu apare pe factura si nu se vede in `VANZARI_DETALII`. Nu se atinge in acest plan,
dar explica de ce pana acum diferenta "disparea" fara ca utilizatorul sa o poata controla.
## Capcana de duplicare
`do_scrie_factura` exista in **doua clase aproape identice** in `COMUN\clase\ofacturare.vc2`:
`frm_facturare_articole` (`:13981-14339`) si `frm_facturare_articole2` (`:18004-18331`). Orice
modificare de comportament trebuie facuta in ambele, altfel un flux ramane in urma. Prima story
stabileste care dintre ele e folosita pe ce tipuri de factura.
## Stories
### S1 — Stabilirea formularului si a fluxului real
Determina care dintre `frm_facturare_articole` / `frm_facturare_articole2` se deschide pentru fiecare
tip de sursa (lista de preturi, comanda, contract, aviz) si de unde vine flagul azi in cursorul de
articole (`PACK_FACTURARE.cursor_preturi`, spec `:335`, body `:2121`). Confirma valoarea implicita
propagata din politica de pret.
*Gata cand:* e notat in `docs\progres.md` ce formular acopera ce tip, si care e sursa implicita a
flagului.
*Blocheaza:* S2.
### S2 — Coloana `pret_cu_tva` in gridul de articole
Adauga in grid o coloana checkbox pentru flag, editabila per linie. Respecta
`COMUN\docs\conventie_ux_formulare.md` (pozitionare, ordine de taburi) si, daca formularul are 2+
griduri, `COMUN\docs\capcana_grid_controlsource.md`.
Editare pe versiunile text `.vc2` + write-back, cu regulile de encoding cp1252 din
`COMUN\docs\conventie_encoding_cp1252.md`.
*Gata cand:* coloana apare si se poate bifa/debifa, in ambele clase.
### S3 — Recalcularea afisata la schimbarea flagului
La bifare/debifare, valorile de pe linie si totalul facturii se recalculeaza imediat, folosind
aceeasi ramificare ca `prelucreaza_facturacrs` (`ofacturare_comun.prg:1816-1880`), ca listarea sa dea
identic cu ce vede utilizatorul pe ecran.
*Gata cand:* bifarea unei linii schimba totalul afisat coerent, fara reincarcarea formularului.
*Depinde de:* S2.
### S4 — Pretul editabil in modul "cu TVA"
Cu flagul pe 1, campul de pret accepta pretul cu TVA tastat de utilizator si il trimite ca atare la
`adauga_articol_factura` prin `V_PRETURI_CU_TVA_TEMP = 1`. Verifica precizia de rotunjire folosita
(`gnPPretV`, `gnPc` — vezi `ofacturare_comun.prg:1817-1821`).
*Gata cand:* o factura cu o singura linie, pret 100.00 cu TVA, cantitate 1, da total exact 100.00.
*Depinde de:* S2.
### S5 — Test pe fluxul real
Test headless conform `COMUN\docs\depanare_testare_vfp.md`, plus verificare UI conform
`COMUN\docs\testare-ui-vfp.md`. Cazuri: cota standard si cota redusa; cu si fara discount unitar
(ramura `discount_evidentiat` din `calculeaza_total_tva_fact`, `:15899-15949`); doua linii cu flaguri
diferite pe aceeasi factura.
*Gata cand:* toate cazurile dau acelasi rezultat la introducere, la listarea initiala si la
relistare.
*Depinde de:* S3, S4.
### S6 — Editarea ulterioara a flagului
Modificarea flagului pe o factura deja emisa **nu intra aici** — depinde de mecanismul de editare
din planul #6, pentru ca schimbarea afecteaza sumele si deci notele contabile. Story-ul se rezuma la
a nota dependenta si a lasa punctul de extensie curat.
*Gata cand:* dependenta e consemnata in `plan_06_editare_factura.md`.
### S7 — Diff, review, changelog
Diff ca fisier in `docs\`, commit doar dupa aprobare. Changelog `:nou:` sau `:modificare:`, 1-3 fraze
non-tehnice, bump pe seria 2.11.x.
## Riscuri
- Flagul exista deja pe linii istorice; expunerea lui in UI nu schimba date vechi, dar face vizibile
eventuale inconsistente deja prezente. De privit inainte de livrare cate linii au flagul diferit
de valoarea implicita a politicii.
- Rotunjirea per unitate se face in `PACK_SESIUNE.calculeaza_pret_*`, pachet care **nu a fost gasit**
in arhiva de migrari cautata. Daca S4 arata diferente de rotunjire, e nevoie de o cautare dedicata
dupa `create or replace package pack_sesiune` in `D:\ROA\DATABASE\SCRIPTURI_CLAR`.
- Duplicarea `frm_facturare_articole` / `_articole2` e sursa clasica de "merge pe un flux, nu si pe
celalalt".
## Varianta respinsa
Denormalizarea completa (salvarea TVA unitar si a valorii TVA pe linie, cu recitire in toate
view-urile si rapoartele) — minimum 6 zone distincte: DDL cu migrare de date istorice, ~4 proceduri
in `PACK_FACTURARE`, 3-4 view-uri (`fact_vrap_fact_articole`, `fact_vrap_articole_vandute`,
`fact_vfacturi`, `fact_vfacturi2`), raportul dinamic din
`COMUN\programe\oproceduri_rapoarte_fact.prg:581-590`, gridul si rapoartele `.frx`. Nu se justifica
atat timp cat flagul per linie rezolva cazul real.

View File

@@ -7,9 +7,9 @@ si verificari proprii, ca sa nu se amestece.
| # | Plan | Volum | Ce atinge | Stare |
|---|---|---|---|---|
| — | #8 — denormalizare VANZARI | **mediu** (scop extins 06.08) | `PACK_FACTURARE` + ambele view-uri (COMUN) + reparare date | **TERMINAT si COMIS** 07.08.2026 (r17990-r17993). Planul a fost sters la curatenie; istoricul e in `progres.md` |
| — | [#7 — pret cu TVA pe linie](plan_07_pret_cu_tva_pe_linie.md) | mic-mediu | doar VFP, gridul de articole din `frm_facturare_articole` | **TERMINAT** 08.08.2026 (changelog 2.11.14). Editarea flagului pe o factura **deja salvata** a fost mutata explicit in #6 |
| 1 | [#6 — editare factura emisa](plan_06_editare_factura.md) | mediu | `omodificari.vc2` (COMUN) + `PACK_FACTURARE` | **IN LUCRU, aproape gata** — S1-S7 si S9 comise (S4b incheiat 11.08.2026). Ramane **S8**: 2 esecuri reale pe „factura din aviz" (rulajele nu se refac pe nota noua) + 3 tipuri de sursa neacoperite. Stare: antetul planului |
| 2 | [#13 — formular unificat + editare prin regenerare](plan_13_unificare_formular_facturare.md) | **mare** | `ofacturare.vc2` + `ofacturare.prg` + `PACK_FACTURARE` (COMUN) | **PROIECTAT INTEGRAL, cod neatins** (17 runde de proiectare, 11.08.2026). Etapele I si II sunt proiectate story cu story (S1-S14); raman testele (S6, S12) si inchiderea (S13). **65 de decizii luate**, niciuna deschisa. Perimetrul cu #6 e transat: **#13 incepe dupa terminarea lui #6** (decizia 30). Mockup: [`mockup_13_formular_unificat.html`](mockup_13_formular_unificat.html). Stare curenta: `handoff_13_formular_unificat.md` |
| — | #7 — pret cu TVA pe linie | mic-mediu | doar VFP, gridul de articole din `frm_facturare_articole` | **TERMINAT** 08.08.2026 (changelog 2.11.14). Editarea flagului pe o factura **deja salvata** a fost mutata in #6. Planul a fost sters la curatenie; istoricul e in `progres.md` |
| — | #6 — editare factura emisa | mediu | `omodificari.vc2` (COMUN) + `PACK_FACTURARE` | **INCHIS** 20.08.2026 (r18026, changelog 2.11.15). Planul a fost sters la curatenie; istoricul — inclusiv cele 2 esecuri ramase pe „factura din aviz" din S8 — e in `progres.md` |
| 1 | [#13 — formular unificat + editare prin regenerare](plan_13_unificare_formular_facturare.md) | **mare** | `ofacturare.vc2` + `ofacturare.prg` + `PACK_FACTURARE` (COMUN) | **PROIECTAT INTEGRAL, cod neatins** (17 runde de proiectare, 11.08.2026). Etapele I si II sunt proiectate story cu story (S1-S14); raman testele (S6, S12) si inchiderea (S13). **65 de decizii luate**, niciuna deschisa. Perimetrul cu #6 e transat: **#13 incepe dupa terminarea lui #6** (decizia 30). Mockup: [`mockup_13_formular_unificat.html`](mockup_13_formular_unificat.html). Stare curenta: `handoff_13_formular_unificat.md` |
| — | [#12 — nomenclator ca sursa de pret](plan_12_nomenclator_ca_lista_preturi.md) | mediu | depinde de varianta | valabil, **amanat** |
| — | [#11 — integrare politici de preturi](plan_11_integrare_politici_preturi.md) | mare | COMUN + ROAPRETURI | valabil, **amanat** |
| — | [#10 — integrare contracte](plan_10_integrare_contracte.md) | mare | COMUN + ROACONTRACTE | valabil, **amanat** |
@@ -27,8 +27,8 @@ care mai au nevoie de analiza** inainte de a fi pornite:
- **#10** — go/no-go-ul nu e inchis. Datele de la un client inclina spre no-go, dar e un singur
client; mai trebuie aceleasi cifre de la 2-3 clienti care chiar lucreaza pe contracte.
Se reiau dupa terminarea lui #8, #7 si #6, cu analiza reluata de unde s-a oprit (rapoartele din
`docs\cercetare\`).
#8, #7 si #6 sunt terminate, deci conditia de reluare e indeplinita; analiza se reia de unde s-a
oprit (rapoartele din `docs\cercetare\`).
## Dependente reale intre planuri

View File

@@ -2372,3 +2372,45 @@ Raport complet: **`docs\raport_r7_sincronizare.md`**. Diff-ul commit-ului unic (
Back-fill-ul de la reincarcare nu repara: are garda `WHERE EMPTY(NVL(...,0))`, deci prinde doar
zerourile. Raportat ca defect, **nereparat** - decide Marius.
- **Nimic nu e comis.** Se asteapta aprobarea pe `docs\diff_r6_r7_pentru_commit.md`.
## Curatenie dupa inchiderea planului #6 - 20.08.2026
Planul #6 e inchis (SVN r18026, changelog 2.11.15). S-au sters **64 de fisiere** de lucru care nu
mai au consumator: planul si proiectarea (`plan_06_*`), brief-urile, diff-urile, rapoartele,
propunerile de runda 4/5/6, `rec_s4b_etapa2`, `review_s5_grid_articole`, `verificare_s5_writeback`,
`livrare_s5`, `diagnostic_pagina_articole_ux`, `raport_sweep_encoding`, plus 40 de `rec_*` din
`docs\cercetare\`. A plecat si `verificare_vfacturi.sql`, testul de regresie al planului #8, terminat
demult. Totul e recuperabil din git (`5b52cb3` si mai vechi).
**Ce s-a pastrat, si de ce:**
- planurile deschise sau amanate: **#13** (proiectat integral, urmeaza la rand), #12, #11, #10, #7,
`plan_index.md`;
- materialul lui #13: `handoff_13_formular_unificat.md`, `mockup_13_formular_unificat.html`,
`S1_inventar_campuri_formular_unificat.md`;
- `conventii_mediu_oracle.md`;
- din `docs\cercetare\`: toate cele 56 de fisiere non-`rec_*` si **7** `rec_*` care sunt inca
citate de cercetarile vii (`rec_cale_vanzari_detalii`, `rec_d42_efactura`,
`rec_datoria6_baza_regresie`, `rec_dec42_proiectare`, `rec_editare_factura`, `rec_s4_runda1`,
`rec_s5_oracle_vanzari`). Criteriul n-a fost numele, ci referintele: **#13 se sprijina pe
`docs\cercetare\` cu 86 de trimiteri**, deci folderul nu se putea sterge in bloc.
Trimiterile catre fisierele sterse care raman in acest jurnal (39) si cele 5 mentiuni in proza din
`plan_07`, `plan_13`, `handoff_s4_runda1`, `rec_s4_runda1` si `rec_cale_vanzari_detalii` sunt
istorice - se rezolva din git, ca la curatenia planului #8.
### Completare: si planurile #7 si #8 - 20.08.2026
- **#7** (pret cu TVA pe linie, terminat 08.08.2026, changelog 2.11.14): sters
`plan_07_pret_cu_tva_pe_linie.md`, singurul lui artefact ramas.
- **#8** (denormalizare VANZARI, terminat si comis 07.08.2026, r17990-r17993): **nu mai era nimic
de sters**. Planul plecase la curatenia anterioara, testul lui de regresie
(`verificare_vfacturi.sql`) si `rec_s8_*` au plecat mai sus, in acelasi val.
Atentie la o capcana de nume: `cercetare\s8_incarcare_document.md`, `s8b_rutarea_scrierii.md` si
`audit_vanzari_creare_modificare_stergere.md` **nu** tin de planul #8 - sunt story-uri ale lui
**#13** si sunt citate de `plan_13` si de handoff-ul lui. Raman. Restul mentiunilor de
"denormalizare" din `cercetare\` sunt trimiteri de context catre #8, in cercetari inca vii.
Total sters in aceasta curatenie: **65 de fisiere**. In `docs\` raman 10 fisiere plus
`docs\cercetare\` cu 63.

View File

@@ -1,84 +0,0 @@
# Propunere: precizia pretului de achizitie la trimiterea liniilor de factura (FACT-008)
Diagnostic: `docs/cercetare/rec_fact008_pret_achizitie_zecimale.md`.
Diff: `docs/diff_fact008_precizie_pret.patch` — **aplicat pe 18.08.2026**, necomis.
In productia vending optiunea `PPRET` a fost pusa pe 4 si eroarea a disparut, confirmand diagnosticul.
Modificarea de cod acopera acelasi lucru independent de configurare.
## Modificarea propusa
Un singur tipar, in 8 locuri: serializarea in SQL foloseste precizia declarata a cursorului, nu
`gnPPret` / `gnPPretV` / `gnPPretVal` singure.
```foxpro
- Alltrim(Str(poArt.pret_achizitie,18,gnPPret))
+ Alltrim(Str(poArt.pret_achizitie,18,Max(gnPPret,4)))
```
Cursoarele sunt deja definite `pret_achizitie N(20, Max(gnPPret,4))`, `pretd N(20,
Max(gnPPretVal,4))`, `pretv_orig N(20, Max(gnPPretV,4))` — modificarea doar aliniaza emiterea cu
declaratia. Coloanele tinta din Oracle suporta precizia (`STOC.PRET/PRETV/PRETD` au scale 4,
`VANZARI_DETALII_TEMP.PRET_ACHIZITIE` scale 6).
| fisier | linii | camp |
|---|---|---|
| `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole.do_scrie_articole`) | 14073, 14074, 14087 | `pret_achizitie`, `pretd`, `pretv_orig` |
| `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole2.do_scrie_articole`) | 18108, 18109, 18122 | `pret_achizitie`, `pretd`, `pretv_orig` |
| `COMUN\programe\ofacturare_stoc.prg` (`adauga_articol_factura_stoc`) | 360, 361 | `pret_achizitie`, `pretd` |
`pret_achizitie` rezolva eroarea raportata. `pretd` si `pretv_orig` sunt aceeasi eroare, latenta:
`descarca_gestiune` le compara si pe ele prin egalitate exacta (`A.PRETD = V_PRETD`,
`A.PRETV = V_PRETV_ALES`), iar coloanele din `STOC` au tot scale 4. La vending nu se manifesta
(`pretv = 0`, `pretd = 0` peste tot), dar pe o gestiune tinuta la pret de vanzare ar da acelasi
FACT-008. Daca se prefera modificarea strict minima, se pot pastra doar cele 3 linii cu
`pret_achizitie` — spun explicit ca as include si celelalte 5.
## Ce NU propun
- **Rotunjire la importul eFactura** (`ROACONT\COMUN\clase\anaf_efactura.vc2`, liniile 12727,
12744, 12786). Ar strica valoarea receptiei: 8.559 x 500 = 4279.50 fata de 4279.28 cat e valoarea
reala a facturii. Zecimalele sunt corecte, transportul din facturare e cel gresit. (Ramane
discutabila inconsecventa dintre `4` la 12744/12786 si `6` la 12727 — dar e cosmetica, nu
cauza.)
- **Relaxarea predicatului din `pack_facturare.descarca_gestiune`** (`round(A.PRET, ...)`). Pe un
articol cu mai multe loturi in aceeasi gestiune ar putea potrivi lotul gresit si ar descarca alt
cost.
## Deblocare imediata in productie, separat de cod
`OPTIUNI.PPRET` de la 3 la 4 la vending — facut de Marius pe 18.08.2026, eroarea a disparut. Are
efect fara recompilare si a deblocat toate cele 14 articole din stoc care dadeau FACT-008. Cu
`PPRET = 4`, `Max(gnPPret,4)` din diff devine oricum echivalent, deci cele doua masuri nu se bat cap
in cap. Optiunea ramane pe 4 pana cand versiunea noua e in productie.
## Aplicare (dupa aprobare)
1. `.prg` — editare directa, atentie la CRLF.
2. `.vc2` — editare byte-safe (fisierul e cp1250 cu diacritice; liniile atinse sunt ASCII) apoi
write-back cu `txt2vcx.ps1 -AllowComun`, si verificare pe binar prin reconversie in cache
temporar + diff, nu pe mtime.
3. `COMUN\` e partajat de toate produsele ROA si versionat separat (`comun.git`) — modificarea
atinge si ROAGEST/ROAACNPRO/ROAIMOB. Commit-ul se face din `COMUN\`.
4. Changelog: intrarea intra la `2.11.15`, tag `:eroare:` — versiunea nu se bumpeaza, 2.11.15 nu e inca in productie.
5. Fara test headless util aici: eroarea apare doar cu un rand real din `STOC` cu pret pe 4
zecimale. Verificarea practica e o factura pe articolul 5155 dupa recompilare.
## Scriptul PPRET = 4 pentru toate firmele: abandonat
Scriptul a fost scris si apoi sters (`SCRIPTURI_CLAR\2026\08\ff_2026_08_19_01_COMUN_OPTIUNI_PPRET.sql`).
Decizia lui Marius, 19.08.2026: nu e nevoie de el.
Dupa ce intra versiunile cu `Max(gnPPret,4)`, scriptul nu mai repara nimic, iar `PPRET` nu e doar
precizie de transport — conduce si rotunjirea pretului unitar la NIR-ul introdus manual
(`ointroduceri.vc2`: `Round(valoare/cantitate, gnPPret)`) si mastile de afisare. Pus pe 4 la toate
firmele ar fi schimbat rotunjirea receptiilor manuale la toti clientii, ca sa repare ceva ce codul
repara deja. `versiune_db.txt` a ramas nebumpat.
Ramane de acoperit prin cod, nu prin configurare: `ROAGEST\Programe\ofactureaza.prg` (liniile 267,
268, 280) inca trunchiaza, iar copiile `COMUN` din celelalte produse sunt in urma si primesc
reparatia la urmatorul `svn update` plus recompilare.
**La vending PPRET ramane pe 4 pana cand versiunea noua e in productie** — acum e singurul lucru care
tine facturarea in picioare acolo. Dupa punerea in productie se poate reveni la 3, daca nu se doresc
preturi de achizitie pe 4 zecimale la receptiile manuale.

View File

@@ -1,286 +0,0 @@
# Runda 4 - totalul notelor, dialogul de sincronizare, explicatia TVA, codul de TVA
Diagnostic pe semnalarile din proba pe factura din aviz (document fara rulaje).
Fiecare punct: constatarea, dovada (fisier:linie), propunerea.
## Stare: aplicat in text si scris in binar, necomis
Decizii luate: la explicatia TVA - **varianta B** (dialogul `caut_explicatie_tva`, cu
corelarea codului SAF-T si filtrarea pe cota liniei); la punctul 2 - **fara** avertisment
suplimentar la salvare.
| ce | unde | write-back |
|---|---|---|
| 1. Total note se reface la orice recalculare a notei | `COMUN\clase\omodificari.vc2` | facut, `.vcx` la zi |
| 3. capete de coloana dinamice + labelul de explicatii eliminat | `COMUN\clase\omodificari.vc2` | facut, `.vcx` la zi |
| 4. codul de TVA nu mai vine ca Memo | `COMUN\programe\ofacturare_editare.prg` | n/a (`.prg`) |
| 5. explicatia TVA pe linia de articol + corelare taxcode | ambele fisiere | facut, `.vcx` la zi |
| 6. salvarea liniilor existente pastreaza toate campurile editabile | `COMUN\programe\ofacturare_editare.prg` | n/a (`.prg`) |
Diff-uri pentru review: `docs\diff_runda4_omodificari.patch`,
`docs\diff_runda4_ofacturare_editare.patch`. **Necomis** - nici git, nici SVN.
Verificat dupa write-back: textul reconvertit din `.vcx` e identic cu `.vc2` (0 diferente),
zero caractere corupte.
---
## 1. "Total note" nu se actualizeaza cand modific valoarea notei
**Nu e din cauza rulajelor.** Bara de totaluri se recalculeaza doar din evenimente de pe
partea de **articole**, niciodata din grila de note.
`ActualizeazaBaraTotaluri` (`COMUN\clase\omodificari.vc2:12959`) calculeaza `nTotalLiniiRon`
/ `nTotalNetRon` si apeleaza `ActualizeazaVerdictActRul` (`:13001`), care pune
`This.nTotalActRon` - adica exact valoarea afisata in "Total note"
(`txtTotalActArt.ControlSource = "thisform.nTotalActRon"`, `:12878-12880`).
Apelurile ei in productie sunt doar acestea:
| linie | context |
|---|---|
| `omodificari.vc2:13488` | `calculeaza_valori_articol` (editare in grila de articole) |
| `omodificari.vc2:14920` | `Show` (o data, la deschidere) |
| `omodificari.vc2:16604` | `pgfArticole.PAGE3.Activate` (la intrarea pe pagina Articole) |
| `omodificari.vc2:16698` | `txtDiscountArt.Valid` |
| `omodificari.vc2:16946` | `frm_sincronizare_articole.inainte_de_do_termin` |
| `ofacturare_editare.prg:1023` | editorul de articole din nota |
Editarea sumei pe randul de nota trece prin `Grid1.Column9.Text1.Valid`
(`omodificari.vc2:16044`), care apeleaza `Thisform.CalculeazaTotal()` -
`calculeazatotal` (`:13458`) recalculeaza numai `This.nSuma` (totalul setului de note) si
face `txtSuma.Refresh()`. Bara de jos ramane cu valoarea de la ultimul eveniment de articol.
Bara se reimprospateaza indirect daca iesi si reintri pe pagina Articole (`PAGE3.Activate`) -
de aceea uneori pare ca "s-a actualizat singura".
### Propunere 1 - o linie
La finalul lui `calculeazatotal` (`omodificari.vc2:13458-13475`), dupa
`Select (m.lcSelect)`:
```foxpro
This.ActualizeazaBaraTotaluri()
```
`ActualizeazaBaraTotaluri` are deja garda proprie la inceput
(`IF !This.lAreArticoleVanzari OR !Used('tvd') OR !Used('tvanz') OR Reccount('tvanz') = 0`),
deci pe notele fara articole nu face nimic. `calculeazatotal` e apelata din 8 locuri
(inclusiv `Show`, `:14878`), toate momente in care bara oricum trebuie sa fie corecta.
Alternativa mai ingusta (doar `Grid1.Column9.Text1.Valid`) acopera doar suma, nu si
stergerea/adaugarea de randuri de nota - nu o recomand.
---
## 2. Nu a aparut mesajul de verificare la salvare
**Aici da, e din cauza rulajelor** - si e comportament voit.
`SemnaturaDivergenteSincronizare` (`omodificari.vc2:14805`) iese devreme cu semnatura goala
cand nu exista randuri active in `trul`:
```foxpro
*!* fara rulaje nu exista cu ce compara: propunerea ar marca toate articolele ca "Semnalare"
SELECT trul
COUNT FOR Nvl(sters,0) <> 1 TO lnRanduriRul
IF m.lnRanduriRul = 0
... RETURN m.lcSemnatura && ''
```
iar declansatorul de la salvare (`:14449-14458`) porneste dialogul numai daca semnatura e
nevida si difera de cea de la deschidere. Deci: document fara rulaje -> niciun dialog.
Acelasi lucru il spune si verdictul din bara: *"nu se aplica (documentul nu are rulaje)"*
(`:13089`).
**Important, ca sa nu ramana o asteptare gresita:** mecanismul de sincronizare compara
**rulaje vs articole**, niciodata **note vs articole**. Chiar daca documentul ar fi avut
rulaje, modificarea sumei de pe randul de nota nu ar fi deschis dialogul - divergenta
note/articole e semnalata exclusiv informativ, prin verdictul din bara
(`ActualizeazaVerdictActRul`, `:13001`), si acolo, la 305.00 vs 306.00, ar fi trebuit sa
scrie "divergent" imediat dupa editare. Nu a scris-o din cauza punctului 1.
**Nicio schimbare de comportament** aici, decis: dialogul de sincronizare n-are ce aplica
fara rulaje, iar avertismentul simplu la salvare nu se face. Punctul 1, odata reparat, aduce
singurul semnal care lipsea (verdictul devine "divergent" pe loc).
---
## 3. Dialogul de sincronizare: coloane clare, fara labelul de explicatii
Starea de acum (`omodificari.vc2:16703` - `frm_sincronizare_articole`):
- capete de coloana fixe: `Cantitate veche` / `Cantitate noua` / `Pret vechi` / `Pret nou`
(`:16838-16867`);
- `lblAvertisment` (`:16871-16882`) explica ce inseamna: *"Vechi = valorile care se
salveaza acum; Nou = valorile din sursa aleasa mai sus..."*.
Semantica reala, verificata in `ConstruiestePropunereSincronizare`
(`ofacturare_editare.prg:707-710`): `vechi = tinta` (ce se suprascrie),
`nou = sursa` (de unde se preia). Directia se alege din `optDirectie` (`:16888`):
`RUL_SURSA` (rulajul e sursa) sau `ARTICOLE_SURSA`.
### Propunere 3 - capete dinamice pe directie + eliminarea labelului
In `ConstruiesteEnumerare` (`:16913`), care ruleaza deja la deschidere si la fiecare
schimbare de directie, se seteaza captiunile:
| coloana | `RUL_SURSA` | `ARTICOLE_SURSA` |
|---|---|---|
| 2 (`cCantVecheProp`) | `Cantitate in factura (se inlocuieste)` | `Cantitate in rulaj (se inlocuieste)` |
| 3 (`cCantNouaProp`) | `Cantitate in rulaj (se preia)` | `Cantitate in factura (se preia)` |
| 4 (`cPretVechiProp`) | `Pret in factura (se inlocuieste)` | `Pret in rulaj (se inlocuieste)` |
| 5 (`cPretNouProp`) | `Pret in rulaj (se preia)` | `Pret in factura (se preia)` |
Se sterge `lblAvertisment`; singura informatie din el care nu intra in capete este
*"daca renuntati, salvarea continua neschimbata"* - aceea exista deja ca ToolTip pe
`But_renunt1` (`:16752`, "Inchide fara sa schimbe nimic - salvarea continua"). Propun sa o
mut in ToolTipText-ul gridului sau sa o las doar pe buton; spune tu care variantă.
Ajustari de layout necesare: `HeaderHeight` 22 -> 32 (capetele au `WordWrap = .T.`, doua
randuri), latimile coloanelor 2-5 de la 88 la ~108, si gridul se poate inalta cu ~36 px
in locul labelului eliminat.
**Atentie la write-back:** `frm_sincronizare_articole` e clasa in `omodificari.vc2`, deci
modificarea e pe text + `txt2vcx.ps1`, nu in IDE.
---
## 4. Alegerea codului de TVA arata "Memo"
Confirmat, si cauza e exact cea spusa.
`ArticoleNotaEditor.CautaCodTva` (`COMUN\programe\ofacturare_editare.prg:1094-1100`):
```foxpro
lcSelect = [select taxname, procent_taxa, taxcode from vsaft_taxtable]
RETURN cauta_alfa(m.lcSelect, [1=2], [], [taxname], [taxname,procent_taxa], ;
[Alegeti codul de TVA], [Denumire,Procent], [], .F., [1=1], [taxname], 1, 0, [taxcode])
```
`cauta_alfa` trimite select-ul mai departe la `gencursor` (`COMUN\programe\cauta_alfa.prg:151`),
care il pune pe `SELECTCMD`-ul unui CursorAdapter legat de `goConn.nHandle`
(`COMUN\programe\gencursor.prg:27-31`) - **deci e SQL Oracle, executat pe server**.
`taxname` din view depaseste latimea declarata de 254 de caractere, iar driverul mapeaza
coloana pe Memo; grila din `cauta_alfa_form` afiseaza literalmente "Memo".
**Latimea declarata, nu cea reala** - asta e detaliul care conteaza. Definitia view-ului
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2022\03\ff_2022_03_14_01_COMUN_SAFT.sql:566-582`):
```sql
create or replace view vsaft_taxtable as
select ...
substr(a.taxcode || '-' || a.descriere, 1, 250) as taxname,
```
cu `descriere VARCHAR2(250)` in tabela (`ff_2022_03_08_01_COMUN_SAFT.sql:28`). Concatenarea
`taxcode || '-' || descriere` are latime declarata ~291 (NUMBER convertit implicit + 1 +
250), iar **`SUBSTR` nu micsoreaza latimea declarata a rezultatului** - ramane cea a
intrarii. De aceea `substr(...,1,250)` deja existent in view nu a rezolvat nimic, si de
aceea nici un `substr(taxname,1,200)` in clientul nostru nu ar rezolva singur: driverul tot
ar vedea o coloana > 254 si tot ar da Memo. Trebuie **`CAST`**, care e singurul care fixeaza
latimea declarata.
`LEFT` nu exista in Oracle - `SUBSTR` e idiomul folosit deja in cod
(`COMUN\programe\updateserver.prg:1357`, `substr(a.adresa,1,250) as adresa`).
### Propunere 4 - CAST, in tabela derivata
Nu merge simplu la nivelul de sus: `deca_baza.afisare` (`COMUN\clase\decabaza.vc2:36-73`)
concateneaza `WHERE` si `ORDER BY` **la acelasi nivel de query**, iar Oracle nu accepta
aliasul de coloana in `WHERE` (ORA-00904). Filtrul de cautare si ordinea sunt ambele pe
`taxname` (parametrii 4 si 11 din apel), deci forma sigura e:
```foxpro
lcSelect = [select * from (select cast(substr(taxname,1,200) as varchar2(200)) as taxname, ] + ;
[procent_taxa, taxcode from vsaft_taxtable)]
```
Restul apelului ramane neschimbat. De testat dupa aplicare: deschiderea dialogului, cautarea
"incepe cu" pe denumire (verifica ca `WHERE` merge pe aliasul din tabela derivata) si
returnarea corecta a `taxcode`.
**Varianta alternativa, doar pe client:** `cauta_alfa` primeste ca parametru 3 o
`CURSORSCHEMA` (acum `[]`, vezi apelul). Trecand
`[taxname C(200), procent_taxa N(6,2), taxcode N(6)]` s-ar forta tipul in cursorul VFP fara
sa se atinga SQL-ul. Merge, dar leaga apelul de structura exacta a view-ului; prefer CAST-ul.
**De reparat separat, daca vrei:** acelasi `substr` fara CAST din view il mosteneste si
`typename` (`:569`) - orice alt loc care afiseaza `vsaft_taxtable.typename` are aceeasi
problema. Nu am verificat unde se mai foloseste.
**Nota:** celelalte alegeri de taxcode (randul de nota, `omodificari.vc2:3926` / `:8491`,
si rulajele, `rulaje.vc2:5289`) citesc cursorul **local** `saft_taxtable`, unde `taxname` e
C(250) - acolo nu apare Memo. Problema e strict pe calea Oracle din `CautaCodTva`.
---
## 5. Explicatia TVA in editarea articolelor
Lipseste, si se poate adauga - campul exista deja in cursor.
`tvd` are `id_jtva_coloana I NULL` de la creare (`omodificari.vc2:14735`) si e completat
din articol la adaugare (`AdaugaLinieTvdDinArticol`, `:13144`). Ce lipseste:
1. **coloana in grila** `grdArticoleFactura` - coloanele actuale sunt 1-15
(`:12345-12454`), fara nimic pe `id_jtva_coloana`;
2. **editorul** - `ArticoleNotaEditor` (`ofacturare_editare.prg:1060-1090`) trateaza doar
`denumire`, `nume_gestiune`, `nume_val` si taxcode.
Sablon existent, de copiat: randul de rulaj face exact asta -
afisare prin `Iif(Seek(trul.id_jtva_coloana,'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')`
(`omodificari.vc2:9253`) si editare prin combo cu
`RowSource = "select denumire, id_jtva_coloana from crsJtvaTemp order by denumire into cursor crsJtvaTempX"`
(`:9652-9655`). Randul de nota foloseste dialogul `caut_explicatie_tva`
(`COMUN\programe\ocautare.prg:3174`), apelat din `do_modifica_explicatie_tva`
(`omodificari.vc2:14049`), care pune `id_jtva_coloana`, `explicatie_tva` si `proc_tva`.
### Aplicat - varianta B (dialogul `caut_explicatie_tva`)
1. **Coloana** `cExplicatieTvaArt` in `grdArticoleFactura` (ColumnCount 15 -> 16), readonly,
care afiseaza denumirea prin
`Iif(Seek(Nvl(tvd.id_jtva_coloana,0),'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')` -
acelasi tipar ca pe randul de rulaj. **Coloana e ultima in grila**, dupa Valoare: ordinea
naturala e cea a indicilor, iar mutarea ei langa Taxcode ar fi cerut `ColumnOrder` pe toate
celelalte. Daca o vrei langa Taxcode, e o a doua iteratie, doar de asezare.
2. **Text1.GotFocus** pune explicit `Thisform.pccontrol = 'tvd.id_jtva_coloana'` - coloana are
ControlSource expresie, deci `Alltrim(This.ControlSource)` (tiparul celorlalte coloane) n-ar
fi dat un nume de camp utilizabil.
3. **`ArticoleNotaEditor.CautaExplicatieTva`** apeleaza `caut_explicatie_tva` cu filtrul de cota
calculat din linie: `Round((Nvl(tvd.proc_tvav,0) - 1) * 100, 2)` - `proc_tvav` e in forma
1.21, exact ca `tact.proc_tva` la randul de nota. Cota 0/goala = lista completa.
`tlTipEx` ramane gol (toate explicatiile cu `id_jtva_coloana > 0`), ca in ramura `Else` a
randului de nota; daca vrei restrangerea la TVA exigibil din JV, e un parametru in plus.
4. **Corelarea codului SAF-T**: alegerea scrie `id_jtva_coloana` si `proc_tvav`
(`(cota_tva + 100) / 100`), apoi cheama `UpdateExplicatieSAFTArt` - metoda noua in
`frm_modific2024`, copie a lui `UpdateExplicatieSAFTRul` cu `tvd` in loc de `trul`
(acelasi `GetTaxCodeIdPart`, aceeasi garda `gl406`). La final `calculeaza_valori_articol`,
ca valoarea liniei si bara de totaluri sa urmeze noua cota.
---
## 6. Defect gasit pe drum: salvarea pierde tacut alegerile de pe liniile existente
`ScrieArticoleFacturaEditate` (`ofacturare_editare.prg:471`) actualiza liniile deja salvate
**doar** cu `sters`, `cantitate`, `pret`, `pret_cu_tva`. Restul campurilor apareau doar in
INSERT-ul liniilor noi. Consecinta: pe o linie existenta, codul de TVA ales din nomenclator,
gestiunea, valuta, articolul, pretul de achizitie si discountul se pierdeau la salvare fara
niciun mesaj - defect care exista **inainte** de explicatia TVA, nu introdus de ea.
UPDATE-ul acopera acum toate campurile pe care grila si nomenclatoarele le pot schimba:
`pret_achizitie`, `proc_tvav`, `discount_unitar`, `id_articol`, `id_gestiune`, `id_valuta`,
`id_jtva_coloana`, `taxcode`, `cont`.
---
## De probat
1. **Total note**: modifica suma pe un rand de nota -> "Total note" se schimba pe loc, iar
verdictul trece pe "divergent" daca nu mai bate cu articolele.
2. **Cod TVA**: pe pagina Articole, coloana Taxcode + butonul de modificare -> lista trebuie
sa arate denumirile, nu "Memo"; cautarea "incepe cu" pe denumire trebuie sa filtreze.
3. **Explicatie TVA**: pe o linie cu cota 21%, lista trebuie sa contina doar explicatiile de
21%; dupa alegere, coloana Taxcode se schimba singura.
4. **Persistenta**: alege cod TVA / explicatie TVA pe o linie **deja salvata**, salveaza,
redeschide - valorile trebuie sa fie acolo.
5. **Dialogul de sincronizare** (document cu rulaje): capetele se schimba cand comuti directia,
iar legenda rosie de jos nu mai exista.

View File

@@ -1,294 +0,0 @@
# Runda 5 - editare inline pe pagina Articole, totalurile facturii din aviz, liniile de rata
Cinci semnalari din proba lui Marius, 19.08.2026. Fiecare punct: constatarea, dovada
(`fisier:linie` sau interogare pe `MARIUSM_AUTO@ROA_CENTRAL`), propunerea.
**Stare: nimic aplicat.** Toate punctele de mai jos sunt propuneri. Diff-urile se livreaza dupa
aprobare, iar write-back-ul text->binar vine dupa diff.
Cercetarile din spate: `docs\cercetare\rec_r5_editare_inline_articole.md`,
`docs\cercetare\rec_r5_linii_fara_articol_contract.md`,
`docs\cercetare\rec_r5_totaluri_factura_din_aviz.md`.
---
## 1. Editare inline pe pagina Articole: serie, lot, explicatie, procent TVA
Coloanele cerute sunt azi `ReadOnly = .T.` in `grdArticoleFactura`
(`COMUN\clase\omodificari.vc2:12368` serie, `:12375` lot, `:12449` explicatie, `:12419`
`proc_tvav`).
Trecerea pe editabil urmeaza tiparul deja folosit pe `cPretArt`: `Text1.When` refuza editarea cand
`Thisform.lArticoleReadOnly` sau `Nvl(tvd.id_vanzare_set,0) <> 0`
(`omodificari.vc2:16735-16740`), `Text1.Valid` recalculeaza unde e nevoie.
`serie`, `lot`, `explicatie` nu cer niciun recalcul - se scriu direct in cursor.
### Defect care trebuie reparat in aceeasi livrare
`ScrieArticoleFacturaEditate` (`COMUN\programe\ofacturare_editare.prg:505-517`) **nu scrie
`serie`, `lot`, `explicatie` in UPDATE-ul liniilor deja salvate** - cele trei campuri apar doar in
INSERT-ul liniilor noi (`:535-546`). Fara aceasta corectie, editarea inline ceruta s-ar pierde
tacut la salvare pe orice linie existenta. `proc_tvav` e deja in UPDATE, deci pentru el nu e
nimic de facut.
### Procentul de TVA - punctul delicat
`tvd.proc_tvav` e in forma **1.21**, nu 21 (verificat pe date: randurile 1599-1604 din
`VANZARI_DETALII` au toate `1.21`). Se pastreaza forma asta: exista precedent direct pe aceeasi
pagina, coloana `cProc_tva` de pe grila de rulaje e deja editabila liber in aceeasi forma.
Riscul real e altul: pe `tvd`, cota e corelata cu `id_jtva_coloana` (explicatia TVA) si cu
`taxcode` (SAF-T). Azi, alegerea explicatiei scrie cota **si** recoreleaza taxcode-ul
(`ofacturare_editare.prg:1099-1104` -> `UpdateExplicatieSAFTArt`, `omodificari.vc2:15049-15073`).
Daca utilizatorul tasteaza direct cota, corelarea ramane la cota veche: explicatie de 21% langa o
cota de 19%, si `taxcode` gresit in raportarea 406. Pe rulaje riscul nu exista - `trul` nu poarta
aceeasi corelare.
**DECIZIE CERUTA - A sau B:**
| | Ce face | Consecinta |
|---|---|---|
| **A** (recomandat) | La editarea manuala a cotei, `Valid` goleste `id_jtva_coloana` si `taxcode` pe randul respectiv | Explicatia TVA si Taxcode raman goale - semnal vizibil ca trebuie realeasa explicatia; dialogul se deschide deja filtrat pe noua cota |
| **B** | Doar `calculeaza_valori_articol()`, corelarea ramane neatinsa (ca pe rulaje) | Mai simplu, dar lasa tacut un taxcode SAF-T gresit |
Varianta "recoreleaza automat" a fost respinsa: pe aceeasi cota pot exista mai multe explicatii
(JC vs JV, exigibil vs neexigibil - parametrul `tlTipEx` din `caut_explicatie_tva`,
`COMUN\programe\ocautare.prg:3174`), iar alegerea automata a uneia ar fi arbitrara exact acolo unde
conteaza sa fie corecta.
**DECIZIE CERUTA - garda pe serie/lot.** Pretul de achizitie e blocat si pe liniile deja salvate
(`Nvl(tvd.id_vanzare_det,0) <> 0`, `omodificari.vc2:16721-16726`); cantitatea si pretul nu.
Propunerea merge pe garda slaba (serie/lot editabile si pe liniile salvate, blocate doar pe cele
venite din set). Daca trasabilitatea lotului pe linii deja livrate conteaza, se aliniaza la garda
pretului de achizitie.
### Nomenclatoarele pe `InteractiveChange`
Cele cinci coloane cu nomenclator - `cDenumireArt`, `cGestiuneArt`, `cValutaArt`, `cTaxcodeArt`,
`cExplicatieTvaArt` - au deja `Text1.GotFocus` care pune `Thisform.pccontrol`
(`omodificari.vc2:16705-16720`, `:16755-16765`). Lipsesc doar `ReadOnly = .F.` si
`Text1.InteractiveChange`, care cheama `ArticoleNotaEditor.ModificaNomenclator(Thisform.pccontrol)`.
Sablonul e cel de pe grila de note (`omodificari.vc2:5890-5940`): coloana editabila, `GotFocus`
pune `pccontrol`, `InteractiveChange` deschide dialogul. `But_modificaR` ramane functional si
sincron - `BeforeRowColChange`/`AfterRowColChange` nu cer nicio modificare.
Efect lateral cunoscut si acceptat in sablonul existent: primul caracter tastat ajunge in celula
inainte sa se deschida dialogul. Daca utilizatorul renunta la dialog, caracterul ramane vizibil
pana la urmatorul refresh. Se poate atenua cu un `RefreshGrid()` pe ramura de anulare din
`ModificaNomenclator` - **spune daca il vrei in aceasta livrare** sau ramane pe alta runda.
---
## 2. Totalurile goale pe factura editata din aviz
Confirmat pe date, nu dedus.
`VANZARI` id_vanzare=1054 (SSS 100037, tip=4, `IN_VALUTA=0`, `DISCOUNT=0`) are
**`TOTAL_FARA_TVA` / `TOTAL_TVA` / `TOTAL_CU_TVA` = NULL** - de aici coloanele goale din lista.
Are o singura linie activa, `VANZARI_DETALII.id_vanzare_det=1602`, cu **`ID_VALUTA = 2` (EURO)**.
Linia-sursa din aviz (`id_vanzare_det=1599`) are `id_valuta = 3` (RON). Pe linia 1602 difera fata
de aviz si `id_gestiune` (1 vs 2), `taxcode` (310344 vs 310350) si `id_jtva_coloana` (35 vs 37) -
adica exact nomenclatoarele probate in runda 4, valuta inclusa.
### Lantul cauzal
`pack_facturare.recalculeaza_totaluri_vanzari`
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`):
```sql
(case when (lnInValuta = 1 or vd.id_valuta <> pack_def.GetIdMonedaNationala())
then ROUND(vc.curs * vd.pret / vc.multiplicator, lnPreciziePretV)
else vd.pret end) as pret_ron
...
left join vanzari_cursuri vc on vc.id_vanzare = V_ID_VANZARE and vd.id_valuta = vc.id_valuta
```
EURO (2) <> RON (3) -> intra pe ramura de conversie -> `VANZARI_CURSURI` **n-are niciun rand**
pentru id_vanzare=1054 -> `vc.curs` NULL -> `pret_ron` NULL -> `SUM(...)` NULL -> UPDATE-ul final
scrie NULL in cele trei totaluri. Selectul a fost rulat separat pentru 1054: intoarce NULL, NULL.
Nu conteaza care view alimenteaza lista: `FACT_VFACTURI2` citeste direct `VANZARI`, deci vede
NULL-ul scris; `FACT_VFACTURI` recalculeaza live, cu acelasi tip de agregare vulnerabila la NULL,
deci iese la fel. Alegerea intre ele e un prompt runtime
(`Clase\ofundal_facturare.vc2:944-953`).
### De ce lipseste randul de curs
La emitere, `pack_facturare.scrie_cursuri` (acelasi fisier, `:14538`) insereaza in
`VANZARI_CURSURI` cate un rand pentru fiecare valuta distincta, non-nationala, din liniile
documentului. **Calea de editare nu face acest lucru**: `ModificaNomenclator`, ramura `nume_val`
(`ofacturare_editare.prg:1096-1098`), scrie `id_valuta` pe linie, iar
`ScrieArticoleFacturaEditate` (`:512`) il duce in tabela - fara sa atinga `VANZARI_CURSURI`.
Invariantul pe care se bazeaza procedura de totaluri se rupe exact aici.
`VANZARI_CURSURI` se scrie **o singura data, la emitere**, din tabela de staging
`VANZARI_DETALII_TEMP`; nu exista nicio cale care sa-l actualizeze dupa aceea. Deci: linii intr-o
valuta straina pe un document in RON sunt legitime si frecvente - **157 de documente** cu
`in_valuta = 0` au asemenea linii, iar `VANZARI_CURSURI` are randuri pentru **125 de documente in
RON** - dar numai daca alegerea s-a facut **la emitere**. Orice schimbare de valuta facuta ulterior,
din formularul de editare, produce garantat o valuta orfana, fara curs.
Amploarea, masurata: din 235 de randuri `VANZARI` cu `total_cu_tva` NULL in toata baza, **unul
singur** se potriveste acestui defect - chiar SSS 100037 / id_vanzare 1054. Restul au cauze vechi,
cunoscute (120 fara linii active, 109 cu `discount_evidentiat` NULL). Deci nu e nevoie de nicio
reparatie in masa.
Confirmata si regresia: diff-ul fata de `ofacturare_editare.prg.pre_runda_butoane.bak:501-505`
arata ca UPDATE-ul liniilor existente **nu includea `id_valuta`** inainte de runda 4 - alegerea de
valuta se pierdea silentios si nu ajungea niciodata in tabela. Simptomul apare exact de la
extinderea UPDATE-ului.
### Propunere - doua straturi
**(a) Client - repara cauza.** Alegerea valutei pe linia de articol nu poate ramane libera: fara
un rand de curs pe document, orice valuta non-nationala e orfana, iar cursul nu se poate inventa
la editare (la emitere el vine din staging, adica din pretul negociat, nu din cursul zilei).
Garda merge in `ArticoleNotaEditor.ModificaNomenclator`, ramura `nume_val`
(`ofacturare_editare.prg:1078`), langa cea care blocheaza deja liniile din seturi (`:1069`).
Doua forme, **DECIZIE CERUTA**:
| | Regula | Efect |
|---|---|---|
| **a1** (recomandat) | se pot alege doar valutele care au deja rand in `VANZARI_CURSURI` pe documentul curent, plus moneda nationala | corectarea unei linii intre valutele deja folosite pe document ramane posibila; valuta orfana devine imposibila |
| **a2** | nomenclatorul de valuta e blocat cu totul cand `Nvl(tvanz.in_valuta,0) <> 1` | mai simplu si mai strict, dar interzice si corectiile legitime pe cele 125 de documente in RON care au deja cursuri |
**Respinsa**: varianta "salvarea completeaza singura randul lipsa din `VANZARI_CURSURI` cu cursul
zilei documentului". Cursul de la emitere e cel din oferta, nu cursul BNR al zilei - completarea
automata ar scrie un curs plauzibil dar inventat, si ar face totalurile sa arate corect fara sa fie.
**Respinsa si** varianta de a restrange conditia de conversie din procedura la `lnInValuta = 1`:
ar strica exact cele 157 de documente in RON cu linii in valuta, ale caror preturi sunt chiar in
valuta si trebuie convertite.
**(b) DB - plasa de siguranta.** In `recalculeaza_totaluri_vanzari`, UPDATE-ul final nu trebuie sa
poata scrie NULL peste totaluri valide:
```sql
total_fara_tva = NVL(lnTotalFaraTVA, total_fara_tva),
total_tva = NVL(lnTotalTVA, total_tva),
total_cu_tva = NVL(lnTotalCuTVA, total_cu_tva),
```
Pastreaza valoarea veche cand recalculul iese NULL, in loc sa goleasca documentul. E doar plasa
de siguranta, nu inlocuieste (a): fara (a), totalurile ar ramane **vechi si gresite** dupa o
editare, ceea ce e la fel de rau ca goale, doar mai greu de observat.
**Reparatia datelor - un singur document.** Randul 1602 are azi EURO fara curs, iar factura 1054
are totaluri NULL. Se repara punctual: `id_valuta` inapoi pe RON pe randul 1602, apoi un apel la
`recalculeaza_totaluri_vanzari(1054)`. **Nu-l rulez fara sa-mi ceri** - e singura scriere in baza
din toata livrarea, si e pe schema ta de dezvoltare.
---
## 3. `Field ID_ARTICOL does not accept null values` la editarea facturii din contract
Liniile de rata dintr-un contract sunt **prin proiectare** linii `VANZARI_DETALII` fara
`id_articol` (`COMUN\programe\ofacturare_comun.prg:1792` - `crsfacttemp` declara
`id_articol N(20) null` -, `:1836-1840`; documentat si in
`docs\plan_13_unificare_formular_facturare.md:2275`). Baza le accepta:
`VANZARI_DETALII.ID_ARTICOL` e **nullable**, si sunt deja 20 de linii active cu NULL.
Eroarea vine din cursorul de agregare, care nu declara campul ca acceptand NULL:
```
ofacturare_editare.prg:640 CREATE CURSOR agg_tvd (id_articol N(20), ...)
ofacturare_editare.prg:651 INSERT INTO agg_tvd (...) VALUES (tvd.id_articol, ...)
```
`agg_rul` (`:617`) are aceeasi declaratie, dar nu se poate manifesta: `RUL.ID_ARTICOL` e
**NOT NULL** in Oracle si nu exista niciun rand cu NULL.
### Propunere
Liniile fara articol se **exclud din comparatia rulaje <-> articole, chiar la sursa**: ambele
`SCAN`-uri de agregare din `ConstruiestePropunereSincronizare` (`:620` peste `trul`, `:643` peste
`tvd`) primesc in plus conditia pe articol completat. Cheia comparatiei chiar este `id_articol`;
o linie fara cheie nu poate avea corespondent in rulaje prin definitie. Filtrand la sursa, NULL-ul
nu mai ajunge la `INSERT INTO agg_tvd`, deci eroarea dispare de la radacina. Declaratia `NULL` pe
ambele cursoare ramane oricum, ca igiena.
Cele doua alternative si de ce le-am respins - comportamentul VFP a fost **masurat**, nu presupus
(test izolat `vfp9 -A -T` care reproduce `CREATE CURSOR` + `UNION` + `LOCATE FOR`, in scratchpad):
- **Doar declaratia `NULL`, fara sa umblam la comparatii.** `LOCATE FOR camp = NULL` nu gaseste
niciodata nimic, nici cu memvar NULL, nici intre doua cursoare. Randul de rata ar iesi din
`DO CASE` cu `llGasitSursa = .F.` **si** `llGasitTinta = .F.`, ar cadea pe `OTHERWISE` cu 0 = 0 si
n-ar genera nicio linie in `propunere_sincronizare` - **disparitie tacuta**, fara eroare si fara
semnalare. Acelasi rezultat practic ca filtrarea propusa, dar obtinut printr-un efect secundar
nedocumentat, care s-ar schimba brusc daca cineva "repara" mai tarziu comparatiile.
- **`Nvl(...,-1)` in cele cinci `LOCATE`** (`:626`, `:647`, `:671`, `:687`, plus `:849`/`:887`/`:923`
in aplicatoare). `UNION` trateaza NULL = NULL ca acelasi rand la deduplicare, deci **toate**
ratele unui document s-ar agrega laolalta sub un singur pseudo-articol si ar iesi ca divergenta
la fiecare salvare - dialogul de sincronizare s-ar deschide degeaba pe orice factura din contract.
---
## 4. `Linia '' nu are articol asociat` la salvarea facturii din contract
Garda e la `COMUN\clase\omodificari.vc2:14449`, in `inainte_de_do_termin`:
`IF Nvl(id_articol,0) = 0` pe fiecare linie activa din `tvd`. A fost pusa pe premisa ca
`id_articol` vine mereu completat din sursa - premisa infirmata de date (cele 20 de linii de mai
sus).
Denumirea goala din mesaj e reala, nu cosmetica: "RATA 2" sta in coloana `EXPLICATIE`, iar
`tvd.denumire` vine prin join pe `NOM_ARTICOLE` in `VVANZARI_ARTICOLE`
(`ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql`), deci iese NULL pe randul fara articol.
### Propunere
Garda ramane, dar **doar pe liniile noi, nesalvate** (`id_vanzare_det = 0`): acolo lipsa
articolului chiar inseamna ca utilizatorul n-a ales nimic din nomenclator. O linie incarcata din
baza cu `id_articol` NULL e stare legitima si trece.
Regula e crisp si nu se bazeaza pe euristici de tipul "are pret de achizitie, deci e articol
real".
### Defect legat, obligatoriu de reparat odata cu asta
`ScrieArticoleFacturaEditate` scrie azi `Nvl(id_articol,0)` **si in UPDATE (`:512`), si in INSERT
(`:537`)**. `id_articol = 0` nu exista in `NOM_ARTICOLE` (verificat: count = 0), iar coloana are FK
(`FK_VANZARE_DET002`) - deci prima salvare reusita a unei facturi cu linie de rata ar cadea pe
**`ORA-02291`**. Trece la tiparul deja folosit alaturi pentru `id_gestiune`/`id_valuta`/`taxcode`:
`Iif(Isnull(id_articol),[NULL],Alltrim(Str(id_articol)))`.
Acelasi tratament pentru inca trei campuri, dupa numarul de randuri active care au azi NULL in
baza (masurat pe `VANZARI_DETALII`, `sters = 0`, 1124 randuri in total):
| Camp | Randuri cu NULL azi | De ce conteaza |
|---|---|---|
| `pret_achizitie` | **401** | o rata n-are cost de achizitie; `0` inseamna "cost efectiv zero", valoare falsa pentru orice calcul de marja facut direct din tabela |
| `discount_unitar` | **238** | NULL si 0 sunt echivalente ca sens, dar salvarea ar rescrie tacut 238 de randuri |
| `proc_tvav` | **2** | e multiplicator (1.21), nu procent - `0` nu inseamna "fara TVA", ci anuleaza orice calcul care-l inmulteste |
Restul raman cum sunt: `pret` are coloana NOT NULL si garda proprie la `:14441`, `cantitate` e
filtrata de garda de la `:14433` si oricum n-are niciun NULL in baza, `pret_cu_tva` e flag 0/1.
---
## 5. Optional, de decis separat
`VVANZARI_ARTICOLE` ar putea capata acelasi fallback pe care il are view-ul vechi
`fact_vfacturi_detalii`: `NVL2(vd.id_articol, na.denumire, vd.explicatie) as denumire`. Ar face ca
"RATA 2" sa apara si in coloana Denumire, nu doar in Explicatie. E o modificare de view, deci un
script DB separat - **nu o bag in aceasta livrare** fara sa o ceri.
---
## Ce nu se atinge
- Ordinea coloanelor din grid (`cExplicatieTvaArt` ramane ultima) - mutarea ei langa Taxcode cere
`ColumnOrder` pe toate coloanele.
- Datele din Oracle - nicio reparatie de randuri existente fara cerere explicita.
- Dialogul de sincronizare si bara de totaluri - neschimbate fata de runda 4.
## Fisiere atinse de propunere
| Fisier | Puncte | Write-back |
|---|---|---|
| `COMUN\clase\omodificari.vc2` | 1 (grid + evenimente), 4 (garda) | necesar (`txt2vcx.ps1 -AllowComun`) |
| `COMUN\programe\ofacturare_editare.prg` | 1 (UPDATE), 2a (garda de valuta), 3 (agregare), 4 (scriere NULL) | n/a |
| script DB nou, `PACK_FACTURARE` | 2b (NVL pe UPDATE-ul de totaluri) | script separat, dupa aprobare |

View File

@@ -1,437 +0,0 @@
# Propunere runda 6 - grid Articole: nomenclatoare, zecimale, aspect, meniu, buton frm_facturi
Stare: **diagnostic incheiat, nimic aplicat.** Cele sase semnalari din proba pe ecran din
20.08.2026 sunt diagnosticate cu dovada pe cod. Fiecare punct de mai jos are cauza, interventia
propusa si ce anume trebuie sa decizi.
Cercetari de sprijin:
`docs\cercetare\rec_r6_nomenclatoare_grid.md`, `docs\cercetare\rec_r6_zecimale_si_aspect_grid.md`,
`docs\cercetare\rec_r6_meniu_editare_factura.md`,
`docs\cercetare\rec_r6_buton_modificare_frm_facturi.md`.
---
## Cum functioneaza de fapt gridul de Articole azi
Miezul, verificat direct pe cod - fara asta punctele 1, 2 si 4 par contradictorii:
Cinci coloane au nomenclator (`GotFocus` + `InteractiveChange`), enumerate din
`COMUN\clase\omodificari.vc2`:
| Coloana | ControlSource | GotFocus | InteractiveChange |
|---|---|---|---|
| `cDenumireArt` (articol) | `tvd.denumire` | :16705 | :16710 |
| `cGestiuneArt` | `tvd.gestiune` | :16734 | :16739 |
| `cTaxcodeArt` | `tvd.taxcode` | :16810 | :16815 |
| `cValutaArt` | `tvd.valuta` | :16821 | :16826 |
| `cExplicatieTvaArt` | **expresie** (vezi mai jos) | :16722 | :16728 |
Toate cinci au `Text1.ReadOnly = .T.`. Asta **nu** e o greseala: mecanismul se sprijina fix pe ea.
Intr-un grid VFP, cand celula curenta e read-only si coloana e legata de un **camp real**, tastarea
declanseaza cautarea incrementala a gridului, care schimba `Value`, ceea ce declanseaza
`InteractiveChange`, care deschide nomenclatorul. Asa merge `taxcode` la tastare.
`cExplicatieTvaArt` e singura exceptie, si de aici vine punctul 1:
```
omodificari.vc2:12467
Column16.ControlSource = "Iif(Seek(Nvl(tvd.id_jtva_coloana,0),'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')"
```
Coloana afiseaza *denumirea* din `crsJtvaTemp`, deci e legata de o **expresie**, nu de un camp.
Nu exista camp pe care sa se faca seek, deci `Value` nu se schimba niciodata la tastare, deci
`InteractiveChange` nu porneste. Nu e o garda gresita si nu lipseste nicio ramura din dispecer -
e o consecinta structurala a felului in care coloana isi ia textul.
Butonul lateral **Modificare** ocoleste complet problema fiindca nu citeste `ControlSource`, ci
`Thisform.pccontrol`, pe care `GotFocus` il pune explicit:
```
omodificari.vc2:16722-16726
PROCEDURE ... cExplicatieTvaArt.Text1.GotFocus
*!* coloana afiseaza denumirea din crsJtvaTemp, deci ControlSource e expresie - campul editat se da explicit
Thisform.pccontrol = 'tvd.id_jtva_coloana'
Thisform.pncolumnorder = This.Parent.ColumnOrder
```
**De aici rezulta fixul care rezolva punctele 1 si 2 dintr-o singura miscare**: `DblClick` se
sprijina si el pe `pccontrol`, deci merge uniform pe toate cele cinci coloane, inclusiv pe
explicatie TVA, unde tastarea nu are cum sa mearga niciodata.
---
## Punctul 1+2 - dublu-click pe toate nomenclatoarele
**Cauza:** `DblClick` nu exista deloc pe `grdArticoleFactura` azi. Explicatie TVA n-are nici calea
de tastare, din motivul de mai sus.
**Interventia propusa:** cinci metode noi `DblClick`, cate una per coloana cu nomenclator, pe
controlul din coloana (`<coloana>.Text1.DblClick`, nu pe coloana), cu corp identic - acelasi corp
de trei linii ca `InteractiveChange`-urile existente:
```foxpro
PROCEDURE pgfArticole.PAGE3.grdArticoleFactura.<coloana>.Text1.DblClick
Local loEditor
loEditor = Createobject('ArticoleNotaEditor', Thisform)
loEditor.ModificaNomenclator(Thisform.pccontrol)
ENDPROC
```
Nimic de schimbat in dispecer. `pccontrol` e deja corect la momentul dublu-click-ului: primul clic
da focusul si declanseaza `GotFocus`, al doilea completeaza `DblClick`.
**Efect:** explicatie TVA capata in sfarsit o cale din grid (dublu-click), iar celelalte patru
capata a doua cale, pe langa tastare.
**De decis:** nimic - e curat si aditiv. Confirma doar ca vrei si pe `cDenumireArt` (articol),
nu doar pe cele patru mici.
---
## Punctul 3 - trei zecimale la Pret
Aici formularea ta era aproape exacta, dar cu variabila inversata, si asta e chiar defectul.
Coloana **este** deja controlata de o variabila globala:
```
omodificari.vc2:12391 Column6.InputMask = (get_mask(12,gnPPRET)) && cPretArt = tvd.pret
omodificari.vc2:12400 Column7.InputMask = (get_mask(12,gnPPRET)) && cPretAchizitieArt
```
Ambele coloane primesc `gnPPret`. Dar `gnPPret` e precizia **pretului de achizitie** (=3 in mediul
investigat, `OPTIUNI.PPRET`), iar `tvd.pret` e pretul de **vanzare** al liniei, a carui precizie e
`gnPPretV` (`oinit_optiuni.prg:515`: `Store 0 To gnPPretV && nr. de zecimale pret vanzare`).
Regula e consecventa in restul suitei, si se vede pe doua coloane vecine in acelasi fisier:
```
ofacturare.vc2:3490 Column5.InputMask = (get_mask(14,gnPPret)) && "pret" = achizitie
ofacturare.vc2:3497 Column6.InputMask = (get_mask(14,gnPPretV)) && "pretv" = vanzare
```
Chiar scrierea in Oracle a coloanei `VANZARI_DETALII.PRET` - aceeasi din care se umple `tvd.pret` -
foloseste `gnPPretV`:
```
ofacturare_stoc.prg:367
Alltrim(Str(poArticol.Pret,18,Iif(poDate.in_valuta = 1,gnPVal,gnPPretV)))
```
**Cauza:** la scrierea sectiunii de grid s-a copiat masca de la coloana de achizitie pe coloana de
vanzare de langa ea.
**Interventia propusa:** `omodificari.vc2:12391`, `get_mask(12,gnPPRET)` -> `get_mask(12,gnPPretV)`.
O singura linie.
**Doua lucruri conexe, de decis separat:**
- `cPretCuTvaArt` (`omodificari.vc2:12404-12409`) **n-are deloc** `Format`/`InputMask`, deci afiseaza
precizia bruta a cursorului (4 zecimale), necontrolata de nimic. Il aliniem la `gnPPretV` in
aceeasi runda, sau il lasam?
- Linia din `ofacturare_stoc.prg:367` arata ca pe documentele **in valuta** precizia corecta e
`gnPVal`, nu `gnPPretV`. `InputMask` de pe coloana se evalueaza o singura data, la instantierea
clasei, cand moneda documentului inca nu e cunoscuta in acel context - deci o masca fidela pe
ambele cazuri ar cere setare la runtime, in `Init`, dupa ce se stie `in_valuta`. **Recomand sa
NU intram acum**: e o rafinare separata, iar simptomul pe care l-ai vazut se rezolva complet cu
schimbarea de o linie. Semnalez doar ca ramane o inexactitate cunoscuta pe facturile in valuta.
---
## Punctul 4 - fundalul gri
Aici concluzia e in doua parti, si a doua e mai serioasa decat pare din enunt.
Conventia de culoare exista deja in aplicatie, si chiar in acelasi formular, pe pagina **Rulaje**:
| Aspect | Semnificatie | Exemplu |
|---|---|---|
| alb `255,255,255` + `ReadOnly=.F.` | se scrie direct, prin tastare | `grdRulaje.cPret.Text1`, :9990 |
| verde `160,255,205` + `ReadOnly=.F.` | se editeaza, dar prin dialog/nomenclator | `grdRulaje.cGest.Text1`, :9689; `cDenumire.Text1`, :9595 |
| gri `225,225,225` + `ReadOnly=.T.` | nu se editeaza | - |
### 4a. Cele cinci coloane cu nomenclator sunt colorate gresit
`cDenumireArt`, `cGestiuneArt`, `cTaxcodeArt`, `cValutaArt`, `cExplicatieTvaArt` au toate
`BackColor = 225,225,225` (gri) desi au editor. Omologul direct al lui `cGestiuneArt` de pe pagina
Rulaje (`cGest`, acelasi tipar `GotFocus`/`InteractiveChange`/`ArticoleNotaEditor`) e **verde**.
Deci acelasi mecanism functional e colorat diferit in doua pagini ale aceluiasi formular.
**Interventia propusa:** cele cinci trec pe verde `160,255,205`.
**Recomand sa NU atingem `Text1.ReadOnly`** pe ele, desi pe Rulaje verdele vine cu `ReadOnly=.F.`
Motivul: pe Articole, calea de tastare merge **tocmai fiindca** sunt read-only (cautarea
incrementala). Ai probat-o azi pe `taxcode` si functioneaza. Daca le facem `.F.`, tastarea ar scrie
direct in celula in loc sa declanseze cautarea - alt comportament, netestat, cu risc de a scrie
gunoi in `tvd.taxcode`. Verdele singur spune adevarul si nu misca nimic functional.
### 4b. Serie, lot si explicatie NU sunt editabile azi - griul spune adevarul
Asta e partea care nu se potriveste cu ce ai scris, si cred ca e o bucata neterminata din runda 5.
Aceste trei coloane au `Column.ReadOnly = .F.` si o garda `.When` relaxata in runda 5
(`cSerieArt` :16804, `cLotArt` :16745, `cExplicatieArt` :16716 - toate `IF lArticoleReadOnly OR
id_vanzare_set <> 0 -> RETURN .F.`). Dar controlul din coloana a ramas neatins:
```
omodificari.vc2:12734-12743 cSerieArt.Text1 BackColor = 225,225,225 ReadOnly = .T.
omodificari.vc2:12631-12640 cLotArt.Text1 BackColor = 225,225,225 ReadOnly = .T.
omodificari.vc2:12569-12578 cExplicatieArt.Text1 BackColor = 225,225,225 ReadOnly = .T.
```
Comparatie de control, pe o coloana pe care sigur o editezi:
```
omodificari.vc2:12485-12494 cCantitateArt.Text1 BackColor = 255,255,255 ReadOnly = .F.
```
Nu au nici cale prin buton: `but_modificaR` se activeaza in `AfterRowColChange` (:16678-16682) doar
cand `Thisform.pncolumnorder = nColIndex`, iar `pncolumnorder` se scrie exclusiv in `GotFocus` -
care exista doar pe cele cinci coloane cu nomenclator. Pe serie/lot/explicatie butonul ramane
dezactivat.
**Deci: garda a fost relaxata in runda 5, dar campurile au ramas read-only. Nu se pot edita pe
nicio cale.** Griul e onest; intentia din runda 5 e cea neimplinita.
**Interventia propusa:** cele trei trec pe alb `255,255,255` + `Text1.ReadOnly = .F.`, ca
`cCantitateArt`. Abia atunci devin editabile prin tastare, cum s-a decis in runda 5.
**De decis - e o schimbare reala de comportament, nu cosmetica:** confirmi ca serie, lot si
explicatie trebuie sa devina scriabile direct in celula pe liniile deja salvate? (Garda `.When`
existenta ramane si continua sa blocheze liniile din seturi si facturile intrate in e-Factura.)
---
## Punctul 5 - textul din meniu
Eticheta **nu e in `.mnx`** - cautarea in toate meniurile proiectului n-a gasit-o. E un literal
intr-o metoda, deci se poate schimba prin text + write-back, fara interventie manuala in IDE:
```
COMUN\clase\ofacturare_comun.vc2:4930
lnOptiune = xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')
```
**Interventia propusa:** a doua optiune devine
`Editare \<factura (note, rulaje, articole)`, cu acceleratorul `\<F` pastrat.
`ToolTipText = "Modificare (CTRL+M)"` de pe butonul generic (`cmd_butoane.vc2:194`) **nu se
atinge** - e mostenit de peste 100 de formulare din toata suita; un text despre facturi acolo ar
minti pe ecranele de parteneri, personal, stocuri.
**Doua observatii inainte sa confirmi textul:**
- Paginile reale ale formularului nu se numesc "Note / Rulaje / Articole":
```
omodificari.vc2:8725 PAGE1.Caption = "Rulaje materii prime, materiale, marfuri, obiecte inventar (303)"
omodificari.vc2:8728 PAGE2.Caption = "Rulaje obiecte inventar in folosinta (8039)"
omodificari.vc2:8731 PAGE3.Caption = "Articole factura"
```
Nu exista pagina "Note" - liniile notei sunt corpul principal al formularului, nepaginat.
Formularea ta descrie corect cele trei zone functionale, dar nu citeaza etichetele de pe ecran.
E in regula asa, sau vrei alt text?
- Pagina Articole e **conditionata** (`omodificari.vc2:14927-14936`): apare doar cand nota se
mapeaza pe exact o vanzare (`Reccount('tvanz') = 1`); altfel `PageCount = 2`. Un text care
promite mereu "articole" va fi inexact pe notele fara vanzare atasata. Nu e grav - dar sa stii.
---
## Punctul 6 - explicatie TVA in butonul de modificare din frm_facturi
**Premisa ta e corecta si am verificat-o direct pe baza**, nu din raport. Procedura care salveaza:
```sql
PROCEDURE modifica_explicatie_articol(V_ID_VANZARE_DET IN NUMBER,
V_EXPLICATIE IN VARCHAR2,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL) is
BEGIN
UPDATE VANZARI_DETALII
SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE
WHERE ID_VANZARE_DET = V_ID_VANZARE_DET;
END modifica_explicatie_articol;
```
Doua coloane, fara recalcul, fara atingerea cantitatii/pretului/cotei. Butonul e intr-adevar
"campurile care nu modifica valorile". (Detaliu lateral: `V_ID_UTIL` e primit si nefolosit.)
Traseul azi: `But_modifica2` (`ofacturare_comun.vc2:1436`) -> `do_modifica_explicatie()` (:4642) ->
dialogul `frm_modifica_articol_factura` (:5129), cu `explicatie` (editbox) si `taxcode` (combo pe
lista plata de coduri SAF-T, fara legatura cu explicatia TVA).
**Aici e capcana.** Sablonul din `frm_modific2024` **nu se poate copia 1:1**: acolo alegerea
explicatiei TVA scrie `id_jtva_coloana` **si** `proc_tvav` (cota noua) **si** recheama
`calculeaza_valori_articol()` (`ofacturare_editare.prg:1100-1147`, `omodificari.vc2:15049`). Adica
exact ce butonul asta nu face si nu trebuie sa faca.
**Interventia propusa, ca sa ramana neutru valoric:**
1. In dialog, combo nou pe explicatie TVA, **filtrat pe cota curenta a liniei** - ca sa nu se poata
alege o explicatie cu alta cota. Asta e ce pastreaza promisiunea "nu modifica valorile".
2. La alegere, se deriva `taxcode` prin `GetTaxCodeIdPart` (`oproceduri_comune.prg`, functie
globala, apelabila din orice formular) si se scrie in combo-ul existent. `proc_tvav` **nu** se
atinge.
3. Piesele sunt refolosibile ca atare: `caut_explicatie_tva` (`ocautare.prg`) si `GetTaxCodeIdPart`
sunt globale. Cod VFP nou estimat: ~30-40 de linii.
**Blocajul, si de asta punctul 6 nu poate intra in aceeasi livrare cu 1-5:** `id_jtva_coloana` n-are
unde sa se salveze. Procedura Oracle primeste doar explicatie si taxcode. Daca scriem doar
`taxcode`, explicatia TVA a liniei ramane desincronizata de codul fiscal - adica exact defectul pe
care vrem sa-l evitam. Deci e nevoie de **un parametru nou in `pack_facturare.modifica_explicatie_articol`**,
schimbare PL/SQL in afara acestui repo, de coordonat separat.
**De decis:**
- Confirmi filtrarea pe cota curenta (varianta neutra)? Alternativa - lista completa de explicatii,
cu recalcul - ar transforma butonul in altceva decat e azi si l-ar suprapune peste editarea din
`frm_modific2024`. **Recomand filtrarea.**
- Vrei sa pornim modificarea PL/SQL, sau punctul 6 asteapta?
**Bonus, deja rezolvat:** gridul din `frm_facturi` afiseaza deja "Explicatie TVA" / "Explicatie TVA 2"
ca si coloane read-only (`ofacturare_comun.vc2:1679-1687`) - nu e nimic de adaugat in grid, doar in
dialog.
---
## Ce propun sa intre in livrarea imediata
Punctele **1, 2, 3, 4, 5** - toate in `COMUN\clase\omodificari.vc2` si
`COMUN\clase\ofacturare_comun.vc2`, ambele write-back-abile. Fara atingerea bazei de date.
| # | Fisier | Interventie | Volum |
|---|---|---|---|
| 1+2 | `omodificari.vc2` | 5 metode `DblClick` noi | ~25 linii |
| 3 | `omodificari.vc2:12391` | `gnPPRET` -> `gnPPretV` | 1 linie |
| 4a | `omodificari.vc2` | 5 `BackColor` -> verde `160,255,205` | 5 linii |
| 4b | `omodificari.vc2` | 3 `BackColor` -> alb + `ReadOnly=.F.` | 6 linii |
| 5 | `ofacturare_comun.vc2:4930` | textul optiunii 2 din `xmenu` | 1 linie |
Punctul **6** ramane separat - depinde de o schimbare PL/SQL.
## Ce astept de la tine inainte sa deleg aplicarea
1. **Punctul 4b** - confirmi ca serie/lot/explicatie devin scriabile in celula? (singura schimbare
reala de comportament din lot)
2. **Punctul 3** - aliniem si `cPretCuTvaArt` la `gnPPretV`, sau il lasam?
3. **Punctul 5** - textul exact: `Editare \<factura (note, rulaje, articole)`?
4. **Punctul 1+2** - `DblClick` si pe coloana de articol (`cDenumireArt`), sau doar pe celelalte patru?
5. **Punctul 6** - pornim schimbarea PL/SQL acum, sau asteapta?
Nimic nu e aplicat. Niciun fisier de cod n-a fost atins in aceasta runda, nu s-a facut write-back,
nu s-a rulat `git_sync.ps1`, nu s-a scris nimic in Oracle (doar un `SELECT` din `all_source` pentru
sursa procedurii), nu s-a comis nimic.
---
# DECIZIILE LUI MARIUS - 20.08.2026, luate, se aplica
| Punct | Decizie |
|---|---|
| 1+2 `DblClick` | **toate cinci** coloanele cu nomenclator, inclusiv `cDenumireArt` |
| 3 Pret | `gnPPRET` -> `gnPPretV` la `omodificari.vc2:12391` |
| 3 conex `cPretCuTvaArt` | **se aliniaza** la `get_mask(12,gnPPretV)` |
| 4a cele 5 cu nomenclator | verde `160,255,205`; `Text1.ReadOnly` ramane `.T.` |
| 4b serie / lot / explicatie | **devin scriabile**: alb `255,255,255` + `Text1.ReadOnly = .F.` |
| 5 meniu | `Editare \<factura (note, rulaje, articole)`; Caption-urile paginilor **nu** se ating |
| 6 explicatie TVA in `frm_facturi` | **lista completa, CU recalcul**; PL/SQL **porneste acum** |
## Consecinta punctului 6, semnalata si acceptata
Cu lista completa si recalcul, butonul `But_modifica2` **nu mai e** "campuri fara impact pe
valori". Devine o a doua cale de editare care schimba cota si totalurile documentului. Concret,
fata de estimarea initiala din propunere, modificarea PL/SQL creste: procedura nu mai primeste doar
`id_jtva_coloana`, ci si `proc_tvav`, si trebuie sa antreneze recalcularea valorilor liniei si a
totalurilor documentului. Marius a ales aceasta varianta dupa ce consecinta a fost semnalata.
## Cum se imparte aplicarea
Un singur scriitor per fisier - regula de baza, ca sa nu se piarda modificari tacut.
| Agent | Fisier | Puncte |
|---|---|---|
| `apply-grid` | `COMUN\clase\omodificari.vc2` + write-back | 1+2, 3, 4a, 4b |
| `apply-meniu` | `COMUN\clase\ofacturare_comun.vc2` + write-back | 5 |
| `design-plsql` | script in `D:\ROA\DATABASE\SCRIPTURI_CLAR\` (doar scris, **nerulat**) | 6 - partea DB + proiectarea partii VFP |
Partea **VFP** a punctului 6 (combo-ul din `frm_modifica_articol_factura`) atinge tot
`ofacturare_comun.vc2`, deci **nu porneste in paralel cu `apply-meniu`** si nici inainte ca
semnatura procedurii sa fie fixata. Intra intr-un al doilea val, dupa ce `design-plsql` livreaza
contractul si `apply-meniu` termina.
Commit-ul (git sau svn) **nu** se da - nici in acest val, nici in urmatorul.
---
# Constatare pe baza, 20.08.2026 - corectie la handoff-ul rundei 5
Handoff-ul rundei 5 spune despre `ff_2026_08_20_01_COMUN_PACK_FACTURARE.sql` (garda `FACT-025` pe
totaluri) ca e **"nerulat pe schema `MARIUSM_AUTO` (`ROA_CENTRAL`)"**. **Nu mai e adevarat.** Verificat direct in `ALL_SOURCE`,
schema `MARIUSM_AUTO`, corpul lui `PACK_FACTURARE`:
```
lnLiniiActive in DB = 3 aparitii
FACT-025 in DB = 2 aparitii
```
Deci **scriptul 01 a fost aplicat pe schema `MARIUSM_AUTO` (`ROA_CENTRAL`)** intre timp. Consecinte, toate favorabile:
- `ff_2026_08_20_02` a fost construit din sursa vie a pachetului, deci **contine deja** modificarile
scriptului 01. Nu exista riscul ca aplicarea lui 02 sa dea inapoi runda 5.
- Formularea din raportul lui `design-plsql` — *"trebuie aplicat DUPA/impreuna cu 01"* — e
depasita: 01 e deja in baza, 02 il include.
**Capcana ramasa, de retinut:** daca cineva **re-aplica** `ff_2026_08_20_01` **dupa** ce s-a aplicat
`02`, sterge modificarile punctului 6. Scriptul 01 e consumat; nu se mai ruleaza.
Sursa de derivare a cotei, confirmata pe dictionar: `MARIUSM_AUTO.JTVA_COLOANE.COTA_TVA` exista.
---
# REVIZUIRE punctul 6 - 20.08.2026, decizie schimbata de Marius
Textual: **"nu vreau sa schimb cota de tva, maxim explicatia de tva si taxcode, aferente cotei de
tva, ca sa nu se modifice totaluri"**.
Se revine la varianta **filtrata pe cota curenta** - cea recomandata initial in propunere.
Varianta "lista completa, CU recalcul", aleasa in prima runda de intrebari, e **ABANDONATA**.
## Ce ramane
Butonul `But_modifica2` din `frm_facturi` **redevine neutru valoric**, cum era si cum il descrisese
Marius de la inceput. Se scriu trei coloane: `EXPLICATIE`, `TAXCODE`, `ID_JTVA_COLOANA`.
`PROC_TVAV` **nu** se atinge, totalurile documentului **nu** se recalculeaza.
Parametrul nou din PL/SQL, `V_ID_JTVA_COLOANA IN NUMBER DEFAULT NULL`, ramane necesar: fara el,
explicatia aleasa n-ar avea unde sa se salveze si ar ramane desincronizata de `taxcode`.
## Ce cade din discutia de gardi
Toata sectiunea A5 din `docs\propunere_runda6_punct6_plsql.md` era consecinta recalculului. Fara el:
- **Garda pe seturi (`FACT-027`) cade.** Motivul ei era agregarea `MAX(proc_tvav)` peste
componentele setului; odata ce `proc_tvav` nu mai e atins, componentele isi pot schimba linistit
explicatia si taxcode-ul.
- **Garda e-Factura cade.** Butonul redevine neutru valoric, iar azi nu are nicio astfel de
verificare - a o adauga acum ar bloca ceva ce functioneaza, fara ca decizia s-o ceara.
- **Garda pe valuta** oricum nu fusese propusa.
## Garda care ramane, si de ce e in PL/SQL
Neutralitatea valorica e chiar cerinta lui Marius, deci se **impune in baza**, nu se lasa doar pe
seama filtrarii combo-ului in interfata: daca filtrul din VFP e gresit sau ocolit, baza trebuie sa
refuze.
Daca `V_ID_JTVA_COLOANA` e dat si `JTVA_COLOANE.COTA_TVA` a explicatiei alese **difera** de
`PROC_TVAV`-ul liniei, procedura nu scrie nimic si arunca `RAISE_APPLICATION_ERROR` - stilul
`FACT-025` din runda 5: esec vizibil cu rollback, niciodata scriere tacuta.
Raman si gardele ieftine: linie inexistenta sau stearsa, explicatie TVA inexistenta sau fara cota
valida.
## Starea livrabilelor
`ff_2026_08_20_02_COMUN_PACK_FACTURARE.sql`, in forma scrisa la 14:22, implementeaza varianta
**respinsa** (recalcul + `FACT-027`). Se **rescrie peste el**, pornind de la sursa vie din
`MARIUSM_AUTO` - nu se lasa doua fisiere cu acelasi scop pe disc, exact capcana din runda 5 cand o
varianta respinsa a stat alaturi de cea buna. **Scriptul nu a fost rulat pe schema `MARIUSM_AUTO` (`ROA_CENTRAL`) in nicio forma.**
Partea VFP: combo-ul din `frm_modifica_articol_factura` trebuie **filtrat pe cota liniei**.

View File

@@ -1,320 +0,0 @@
# Runda 6, punctul 6 — proiectare PL/SQL: explicatie TVA in butonul de modificare din `frm_facturi`
> **NOTA — decizie schimbata, 20.08.2026.** Prima varianta discutata cu Marius era "lista completa
> de explicatii TVA, CU recalcul": alegerea unei explicatii cu alta cota scria si `proc_tvav`, si
> recalcula totalurile documentului. **Varianta a fost respinsa de Marius** dupa ce consecinta
> (butonul nu mai era neutru valoric) a fost semnalata — cuvintele lui: *"nu vreau sa schimb cota
> de tva, maxim explicatia de tva si taxcode, aferente cotei de tva, ca sa nu se modifice
> totaluri"*. Documentul de fata reflecta **varianta finala, aprobata**: filtrata pe cota curenta,
> **fara** recalcul. Nu reintroduce varianta cu recalcul fara o decizie noua, explicita, a lui
> Marius.
Livrabile: scriptul PL/SQL (Partea B, mai jos) + acest document (Partea A). **Nimic nu s-a rulat
pe Oracle** in afara de `SELECT`-uri de investigatie. Niciun fisier VFP n-a fost atins.
---
## A1. Cum se face azi acelasi lucru in `frm_modific2024`
Sablonul complet, verificat pe cod (nu e in domeniul acestei livrari, doar citit, si e citat aici
doar ca sa arate *de ce* nu se copiaza 1:1 pentru `frm_facturi` — vezi A2):
```
COMUN\programe\ofacturare_editare.prg:1100-1105 (ArticoleNotaEditor.ModificaNomenclator)
CASE m.lcCamp == 'id_jtva_coloana'
REPLACE id_jtva_coloana WITH loCauta.id_jtva_coloana, ;
proc_tvav WITH (Nvl(loCauta.cota_tva, 0) + 100) / 100
This.oForm.UpdateExplicatieSAFTArt()
This.oForm.calculeaza_valori_articol()
```
Lantul, in ordine:
1. **`loCauta`** vine din `This.CautaExplicatieTva()` (`ofacturare_editare.prg:1142-1147`), care
cheama `caut_explicatie_tva(id_jtva_curent, , , , lnCotaFiltru)` — **filtrat pe cota liniei
curente** (`ocautare.prg:3174-3238`, filtrul la `:3218-3220`). Returneaza un obiect cu
`.id_jtva_coloana`, `.denumire`, `.cota_tva`.
2. **Scrierea locala** (pe cursorul `tvd`, in memorie, inca nesalvat): `id_jtva_coloana` primeste
valoarea aleasa, `proc_tvav` primeste `(cota_tva + 100) / 100` — calculat in VFP.
3. **`UpdateExplicatieSAFTArt()`** (`COMUN\clase\omodificari.vc2:15049-15074`) deriva `taxcode` din
`id_jtva_coloana` prin functia globala **`GetTaxCodeIdPart(an, luna, data_act, id_jtva, id_part,
n50, n100, neexigibil)`** (`COMUN\programe\oproceduri_comune.prg:6059-6125`).
4. **`calculeaza_valori_articol()`** recalculeaza valoarea liniei pe cursorul local `tvd`.
5. La salvarea intregului formular, `ScrieArticoleFacturaEditate()`
(`ofacturare_editare.prg:452-573`) scrie liniile si cheama o singura data, pentru tot
documentul, `pack_facturare.recalculeaza_totaluri_vanzari(id_vanzare, discount)`.
**Important pentru varianta finala**: acest sablon presupune ca `id_jtva_coloana` **poate** aduce
o cota diferita — de-asta exista filtrul pe cota in `caut_explicatie_tva` doar ca o *reducere* a
listei, nu ca o *garda*. Pentru `frm_facturi`, decizia lui Marius transforma filtrul de cota
dintr-o comoditate de UI intr-o **conditie obligatorie**, impusa si in baza (A5).
---
## A2. Unde cade validarea — nu mai exista recalcul de pus undeva
Cu decizia finala, **nu se recalculeaza nimic**: nici valoarea liniei, nici totalurile
documentului. `proc_tvav` nu se scrie. Intrebarea "VFP sau PL/SQL" din varianta initiala nu se mai
pune pentru un recalcul — dar ramane o intrebare echivalenta pentru **garda de neutralitate**
(cota explicatiei alese trebuie sa fie egala cu cota liniei): unde se impune?
**Raspuns: in PL/SQL**, in `pack_facturare.modifica_explicatie_articol` insusi.
Argumente:
1. **Neutralitatea valorica e chiar cerinta lui Marius** — nu un detaliu de implementare. Daca ar
fi impusa doar prin filtrarea combo-ului in VFP (cum era propunerea initiala, inainte de
recalcul), un bug de UI, un combo needitat corect, sau orice cale ocolitoare ar putea trimite
un `id_jtva_coloana` cu alta cota, iar baza ar scrie-o fara sa clipeasca — exact ce Marius nu
vrea.
2. **`frm_facturi` nu are, si acum nici nu are nevoie de**, un motor de calcul local — cererea nu
mai atinge `calculeaza_valori_articol()` sau echivalentul lui. PL/SQL are tot ce ii trebuie
pentru garda: `VANZARI_DETALII.PROC_TVAV` (linia) si `JTVA_COLOANE.COTA_TVA` (explicatia).
3. **E acelasi stil ca FACT-025** (runda 5): validare in baza, esec zgomotos cu rollback, nu o
validare "de bune maniere" doar in client.
**Ce pierde varianta "doar VFP"** (garda doar prin filtrarea combo-ului, fara verificare in
PL/SQL): nimic in flux normal — dar orice cale care ocoleste combo-ul (alt formular viitor, un
script, o corectie manuala printr-un alt ecran care cheama aceeasi procedura) ar putea scrie o
cota diferita nedetectat. Cum procedura oricum trebuie sa primeasca `id_jtva_coloana` ca sa-l
scrie, verificarea in PL/SQL nu costa un apel suplimentar — e in aceeasi tranzactie.
---
## A3. Semnatura noua
```sql
PROCEDURE modifica_explicatie_articol(V_ID_VANZARE_DET IN NUMBER,
V_EXPLICATIE IN VARCHAR2,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL,
V_ID_JTVA_COLOANA IN NUMBER DEFAULT NULL);
```
Neschimbata fata de prima varianta ca lista de parametri — un singur parametru nou,
**`V_ID_JTVA_COLOANA`, `DEFAULT NULL`** — dar **semantica e diferita**: nu mai e "valoarea de
scris fara conditii", ci "valoarea de scris **doar daca** cota ei coincide cu cota liniei".
Procedura **nu** mai scrie `PROC_TVAV` si **nu** mai cheama `recalculeaza_totaluri_vanzari`.
**Compatibilitate inapoi — verificata, nu presupusa** (neschimbat fata de investigatia initiala):
- **Apelant unic in tot codul VFP**: `COMUN\clase\ofacturare_comun.vc2:5236`
(`inainte_de_do_termin`, dialogul `frm_modifica_articol_factura`). Cautare pe intregul working
copy (`.vc2`, `.sc2`, `.prg`) — niciun alt loc nu cheama
`pack_facturare.modifica_explicatie_articol`.
- **Niciun apelant PL/SQL**: `SELECT` pe `ALL_SOURCE`, text `MODIFICA_EXPLICATIE_ARTICOL`,
excluzand `PACK_FACTURARE` insusi — zero randuri, in orice schema din `ROA_CENTRAL`.
- Apelul existent trimite exact 3 parametri pozitionali + `?poRec.taxcode` — `V_ID_JTVA_COLOANA`
ramane `NULL`, neschimbat.
- **Ramura `V_ID_JTVA_COLOANA IS NULL` reproduce byte-cu-byte `UPDATE`-ul vechi** (aceleasi doua
coloane, acelasi `WHERE`, fara verificare noua) si face `RETURN` imediat.
---
## A4. Cazurile care nu trebuie sa treaca tacut
Pastrez regula din runda 5: cand nu se poate valida ceva desi exista date pentru validare,
procedura **nu scrie nimic** si arunca `RAISE_APPLICATION_ERROR` (rollback). Cod de eroare
urmator liber la momentul scrierii: **FACT-025** — confirmat direct pe sursa vie din
`MARIUSM_AUTO.PACK_FACTURARE` (comentariul `-- ultima eroare atribuita`), neschimbat fata de
runda 5 (runda 5 e deja aplicata, vezi nota din Partea B despre corectia facuta aici). Patru coduri
noi, FACT-026..029:
| Cod | Cand se arunca | De ce nu trece tacut |
|---|---|---|
| **FACT-026** | `V_ID_JTVA_COLOANA` dat, dar `V_ID_VANZARE_DET` nu (mai) exista sau are `STERS <> 0` | Fara o linie activa gasita nu exista `PROC_TVAV` de comparat — a continua ar insemna fie un `UPDATE` pe 0 randuri, fie o comparatie pe o valoare nedefinita |
| **FACT-027** | `V_ID_JTVA_COLOANA` dat, dar nu exista in `JTVA_COLOANE` (sau are `STERS <> 0`) | Fara `cota_tva` validata nu exista cu ce sa se compare cota liniei |
| **FACT-028** | Linia gasita (FACT-026 trecut), dar `PROC_TVAV` al ei e `NULL` | Comparatia `NULL <> x` nu e nici adevarata nici falsa in PL/SQL — a lasa `IF`-ul sa treaca tacut pe langa ea ar insemna sa scrii o explicatie fara sa stii daca respecta neutralitatea. Cazul e real: **2 randuri active au `PROC_TVAV IS NULL`** azi (confirmat cu `SELECT COUNT(*) FROM vanzari_detalii WHERE sters=0 AND proc_tvav IS NULL` — acelasi fapt semnalat in runda 5) |
| **FACT-029** | Ambele valori cunoscute, dar `ROUND(cota liniei, 4) <> ROUND(cota explicatiei, 4)` | Aceasta e garda de neutralitate ceruta de Marius — orice diferenta de cota inseamna ca alegerea ar schimba taxa liniei, deci si totalurile, daca s-ar scrie |
**Comparatia de cota** — decizie si motivare: `VANZARI_DETALII.PROC_TVAV` e `NUMBER(10,4)`,
`JTVA_COLOANE.COTA_TVA` e `NUMBER(10,0)` (confirmat pe dictionar: `ALL_TAB_COLUMNS`). Cota
explicatiei se converteste cu aceeasi formula ca in VFP, `(NVL(cota_tva,0)+100)/100`. Ambele
numere provin din fractii cu numitor 100 (19/100, 9/100, 5/100, 0/100) — reprezentabile exact in
tipul `NUMBER` (zecimal, nu binar) al Oracle, deci nu exista risc de eroare de rotunjire in
practica; `ROUND(..., 4)` pe ambele parti e adaugat ca **toleranta explicita, documentata**, nu ca
raspuns la o problema observata, ca o eventuala a cincea zecimala reziduala pe date vechi sa nu
produca un refuz fals. `NULL` pe linie **nu** e tratat ca "egal cu orice" si nici ca "diferit de
orice" prin comparatie implicita — e verificat explicit (`IF lnProcTvavLinie IS NULL THEN RAISE`),
inaintea comparatiei, tocmai ca sa nu se bazeze pe semantica NULL a lui `<>` din PL/SQL (care ar fi
lasat garda sa treaca tacut).
**Ce NU arunca eroare**: `V_ID_JTVA_COLOANA IS NULL` (apelul vechi — niciun comportament nou).
---
## A5. Riscul — categorii de documente, verdict pe fiecare
### a) Facturi intrate in e-Factura
**Nu exista azi nicio garda** pe acest flux (confirmat: `do_modifica_explicatie`/
`inainte_de_do_termin` nu verifica `EsteInEFactura`/`anaf_efactura` deloc). Cu varianta finala
(fara recalcul, fara scriere de `proc_tvav`), butonul **ramane neutru valoric** — `EXPLICATIE`,
`TAXCODE` si `ID_JTVA_COLOANA` sunt metadate de raportare, nu valori financiare ale liniei.
**Decizie: nu se adauga garda e-Factura**, nici in VFP, nici in PL/SQL — situatia de azi nu se
schimba, iar Marius nu a cerut-o.
**Observatie separata, semnalata, nu implementata**: `TAXCODE` **este** el insusi o valoare
raportata in SAF-T/e-Factura (coloana `taxcode` pe `VANZARI_DETALII`, folosita la generarea
declaratiei 406 si potential in fisierul e-Factura). Schimbarea lui pe o factura **deja trimisa**
in e-Factura (`ANAF_EFACTURA`) nu modifica totaluri, dar **poate produce o discrepanta reala**
intre ce s-a raportat/trimis deja si ce arata acum baza — factura trimisa nu se retrimite automat.
Daca acest risc conteaza pentru Marius, e o garda separata, de decis explicit (nu a fost ceruta
aici si nu blocheaza livrarea curenta).
### b) Facturi din seturi (`id_vanzare_set`)
Riscul din varianta initiala (agregarea `MAX(c.proc_tvav)` pe componentele de set in
`recalculeaza_totaluri_vanzari`) **dispare complet**, pentru ca `proc_tvav` nu mai e atins de
aceasta procedura. **Confirmat pe cod, nu presupus**: am recitit interogarea de agregare din
`recalculeaza_totaluri_vanzari` (`PACK_FACTURARE` body, ~liniile 14804-14979 din sursa vie) —
coloanele citite din `VANZARI_DETALII` pentru total sunt `pret`, `proc_tvav`, `cantitate`,
`diferenta`, `discount_unitar`, `id_valuta`, `pret_cu_tva`, `pret_achizitie`; **nici
`EXPLICATIE`, nici `TAXCODE`, nici `ID_JTVA_COLOANA` nu apar nicaieri in aceasta procedura** (si
oricum procedura de fata nu o mai cheama). Nu exista alt loc in `PACK_FACTURARE` unde
`recalculeaza_totaluri_vanzari` sau vreo alta procedura de agregare a totalurilor sa citeasca
aceste trei coloane.
**Decizie: fara garda pe `id_vanzare_set`.** Componentele de set isi pot schimba linistit
explicatia si taxcode-ul, ca orice alta linie — motivul pentru care runda 5/varianta initiala ar
fi blocat asta (efectul lui `proc_tvav` pe agregarea de set) nu se mai aplica.
### c) Facturi in valuta
Recalculul (singurul loc unde intra in joc `VANZARI_CURSURI`, scris doar la emitere — runda 5) nu
mai e apelat de aceasta procedura. **Fara relevanta pentru varianta finala** — nu exista nimic de
garda aici.
---
## Partea B — scriptul
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_20_02_COMUN_PACK_FACTURARE.sql` —
**suprascris** cu varianta finala (nu a ramas pe disc varianta cu recalcul, respinsa; a fost
inlocuita integral, nu lasata alaturi).
Pornit de la **sursa vie**, re-citita din `ALL_SOURCE` chiar pentru aceasta livrare (nu de la
scriptul anterior, respins): schema `MARIUSM_AUTO` (`current_schema` al conexiunii read-only
folosite). Diff fata de sursa vie, verificat cu `diff` linie-cu-linie:
- antetul (comentariu descriptiv, fara `*!*`/data/autor, ca la scriptul din runda 5)
- comentariul de tracking `FACT-025` -> `FACT-029`
- spec: parametrul nou `V_ID_JTVA_COLOANA IN NUMBER DEFAULT NULL` la
`modifica_explicatie_articol` (1 bloc, 4 -> 5 linii)
- body: acelasi parametru in antetul procedurii, plus corpul nou din A3/A4 — validare, **fara**
scriere de `PROC_TVAV`, **fara** apel de recalcul (1 bloc, 9 -> 49 linii); restul pachetului
(peste 16000 de randuri) neatins — verificat cu `diff`, nicio alta diferenta
- linia `exec pack_migrare.UpdateVersiune('ff_2026_08_20_02_COMUN_PACK_FACTURARE')` + `commit;`
(numele fisierului nu s-a schimbat, doar continutul)
Fisier scris in ASCII (0 octeti > 0x7F, verificat), CRLF pe toate liniile. **Scriptul nu a fost
rulat.**
**Corectie fata de raportul initial**: sectiunea A5(c) din prima versiune a acestui document
afirma ca `ff_2026_08_20_01_COMUN_PACK_FACTURARE.sql` (FACT-025, runda 5) trebuie aplicat
**inainte** de scriptul curent. Verificat direct pe baza: **`...20_01` e deja aplicat** — sursa
vie din `MARIUSM_AUTO.PACK_FACTURARE` contine deja `lnLiniiActive`/`FACT-025`. Deci **ordinea
corecta e: se aplica doar scriptul curent** (`...20_02`); re-aplicarea lui `...20_01` dupa el ar
suprascrie/sterge continutul de fata. (Fisierul `...20_01` insusi nu mai e prezent in
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\` la momentul acestei livrari — semnalat ca observatie,
nu afecteaza corectitudinea scriptului curent, care a fost construit din sursa vie din Oracle, nu
din acel fisier.)
---
## Ce ramane de facut in VFP
Pentru agentul care va scrie codul VFP (nu porneste inainte ca semnatura de mai sus sa fie
confirmata/aplicata, si nu in paralel cu `apply-meniu` — ambele ating `ofacturare_comun.vc2`):
1. **Combo nou in `frm_modifica_articol_factura`** (`ofacturare_comun.vc2:5129-5253`), langa
`cbo_saft` existent. Populat cu explicatii TVA **filtrate pe cota curenta a liniei** — apel
**`caut_explicatie_tva(poRec.id_jtva_coloana, , , , lnCotaFiltru)`**, cu `lnCotaFiltru` calculat
la fel ca in `CautaExplicatieTva` (`ofacturare_editare.prg:1142-1147`):
`Round((Nvl(poRec.proc_tvav, 0) - 1) * 100, 2)`. **Nu** lista completa — asta era varianta
respinsa. Filtrarea in UI e o comoditate (userul nu vede optiuni pe care oricum baza le va
refuza), garda reala e in PL/SQL (A2/A4). Sursa datelor: `poRec` are deja `id_jtva_coloana`,
`jtva_coloana`, `proc_tvav`, `taxcode` disponibile (schema `crsDetalii`,
`oproceduri_facturare.prg:382-387`).
2. **La alegere**: deriva `taxcode` in **VFP** (nu in PL/SQL — precizarea lui Marius confirma
directia, iar aici e verificat explicit ca se poate), prin
`GetTaxCodeIdPart(gnAn, gnLuna, poRec.data_act, ...)`, si trimite rezultatul prin parametrul
existent `V_TAXCODE`. **Verificat pe cod, nu presupus, ca `GetTaxCodeIdPart` e apelabila din
`frm_facturi`:**
- `GetTaxCodeIdPart` (`oproceduri_comune.prg:6059-6125`) nu presupune niciun cursor/formular
specific — ia doar parametri simpli si isi gestioneaza singura cursorul `jtva_coloane` (il
deschide cu `update_jtva_coloane()` daca nu e deja deschis, si il inchide la iesire daca ea
l-a deschis, `:6092-6118`).
- Lantul ei de apeluri interne — `update_jtva_coloane` (`updateserver.prg:553`),
`VERIFICA_RTVAI`/`GetCodFiscalPartenerById` (ambele in `oproceduri_comune.prg`), `GetTaxCode`
(`oproceduri_comune.prg:6130`) — sunt toate in fisiere **inregistrate necondiționat**
(`roafacturare.prg:187` `OPROCEDURI_COMUNE.PRG`, `:189` `updateserver.PRG`), spre deosebire de
`ofacturare_editare.prg` (`:214`, care e cel condiționat pe produs — de acolo vine restrictia
pe `EsteInEFactura`, nu de aici). Deci **taxcode-ul se deriva in VFP, in `frm_facturi`**, exact
ca in `frm_modific2024`, fara nicio schimbare de plan fata de propunerea initiala.
- `poRec` (din `crsDetalii`) are `data_act`? De verificat la implementare — schema confirmata
(`oproceduri_facturare.prg:382-387`) nu listeaza explicit `data_act` pe `crsDetalii`;
echivalentul e pe `crsfacturi` (headerul), de adus prin `id_vanzare`. `id_part` vine din
`crsfacturi.id_part` (`oproceduri_facturare.prg:341`). `n50`/`n100` = `.F.` (ca in
`UpdateExplicatieSAFTArt`, "nu se mai folosesc"); `neexigibil` — de stabilit explicit (sablonul
din `omodificari.vc2` il deriva din `tAct.scd`/`scc`, cursor care nu exista in `frm_facturi`;
probabil `.F.` e suficient, dar nu presupune, confirma).
3. **Lista/combo-ul poate iesi goala — trateaz-o explicit, nu lasa un dropdown gol fara
explicatie.** Verificat pe date (`SELECT` direct, read-only): pentru **toate cele 8 cote
distincte folosite azi pe linii active** (`0, 5, 9, 11, 19, 20, 21, 24` — 1122 linii in total),
**exista cel putin o explicatie TVA activa in `JTVA_COLOANE` cu aceeasi cota** (`id_jtva_coloana
> 0 AND NVL(sters,0)=0`) — deci lista **nu iese goala azi pentru nicio cota reala**. Singurele
**2 linii** unde interogarea ar iesi goala sunt exact cele cu `PROC_TVAV IS NULL` (acelasi 2
randuri de la FACT-028/runda 5) — pentru ele problema nu e "nicio explicatie cu aceasta cota", ci
"cota liniei insasi nu se cunoaste", un caz diferit si deja acoperit (PL/SQL va refuza oricum cu
FACT-028 daca s-ar incerca salvarea). Practic: **cazul "lista goala pentru o cota reala,
cunoscuta" e azi 0/1122 — teoretic posibil doar pentru o cota noua, adaugata pe o linie fara ca
nomenclatorul `JTVA_COLOANE` sa aiba inca o explicatie activa cu acea cota.**
**Recomandare de comportament** (proiectare, nu implementare): cand interogarea de populare
(`Reccount()` pe cursorul RowSource) iese cu 0 randuri, combo-ul ramane **dezactivat**
(`Enabled = .F.`), cu un text vizibil langa el (label sau tooltip, nu `messagebox` modal — nu
trebuie sa blocheze restul dialogului, care ramane editabil pe `explicatie`/`taxcode` ca azi):
> *Nu exista nicio explicatie TVA activa cu aceeasi cota ca linia facturata (cota: `<cota>` %).*
`<cota>` = `Round((Nvl(poRec.proc_tvav,0)-1)*100,2)`, acelasi calcul ca la filtrul de populare.
Daca `poRec.proc_tvav` e `NULL` (cele 2 linii identificate), afiseaza in loc:
> *Cota de TVA a acestei linii nu este cunoscuta; explicatia TVA nu poate fi modificata prin
> acest buton.*
4. **Fara garda e-Factura, fara garda de set** in aceasta parte (A5) — butonul ramane disponibil ca
azi pe orice linie/document.
4. **Apelul de salvare** (`inainte_de_do_termin`, `:5225-5233`): adauga al cincilea parametru
pozitional `?poRec.id_jtva_coloana` la textul SQL existent (`NULL`/`.NULL.` cand userul n-a
atins combo-ul nou, ca sa ramana pe ramura veche a procedurii).
5. **Trateaza eroarea FACT-029** (si FACT-026/027/028) ca orice alt esec `goExecutor.oExecuta` —
mesajul Oracle ajunge deja vizibil prin mecanismul existent (`amessagebox` in `oExecuta`), deci
nu trebuie cod nou de afisare, doar sa nu presupui ca apelul reuseste mereu.
6. **Refresh dupa salvare**: `Thisform.actualizeaza_grid2()` (apelat deja la `gnButon=1`) e
suficient acum — headerul (`crsfacturi`, totalurile) **nu se schimba**, deci nu mai e nevoie de
`Thisform.do_cauta()` suplimentar (recomandarea din varianta initiala nu se mai aplica).
**Nu s-a scris cod VFP in aceasta livrare** — punctele de mai sus sunt proiectare, nu
implementare.
---
## Rezumat surse
| ce | fisier:linie |
|---|---|
| sablonul de sincronizare (runda 5, `frm_modific2024`) | `COMUN\programe\ofacturare_editare.prg:1100-1147`, `COMUN\clase\omodificari.vc2:15049-15074` |
| `GetTaxCodeIdPart` | `COMUN\programe\oproceduri_comune.prg:6059-6125` |
| `caut_explicatie_tva` (filtru de cota la `:3218-3220`) | `COMUN\programe\ocautare.prg:3174-3238` |
| `do_modifica_explicatie` / dialog / salvare | `COMUN\clase\ofacturare_comun.vc2:4642-4659`, `:5129-5253`, `:5225-5233` |
| `crsDetalii` / `crsfacturi` (schema, populare) | `COMUN\programe\oproceduri_facturare.prg:340-412` |
| `EsteInEFactura` (doar ROAFACTURARE) | `COMUN\programe\ofacturare_editare.prg:1-30` |
| `recalculeaza_totaluri_vanzari` — nu mai e apelata din aceasta procedura, citata doar pentru A5(c) | `MARIUSM_AUTO.PACK_FACTURARE`, body linia 14784 (sursa vie) |
| garda FACT-025 (runda 5, deja aplicata) | `docs\raport_runda5_script_nvl_totaluri.md` |
| `modifica_explicatie_articol` — pristina, confirmata pe baza | `MARIUSM_AUTO.PACK_FACTURARE`, spec linia 939, body linia 13271 (sursa vie la momentul acestei livrari) |
| `PROC_TVAV`/`COTA_TVA` — precizie confirmata pe dictionar | `ALL_TAB_COLUMNS`: `VANZARI_DETALII.PROC_TVAV` = `NUMBER(10,4)`, `JTVA_COLOANE.COTA_TVA` = `NUMBER(10,0)` |
| 2 randuri active cu `PROC_TVAV IS NULL` | `SELECT COUNT(*) FROM vanzari_detalii WHERE sters=0 AND proc_tvav IS NULL` (verificat direct) |
| scriptul (suprascris) | `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_20_02_COMUN_PACK_FACTURARE.sql` |

View File

@@ -1,213 +0,0 @@
# 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.

View File

@@ -1,113 +0,0 @@
# Raport runda 6, punctul 6, partea VFP
## GATA — text editat, write-back facut si verificat
Implementare completa a partii VFP din punctul 6 (explicatia TVA in dialogul de modificare a
articolului din `frm_facturi`), conform `docs\brief_r6_punct6_vfp.md`, punctele M1-M5, plus
doua corectii cerute de team-lead la review (TabIndex complet, `Destroy` inchide ambele
cursoare). Scris pe binar cu succes; verificarile de mai jos confirma sincronizarea text/binar.
## Inventar modificari (numerotare finala, dupa write-back)
Fisier: `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2` (+ `ofacturare_comun.vcx`/`.vct`).
- **M1** — `frm_facturi.do_modifica_explicatie` (`:4642`): la `:4650-4651`, doua
`AddProperty(poRec, 'data_act', crsfacturi.data_act)` / `AddProperty(poRec, 'id_part',
Nvl(crsfacturi.id_part, 0))`, intre `poRec.explicatie = NVL(...)` si `If poRec.sters = 0`.
- **M2** — clasa `frm_modifica_articol_factura` (`:5132-5369`):
- Layout: `Height` 370->431 (`:5155`); `_shape3.Top` 323->384 (`:5192`); `cbo_saft.Top`
329->390 (`:5234`); `lbSaft.Top` 332->393 (`:5284`).
- `OBJECTDATA` ZOrder (`:5140-5143`): 4 linii noi — `_shape4`, `lbExplTva`, `cbo_expl_tva`,
`lbExplTvaInfo`.
- Proprietate noua `nidjtvaales`: `*p: nidjtvaales` in `*<DefinedPropArrayMethod>` (`:5147`,
intre `*p: nid` si `*p: ntip_selectie`), valoare initiala `nidjtvaales = 0` in `*<PropValue>`
(`:5158`).
- Obiecte noi (ADD OBJECT): `_shape4` (`:5197`), `cbo_expl_tva` (`:5207`, `ControlSource` gol,
`RowSource` populat in `Init`), `lbExplTva` (`:5257`, caption `Explicaţie TVA`, diacritic
cp1250 `0xFE`), `lbExplTvaInfo` (`:5266`).
- **M3** — `frm_modifica_articol_factura.Init` (`:5313-5353`): la final (dupa blocul
`citeste_lungcampexplart`), populare `cbo_expl_tva` din `vjtva_coloane` filtrat pe cota TVA a
liniei, cu cele 3 ramuri din brief (cota necunoscuta la `:5331-5336`; gasit la `:5342-5345`;
negasit la `:5346-5350`).
- **M4** — `cbo_expl_tva.InteractiveChange` (`:5355-5367`, metoda noua — override de eveniment
de baza, nu necesita `*m:`): seteaza `Thisform.nidjtvaales`, `poRec.id_jtva_coloana`, si sub
garda `gl406` recalculeaza `poRec.taxcode` prin `GetTaxCodeIdPart`.
- **M5** — `inainte_de_do_termin` (`:5298-5311`): parametrul 5 (`lcParamJtva`, `:5301-5302`)
construit literal (nu bind), `null` cand `Thisform.nidjtvaales` n-a fost atins.
- **In plus fata de M1-M5, cerut/corectat la review**:
- `PROCEDURE Destroy` (`:5288-5296`) — inchide `crsExplTvaCbo` (RowSource-ul combo-ului) si
`crsExplTvaArt` (cursorul intermediar din `Init`) daca raman deschise, apoi `DoDefault()`
(`:5295`) — lantul de curatenie mostenit din `frm_termin_renunt` (`_frm_child.vcx`) nu e
taiat; acelasi tipar (cleanup local + `DODEFAULT()`) e deja folosit in fisier la
`frm_modifica_factura.Destroy` (`:5720-5722`).
- `TabIndex`, ordine completa: `Ed_tx_simplu1`=1, `cbo_expl_tva`=2 (nou), `cbo_saft`=2->3,
`BUT_TERMIN1`=3->4, `But_renunt1`=4->5, `Lb_titlu_alb_b121`=5->6, `lbSaft`=6->7, `lbExplTva`=8
(nou); `lbExplTvaInfo` ramane fara `TabIndex` (`Visible = .F.`).
Ordinea fizica finala a metodelor in clasa: `Destroy`, `inainte_de_do_termin`, `Init`,
`cbo_expl_tva.InteractiveChange` — confirmata din textul regenerat de FoxBin2Prg la fidelity-check
(nu presupusa).
## Write-back — confirmat
`txt2vcx.ps1 -TextFile COMUN\clase\ofacturare_comun.vc2 -ProjectRoot D:\ROA\ROAFACTURARE
-CacheRoot D:\ROA\ROAFACTURARE -AllowComun`:
- Prima rulare (pe textul M1-M5, inainte de corectiile de TabIndex/`Destroy`): **ESEC pe ordine**
— exact avertismentul din brief ("FoxBin2Prg nu pastreaza ordinea textuala pentru ADD OBJECT
multiple"). Am adoptat ca sursa blocul clasei `frm_modifica_articol_factura` din
`<staging>\verify\ofacturare_comun.vc2` (ordinea canonica ADD OBJECT: `_shape3, _shape4,
cbo_expl_tva, cbo_saft, Ed_tx_simplu1, lbExplTva, lbExplTvaInfo, lbSaft`), fara sa ating nicio
alta clasa din fisier.
- Rularea finala (dupa corectiile de TabIndex + `Destroy`, cu semnal `WRITE-BACK ACUM` de la
team-lead, lock-ul lui Marius eliberat): **OK** — `1/1 fisier(e) scrise cu succes`, fara alt
esec de fidelitate.
**Dovada de sincronizare text/binar** (nu `mtime` — reconversie separata + diff):
`vcx2txt.ps1 -Source ofacturare_comun.vcx -Force` intr-un cache temporar separat, apoi
`diff` cu textul din arbore -> **0 linii diferenta**.
## Verificari encoding (inainte / dupa, pe tot parcursul)
- **Octeti > 0x7F**: 12 inainte de orice editare -> **13** dupa toate editarile si dupa
write-back (neschimbat de la corectiile de TabIndex/`Destroy` — nu ating diacritice). Singurul
octet nou e `0xFE` (diacriticul `ț` din caption-ul `lbExplTva`, `Explicaţie TVA`); zero
aparitii de `EF BF BD` (fara corupere UTF-8).
- **Octetul diacritic verificat explicit** (cerut de team-lead): `lbExplTva.Caption` (`:5258`) are
octetul `0xFE` exact in aceeasi pozitie semantica ca `Lb_titlu_alb_b121.Caption` (`:5170`,
`"Modific\xE3 explica\xFEie articol"`) din aceeasi clasa — acelasi octet `0xFE` = `ț` cp1250 in
ambele, confirmat byte-cu-byte (`od -c`) pe fisierul final, dupa write-back.
- **LF izolate** (`0x0A` fara `0x0D` inainte): 0, atat inainte cat si dupa toate editarile si dupa
write-back. Fisierul final are 7540 de linii, toate CRLF.
## Scop
`git status` in `D:\ROA\ROAFACTURARE\COMUN` (repo propriu al lui COMUN, unde `.vc2` e versionat
si `.vcx`/`.vct` sunt gitignored prin design — `git check-ignore` confirma `*.VCX`/`*.VCT` in
`.gitignore`): **doar** `clase/ofacturare_comun.vc2` modificat (117 insertii, 11 stergeri),
niciun alt fisier. Niciun PL/SQL, niciun `omodificari.vc2`, niciun grid de articole, niciun
`Text1.ReadOnly` atins — conform interdictiilor.
## Ce n-a fost facut si de ce
Nimic din scopul brief-ului a ramas nefacut. Singura abatere fata de fluxul standard a fost
timpul de asteptare intre editarea textului si write-back, cauzat de un lock legitim
(sesiunea VFP activa a lui Marius pe acelasi binar) — nu de o problema de implementare; write-back-ul
s-a facut abia dupa ce team-lead a confirmat eliberarea lock-ului si a dat semnalul explicit.
## Nu s-a comis nimic si nu s-a rulat nicio sincronizare
Nu s-a rulat `git_sync.ps1`, `roa_sync.bat`, `svn update/commit`, `git commit/push`. Nu s-a pornit
VFP, nu s-a deschis IDE-ul. Commit-ul ramane la latitudinea sesiunii principale, dupa aprobare.
## Fisiere atinse in aceasta sesiune
- `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2` — editat, write-back facut, verificat
(0 linii diferenta la reconversie).
- `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vcx` / `.vct` — regenerate prin write-back,
verificate byte-identic cu textul din arbore (via reconversie separata).
- `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2.pre_r6p6.bak`,
`ofacturare_comun.vc2.pre_r6p6_fix2.bak` — backup-uri locale (baseline-uri intermediare),
netracked de git, de sters la curatenie (`COMUN\utile\curatenie.ps1`).
- `D:\ROA\ROAFACTURARE\docs\raport_r6_punct6_vfp.md` — acest raport.
- Niciun proces deschis, nicio tranzactie Oracle deschisa, niciun date de test consumate.

View File

@@ -1,138 +0,0 @@
# Raport runda 7 — sincronizarea articole-rulaje
Scris 20.08.2026, 22:40. Acopera M1, M2 (dupa corectia de perimetru), rularea celor doua suite si
raspunsul la intrebarea de fapt ramasa deschisa.
## M1 — sincronizarea porneste doar din buton — GATA
Blocul "al doilea punct de declansare" a fost sters din `frm_modific2024.inainte_de_do_termin`.
Metoda se termina acum la `RETURN m.llRet`, imediat dupa validarile pe `tvd`; nu mai exista niciun
apel de sincronizare pe drumul salvarii.
`AfiseazaDialogSincronizareArticole` are un **singur** apelant ramas:
`omodificari.vc2:16665`, adica butonul `pgfArticole.PAGE3.cmdSincronizeazaArticole.Click`.
Definitia metodei e la `:13184`.
**Ramase fara consumator, semnalate, NEsterse** (conform deciziei):
| membru | unde |
|---|---|
| `SemnaturaDivergenteSincronizare` (metoda) | `omodificari.vc2:14829`, declarata `*m:` la `:6820` |
| `cSemnaturaSincronizare` (proprietate) | declarata `*p:` la `:6831`, valoare initiala `:6870` |
| atribuirea care le leaga | `:14945`, in `IncarcaArticoleFactura`-ul din ramura cu articole |
Proprietatea e **scrisa o data si citita niciodata**; metoda e apelata doar ca sa alimenteze acea
scriere. Ambele sunt acum cod mort, dar functional inofensiv.
### Write-back — FACUT si dovedit
`txt2vcx.ps1 ... -AllowComun` a esuat prima data la fidelity check: subagentul rundei precedente
lasase **linia 14485 goala**, desi in original era `\t\t` (o linie de spatiere pe care stergerea
n-ar fi trebuit s-o atinga). Restaurata la `\t\t`; diff-ul fata de HEAD e acum exact blocul de 11
linii sters, nimic altceva.
Dovada pe binarul din proiect, nu pe `mtime`: reconversie `vcx2txt.ps1` intr-un cache temporar,
**md5 identic** (`d23691466b7017380fe36099943b3208`), **diff 0 linii**. Binare la 22:20:09,
text la 22:20:10. Encoding: 6 octeti >0x7F (`0xAA`, `0xE3`, `0xFE` — cp1250), zero `EF BF BD`,
CRLF pe toate cele 17 145 de linii.
## M2 — `pretv` si `tvav` in rulaj — GATA, perimetru corectat
`AplicaModificareTrul` (`COMUN\programe\ofacturare_editare.prg:926`) scrie acum:
```
REPLACE (m.lcCampCant) WITH m.tnCantitateNoua IN trul
REPLACE pretv WITH m.lnPretv, pretvtva WITH m.lnPretvtva, tvav WITH m.lnTvav IN trul
```
Cele trei campuri de valoare (`valoarev`, `valtvav`, `valoarevcTVA`) pe care subagentul apucase
sa le adauge din varianta initiala a brief-ului **au fost scoase**, impreuna cu liniile care le
calculau (`lnValoarecTVA`, `lnValoareTVA`, `lnValoareFtva`) si cu `lnCantitate`, ramas nefolosit.
`LOCAL`-urile si comentariile-antet ale ambelor functii (`AplicaModificareTrul` si
`AplicaSincronizareArticole`) au fost aduse la zi.
Formula e cea canonica din `calculeaza_valori_rul`, ramura `pretvtva` (`omodificari.vc2:13573-13575`),
cu `gnPPretV` pentru preturi — reutilizata, nu reinventata.
`.prg`, deci fara write-back. Fisierul e ASCII curat, CRLF.
## Teste — ambele suite verzi
O suita per apel, `.fxp` sters inainte de fiecare rulare.
| suita | rezultat |
|---|---|
| `test_s4b_sincronizare.prg` | **44 PASS / 0 FAIL**, zero erori in log |
| `test_s4b_dialog.prg` | **35 PASS / 0 FAIL**, zero erori in log |
### Prima rulare a picat — fixture, nu cod
`test_s4b_sincronizare` a dat initial **40 PASS / 2 FAIL**, cu erori in log:
```
EROARE 12 [APLICAMODIFICARETRUL:942] Variable 'GNPPRETV' is not found.
EROARE 12 [APLICAMODIFICARETRUL:947] Variable 'PRETV' is not found.
```
Ambele vin din harness, nu din codul de productie:
1. testul definea `gnPC`, dar **nu si `gnPPretV`** — suitele surori din `achizitie_import` il pun
la `4` (`test_adauga_factura_ui.prg:167`);
2. cursorul `trul` construit de `CreeazaTrulTest` avea `pretvtva`, dar **nu si `pretv`/`tvav`` —
campuri pe care tabela reala `RUL` le are (verificat in Oracle, vezi mai jos) si pe care
`calculeaza_valori_rul` le scrie de ani de zile.
Modificari in `COMUN\utile\Teste\editare_factura\test_s4b_sincronizare.prg`:
- `PUBLIC gnPPretV = 4` daca nu exista deja, dupa blocul identic al lui `gnPC`;
- `pretv N(14,4)` si `tvav N(14,4)` adaugate in `CREATE CURSOR trul`;
- asserturile C1 1300/1400 verifica si `pretv`/`tvav` (pe `proc_tvav = 1`, adica TVA 0);
- **caz nou C1 1600**, cu TVA 19% real, ca defalcarea sa fie efectiv acoperita:
`238 / 2 buc = 119` cu TVA -> `pretv 100 + tvav 19`. Fara el, toate cazurile din sectiunea C
aveau `proc_tvav = 1` si `tvav` ar fi iesit 0 orice s-ar fi scris in formula.
De aici cresterea 42 -> 44 de cazuri.
## Intrebarea de fapt: cine recalculeaza `valoarev`/`valtvav`/`valoarevcTVA` dupa sincronizare?
**Nimeni.** A treia concluzie din cele trei posibile. Se raporteaza ca defect, **nu s-a reparat**.
Lantul complet, verificat cap-coada:
| pas | unde | ce face cu cele trei campuri |
|---|---|---|
| incarcare nota | `ofacturare_editare.prg:96-104` | `valoarev`/`valtvav` vin din `vrul_tot`, completate **doar daca sunt goale** (`WHERE EMPTY(NVL(...,0))`); `valoarevcTVA` e calculat **o singura data**, `valoarev + valtvav AS valoarevctva`, la construirea `RUL_TEMP` -> `trul` |
| sincronizare | `ofacturare_editare.prg:947` | scrie `cant`/`cante`, `pretv`, `pretvtva`, `tvav`. **Nu le atinge** |
| dupa "Aplica" | `omodificari.vc2:17097` | `ActualizeazaBaraTotaluri()` + refresh grid. Bara **doar insumeaza `tvd.valoare`** (`:13009`) — nu atinge `trul` |
| salvare, pas 1 | `omodificari.vc2:14379` | `inainte_de_do_termin` pune doar `id_set` pe `trul` si valideaza `tvd` |
| salvare, pas 2 | `ofacturare_comun.vc2:3812-3814` | `Select * From trul Into Cursor RUL_TEMP`, apoi `Replace All id_util..., sters With 0`. **Copie verbatim** |
| salvare, pas 3 | `oscrie_in_fisiere.prg:131` | `sql_temp_insert('rul_temp','RUL_TEMP')` — bulk insert, fara calcul |
| salvare, pas 4 | `PACK_CONTAFIN.pck:2013` | `INSERT INTO <schema>.RUL (<lista>) SELECT <lista> FROM RUL_TEMP`, lista fiind coloanele reale ale lui `RUL` (`LISTA_CAMPURI`, `:3287`). **Fara recalcul** |
| post-procesare | `PACK_CONTAFIN.pck:8601` | `finalizeaza_modificare_nota` renumeroteaza `cod`, atinge `atasamente_vanzari`/`nom_lucrari`. **Nimic pe valori** |
### Ce ajunge efectiv gresit in Oracle
Interogat pe `MARIUSM_AUTO` / `ROA_CENTRAL`, coloanele reale ale tabelei `RUL`:
```
CANT, CANTE, PRETV, PRETVTVA, TVAV, VALOAREV, VALTVAV (7 din 8 cerute)
```
`VALOAREVCTVA` **nu e coloana in `RUL`**. Exista doar in cursorul Fox, calculat la incarcare
pentru grid si pentru bara de totaluri (`csumcolumns`) — nu se salveaza nicaieri.
Deci, concret:
- **`VALOAREV` si `VALTVAV` ajung invechite in Oracle.** Dupa sincronizare, cantitatea si pretul
de pe rand sunt cele noi, iar cele doua valori raman cele dinainte. La o reincarcare ulterioara
a notei nu se repara singure: back-fill-ul de la incarcare are garda `WHERE EMPTY(NVL(...,0))`,
deci prinde doar valorile zero, nu si pe cele nenule dar gresite.
- **`VALOAREVCTVA` nu ajunge in Oracle deloc**, dar ramane invechit **in ecran** pana la
reincarcarea notei: gridul de rulaje si bara de totaluri arata suma veche.
Iesirea manuala exista: daca utilizatorul atinge randul de rulaj in gridul de pe pagina 1,
`calculeaza_valori_rul` recalculeaza toate cele sase campuri deodata (`omodificari.vc2:13587`).
Sincronizarea insa porneste de pe pagina 3 si nu poate apela acea metoda direct — isi alege
cursorul din `pgfArticole.ActivePage` (`:13532-13533`) si ar scrie in `trul_obinv`.
**Decizia daca se repara si cum e a lui Marius.** Nu s-a atins nimic.

View File

@@ -1,57 +0,0 @@
# Sweep encoding — fisiere text FoxBin2Prg (.vc2/.sc2/.fr2/.mn2/.lb2/.pj2/.prg)
Scop: cautare exhaustiva, pe octeti, a semnaturilor de stricare encoding (`EF BF BD` /
U+FFFD, mojibake UTF-8 cu lead-byte `C3`/`C4`/`C5`/`C8`, BOM `EF BB BF` la inceput de
fisier) in toate fisierele text urmarite de git, in ambele arbore-uri:
- **`D:\ROA\ROAFACTURARE`** (repo principal, remote `gitea.romfast.ro:romfast/roafacturare.git`)
- **`D:\ROA\ROAFACTURARE\COMUN`** (repo separat, `gitea.romfast.ro:romfast/comun.git`)
## Acoperire
| Repo | Fisiere `.vc2/.sc2/.fr2/.mn2/.lb2/.pj2/.prg` scanate |
|---|---|
| ROAFACTURARE (principal) | 75 (case-insensitive, verificat separat ca nu exista variante `.PRG` majuscul scapate) |
| COMUN | 631 (621 din glob case-sensitiv + 10 `.PRG` majuscul gasite separat: `programe/obj2arr.PRG`, `utile/hpdf/_libpdf.PRG`, `utile/hpdf/build_err_msgs.PRG`, `utile/nfXml/nfxmlCREATE.PRG`, `utile/nfXml/nfXmlMap.PRG`, `utile/nfXml/nfxmlread.PRG`, `utile/web/wwapi.PRG`, `utile/web/wwConfig.PRG`, `utile/web/wwHttp.PRG`, `utile/web/wwutils.PRG`) |
Metoda: scan Perl byte-safe (`<:raw`, `LC_ALL=C`) pe continutul curent (working tree =
HEAD, fara modificari necomise in niciunul din cele doua repo-uri la momentul scanarii).
Niciun blob nu a fost extras cu `>` din PowerShell (doar Bash/`git cat-file`, cum s-a
cerut).
## Rezultat: repo principal ROAFACTURARE
**Zero fisiere afectate.** Nicio secventa `EF BF BD`, niciun mojibake `C3/C4/C5/C8` +
continuare, niciun BOM, in niciunul din cele 75 de fisiere.
## Rezultat: COMUN
5 fisiere din 631 au declansat cel putin un semnal. Verificate individual, pe obiect/
context si istoric (`git log -L`), rezultatul e:
| Fisier | Semnal | Nr. | Cauza reala | Commit | Verdict |
|---|---|---|---|---|---|
| `clase/ofacturare_comun.vc2` | `EF BF BD` (U+FFFD) | 12 | Stricare certa - diacritice cp1250 inlocuite cu U+FFFD (`Marcheazǎ`→`ďż˝`, etc.) | **`13b4f65`** | **Deja cunoscut si alocat** — restaurarea o face `s4fix-butoane`, care detine fisierul. Nu l-am atins (doi scriitori pe acelasi fisier ar fi produs conflict). |
| `clase/ferestre_seturi_indicatori.vc2` | mojibake (fals-pozitiv) | 8 | Linia 5188, tag `<sit_titlu>`: text UTF-8 **legitim** ("Situaţiile financiare anuale încheiate...", pentru o declaratie ANAF/XML care cere UTF-8) inserat deliberat intr-un fisier altfel cp1250 — VFP nu impune o singura codare per fisier, doar stocheaza octetii asa cum au fost tastati/lipiti | `2f59024` (commit-ul initial care a adus fisierul in git) | **Fals pozitiv.** Neschimbat de la primul commit din git — nu e o regresie introdusa de vreun commit urmarit. |
| `programe/oproceduri_comune.prg` | mojibake (fals-pozitiv) | 1 | Linia 5266: `STRTRAN(lcText, "Ă", "A")` — o functie de normalizare/eliminare diacritice care trateaza EXPLICIT mai multe variante de text deja-stricat primit din surse externe; litera UTF-8 `Ă` e parte din tabelul de inlocuiri, nu o stricare | `2f59024` | **Fals pozitiv.** Neschimbat de la primul commit; codul e literal despre curatarea altor stricari, nu e el insusi stricat. |
| `utile/Menu/menutool.vc2` | mojibake (fals-pozitiv) | 162 (122 linii) | Clasa terta `_menuinfo`, comentarii in **chineza** (encoding GBK) — octetii GBK se suprapun coincidental peste tiparul `C3/C4/C5/C8 + continuare` cautat pentru cp1250/UTF-8; nu are nicio legatura cu diacritice romanesti | `2f59024` — fisierul are o **singura** intrare in tot istoricul git (import initial, niciodata modificat) | **Fals pozitiv / afara din scop.** Cod tert neatins vreodata. |
| `utile/excel/ExcelXML.prg` | `EF BF BD` (U+FFFD) | 52 | Comentarii in **portugheza** intr-o biblioteca terta (`"Definição de bordas..."`, `"Não aplica os estilos..."`) — caracterele portugheze au fost inlocuite cu U+FFFD deja **inainte** de primul commit vizibil in git | `2f59024` (prezent inca de la import; nu exista commit anterior de investigat in acest istoric) | **Stricare pre-existenta, in afara ferestrei investigabile.** Nu provine dintr-un commit urmarit (SVN sau sursa originala, inainte de tracking-ul git). Doar comentarii, cod tert, fara text romanesc/UI. Nu am gasit un commit "vinovat" pentru ca nu exista unul in istoricul disponibil. |
Nicio secventa BOM (`EF BB BF`) la inceputul vreunui fisier, in niciunul din cele
706 fisiere (75 + 631).
## Concluzie
In afara de **`clase/ofacturare_comun.vc2`** (deja identificat si alocat lui
`s4fix-butoane`), sweep-ul **nu a gasit nicio alta regresie de encoding introdusa de
vreun commit** in istoricul git al celor doua repo-uri. Cele 4 fisiere ramase sunt fie
fals-pozitive ale euristicii (text UTF-8 legitim/intentionat, cod care proceseaza
explicit variante de mojibake, sau comentarii intr-o alta limba/encoding straina de
cp1250-ul romanesc), fie o stricare deja prezenta la momentul primului commit care a
adus fisierul in git (`2f59024`), deci in afara ferestrei pe care o putem bisecta.
Raport gol pe restul arborelui = rezultat bun: nu s-a mai lovit acelasi bug (U+FFFD prin
suprascriere cp1250) in alta parte.
Fara nicio scriere in arbore, fara write-back, fara pornire VFP — doar investigatie
read-only, conform cerintei.

View File

@@ -1,152 +0,0 @@
# S4b etapa 2 — butonul, dialogul si verificarea la salvare
Continuarea lui handoff intermediar (sters), sectiunea „S4b etapa 2". Diff-ul complet:
**diff aplicat (sters)** (342 randuri adaugate, 0 sterse, un singur fisier atins:
`COMUN\clase\omodificari.vc2`).
**STARE: WRITE-BACK FACUT, NECOMIS.** `omodificari.vcx`/`.vct` sunt regenerate (11.08.2026 09:34),
textul e cel canonic FoxBin2Prg. Nimic nu e intr-o stare periculoasa.
| Verificare | Rezultat |
|---|---|
| compilare la regenerare (`txt2vcx.ps1`) | `P1,E0,S1,X0` — 0 erori |
| fidelity check binar -> text -> octeti, in staging | trecut (`OK`) |
| `git_sync.ps1` dupa write-back | **0 conversii**, 464 la zi, 0 esecuri — text si binar sincrone |
| textul dupa write-back | 342 adaugari, **0 stergeri** fata de HEAD |
| octeti `>0x7F` (diacritice cp1250) | 6 inainte, 6 dupa, aceleasi valori (170, 227, 254) |
| clasele noi chiar in binar (`.vct`, nu in text) | `frm_sincronizare_articole` x15, `propunere_afisata` x27, `cmdSincronizeazaArticole` x3, `lblAvertisment` x2 |
**Prima incercare de write-back a esuat la fidelity check** — ordonarea canonica FoxBin2Prg difera
de cea scrisa de mana (intrarile `OBJECTDATA` ale header-elor de grid se genereaza singure,
`ADD OBJECT`-urile se sorteaza alfabetic, `optDirectie.Click` merge dupa `Unload`). Am adoptat
textul canonic din staging si am reluat — nicio pierdere de continut, diferentele erau **doar** de
ordine si spatii.
## Ce contine
| Piesa | Unde |
|---|---|
| butonul `cmdSincronizeazaArticole` (Left=350, Top=0, W=250, H=22, TabIndex=6) | `ADD OBJECT` in PAGE3 |
| toggle-ul de `Enabled` pe `lArticoleReadOnly` | `frm_modific2024.Show` |
| `cmdSincronizeazaArticole.Click` | `frm_modific2024` |
| `AfiseazaDialogSincronizareArticole` | `frm_modific2024` |
| verificarea la salvare | `frm_modific2024.inainte_de_do_termin`, imediat inainte de `RETURN m.llRet` |
| clasa `frm_sincronizare_articole` (derivata din `frm_termin_renunt`) | la finalul fisierului |
TabIndex 6 e liber in PAGE3 (folosite: 3, 4, 5, 20 — verificat).
## Ce am schimbat fata de patch-ul partial al agentului mort
Patch-ul din diff aplicat (sters) era mai complet decat il descria predarea (avea si
verificarea la salvare, in metoda corecta). Cinci corectii:
**1. Lipsea `cmdSincronizeazaArticole.Click`** — butonul exista, dar nu facea nimic. Adaugat, cu
aceleasi garzi ca la celelalte doua butoane din PAGE3.
**2. Gridul ramanea nelegat dupa schimbarea directiei.** `ConstruiestePropunereSincronizare` face
`Use In propunere_sincronizare` + `CREATE CURSOR` la fiecare apel (`ofacturare_editare.prg:584-589`),
deci un grid legat la design-time de acel cursor isi pierde legatura la primul click pe radio.
Solutie: gridul se leaga de **`propunere_afisata`**, un cursor cu structura identica creat o
singura data in `Load` (inainte de instantierea controalelor, ca sa existe cand gridul se leaga) si
doar golit + reumplut la fiecare recalcul. Nicio reasignare de `RecordSource` la runtime.
**3. Declansarea la salvare era pe `Reccount(...) > 0`** — ar fi deschis dialogul la *fiecare*
salvare a oricarei facturi cu articole nestocate sau in valuta, pentru ca acelea produc linii `N-A`
in propunere (contract, punctele 1 si 3). Acum se numara doar
`Modificare` / `Adaugare` / `Semnalare`: divergente reale. `N-A` inseamna „nu se poate compara", nu
„difera", si nu mai opreste pe nimeni la salvare.
**4. `Pemstatus(This.oFormArticole, ...)` fara garda de tip** — `oFormArticole` are valoarea
implicita `.F.`, iar `Pemstatus` pe un logic da eroare. Adaugat `Vartype(...) == 'O'`.
**5. Adaugata eticheta de avertisment** ceruta de `propunere_s4b_sincronizare.md` punctul 6
(„Propunere calculata automat - verificati valorile inainte de aplicare"), plus curatarea cursorului
in `Unload`.
## Verificat, nu presupus
| Ce | Cum |
|---|---|
| `_frmbase.WindowType = 1` (dialogul e modal) | `_frm_base.vc2`, blocul `PropValue` |
| `do_termin` cheama `inainte_de_do_termin()` si inchide doar pe `.T.` | `_frm_base.vc2:364-374` |
| `frm_termin_renunt` are `But_termin1` + `But_renunt1` | `_frm_child.vc2:23-51` |
| tiparul apelantului (`CreateObject` + `Show`, citirea lui `gnButon`) | `ofacturare_comun.vc2:4651-4656` |
| clasele `_optiongrup`, `_grdrow`, `_label` exista | `_baza.vc2:435`, `_grd_base.vc2:445` |
| `InputMask = (get_mask(12,gnPCANT))` e tipar real | `omodificari.vc2:12374` s.a. |
| `_frm_child.vcx` nu are nevoie de `SET CLASSLIB` | precedentul `frm_modifica_articol_factura` |
| toate formularele sunt pe `DataSession` implicit (1) | niciun `DataSession` in `_frm_base`/`_baza`/`omodificari` |
| `SET SAFETY OFF` global (pentru `ZAP`) | `COMUN\programe\proceduri.prg:5` |
| octetii cp1250 din fisier sunt neatinsi | cens `>0x7F` = 6 inainte si dupa; diff-ul are **0 stergeri** |
**Netestat**: nimic nu a fost rulat. Dialogul e UI, deci nu e testabil `-A -T` (aceeasi limitare ca
`frm_articol_factura`, `propunere_s4b_sincronizare.md` punctul 7). Logica pura din spate ramane
acoperita de `test_s4b_sincronizare.prg` (35/0), neschimbata de aceasta livrare.
## Doua lucruri de confirmat cu Marius
**A. Aplicarea la salvare ocoleste validarile deja rulate.** Daca utilizatorul apasa „Aplica" in
dialogul deschis *la salvare*, `tvd` se modifica **dupa** ce validarile din
`inainte_de_do_termin` (pret de achizitie, linii sterse) au trecut deja. Alternativa ar fi
reluarea validarilor dupa aplicare — nu am facut-o, ca sa nu extind scopul.
**B. `N-A` nu mai declanseaza dialogul la salvare** (corectia 3). E o alegere de produs facuta de
mine: altfel dialogul ar aparea la fiecare salvare pe documentele in valuta.
> **B — CONFIRMAT de Marius, 12.08.2026. Ramane cum e, nu se schimba nimic.** Numaratoarea de la
> `omodificari.vc2:14444` sta pe `Inlist(Alltrim(actiune),'Modificare','Adaugare','Semnalare')`.
> Pretul acceptat, explicit: pe liniile in valuta si pe cele nestocate o divergenta reala nu mai e
> semnalata **la salvare** — se vede in schimb oricand prin butonul manual „Sincronizeaza articole",
> care le arata in grid.
>
> **A ramane deschis** si si-a schimbat forma — vezi `progres.md`, sectiunea despre validarea de
> cantitate. Plasa de siguranta pe care o propuneam initial **nu se mai face**: singura validare care
> ar fi putut pica dupa „Aplica" e cea de cantitate, iar premisa ei e sub semnul intrebarii.
## Defect preexistent gasit pe drum — REPARAT (aprobat separat, 11.08.2026)
`omodificari.vc2:16495`, in `frm_modific2024.pgfArticole.PAGE3.cmdAdaugaArticol.Click`:
```
IF !Used('tvd') OR !This.lAreArticoleVanzari OR Thisform.lArticoleReadOnly
```
`This` e **butonul**, nu formularul — `lAreArticoleVanzari` e proprietate a lui `frm_modific2024`
(`omodificari.vc2:12987`, `:14788`, `:14795`, `:14802` o folosesc corect, toate din metode ale
formularului). VFP scurtcircuiteaza `OR`: cand `tvd` exista — adica in exact cazul in care butonul
e activ — primul termen e `.F.` si se evalueaza al doilea, pe buton, unde proprietatea nu exista.
Nu exista metoda `Error` in `_frmbase`/`_baza`/`omodificari` care sa inghita eroarea.
Concluzia: **„Adauga articol" dadea eroare la orice click real**. Suitele existente nu prind asta
pentru ca apeleaza metodele formularului, nu simuleaza click-ul.
**Nu era una, ci doua.** Cautarea sistematica dupa acelasi tipar (proprietati ale formularului
accesate cu `This.` din metode de obiect) a mai gasit una, in aceeasi metoda, la `:16565`:
```
AddProperty(poDate, 'tip', This.nTipVanzare)
```
Ambele reparate cu `Thisform.`. In tot `frm_modific2024` nu mai exista alta: cautarea a acoperit
`lArticoleReadOnly`, `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`, `pgfArticole`,
`ActualizeazaBaraTotaluri`, `AdaugaLinieTvdDinArticol`, `calculeaza_valori_articol`, filtrata pe
metode de obiect (`.Click`/`.Valid`/`.InteractiveChange`/...). Celelalte 4 folosiri ale lui
`This.lAreArticoleVanzari` sunt in metode ale **formularului** (`ActualizeazaBaraTotaluri`, `Show`)
si sunt corecte.
Nu intra in changelog: 2.11.15 nu e inca in productie, deci e un defect al codului nelivrat.
## Changelog
Paragraf nou in blocul **2.11.15** (`:nou:`), nu versiune noua — 2.11.16 a fost dat inapoi
„pana la punerea in productie" (commit `09f9d47`). Descrie butonul, alegerea directiei, liniile
marcate si lasate neatinse, si comparatia de la salvare cu optiunea de a salva fara sincronizare.
Fisierul `changelog_roafacturare.txt` avea deja **un octet stricat in HEAD** (`EF BF BD`, un `©`
transformat candva in U+FFFD, in intrarea despre „ROA Romfast SRL"). E preexistent, **nu l-am
atins**; cenzul ramane 3 octeti `>0x7F` inainte si dupa editarea mea.
## Ce urmeaza
1. **Rebuild `roafacturare.exe`** din IDE si testul pe ecran. Metodele `.vcx` sunt deja compilate
de `txt2vcx.ps1`; EXE-ul nu.
2. Punctele A si B de confirmat.

View File

@@ -1,62 +0,0 @@
# Review: diff aplicat (sters) (omodificari.vc2, frm_modific2024)
Status: GATA. Review read-only, fara editari de cod. Nicio tranzactie/proces deschis.
## Scop verificat
diff aplicat (sters) pe `COMUN/clase/omodificari.vc2`:
- insereaza coloana de grid `cPretAchizitieArt` (legata de `tvd.pret_achizitie`) intre
`cPretArt` (Column6) si fostul `cPretCuTvaArt` (Column7), renumeroteaza Column7..14 -> 8..15;
- adauga campul `id_vanzare_set` in cursorul `tvd`;
- adauga validare noua in `inainte_de_do_termin` (bloc `OFACTURARE_EDITARE`);
- adauga/modifica handlere `When` pe `cCantitateArt.Text1`, `cPretArt.Text1`,
`cPretAchizitieArt.Text1`, `cPretCuTvaArt._checkbox1`.
## Verificat si confirmat OK (fara regresie)
- Renumerotarea Column7->15: toate proprietatile (`ControlSource`, `Name`, `Format`,
`InputMask`, `ReadOnly`, `Width`, `Sparse`, `DynamicForeColor`) se pastreaza identic fata
de valorile pre-diff, verificat linie cu linie in hunk-urile de la
`omodificari.vc2:12341-12450`.
- Nicio alta parte a fisierului nu refera coloanele `grdArticoleFactura` pe index numeric
(grep confirmat - singurele hit-uri `Grid1.ColumnN` apartin altui grid, in alta sectiune a
clasei), deci renumerotarea nu putea sparge tacut o referinta indexata.
- Coloana noua `cPretAchizitieArt` primeste acelasi tratament `DynamicForeColor` ca surorile ei.
- Garda de editare pentru `cPretAchizitieArt` (blocheaza editarea cand `id_vanzare_det<>0`)
e consistenta cu `COMUN/programe/ofacturare_editare.prg` (`ScrieArticoleFacturaEditate`):
UPDATE-ul pentru liniile existente NU scrie `pret_achizitie` inapoi, deci blocarea editarii
exact pe acele randuri e corecta, nu o scapare.
## Findings (3, niciunul cu severitate "blocker" cert, dar merita fix inainte de commit)
1. **omodificari.vc2:14330** - blocul nou de validare (`IF "OFACTURARE_EDITARE" $ ...`) face
`SELECT tvd` + `SCAN ... RETURN .F.` fara sa salveze/restaureze `Recno()` pe tvd inainte de
return; restaureaza doar workarea activa (`SELECT (m.lnAreaTvd)`). Toate celelalte metode
din fisier care fac SCAN pe un workarea (15+ precedente gasite prin grep pe `lnRecno`)
salveaza `Recno()` inainte si fac `GOTO`/`GO` inapoi. Scenariu: userul incearca sa salveze,
o linie mai jos pica validarea -> pozitia curenta in tvd ramane unde s-a oprit SCAN-ul (nu
randul pe care userul lucra), posibil sa sara vizual randul selectat in grid dupa esec.
2. **omodificari.vc2:14350** - avertismentul de `pret_achizitie=0` la salvare exempteaza doar
`id_vanzare_det<>0`, dar garda de editare `cPretAchizitieArt.Text1.When` (linia 16514)
exempteaza si `id_vanzare_set<>0`. Un rand nou dintr-un set de articole
(`id_vanzare_set<>0`, `id_vanzare_det=0`) cu `pret_achizitie` 0/null va primi nag-ul Da/Nu
la fiecare salvare, fara ca userul sa poata edita campul ca sa-l corecteze (editarea e
blocata de guard).
3. **omodificari.vc2:16513** - `cPretAchizitieArt.Text1` are `When` (seteaza `oldvalue`) dar nu
are un `Valid` pereche, spre deosebire de `cCantitateArt.Text1` (16499) si `cPretArt.Text1`
(16520), care compara oldvalue/newvalue si apeleaza
`Thisform.calculeaza_valori_articol()` -> `REPLACE lmodificat WITH .T.`. Editarea izolata a
`pret_achizitie` nu marcheaza randul `lmodificat`. Nu am gasit un consumator cert al
`lmodificat` care sa depinda de asta pentru persistenta (INSERT-ul de linii noi in
`ScrieArticoleFacturaEditate` nu filtreaza dupa `lmodificat`), deci impactul functional
e incert, dar inconsistenta cu patternul stabilit ramane.
## Ce NU s-a facut (in afara scopului acestui review)
- Nu s-a validat `ofacturare_editare.prg` / partea Oracle in detaliu (in grija altor agenti
din sesiune: s5-helper, s5-oracle, s5-script, s5-view).
- Nu s-a rulat harness-ul de teste headless.
- Niciun fix nu a fost aplicat - doar review, findings-urile de mai sus asteapta decizie
inainte de commit.

View File

@@ -1,64 +0,0 @@
# Verificare write-back binar S5 (omodificari, ofacturare_comun, comun)
Verificare de stare, fara modificari de productie. Nicio scriere in arbore, niciun commit.
## Metoda
Conversie binar -> text intr-un director de cache TEMPORAR (nu in arbore), pentru fiecare
`.vcx` din `COMUN\clase\`:
```
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 -Source "D:\ROA\ROAFACTURARE\COMUN\clase\<f>.vcx" -CacheRoot "<scratchpad>\verif_wb" -Force
```
Cache temporar folosit:
`C:\Users\mmari\AppData\Local\Temp\claude\D--ROA-ROAFACTURARE\c8e7df05-b882-4b45-8548-cd08f55d7be1\scratchpad\verif_wb\`
Toate cele trei conversii au raportat `OK` / `Esuate: 0`.
## Tabel rezultate
| fisier | octeti text arbore | octeti text regenerat | linii diferite | identic DA/NU | cens octeti >0x7F | cens conform reperului DA/NU | linia agatarii |
|---|---|---|---|---|---|---|---|
| omodificari.vc2 | 539470 | 539470 | 0 | **DA** | `2 aa . 2 e3 . 2 fe`, EF BF BD: 0 | **DA** | n/a (dovedit anterior, nu contine blocul de agatare) |
| ofacturare_comun.vc2 | 242984 | 242984 | 0 | **DA** | `1 aa . 3 ba . 2 ce . 2 e3 . 2 ee . 2 fe`, EF BF BD: 0 | **DA** | `ofacturare_comun.vc2:3828-3830` |
| comun.vc2 | 330100 | 330100 | 0 | **DA** | `1 ee`, EF BF BD: 0 | **DA** | `comun.vc2:2491-2493` |
`cmp` byte-for-byte intre textul din arbore si textul regenerat din binar: identic pe toate
trei (`cmp -s` fara diferente, 0 linii `diff`).
## Cens de octeti (detaliu)
Repere asteptate (git HEAD b9eba29, starea de dinaintea S5) vs. masurat acum pe `.vc2` din arbore:
- `omodificari.vc2`: asteptat `2 aa · 2 e3 · 2 fe`, 0x `EF BF BD` -> masurat `2 aa . 2 e3 . 2 fe`, 0 -> **conform**
- `ofacturare_comun.vc2`: asteptat `1 aa · 3 ba · 2 ce · 2 e3 · 2 ee · 2 fe`, 0 -> masurat `1 aa . 3 ba . 2 ce . 2 e3 . 2 ee . 2 fe`, 0 -> **conform**
- `comun.vc2`: asteptat `1 ee`, 0 -> masurat `1 ee`, 0 -> **conform**
Nicio abatere fata de reper.
## Blocul de agatare (3 randuri, cu `-1`, NU `0`)
`ofacturare_comun.vc2:3828-3830`:
```
3828: If lnSucces > 0 And "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) And Used('tvanz') And Reccount('tvanz') = 1
3829: lnSucces = Iif(ScrieArticoleFacturaEditate(tvanz.id_vanzare), 1, -1)
3830: Endif
```
`comun.vc2:2491-2493`:
```
2491: If lnSucces > 0 And "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) And Used('tvanz') And Reccount('tvanz') = 1
2492: lnSucces = Iif(ScrieArticoleFacturaEditate(tvanz.id_vanzare), 1, -1)
2493: Endif
```
Ambele confirmate exact ca in specificatie: 3 randuri, `-1` (nu `0`).
## Verdict
Toate trei fisierele (`omodificari.vc2`, `ofacturare_comun.vc2`, `comun.vc2`) au write-back-ul
binar sincronizat cu textul din arbore, dovedit prin reconversie + comparatie octet cu octet
(nu doar mtime). Cens de octeti diacritice conform reperului pe toate trei, fara secvente
`EF BF BD` (fara semne de corupere UTF-8). Blocul de agatare prezent si corect in
`ofacturare_comun.vc2` si `comun.vc2`.

View File

@@ -1,74 +0,0 @@
-- verificare_vfacturi.sql
-- Compara valoare calculata (FACT_VFACTURI) cu valoare denormalizata (FACT_VFACTURI2)
-- pentru fiecare coloana comuna, pe toate facturile din schema curenta.
-- Testul de regresie al planului #8 (docs\plan_08_denormalizare_vanzari.md).
--
-- Rulare (schema de dezvoltare, conform COMUN\docs\scripturi-migrare-db.md):
-- & 'D:\ROA\instantclient_19_18\sqlplus.exe' -L -S 'MARIUSM_AUTO/<parola>@ROA_CENTRAL' '@verificare_vfacturi.sql'
--
-- Rezultatul: o linie per coloana cu diferente, in ordinea descrescatoare a numarului de facturi.
-- Coloanele identice nu se afiseaza.
set serveroutput on size unlimited
set linesize 200 pagesize 0 feedback off heading off verify off
declare
lcSel clob := 'select ';
lcCol varchar2(30);
lnPrim number := 1;
lcRez varchar2(32767);
type t_nume is table of varchar2(30);
laNume t_nume := t_nume();
begin
for c in (select c.column_name
from all_tab_columns c
where c.owner = user
and c.table_name = 'FACT_VFACTURI'
and c.data_type in ('NUMBER','VARCHAR2','CHAR','DATE')
and c.column_name <> 'ID_VANZARE'
and exists (select 1 from all_tab_columns d
where d.owner = c.owner and d.table_name = 'FACT_VFACTURI2'
and d.column_name = c.column_name)
order by c.column_id)
loop
lcCol := c.column_name;
laNume.extend; laNume(laNume.count) := lcCol;
if lnPrim = 0 then lcSel := lcSel || q'[||','||]'; end if;
-- decode trateaza NULL = NULL ca egalitate
lcSel := lcSel || 'sum(decode(a.'||lcCol||',b.'||lcCol||',0,1))';
lnPrim := 0;
end loop;
lcSel := lcSel || ' from fact_vfacturi a join fact_vfacturi2 b on a.id_vanzare = b.id_vanzare';
execute immediate lcSel into lcRez;
dbms_output.put_line('--- coloane cu diferente intre FACT_VFACTURI (calculat) si FACT_VFACTURI2 (denormalizat) ---');
declare
lnPoz number := 1;
lnVirg number;
lcVal varchar2(50);
lnTotal number := 0;
begin
for i in 1 .. laNume.count loop
lnVirg := instr(lcRez, ',', lnPoz);
if lnVirg = 0 then
lcVal := substr(lcRez, lnPoz);
else
lcVal := substr(lcRez, lnPoz, lnVirg - lnPoz);
lnPoz := lnVirg + 1;
end if;
if to_number(lcVal) > 0 then
dbms_output.put_line(lpad(lcVal, 8) || ' ' || laNume(i));
lnTotal := lnTotal + 1;
end if;
end loop;
dbms_output.put_line('--- ' || lnTotal || ' coloane diferite din ' || laNume.count || ' comparate ---');
end;
end;
/
select '--- perechi comparate: ' || count(*) || ' ---'
from fact_vfacturi a join fact_vfacturi2 b on a.id_vanzare = b.id_vanzare;
exit