docs: runda 6 - cercetari, propuneri si conventia mediului Oracle

Cercetarile si propunerile rundelor 4-6 pe editarea facturii emise. Starea
rundei 6 si ce s-a stabilit intra in progres.md.

Nou: docs/conventii_mediu_oracle.md - pe dev/test se lucreaza numai cu
ROA_CENTRAL si schemele CONTAFIN_ORACLE si MARIUSM_AUTO; schema ACN nu se
foloseste. Scripturile de pachet sunt necalificate, deci schema tinta o
decide sirul de conectare, nu fisierul.

roafacturare.pj2 regenerat de git_sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013gYCNE26G1G7UoGu2ju7aS
This commit is contained in:
2026-08-20 16:35:03 +03:00
parent b5a7108f34
commit ca3c5d7eea
26 changed files with 5316 additions and 103 deletions

View File

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,87 @@
# Conventii - mediul Oracle de dev/test
Regula lui Marius, 20.08.2026. Se aplica **de fiecare data**, si sesiunii principale, si
subagentilor. Nu e context, e conventie de lucru.
## 1. Nu se spune "baza"
Termenul e vag si a produs deja o intrebare care n-ar fi trebuit sa existe: *"ce este baza? care
schema?"*. Se scrie intotdeauna **schema + instanta**, explicit:
> "se ruleaza pe schema `MARIUSM_AUTO` de pe `ROA_CENTRAL`"
nu "se ruleaza pe baza". Acelasi lucru in rapoarte, in handoff-uri, in propuneri si in mesaje.
## 2. Pe dev/test se lucreaza NUMAI cu ROA_CENTRAL
Instanta: **`ROA_CENTRAL`**. Schemele permise, singurele doua:
| Schema | Ce este |
|---|---|
| **`MARIUSM_AUTO`** | schema **de firma** - datele si pachetele de business (`VANZARI`, `VANZARI_DETALII`, `JTVA_COLOANE`, `PACK_FACTURARE`...). Aici se lucreaza pe functionalitate. |
| **`CONTAFIN_ORACLE`** | schema **comuna tuturor schemelor de firma**: pachete comune, actualizarea bazei de date, utilizatori, drepturi de utilizator, nomenclatorul de firme. Se creeaza **pe fiecare server**. |
Nimic altceva. Daca o sarcina pare sa ceara altceva, se intreaba intai.
Modelul de retinut: fiecare firma are schema ei (in dev/test, `MARIUSM_AUTO`), iar
`CONTAFIN_ORACLE` sta alaturi, o singura data per server, si deserveste toate schemele de firma.
Deci o modificare in `CONTAFIN_ORACLE` e **transversala peste toate firmele de pe acel server** -
se trateaza cu grija corespunzatoare, nu ca o schimbare locala.
## 3. Schema `ACN` nu se foloseste si nu se citeaza
Nu se interogheaza, nu se ia ca sursa de adevar, nu se pomeneste in rapoarte ca element de
comparatie. Daca apare in rezultatul unei interogari pe `ALL_OBJECTS`/`ALL_SOURCE`, se **filtreaza
din start**, nu se comenteaza.
Consecinta practica pentru interogarile de dictionar: se pune **intotdeauna** filtrul de `owner`.
Fara el, randurile mai multor scheme se intercaleaza si output-ul devine ilizibil - capcana deja
platita o data la citirea unui corp de pachet din `ALL_SOURCE`.
```sql
select text from all_source
where owner = 'MARIUSM_AUTO' -- niciodata fara aceasta linie
and name = 'PACK_FACTURARE'
and type = 'PACKAGE BODY'
order by line;
```
## 4. Conectare
```
& 'D:\ROA\instantclient_19_18\sqlplus.exe' -L -S 'MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL' '@fisier.sql'
```
Fisierul `.sql` se scrie in **ASCII**. SQL trimis prin pipe din PowerShell e spart de BOM.
## 5. Ce inseamna "se ruleaza un script de pachet"
Scripturile din `D:\ROA\DATABASE\SCRIPTURI_CLAR\` **nu modifica date** - inlocuiesc **cod stocat in
Oracle** (`CREATE OR REPLACE PACKAGE` + `PACKAGE BODY`). De retinut:
- Efectul e **imediat si pentru toti cei conectati**. Nu seamana cu VFP, unde fiecare statie ruleaza
propriul `.exe`.
- Se recreeaza si **specificatia**, nu doar corpul - asta invalideaza obiectele dependente, care se
recompileaza la prima folosire.
- **Nu exista "undo".** Revenirea inseamna rularea unei versiuni anterioare a pachetului.
- Scripturile sunt scrise **necalificat** (`CREATE OR REPLACE PACKAGE "PACK_FACTURARE"`, fara
`CONNECT`, fara prefix de schema). Deci **se aplica schemei cu care esti conectat**, nu au tinta
proprie. Schema tinta o decide exclusiv sirul de conectare.
- Rularea unui script pe orice schema **se face doar la cererea explicita a lui Marius**. Un agent
scrie scriptul; nu il aplica.
## 6. Stare constatata pe `ROA_CENTRAL`, 20.08.2026
Utila ca reper; de reconfirmat daca a trecut timp.
```
DB_NAME: ROA server: 4e73d257c791 alias TNS: ROA_CENTRAL
```
- `MARIUSM_AUTO` isi detine **propriile tabele** (`VANZARI_DETALII`, `JTVA_COLOANE` sunt TABLE in
schema, nu sinonime catre alta schema). Deci cifrele obtinute conectat ca `MARIUSM_AUTO` sunt ale
datelor din `MARIUSM_AUTO`.
- `CONTAFIN_ORACLE` are 393 de obiecte si **nu** detine `VANZARI_DETALII`/`JTVA_COLOANE` - coerent
cu rolul ei: infrastructura comuna (utilizatori, drepturi, firme, actualizare), nu date de firma.
- `MARIUSM_AUTO.PACK_FACTURARE`: PACKAGE si PACKAGE BODY **VALID**, corpul modificat ultima data
**20.08.2026 08:43** (aplicarea scriptului `ff_2026_08_20_01`, garda `FACT-025`).

View File

@@ -0,0 +1,109 @@
# 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,7 +1,84 @@
# Handoff — #13 formular unificat de facturare + editare prin regenerare
Sesiune: 11.08.2026, **runda 16** (istoricul de mai jos e cumulativ; blocul rundei 14 e pastrat ca
Sesiune: 11.08.2026, **runda 17** (istoricul de mai jos e cumulativ; blocul rundei 14 e pastrat ca
atare). Runda 13 predase dupa cinci proiectari si deciziile 44-50.
> ## RUNDA 17 — „Urmatorul bloc de lucru" S-A GOLIT. Proiectarea lui #13 e INCHEIATA.
>
> Runda 17 a executat **exact cele doua sarcini** care mai ramasesera (punctele 4 si 5 din lista de
> jos) si **nu a deschis niciuna noua**.
>
> > ### DECIZIA 66 (runda 17) RASTOARNA DECIZIA 64 — citeste asta inainte de orice paragraf despre asezare
> > **Motivul discountului sta LANGA DISCOUNT, in banda de totaluri, nu ca a treia sectiune jos.**
> > Formularea lui Marius: *„motiv discount vreau sa fie langa discount, nu a treia coloana"*.
> > **Randul de jos revine la DOUA sectiuni** (incasare, alte date), fiecare pe jumatate de latime —
> > adica exact decizia 57, punctul 2, neatinsa. **Argumentul „~440 px fiecare, strans" cade odata cu
> > decizia 64.** Mai jos, in istoricul cumulativ al rundei 16, decizia 64 apare inca in vigoare in
> > cateva paragrafe: **sunt istorie, nu stare curenta.** Locurile decisive sunt corectate; daca
> > gasesti unul necorectat, **decizia 66 castiga**. Enuntul complet, cu regula de activare adaugata de
> > proiectare (campul e activ **doar cand discountul nu e zero**): **in plan**, la „Decizia 66".
>
> **O singura decizie noua, 66**, deci numerotarea se opreste la **66**.
> ### AUDIT DE INCHIDERE (18.08.2026) — planul e curatat de marcajele „deschis" ramase in urma
> Verificare ceruta de Marius inainte de a inchide sesiunea: **e planul finalizat?** Raspuns: **da ca
> proiectare**, dar avea **noua intrebari fantoma** — blocuri „De decis de Marius" si „ramane deschis"
> care supravietuisera deciziilor care le inchisesera. Un agent proaspat le-ar fi citit ca sarcini si
> ar fi redeschis lucruri transate. **Toate au fost stinse**, fiecare cu decizia care o inchide, in
> **8 linii** din plan (verificat prin diff fata de o copie de siguranta: zero modificari colaterale):
> - trei blocuri **„De decis de Marius"** (S5c, S8b, S11) → inchise de deciziile **51**, **52**, **53**
> si **56**; textul ramane ca **inventar al recomandarilor acceptate**, nu ca intrebari;
> - doua afirmatii „ramane deschisa asezarea campului de motiv" → inchise de **decizia 66**;
> - „din intrebarea 7 ramane deschisa partea de tipuri 48/49" → inchisa de **decizia 60**.
>
> **Ce a ramas deschis, si e corect asa — trei puncte, toate de implementare, niciunul blocant:**
> 1. **Relistarea unei facturi vechi deja trimise** (decizia 62, rest semnalat): dupa decizia 59 ar
> produce N randuri de discount in loc de unul, deci hartie diferita de originalul din eFactura.
> Relistarea nu e modificare, deci `EsteInEFactura` n-o acopera. **Nedecis, semnalat lui Marius.**
> 2. **`PROC_TVAV` ca parametru** (consecinta 3 a deciziei 54): doar daca se cere reproducere exacta
> peste o modificare legala de cota. **De decis separat.**
> 3. **De unde se reconstituie `IN_STOC`** (cerinta rundei 17): preconditie de proiectare a lui S8,
> trei variante scrise, niciuna aleasa.
> **Cod: neatins** — nici un `.vc2` / `.sc2` / `.prg` deschis macar. **Zero Oracle** in aceasta runda,
> nici macar `SELECT`. **Niciun commit dat de aceasta sesiune.**
>
> **Punctul 4 — golul `IN_STOC` din S8 — INCHIS**, prins acolo unde cerea handoff-ul precedent (in nota
> de executie a lui S8, nu la S12). Trei editari in plan, toate verificate pe disc dupa ce sesiunea de
> la #6 a rescris `docs\` in paralel:
> - `plan_13:3520-3546` — caseta „CERINTA DE EXECUTIE ADAUGATA IN RUNDA 17";
> - `plan_13:3567-3569` — criteriul intra in „gata cand" al lui S8;
> - `plan_13` la S10, consecinta 1 — marcata **PRELUAT**, ca sa nu se redescopere.
>
> **Punctul dur, si e mai greu decat suna cerinta:** valoarea istorica a lui `IN_STOC` **nu e stocata
> nicaieri** — nu e coloana pe `VANZARI_DETALII` (verificat pe DB inca de la S10), traieste doar in
> temp. Deci „S8 incarca `IN_STOC` din document" **nu e implementabil ca atare azi**. De unde se
> reconstituie e o **preconditie de proiectare a lui S8** — trei variante scrise in plan (din urma
> lasata in rulaje/gestiune; nomenclatorul curent ca aproximatie **declarata**; coloana noua, deci
> migrare DB inainte de EXE). **Nu s-a ales niciuna** si nu se presupune niciuna.
>
> **Punctul 5 — mockup v9 — LIVRAT.** `docs\mockup_13_formular_unificat.html`, 71 336 octeti (era
> 67 075 la v8). Asezarea e acum **varianta D** cu **randul de jos in doua** si motivul discountului in
> banda de totaluri (deciziile 57 + 66):
> antet strans pe doua randuri fara titluri de grup; panoul „Discount pe document" **disparut**; banda
> de totaluri **in interiorul panoului Articole, dupa `.gridbar`**, deci lipita de grid, cu procentul
> de discount editabil pe loc, **motivul discountului imediat dupa suma pe care o explica** (decizia
> 66) si totalul mare singur la dreapta; sub ea, **doua** sectiuni colapsabile egale
> (incasare · alte date), cu **rezumatul continutului pe randul inchis**.
> Legenda are acum 5 callout-uri (2 si 4 repurpozate, 5 nou). In text au intrat deciziile
> **52, 53, 54, 59, 60, 61, 62, 63+65**. Raport: `docs\cercetare\mockup_v9_modificari.md`.
> **Verificat de sesiunea principala, nu preluat din raport:** 0 taguri nechise, 0 inchideri
> neasteptate, `<style>` unic si inchis.
>
> **Mockup-ul E REPUBLICAT** (Marius a cerut-o in aceeasi runda), **pe URL-ul existent, nu pe unul nou**:
> **https://claude.ai/code/artifact/e9d73e86-7518-4f50-a998-43c49e083142**. Cu asta decizia 33 e
> consumata — URL-ul nu mai e in urma, e la v9. **URL-ul e notat acum si in plan**, la S1; pana in runda
> 17 nu exista nicaieri in `docs\` si a trebuit scos cu `Artifact action: "list"`.
> Inainte de publicare s-a facut **WebFetch pe URL** (obligatoriu, altfel publicarea e refuzata) si s-a
> confirmat ca versiunea online era **v8**, deci v9 e superset si nu s-a suprascris nimic.
>
> **Ce ramane dupa runda 17:** *nimic de proiectat*. Raman **testele (S6, S12)** si **inchiderea
> (S13)**, si amandoua cer cod, care nu poate incepe inainte de #6 (decizia 30). Prima sarcina la
> pornire: **spargerea planului pe stories** + nota de executie a primeia (decizia 55).
Stare: **ETAPA I E PROIECTATA INTEGRAL. ETAPA II E PROIECTATA INTEGRAL — S8 a fost ultima poveste, si
e gata. Raman testele (S6, S12), diff/review (S13), si O SINGURA VERIFICARE PE COD, in curs. Cod:
neatins.** Runda 14 a livrat **doua rapoarte** (S8 in detaliu, garda pe aviz), a luat **deciziile
@@ -134,7 +211,16 @@ antet). Nu mai ramane nicio intrebare deschisa pentru Marius.**
> „Capcane de mediu".
> ### ATENTIE — copia de lucru are modificari necomise care NU sunt ale rundei 14
> `git status` arata doar `docs/` netracked, dar **SVN e sursa de adevar aici** si arata mult mai mult:
> **Actualizat in runda 17, masurat, nu copiat.** Ce e necomis **din partea lui #13**: exact trei
> fisiere, toate in `docs\` — `handoff_13_formular_unificat.md`, `mockup_13_formular_unificat.html`
> (v9) si `cercetare\mockup_v9_modificari.md` (netracked). Nimic altceva. Editarile rundei 17 in
> `plan_13` si `plan_index` **apar deja comise**, prinse in commit-ul de curatenie al sesiunii de la
> #6 — vezi „Capcane de mediu", prima intrare.
>
> Restul de mai jos e al lui **#6** si/sau zgomot de compilare VFP, si e **inca acolo** (verificat cu
> `svn status` in runda 17: `changelog_roafacturare.txt`, `roafacturare.PJX` / `.PJT`, `versiune_db.txt`,
> plus in `COMUN\`: `anaf_efactura`, `comun`, `ofacturare_comun`, `omodificari`, doua ferestre de import
> si `ofacturare_editare.prg`). Lista originala, pastrata ca atare:
> ```
> M changelog_roafacturare.txt · M roafacturare.PJX / .PJT · M versiune_db.txt
> M COMUN\clase\ofacturare_comun.vcx / .vct · M COMUN\clase\omodificari.vcx / .VCT
@@ -409,14 +495,18 @@ sau separat, dupa.
## Ce ramane de proiectat
**Nu mai e „nimic" — runda 16 a adaugat doua povesti noi, ambele cu cercetare in curs, nu inchise:**
1. **Golul `IN_STOC` din S8** — deschis de verificarea S10 (consecinta 1). Nu e o poveste noua, e o
cerinta in plus pentru S8, de prins in nota lui de executie.
**Actualizare runda 17: punctele 1 si 4 de mai jos sunt FACUTE. Lista e goala pe partea de proiectare
— raman doar 3 (testele si inchiderea), care cer cod.**
1. ~~**Golul `IN_STOC` din S8**~~ — **FACUT in runda 17**, in nota de executie a lui S8. A lasat in
urma o **alegere de proiectare** (de unde se reconstituie valoarea istorica), nu o sarcina.
2. **Canalul care scrie `serie_act` / `numar_act` / `data_act` / `data_scad` / `id_ruta` / `tip_saft` /
`efactura`** pe documentul reemis — **nu apar in `scrie_factura2`**. Se inchide in S9, la implementare.
3. `S6` / `S12` (teste pe flux real) si `S13` (diff, review, changelog) raman la final.
4. **Mockup-ul e la v8 si a ramas cu patru runde in urma.** Nu s-a republicat, conform deciziei 33.
Asezarea zonei de jos e insa **decisa** — varianta D, decizia 57 (cu intrebarea de detaliu
redeschisa de decizia 61, vezi mai sus).
4. ~~**Mockup-ul e la v8**~~ — **FACUT in runda 17: e la v9.1**, cu varianta D, randul de jos in doua
si motivul discountului in banda de totaluri (deciziile 57 + 66), plus deciziile
52/53/54/59/60/61/62/63+65 in text. **Republicat** pe URL-ul existent (vezi punctul 5 din
„Urmatorul bloc de lucru") — decizia 33 e consumata, URL-ul e la zi.
5. **Verificarea custodiei (decizia 60, runda 16) — TERMINATA.** Verdict:
`scrie_fact_aviz_custodie` nu are legatura cu 48/49 (serveste `ntip = 4`); emiterea 48/49 nu
atinge stocul (`cursor_articole_k`, `IN_STOC = 0`); `sterge_factura` n-are ce reversa. **Blocantul
@@ -495,10 +585,10 @@ cele patru intrebari ale rundei 14 puse lui Marius direct, **si asezarea zonei d
de creare fara un transport explicit, la fel cum era riscul pentru `ID_FACT` inainte de decizia
care l-a pastrat. Locul de afisare, ramas deschis aici, se inchide la decizia 65. Enuntul
complet: **in plan**, la „Decizia 63" si la S14.
64. **Asezarea campului de motiv: randul se imparte in trei.** Inchide ultimul punct ramas deschis
din decizia 57, reluat de decizia 61: randul de jos are trei sectiuni pe acelasi rand —
incasare, alte date, motivul discountului — ~440 px fiecare la 1366 px, strans; D ramane
intr-un etaj. Enuntul complet: **in plan**, la „Decizia 64".
64. ~~**Asezarea campului de motiv: randul se imparte in trei.**~~ **RASTURNATA DE DECIZIA 66
(runda 17). Nu mai e in vigoare** — se pastreaza doar ca istorie, ca sa se stie ca varianta „a
treia sectiune jos" a fost incercata si respinsa dupa ce Marius a vazut-o in mockup. Ce e in
vigoare: **motivul sta in banda de totaluri, langa discount; randul de jos are doua sectiuni.**
65. **Auditul (decizia 63) se afiseaza in gridul din `frm_facturi`, nu in formularul facturii.**
Confirma continutul de la decizia 63 (trei perechi data + utilizator: adaugat, modificat,
sters). Inchide ambele puncte ramase de la S14: e pe **antet** (gridul listeaza documente) si
@@ -506,6 +596,16 @@ cele patru intrebari ale rundei 14 puse lui Marius direct, **si asezarea zonei d
implementarii S14: inventarul gridului din `frm_facturi`**, posibil sa existe deja coloane de
audit. Enuntul complet: **in plan**, la „Decizia 65" si la S14.
### Decizia 66 (Marius, runda 17) — luata, nu de reluat. RASTOARNA DECIZIA 64.
66. **Motivul discountului sta LANGA DISCOUNT, in banda de totaluri** — *„motiv discount vreau sa fie
langa discount, nu a treia coloana"*. **Rastoarna decizia 64**: randul de jos ramane cu **doua**
sectiuni (incasare, alte date), fiecare pe jumatate de latime, adica exact decizia 57 punctul 2.
Proiectarea a adaugat o regula care nu i-a fost ceruta explicit si care se poate schimba dintr-o
linie: **campul e activ doar cand discountul de document nu e zero**. Consecinta de asezare,
acceptata: banda de totaluri devine plina si la latimi mici se rupe pe doua randuri. Enuntul
complet: **in plan**, la „Decizia 66".
## Interzis
- **Nu se atinge `COMUN\clase\ofacturare_comun.vc2` si nici `COMUN\programe\ofacturare_editare.prg`** —
@@ -521,11 +621,35 @@ cele patru intrebari ale rundei 14 puse lui Marius direct, **si asezarea zonei d
- **Nu se reargumenteaza deciziile 22, 29, 39, 43, 49, 50, 57** — perimetrul lor e inchis.
- **Nu se mai propune dialog modal pentru incasare / alte date** si **nu se readuce bara de comenzi
jos** — respinse de decizia 57. Nici A / B / C nu se mai propun.
- **Nu se mai propune motivul discountului ca sectiune separata jos** — incercat in v9 si **respins de
decizia 66**. Sta in banda de totaluri, langa discount.
- Fara commit din proprie initiativa: diff-ul se livreaza intai ca fisier in `docs\`.
- **Nu se incepe implementarea pe cod** — #13 porneste dupa terminarea lui #6 (decizia 30).
## Capcane de mediu
- **NOU (runda 17), si e cea care conteaza pentru oricine lucreaza acum: SESIUNEA DE LA #6 COMITE IN
ACEEASI COPIE DE LUCRU, IN PARALEL, INCLUSIV IN `docs\`.** In timpul rundei 17 a dat commit-ul
`b5a7108` („cercetarile comune trec in COMUN, reziduul #6 dispare") — o curatenie care a **rescris 39
de fisiere din `docs\`**, a mutat 14 cercetari in `COMUN\docs\cercetare\` si a **sters 20 de rapoarte**.
Doua consecinte de stiut:
1. **A sters si doua fisiere ale lui #13**, incadrate gresit drept reziduu de #6:
`mockup_v7_modificari.md` si `mockup_v8_modificari.md`. **Nu sunt pierdute** —
`git show d9f5ca4:docs/cercetare/mockup_v8_modificari.md` le scoate. Nu s-au restaurat: erau
nereferite si stergerea a fost o curatenie aprobata a altei sesiuni; **daca le vrei inapoi, e o
comanda, dar se cere lui Marius intai**.
2. **Editarile rundei 17 in plan au fost prinse in acel commit**, nu de mine — `git status` arata
`plan_13` si `plan_index` **curate**, desi le-am scris eu. Nu e o dovada ca n-am scris nimic.
**Verifica pe continut (`grep`), nu pe `git status`.** Toate trei editarile au supravietuit,
confirmat.
**Regula practica:** cat timp #6 lucreaza, orice fisier din `docs\` poate fi rescris sau sters sub
tine intre doua apeluri. Editarile se **reverifica pe continut dupa** ce le-ai facut, iar fisierele
proprii nu se presupun stabile.
- **Ruda punctului de mai sus, platita tot in runda 17: un `grep` care nu gaseste nu dovedeste ca
lipseste.** Am „constatat" ca decizia 62 lipseste din mockup si eram gata s-o adaug — era acolo,
rupta pe doua randuri (`decizia\n62`), iar eu cautasem si forma feminina („trimisa"), nu pe cea din
text („trimis"). **Inainte sa declari ceva lipsa dintr-un fisier, cauta termenul cel mai scurt si
neflexionat**, si abia apoi forma completa.
- **NOU (runda 16): `PACK_CONTAFIN` are DOUA copii pe schema** — `owner = ACN` si
`owner = MARIUSM_AUTO`. O interogare pe `all_source` **fara filtru pe `owner`** intoarce cele doua
corpuri intercalate, adica **text corupt** care pare cod real. Filtreaza mereu pe owner, sau
@@ -595,6 +719,11 @@ trecut chiar prin `oscrie_in_fisiere` — daca cineva „simplifica" tripleta la
`sterge_factura`, rulajele raman in picioare si gestiunea se descarca **de doua ori**, fara niciun
mesaj. Testul se face pe stocul agregat inainte / dupa, nu pe inspectia codului.
**Caz adaugat de runda 17, pereche cu cel de mai sus:** un document emis cu un articol caruia i s-a
schimbat intre timp `IN_STOC` in nomenclator, deschis in formular, **poarta valoarea de la emitere**,
nu pe cea de azi; iar reemiterea lui lasa **stocul agregat neschimbat**. Masurat inainte / dupa, nu
prin inspectia codului. E criteriul care demonstreaza ca golul `IN_STOC` din S8 chiar s-a inchis.
**Nu s-a rulat nimic.** Nu exista inca cod de testat (decizia 30). Testele minime raman in
`canal_cont_venit_fara_politica.md` §6, plus cazurile rundei 11 (aviz cu articol fara politica → `SCD`
ramane `461` / `418`; `ntip = 46` → `scrie_nota` nu se cheama), probele de paritate ale rundei 12
@@ -640,11 +769,19 @@ sursa.
> publicat din alta conversatie — asa a fost si in runda 15, si a mers.
> **`docs\mockup_13_formular_unificat.html` (v8) nu e atins de regula asta** — Marius a cerut-o
> numai pentru mockup-ul asezarii.
4. **Golul de la `IN_STOC` in S8** — flag-ul de la S10 nu ajunge daca S8 nu incarca valoarea cu care s-a
scris documentul. De prins **acum**, in nota de executie a lui S8, nu la S12.
5. **Mockup v9** — v8 a ramas cu patru runde in urma. Nici variantele rundei 14, nici **varianta D a
rundei 15 nu sunt integrate in el** — traiesc doar in artifactul de la punctul 3, iar v8 n-a fost
atins. Cand se face v9, D e asezarea de pornit.
4. ~~**Golul de la `IN_STOC` in S8**~~ — **FACUT in runda 17.** E in plan, in nota de executie a lui S8
(caseta „CERINTA DE EXECUTIE ADAUGATA IN RUNDA 17"), cu criteriul de test in „gata cand" si cu
consecinta 1 de la S10 marcata **PRELUAT**. **Nu se reia.** Ce a ramas in urma lui **nu e o sarcina
de executie, e o alegere de facut la proiectarea lui S8**: valoarea istorica a lui `IN_STOC` nu e
stocata nicaieri, deci trebuie ales **de unde se reconstituie** (una din cele trei variante scrise
in plan). Nu se presupune niciuna.
5. ~~**Mockup v9**~~ — **FACUT in runda 17.** `docs\mockup_13_formular_unificat.html` e la **v9.1**, cu
varianta D, randul de jos in **doua** sectiuni si motivul discountului in banda de totaluri
(decizia 66, care rastoarna decizia 64); raport de modificari:
`docs\cercetare\mockup_v9_modificari.md`. **Republicat**, la cererea lui Marius, pe URL-ul existent:
**https://claude.ai/code/artifact/e9d73e86-7518-4f50-a998-43c49e083142** (notat de acum si in plan,
la S1). Ca sa-l modifici: `WebFetch` pe URL intai — fara asta publicarea e refuzata — apoi `Artifact`
cu `url` = link-ul de mai sus.
6. Dupa aceea **nu mai e proiectare de facut**: #13 asteapta terminarea lui #6 (decizia 30). Prima
sarcina la pornirea implementarii e **spargerea planului pe stories** si scrierea notei de executie
pentru prima dintre ele (decizia 55).

View File

@@ -180,6 +180,21 @@ table.doc td.k{white-space:nowrap; font-family:ui-monospace,"Cascadia Mono",Cons
ul.tight, ol.tight{margin:8px 0 0; padding-left:20px}
ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
/* ---------- v9: banda de totaluri + randul de trei sectiuni (varianta D, decizia 57/64) ---------- */
.totband{display:flex; align-items:center; gap:22px; flex-wrap:wrap; padding:8px 12px; border-top:1px solid var(--line); background:var(--card)}
.tb-item{display:flex; align-items:baseline; gap:7px}
.tb-item .tb-lbl{font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); font-weight:600}
.tb-item b{font-variant-numeric:tabular-nums; font-weight:600; font-size:13px}
.tb-disc{gap:9px}
.tb-disc .inp{width:56px}
.tb-grand{margin-left:auto; display:flex; align-items:baseline; gap:10px}
.tb-grand .tb-lbl{font-size:11px}
.tb-grand b{font-size:17px; font-weight:700; color:var(--accent)}
.tb-reason{flex:1 1 200px; min-width:150px}
.tb-reason .inp{flex:1 1 auto; min-width:0; font-style:italic}
.duo{display:flex; gap:12px; align-items:flex-start}
.duo > .panel{flex:1 1 0; min-width:0}
</style>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
@@ -196,7 +211,7 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
<div class="wrap">
<p class="eyebrow">ROAFACTURARE · todos.txt punctul 13 · mockup, versiunea 8 (runda 11)</p>
<p class="eyebrow">ROAFACTURARE · todos.txt punctul 13 · mockup, versiunea 9 (runda 17)</p>
<h1>Un singur formular de facturare</h1>
<p class="lede">
Datele documentului si articolele in aceeasi fereastra, fara incarcarea prealabila a tuturor
@@ -207,13 +222,17 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
Mockup, nu implementare. Campurile, etichetele si coloanele sunt cele reale, verificate pe cod la
09.08.2026 — rapoartele sunt in <code>docs\cercetare\</code>.
Plan: <code>docs\plan_13_unificare_formular_facturare.md</code>
Asezarea e varianta D (decizia 57, runda 15), cu motivul discountului in banda de totaluri
(decizia 66, runda 17).
</p>
<h2><span class="n">1</span>Formularul</h2>
<p class="sub">
Deschis pe o factura in lei, emisa pe baza de comanda. Antetul e blocat — butonul din bara lui
arata creionul. La introducerea unui document nou arata identic, cu antetul deschis si campurile
goale.
arata creionul. Asezarea e varianta D: antetul strans pe doua randuri, fara panou separat de
discount, banda de totaluri lipita de grid pe toata latimea — cu procentul si motivul discountului
chiar in ea — si randul de jos cu doua sectiuni colapsabile, incasare si alte date. La introducerea
unui document nou arata identic, cu antetul deschis si campurile goale.
</p>
<div class="app">
@@ -233,16 +252,14 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
</span>
</div>
<div class="pb">
<div class="grp">
<p class="gl">Documentul</p>
<div class="row">
<div class="f w-md"><label>Tip document</label><div class="inp ro"><span class="combo">FACTURA</span></div></div>
<div class="f w-sm"><label>Serie</label><div class="inp ro">FF</div></div>
<div class="f w-sm"><label>Numar</label><div class="inp num ro">1 244</div></div>
<div class="f w-sm"><label>Data doc.</label><div class="inp ro">07.08.2026</div></div>
<div class="f w-sm"><label>Scadenta</label><div class="inp ro">06.09.2026</div></div>
</div>
<div class="grp">
<p class="gl">Client si sursa</p>
<div class="row">
<div class="f w-fill"><label>Nume client</label><div class="inp ro">SC EXEMPLU DISTRIBUTIE SRL <span class="find">F4</span></div></div>
<div class="f w-md"><label>Cod fiscal</label><div class="inp ro">RO12345678 <span class="find">ANAF</span></div></div>
<div class="f w-md"><label>Sold curent</label><div class="inp num ro">14 820,00</div></div>
@@ -250,9 +267,6 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
<div class="f w-lg"><label>Gestiune sursa</label><div class="inp ro"><span class="combo">DEPOZIT CENTRAL</span></div></div>
</div>
</div>
<div class="disclosure">▸ Incasare <span class="hint">alocare/dezalocare — comutator propriu</span></div>
<div class="disclosure">▸ Alte date — analitice, delegat si transport, adresa de facturare, text aditional
<span class="callout">2</span><span class="hint">nimic completat peste implicit</span></div>
</section>
<section class="panel">
@@ -312,26 +326,40 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
<span class="gb key">Ins — linie noua</span><span class="gb key">Del — sterge</span>
<span class="gb key">F4 — cauta</span><span class="gb key">Ctrl+N / Ctrl+D</span>
</div>
<div class="totband">
<div class="tb-item"><span class="tb-lbl">Baza</span><b>4 696,00</b></div>
<div class="tb-item"><span class="tb-lbl">Discount articole</span><b>84,00</b></div>
<div class="tb-item tb-disc"><span class="tb-lbl">Discount document</span><span class="callout">4</span>
<span class="inp num">0,00 %</span><b>0,00</b>
<span class="check tiny"><span class="box on">✓</span> evidentiat pe factura</span>
</div>
<div class="tb-item tb-reason"><span class="tb-lbl">Motiv</span><span class="callout">5</span>
<span class="inp ro">motivul discountului…</span>
</div>
<div class="tb-item"><span class="tb-lbl">TVA</span><b>986,16</b></div>
<div class="tb-grand"><span class="tb-lbl">Total factura</span><b>5 682,16 lei</b></div>
</div>
</section>
<div class="foot">
<div class="duo">
<section class="panel">
<div class="ph">Discount pe document <span class="callout">4</span></div>
<div class="pb">
<div class="row">
<div class="f w-xs"><label>Procent</label><div class="inp num">0,00</div></div>
<div class="f w-md"><label>Suma (lei)</label><div class="inp num ro">0,00</div></div>
</div>
<div class="row"><span class="check"><span class="box on">✓</span> Se pune in evidenta discount-ul pe articole in notele contabile si pe factura</span></div>
<div class="ph">▸ Incasare
<span class="right"><span class="stlbl">NUMERAR · 5 570,55 lei</span></span>
</div>
</section>
<section class="panel">
<div class="ph">Totaluri</div>
<div class="ph">▾ Alte date <span class="callout">2</span></div>
<div class="pb">
<div class="tot"><span>Total baza</span><b>4 696,00</b></div>
<div class="tot"><span>Discount articole</span><b>84,00</b></div>
<div class="tot"><span>TVA</span><b>986,16</b></div>
<div class="tot grand"><span>Total factura</span><b>5 682,16 lei</b></div>
<div class="row">
<div class="f w-sm"><label>Venit/Chelt.</label><div class="inp ro">701</div></div>
<div class="f w-sm"><label>Sectie</label><div class="inp ro">—</div></div>
<div class="f w-sm"><label>Responsabil</label><div class="inp ro">—</div></div>
</div>
<div class="row">
<div class="f w-sm"><label>Delegat</label><div class="inp ro">IONESCU V.</div></div>
<div class="f w-sm"><label>Masina</label><div class="inp ro">B 123 XYZ</div></div>
<div class="f w-fill"><label>Adresa facturare</label><div class="inp ro">ca a clientului</div></div>
</div>
</div>
</section>
</div>
@@ -346,18 +374,33 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
scadenta. Fara valuta si fara data de curs — factura e in lei si niciun articol nu are pret in
valuta (sectiunea 4). <b>Cat anume deschide</b>, si de ce nu chiar tot in prima etapa:
sectiunea 2.</p></div></div>
<div class="leg"><span class="callout">2</span><div><h4>Restul, pliat — doua comutatoare</h4>
<p>Analiticele, delegatul/transportul, adresa de facturare si textul aditional sub un comutator;
<b>incasarea</b> sub altul, separat (decizia 41) — e singura cu efecte laterale reale
(alocare/dezalocare de numere) si singura blocata pe document deja emis (decizia 25), deci garda
de non-alocare la toggle se scrie si se testeaza intr-un singur loc. Pe un document <b>deja
emis</b>, analiticele se vad dar nu se editeaza de aici — sectiunea 2.</p></div></div>
<div class="leg"><span class="callout">2</span><div><h4>Randul de jos, doua sectiuni</h4>
<p>Incasarea si alte date (analitice, delegat/transport, adresa de facturare, text aditional)
stau colapsate, una langa alta, nu una sub alta (decizia 57). Sunt independente — se poate tine
deschisa doar una — iar randul inchis arata rezumatul continutului, nu doar titlul: cand
incasarea e completata arata suma si modalitatea, cand nu e nimic completat rezumatul spune asta.
Campurile din interior stau pe orizontala, doua randuri fiecare. <b>A treia sectiune, pentru
motivul discountului, a fost incercata si respinsa</b> — motivul sta langa discount, in banda de
totaluri (decizia 66 rastoarna decizia 64).
<b>Incasarea ramane cea deosebita</b> (decizia 41): e singura cu efecte laterale reale —
alocare/dezalocare de numere — deci garda de non-alocare la deschidere se scrie si se testeaza
intr-un singur loc, si e singura blocata pe un document deja emis (decizia 25). Tot pe document
emis, analiticele se vad dar nu se editeaza de aici — sectiunea 2.</p></div></div>
<div class="leg"><span class="callout">3</span><div><h4>Un singur buton de adaugare</h4>
<p><i>Adauga articole</i> deschide un meniu cu optiunile potrivite sursei — sectiunea 3.
Dispare gridul de sus cu toate articolele din toate politicile.</p></div></div>
<div class="leg"><span class="callout">4</span><div><h4>Discount, procent si valoare</h4>
<p>Amandoua exista deja azi, dar intr-un dialog separat pe fiecare articol. Aici sunt doua
coloane in grid; tastezi in oricare, cealalta se calculeaza — sectiunea 5.</p></div></div>
<div class="leg"><span class="callout">4</span><div><h4>Banda de totaluri, lipita de grid</h4>
<p>Baza, discount articole, discount document si TVA, desfacute pe un singur rand, cu totalul
mare singur la dreapta (decizia 57). Discountul de document se editeaza pe loc, procentul langa
suma; bifa „se pune in evidenta…" de pe panoul care disparea s-a mutat aici, discret. Repartizarea
pe cote de TVA e automata si proportionala (decizia 59) — nu se cere nimic utilizatorului.</p></div></div>
<div class="leg"><span class="callout">5</span><div><h4>Motivul discountului, langa discount</h4>
<p>Singura bucata din reparatia discountului de document care cere UI nou (decizia 61): text
optional, ajunge in <code>AllowanceChargeReason</code> din eFactura (<code>ReasonCode</code>
ramane <code>95</code>). Sta <b>in banda de totaluri, imediat dupa suma pe care o explica</b>
(decizia 66) — nu ca sectiune separata jos, cum se stabilise la decizia 64. Ia latimea ramasa
intre discount si TVA; <b>e activ doar cand discountul de document nu e zero</b> — un motiv fara
discount n-are ce explica.</p></div></div>
</div>
<h2><span class="n">2</span>Modificarea antetului, fara sa treci prin articole</h2>
@@ -704,6 +747,13 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
daca e transmis in eFactura si SAF-T. Daca nu apare pe hartie, documentul tiparit nu explica
totalul.
</p>
<p class="meta">
<span class="pill have">decis</span> <b>Discountul de DOCUMENT (nu cel de linie, de mai sus) se
repartizeaza proportional pe cote de TVA, nu pe cota maxima cum face codul de azi (decizia 59).</b>
E automat, nu cere nimic de la operator. In banda de totaluri (sectiunea 1) se vede o singura suma;
pe factura tiparita, cu cote mixte, pot aparea mai multe randuri „Discount X % Factura", cate unul
per cota.
</p>
<h2><span class="n">6</span>De unde se intra in formular</h2>
<p class="sub">
@@ -794,9 +844,27 @@ ul.tight li, ol.tight li{margin:0 0 8px; color:var(--ink-2); font-size:13.5px}
filtreaza <code>STERS = 0</code>, deci trebuie citita inainte de stergere.</li>
<li><span class="pill check">de verificat</span>
<b>Stergerea si reemiterea in aceeasi tranzactie</b>, fara commit intre ele.</li>
<li><span class="pill check">de verificat</span>
<b>Pretul nu se re-deriva</b> la reemitere, pe ramurile unde serverul il recalculeaza din
documentul sursa.</li>
<li><span class="pill have">decis</span>
<b>La reemitere se scriu valorile din formular, nu se reciteste sursa (decizia 54).</b> Intrebarea
nu mai e ce garda punem peste re-derivare — e daca re-derivarea chiar se produce; raspunsul lui
Marius: nu trebuie sa se produca. Fara avertizare si fara confirmare, a fost respinsa explicit.</li>
<li><span class="pill have">decis</span>
<b>Atasamentul PDF al documentului vechi se sterge la reemitere, nu se remigreaza (decizia 53).</b>
Documentul reemis porneste curat; PDF-ul se regenereaza la prima listare.</li>
<li><span class="pill have">decis</span>
<b>Facturile de marfa in custodie (tipurile 48 si 49) sunt editabile prin #13 (decizia 60).</b>
Cu restrictia pastrata: pe ele nu se pot adauga articole gestionabile
(<code>IN_STOC &lt;&gt; 0</code>).</li>
<li><span class="pill have">decis</span>
<b>Singura restrictie de modificare e ca documentul sa nu fi fost trimis in eFactura (decizia
62).</b> Fara nicio conditie de data, fara retroactivitate.</li>
<li><span class="pill have">decis</span>
<b>Auditul (data + utilizator pentru adaugare, modificare si stergere) se afiseaza in gridul din
<code>frm_facturi</code>, nu in formularul unificat (deciziile 63 si 65).</b> Formularul nu capata
niciun control nou din cerinta de audit.</li>
<li><span class="pill have">decis</span>
<b><code>do_modifica</code> de pe <code>frm_facturi</code> ramane activ pentru multi-selectie
(decizia 52).</b> Formularul unificat preia doar cazul cu un singur document.</li>
<li><span class="pill check">de verificat</span>
<b>Descarcarea de gestiune pe proforma</b>, pe partea Oracle.</li>
<li><span class="pill check">de verificat</span>

View File

@@ -1603,7 +1603,7 @@ Factura" in loc de unul, si nota contabila primeste TVA-ul discountului spart pe
**Cele doua puncte ramase s-au inchis si ele, tot in runda 16.** Campul text optional de motiv —
**decizia 61**: da, se adauga (`AllowanceChargeReason`, `ReasonCode` ramane `95`), cu stocare noua
pe `VANZARI` si un control nou in formular; ramane deschisa doar asezarea lui pe rand. Retroactivi-
pe `VANZARI` si un control nou in formular. Asezarea lui s-a inchis in runda 17 — **decizia 66**: sta in banda de totaluri, langa discount. Retroactivi-
tatea la relistare/retrimitere — **decizia 62**: restrictia de modificare e strict `EsteInEFactura`,
fara legatura cu nicio data; ramane insa deschis, separat, cazul relistarii (nu editarii) unei
facturi vechi deja trimise, vezi decizia 62.
@@ -2063,9 +2063,19 @@ deschis. Ce e de retinut aici, pentru executia lui S1:
repartizare proportionala automata pe cote, discountul **NU cere cota de la utilizator**, deci **nu
e nevoie de o a treia sectiune pentru cota**. Ce mai cere UI e **un camp text optional de motiv**
(pentru `AllowanceChargeReason`), mult mai mic decat o sectiune. **Decizia 61 (runda 16) l-a
materializat: Marius vrea campul. Decizia 64 (runda 16) a inchis si asezarea: randul se imparte
in trei** — incasare, alte date si motivul discountului, pe acelasi rand (varianta grea, cota +
explicatie proprii, ramane exclusa, ca mai sus).
materializat: Marius vrea campul. Asezarea lui s-a inchis definitiv abia in runda 17 — decizia 64
(a treia sectiune jos) a fost **rasturnata de decizia 66: motivul sta in banda de totaluri, langa
suma pe care o explica**, iar randul de jos ramane cu **doua** sectiuni, ca la decizia 57. Varianta
grea (cota + explicatie proprii) ramane exclusa, ca mai sus.
**Cele doua pagini online ale lui #13 — nu se confunda:**
- **mockup-ul formularului** (fisier pe disc, `docs\mockup_13_formular_unificat.html`, **v9** din runda
17, cu asezarea D si motivul discountului in banda de totaluri):
**https://claude.ai/code/artifact/e9d73e86-7518-4f50-a998-43c49e083142**.
Are copie pe disc, si ea e sursa de adevar; artifactul se republica din ea (`WebFetch` pe URL intai,
apoi `Artifact` cu `url`). Decizia 33 e consumata — URL-ul e la zi.
- **pagina cu variantele de asezare** (A/B/C/D), **fara copie pe disc**, deci artifactul **e** sursa de
adevar:
Pagina cu variantele: **https://claude.ai/code/artifact/dfc70d38-856f-48fd-b6af-3da78e9da4f7**
— **nu mai are copie pe disc** (cerinta lui Marius, runda 15: „doar online, ca sa nu mai intretii 2
@@ -3008,7 +3018,7 @@ ambele capcane au raspuns cu dovada. Vestea buna: mecanismul de baza chiar exist
din `do_copiaza`, `cursor_retur_document` (`GESTIONABIL = B.IN_STOC`), si `scrie_corespondente_vanzari`
insasi.
**De decis de Marius (cinci puncte, niciunul blocant):**
**INCHISE, nu de reintrebat (marcaj pus in runda 17).** Cele cinci puncte de mai jos au primit raspuns: punctul 1 (`TIP = 4`) prin **decizia 51**, restul in bloc prin **decizia 56** („da la toate"). Lista ramane ca **inventar al recomandarilor acceptate**, ca sa se stie ce s-a decis si de ce — nu ca intrebari:
1. **`TIP = 4`** — liber azi, dar alocarea e **ireversibila in date** odata intrata in productie. De
confirmat explicit, nu tacit.
2. **Apel pe starea de sesiune a pachetului (`clistaid` / `nid_vanzare`) vs. procedura noua cu
@@ -3226,8 +3236,8 @@ cincilea (48) a respins-o motivat.** Fiecare e scrisa si la locul ei, in poveste
**cercetare TERMINATA** (sectiunea K-bis): cota era regula implicita „cota maxima de pe factura",
explicatia e azi o constanta hardcodata (`ReasonCode="95"`, text „Discount"). **Partea de
repartizare s-a decis — decizia 59, runda 16: proportional pe cote.** Ramane deschis doar campul
text optional de motiv.
*Alegerea variantei de asezare, listata aici ca a treia, s-a inchis intre timp — decizia 57.*
text optional de motiv — **inchis si el: decizia 61** (da, se adauga), cu asezarea fixata de
**decizia 66** (in banda de totaluri, langa discount).
55. **Metoda de executie e obligatorie, si e scrisa in plan.** La implementare planul **se sparge pe
stories**, fiecare story fiind o livrare de sine statatoare; **fiecare pas se testeaza**, nu doar
capetele de etapa (S6 / S12); si **fiecare story trece prin code review dupa implementare si dupa
@@ -3272,9 +3282,10 @@ cincilea (48) a respins-o motivat.** Fiecare e scrisa si la locul ei, in poveste
deschis (b) de la decizia 56) **s-a terminat** — vezi sectiunea K-bis — si repartizarea s-a decis
(**decizia 59, runda 16**: proportional pe cote, fara sa se ceara cota de la utilizator), deci a
treia sectiune „grea" (cota + explicatie proprii) nu mai e in discutie. Ramane varianta minimala:
**decizia 61 (runda 16) a materializat-o** — Marius vrea campul text optional de motiv — si
**decizia 64 (runda 16) a inchis si asezarea: randul se imparte in trei**, ~440 px fiecare la
1366 px, strans. D ramane intr-un etaj.
**decizia 61 (runda 16) a materializat-o** — Marius vrea campul text optional de motiv. Asezarea
lui a oscilat o data: decizia 64 il facea a treia sectiune jos, **decizia 66 (runda 17) o
rastoarna si il muta in banda de totaluri, langa discount**. **Punctul 2 de mai sus ramane deci
exact cum a fost aprobat: doua sectiuni jos, fiecare pe jumatate de latime.** D ramane intr-un etaj.
58. **Mockup-ul asezarii ramane doar online.** Formularea lui: *„nu vreau artifactul html, doar online,
ca sa nu mai intretii 2 variante"*. `docs\mockup_13_variante_asezare_jos.html` **a fost scos din
@@ -3380,10 +3391,11 @@ cincilea (48) a respins-o motivat.** Fiecare e scrisa si la locul ei, in poveste
- cere **un control nou in formular** — singura bucata din reparatia discountului (K-bis /
decizia 59) care chiar cere UI nou, restul fiind repartizare automata fara interactiune.
**Redeschisese, in varianta minimala, intrebarea de asezare de la decizia 57 — INCHISA acum prin
decizia 64 (runda 16): randul de jos se imparte in trei** (~440 px fiecare la 1366 px, strans);
varianta D ramane intr-un etaj. Paragrafele din **S1** si din **decizia 57** sunt actualizate in
acest sens.
**Redeschisese, in varianta minimala, intrebarea de asezare de la decizia 57. INCHISA DEFINITIV
PRIN DECIZIA 66 (runda 17): campul sta in banda de totaluri, langa discount.** Decizia 64, care il
facea a treia sectiune jos, **e rasturnata** — randul de jos ramane cu doua sectiuni, ca la decizia
57. Varianta D ramane intr-un etaj. Paragrafele din **S1** si din **decizia 57** sunt actualizate
in acest sens.
62. **Restrictia de modificare e „documentul sa NU fi fost trimis in eFactura" — si de aici
retroactivitatea NU mai e o intrebare.** Formularea lui: *„singura restrictie pentru modificare
@@ -3421,11 +3433,16 @@ cincilea (48) a respins-o motivat.** Fiecare e scrisa si la locul ei, in poveste
**Poveste noua, proiectata: S14** — verdict complet, recomandare si punctele ramase de decis cu
Marius acolo.
64. **Asezarea campului de motiv: RANDUL SE IMPARTE IN TREI.** Formularea lui: *„campul de motiv
imparte randul in 3"*. **Inchide** ultimul punct ramas deschis din decizia 57 (varianta D de
asezare), reluat de decizia 61: randul de jos al formularului unificat are **trei sectiuni pe
acelasi rand** — incasare, alte date si motivul discountului — nu coboara pe rand propriu, nu
devine D cu doua etaje. La 1366 px inseamna ~440 px de sectiune, strans, si asumat ca atare.
64. ~~**Asezarea campului de motiv: RANDUL SE IMPARTE IN TREI.**~~ **RASTURNATA DE DECIZIA 66
(runda 17) — NU MAI E IN VIGOARE.** Formularea de atunci: *„campul de motiv imparte randul in 3"*;
randul de jos ar fi avut trei sectiuni (incasare, alte date, motivul discountului), ~440 px
fiecare la 1366 px.
**Se pastreaza ca istorie, nu ca regula**, si merita pastrata: varianta a fost **construita in
mockup (v9) si respinsa dupa ce Marius a vazut-o** — *„motiv discount vreau sa fie langa discount,
nu a treia coloana"*. E argumentul cel mai bun din tot planul pentru **de ce se face mockup
inainte de cod**: decizia luata pe descriere s-a intors la prima privire pe forma desenata.
Ce e in vigoare: **decizia 66**, mai jos.
65. **Auditul (decizia 63) se afiseaza in gridul din `frm_facturi`, nu in formularul facturii.**
Formularea lui: *„auditul presupun coloanele dataora si utilizator adaugat/modificat/sters, le
@@ -3442,6 +3459,33 @@ cincilea (48) a respins-o motivat.** Fiecare e scrisa si la locul ei, in poveste
**inventarul gridului din `frm_facturi`** — ce coloane de audit sunt deja acolo, ce surse au —
si abia apoi adauga ce lipseste. Nu se presupune nici ca exista, nici ca nu.
### Decizia 66 (Marius, runda 17) — luata, nu de reluat. RASTOARNA DECIZIA 64.
66. **Motivul discountului sta LANGA DISCOUNT, in banda de totaluri — nu ca a treia sectiune jos.**
Formularea lui: *„motiv discount vreau sa fie langa discount, nu a treia coloana"*.
**Anuleaza decizia 64** (randul de jos se imparte in trei). Randul de jos revine la **doua
sectiuni colapsabile** — incasare si alte date — adica exact la ce aprobase decizia 57, punctul 2,
inainte ca decizia 61 sa redeschida discutia. **Decizia 57 ramane intreaga; nu se mai atinge.**
**Ce inseamna concret, pentru S1 si S3:**
- campul de motiv e un **control in banda de totaluri**, imediat dupa suma discountului de
document, inainte de TVA; ia latimea ramasa pana la totalul mare, care sta la dreapta;
- **cele doua sectiuni de jos redevin pe jumatate de latime** (~660 px la 1366 px), nu ~440 —
argumentul „strans, asumat ca atare" din decizia 64 **cade odata cu ea**;
- **regula de activare, adaugata de proiectare, nu ceruta explicit:** campul e activ **numai cand
discountul de document nu e zero**. Un motiv fara discount n-are ce explica, si ar ajunge in
`AllowanceChargeReason` pe un `AllowanceCharge` inexistent. Daca Marius vrea altfel, e o linie
de schimbat.
**Consecinta de asezare, de stiut la implementare:** banda de totaluri devine plina — baza,
discount articole, discount document (procent + suma + bifa „evidentiat"), motiv, TVA, total. La
latimi mici **se rupe pe doua randuri** (`flex-wrap`), si asta e acceptat: alternativa ar fi
scoaterea bifei „evidentiat" din banda, care n-a fost ceruta.
Restul deciziei 61 (stocare noua pe `VANZARI`, `AllowanceChargeReason`, `ReasonCode` ramane `95`)
**e neatins** — se schimba doar locul controlului in formular.
#### S6 — Test pe fluxul real, formularul unificat
Headless (`COMUN\docs\depanare_testare_vfp.md`) + UI (`COMUN\docs\testare-ui-vfp.md`), cate un caz
pe fiecare sursa: lista de preturi, comanda, contract, aviz, retur, transfer, valuta. Comparatie
@@ -3514,8 +3558,8 @@ luna inchisa, referentiate, sau care au urmasi (retur, factura din aviz, factura
> `ofacturare.vc2:15741-19355`). Deci S8 il tinteste pe el, nu pe `frm_facturare_articole`. Faptul ca
> are `do_adauga_tot` / `do_adauga_articol` proprii, cu logica divergenta (fara testul `llGestionabil`,
> `ofacturare.vc2:17476`, `:17124`), **nu e un motiv de excludere, e exact driftul din 2017 pe care S2
> il inchide** — cele doua se unifica, nu se aleg. Din intrebarea 7 ramane deschisa **numai** partea de
> tipuri 48/49.
> il inchide** — cele doua se unifica, nu se aleg. Partea de tipuri 48/49 din intrebarea 7 **s-a
> inchis prin decizia 60**: sunt editabile. Intrebarea 7 e deci inchisa integral.
> **CERINTA DE EXECUTIE ADAUGATA IN RUNDA 17 — `IN_STOC` se incarca din document, nu din nomenclator.**
> Vine din consecinta 1 a deciziei 54 (vezi S10, „Trei consecinte"), si se prinde **aici**, nu la S12.
@@ -3604,7 +3648,7 @@ dar avea un gol nedocumentat, si el schimba forma solutiei.**
- **Comparatia se face pe valori rotunjite la precizia de scriere** (`gnPc` / `gnPPretV` / `gnPCant`),
nu pe valoarea binara — altfel rotunjirea singura produce diferente.
**De decis de Marius:**
**INCHIS, nu de reintrebat (marcaj pus in runda 17).** Punctul de mai jos are raspuns: **decizia 52** — `do_modifica` ramane activ pentru multi-selectie, iar formularul unificat preia doar cazul cu un singur document. Se pastreaza enuntul, nu intrebarea:
1. **Editarea multipla** — `do_modifica` de azi lucreaza pe multi-selectie, capacitate fara echivalent
in formularul unificat. Se accepta pierderea ei la retragere, sau `do_modifica` ramane activ separat
exact pentru cazul asta?
@@ -3976,7 +4020,7 @@ nu-i gasise.**
- **eFactura, `DOCUMENTE`, listarile, `VANZARI_CANTITATI` — (C)**, toate cheiate pe `ID_FACT` sau
rescrise automat.
**De decis de Marius:**
**INCHISE, nu de reintrebat (marcaj pus in runda 17).** Punctul 1 e transat de cercetarea garzilor din runda 14 (garda exista si e corecta; S7 o muta in pre-flight read-only) — ramane **limitare declarata**. Punctul 2 e rasturnat de **decizia 53**: atasamentul vechi **nu se remigreaza, se sterge**, deci premisa „factura editata ajunge cu PDF-ul vechi" nu se mai produce. Punctul 3 e inchis in runda 14, cu cerinta ca **S7 sa refuze explicit** cele doua tipuri. Se pastreaza ca inventar:
1. **Editarea unui document care are deja retur emis pe el e azi BLOCATA, nu doar riscanta** —
`sterge_factura` arunca `ORA-20000`. Garda e corecta (protejeaza lantul), iar **S7 planifica deja
sa semnaleze conditia inainte de intrarea in formular**, nu ca eroare Oracle la final. Ce ramane de

View File

@@ -4,8 +4,76 @@
incheie. Planurile (`plan_0*.md`) spun *ce* e de facut; acesta spune *unde s-a ajuns*. Istoricul
sesiunilor nu se pastreaza aici — doar starea la zi, deciziile, faptele stabilite si datoriile.
Ultima actualizare: **11.08.2026, seara**. **Punctul #6 e practic terminat: S1-S7 si S9 sunt gata si
comise.** Ramane **S8** — si nu ca lipsa de acoperire, ci cu doua esecuri reale.
Ultima actualizare: **19.08.2026**.
> **RUNDA 19.08.2026 — butoanele de linie trec pe butoanele laterale ale grid-ului de articole.**
> Cerere Marius, executata si testata; **fara commit**.
>
> **Eroarea raportata** (`gnButon este redefinit ilegal` la „Adauga articol") avea cauza in doua
> `PUBLIC gnButon` din `omodificari.vc2` (`cmdAdaugaArticol.Click` si
> `AfiseazaDialogSincronizareArticole`): `gnButon` e declarat **PRIVATE** la nivelul aplicatiei
> (`Programe\roafacturare.prg:400`), iar restul codebase-ului doar il atribuie. Ambele declaratii
> au fost sterse.
>
> **Ce s-a schimbat**: butoanele late „Adauga articol" / „Sterge / Restaureaza linie" au disparut de
> pe pagina; comenzile stau acum pe coloana de butoane din dreapta grid-ului — **but_nouR** (nou,
> clasa `but_nou`, `caction = do_adauga_rul`), `but_copiazaR` (duplica linia), `but_modificaR`
> (nomenclatorul coloanei curente: articol, gestiune, valuta, cod TVA) si `but_stergeR` (comuta
> `sters`). Toate dispecerizeaza dupa `pgfArticole.ActivePage = 3`. „Sincronizeaza cu rulaje..." a
> ramas pe pagina, mutat la stanga (`Anchor = 0`) si evidentiat (fundal galben pal, text bold violet).
>
> **Logica noua sta in `.prg`, nu in binar** — clasa `ArticoleNotaEditor` din
> `COMUN\programe\ofacturare_editare.prg` (`AdaugaLinie`, `ComutaSters`, `DuplicaLinie`,
> `ModificaNomenclator`, `AreNomenclator`, `Editabil`); metodele din `.vc2` sunt apeluri de 3-4 linii.
> Preferinta lui Marius, scrisa acum si in `COMUN\docs\reguli_lucru.md`, punctul 3.
>
> **Capcana platita**: `Createobject('X', p).Metoda()` **nu e sintaxa valida in VFP** — da „Syntax
> error" la rulare, prins de `test_efactura_readonly`. Se trece prin variabila.
>
> **Defect latent reparat in trecere**: `AdaugaLinieTvdDinArticol` si `calculeaza_valori_articol` erau
> metode de clasa **fara intrare `*m:`** in `*<DefinedPropArrayMethod>` — mergeau, dar prima salvare
> din IDE le-ar fi aruncat tacit. Intrarile au fost adaugate.
>
> **Teste**: `test_butoane_laterale_articole` (suita noua) 33/0, `test_efactura_readonly` 21/0,
> `test_s4b_dialog` 35/0, `test_s4b_sincronizare` 42/0, `test_adauga_linie_articol` 20/0,
> `test_ui_sterge_linie` 8/0, `test_ui_culoare_contrast` 1/0, `test_ui_efactura_readonly` 13/0,
> `test_page3_articole` 14/2 (**cele 2 = artefactul cunoscut de grid nematerializat headless**, la
> baseline). Cele patru suite care tinteau butoanele sterse au fost mutate pe butoanele laterale.
>
> **Stare**: `omodificari.vc2` are **write-back-ul facut** (fidelity-check trecut de trei ori); cens de
> octeti >0x7F identic cu baseline-ul (2x`aa`, 2x`e3`, 2x`fe`), zero LF izolati. Patch de review:
> `docs\diff_runda_butoane_laterale_articole.patch`. Backup-uri:
> `COMUN\clase\omodificari.pre_runda_butoane.bak.vc2`,
> `COMUN\programe\ofacturare_editare.prg.pre_runda_butoane.bak`. **Niciun commit, in niciun repo.**
> Changelog **neatins deliberat**: 2.11.15 n-a ajuns la utilizatori, iar intrarea existenta descrie
> deja functionalitatea finala.
>
> **Ramas deschis**: `AdaugaLinieTvdDinArticol` si `calculeaza_valori_articol` sunt inca logica in
> binar — de mutat in `ArticoleNotaEditor` la o runda viitoare (rup testele care le apeleaza direct,
> deci nu s-a facut acum). Pe formularul maximizat, bara de totaluri se suprapune peste randurile 3-4
> ale grid-ului de articole (grid ancorat 15, totalurile nu) — **preexistent**, nu s-a atins.
**Punctul #6 e terminat pe partea care se poate face fara Marius:
S1-S9 sunt gata, inclusiv S8.** Ce ramane cere aplicatia pornita, IDE-ul sau o decizie — nu mai e
lucru de delegat unui agent.
> **PREDARE, 13.08.2026.** Sesiunea a inchis trei lucruri si a deschis zero. **S8** — matricea era deja
> rulata, „cele doua esecuri" erau o stare pierduta la o curatenie; aserttia prea larga s-a restrans.
> **Asimetria de garda** — acceptata, fara atingere de cod. **Validarea de cantitate** — negativul
> (retur, storno, discount) nu mai e respins, iar blocul S4b nu mai deschide un modal pe document gol.
> Regresia: **10/10 suite headless la baseline**.
>
> **Nimic nu e intr-o stare periculoasa.** `omodificari.vc2` are write-back-ul facut si **verificat pe
> binar** (nu pe mtime); zero procese `vfp9.exe`; pe Oracle numai `SELECT` sub `READ ONLY` cu
> `ROLLBACK`; **niciun commit** in niciun repo. Documente de test consumate in acest bloc: **niciunul**
> — matricea S8 nu a fost rerulata, deliberat.
>
> **Prima sarcina a sesiunii urmatoare**: nu incepe cod nou. Citeste punctele 1-6 din lista de mai jos —
> toate cer aplicatia, IDE-ul sau o decizie a lui Marius. **Nu redeschide** asimetria de garda si nu
> reinvestiga S8: ambele sunt inchise mai jos, cu motivele scrise.
>
> **Fire in paralel**: alta sesiune lucreaza pe **#13** (`docs\handoff_13_formular_unificat.md`,
> `mockup_13_formular_unificat.html`, `plan_13_*.md` — apar modificate in `git status`). **Nu le atinge.**
**PREDAREA CURENTA e chiar acest fisier.** Handoff-urile intermediare intre sesiuni au fost
desfiintate (11.08.2026, decizia lui Marius) impreuna cu diff-urile deja aplicate; ce era durabil in
@@ -15,18 +83,24 @@ ele a intrat in antetele fisierelor de test sau in mesajele de commit. Nu mai ca
1. **Stergerea celor sase documente parazite** (`1051`, `1056`, `1057`, `1058`, `1059`, `1060`) prin
aplicatie — ramasa din blocul de creare documente pentru S8.
2. **S8 — doua esecuri reale.** Pe „factura din aviz" (tip 4), din **ambele** puncte de intrare,
rulajele nu se refac pe nota noua (`Reccount(trul)=0`): `test_s8_matrice_surse.prg` da
**42 PASS / 2 FAIL**. Tot acolo, trei tipuri de sursa au fost sarite la ultima rulare (lista de
preturi `1048`, aviz `1052`, factura din contract `1055`), iar harnessul
`COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg` **nu a scris inca niciun document** —
capcanele de mediu (plaja de serii care chiar prinde, ordinea lui `mock_amessagebox`) sunt scrise
in antetul lui.
2. ~~S8 — doua esecuri reale.~~ **INFIRMAT pe 12.08.2026. S8 e gata, n-a fost niciodata un esec** —
vezi sectiunea „S8 INCHIS" de mai jos. Nu relua investigatia.
3. **Rebuild `roafacturare.exe`** din IDE si verificarea pe ecran, pe `1140895` / `1140921` — ambele
contin `IV93900901` (`id_articol = 3598545102`), adica exact cazul corectat pe 11.08.
4. **Punctele A si B** din `docs\rec_s4b_etapa2.md` — **deja implementate**, asteapta doar
confirmarea lui Marius. Nu sunt lucru ramas.
4. **Punctul B** din `docs\rec_s4b_etapa2.md` — **CONFIRMAT 12.08.2026**, ramane cum e.
**Punctul A s-a transformat** intr-o intrebare mai mare, despre validarea de cantitate — vezi
sectiunea de mai jos. **Deschis.**
5. **`git push` nefacut** in ambele repo-uri (remote `romfast`) — decizia lui Marius.
6. **Necomis din 12-13.08.2026, asteapta review.** In `COMUN`: `clase\omodificari.vc2` — validarea de
cantitate (`:14386`) si garda pe blocul S4b (`:14438`), **write-back facut si verificat pe binar**,
diff `docs\diff_cantitate_negativa_garda_s4b.patch`; plus aserttia relaxata din
`utile\Teste\editare_factura\test_s8_matrice_surse.prg`, diff `docs\diff_s8_aserttie_rulaje.patch`.
In `ROAFACTURARE`: `docs\cercetare\rec_s8_matrice.md` (**recuperat** din `fc9c378`, fusese sters din
greseala), `rec_s8_rulaje_tip4.md`, `rec_cantitate_negativa_date.md`, `rec_regresie_cantitate.md`.
**Backup-uri de revenire**, de sters la curatenie: `omodificari.pre_cantitate.bak.vc2` si
`omodificari.pre_garda.bak.vc2`.
**Changelog: nefacut** — de decis daca schimbarea de cantitate intra in `2.11.15` (nu e inca in
productie, deci `:modificare:`, nu `:eroare:`).
**Comis pe ramura `punct6-s4-runda3`, 11.08.2026**: `COMUN` `40a112a` (S4b etapa 2 — butonul,
dialogul `frm_sincronizare_articole`, `id_articol` de la `I` la `N(20)` in toate cele 6 locuri, si
@@ -37,7 +111,8 @@ doua `This.` -> `Thisform.` care faceau butonul „Adauga articol" sa dea eroare
**Cifre de test, citite din log, nu din rapoarte**: `test_s4b_sincronizare` **42/0** (include cazul de
regresie `id_articol = 3598545102`, dovedit cu control negativ — cu campul `I` iese perechea
`Adaugare`+`Semnalare` in loc de `Modificare`), `test_s4b_dialog` **35/0**, `test_s7_rotunjire` **6/0**,
`test_s8_matrice_surse` **42 PASS / 2 FAIL**.
`test_s8_matrice_surse` pe cele patru felii **44/0, 44/0, 26/1, 42/2** (vezi „S8 INCHIS" — cele 3 FAIL
sunt asteptate si explicate, nu defecte).
**Doua capcane de mediu platite pe 11.08, valabile pentru orice sesiune viitoare:**
@@ -58,9 +133,182 @@ trans-proiect au trecut in `COMUN\docs\cercetare\` (valuta si curs, TVA/VANZARI,
`VANZARI` in suita, integrarile #10/#11/#12, watchdog VFP, proiectarea Oracle a lui S5, view-ul
`VVANZARI_ARTICOLE`), iar 20 de rapoarte de executie ale lui #6 au fost sterse.
**Starea la incheierea sesiunii — verificat, nu presupus**: zero procese `vfp9.exe`, zero tranzactii
Oracle deschise (in acest bloc nu s-a atins Oracle), niciun fisier binar editat fara write-back,
ambii arbori git curati fata de commituri.
**Starea la incheierea sesiunii din 13.08.2026 — verificat, nu presupus**: zero procese `vfp9.exe`;
pe Oracle numai `SELECT` sub `SET TRANSACTION READ ONLY` incheiat cu `ROLLBACK`, zero tranzactii
deschise; **`omodificari.vc2` e singurul fisier de cod de productie atins, si are write-back-ul facut
si dovedit pe binar** (condiitile noi apar in `omodificari.VCT`, cele vechi zero) — deci niciun binar
in urma textului; **niciun commit** in niciunul din cele doua repo-uri.
**De verificat pe ecran de Marius** (netestabil headless): mesajul „are cantitatea 0" pe o linie cu
cantitate 0, si salvarea reusita a unei facturi reale cu linie de retur (cantitate negativa).
## DESCHIS — validarea de cantitate din `inainte_de_do_termin` respinge returul si storno-ul?
**Ridicata de Marius, 12.08.2026**, ca observatie la punctul A din `rec_s4b_etapa2.md`: *„pot exista
articole cu cantitate negativa (retur, storno) sau cu pret 0"*.
Ce spune codul, verificat la sursa (`frm_modific2024.inainte_de_do_termin`,
`COMUN\clase\omodificari.vc2:14331-14599`, blocul de validari `:14378-14430`):
| Ancora | Verifica | Efect |
|---|---|---|
| `:14386` | `Nvl(cantitate,0) <= 0` | **BLOCHEAZA** — „are cantitatea 0 sau negativa" |
| `:14394` | `Isnull(pret)` | blocheaza doar `NULL`, **nu** si `0` |
| `:14402` | `Nvl(id_articol,0) = 0` | blocheaza |
| `:14410` | linie noua cu `pret_achizitie = 0` | doar confirmare |
**Pretul 0 nu e o problema** — `:14394` testeaza `Isnull`, deci `0` trece. (E si ramura moarta
cunoscuta: `tvd.pret` vine `NOT NULL` din view.) **Cantitatea negativa insa cade pe `:14386`**, si nu
doar dupa „Aplica" din dialogul S4b — pe **calea normala de salvare**, la orice editare a unui
document cu articole de vanzari.
**Consecinta asupra punctului A**: plasa de siguranta propusa initial (reluarea validarilor blocante
dupa „Aplica") **nu se mai face**. 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 — a carei
premisa e sub semnul intrebarii. O plasa din doua verificari care nu pot pica ar fi cod fara efect.
**REZOLVAT — decizia lui Marius, 12.08.2026: se permite negativul, 0 ramane blocat.** Aplicat pe
`omodificari.vc2:14386-14387`: conditia `Nvl(cantitate,0) <= 0` a devenit `= 0`, mesajul „are
cantitatea 0 sau negativa" a devenit „are cantitatea 0". **O singura linie de logica**, plus mesajul.
**Datele care au sustinut decizia** (`docs\cercetare\rec_cantitate_negativa_date.md`, Oracle
read-only): **65 de linii active cu `cantitate < 0` pe 52 de documente** — grosul pe tipurile `-6`
(25 linii / 24 doc) si `41` (4/4), adica **retur-transfer**, dar si pe `tip 3` (comanda) 10 linii pe 4
documente, care sunt linii **`DISCOUNT`** cu cantitate negativa, cea mai recenta din 26.03.2026. Deci
negativul e si mecanismul de discount pe linie, nu doar retur/storno. `pret = 0`: 19 linii pe 17
documente, toate cu articol — **trec deja**, `:14394` testeaza `Isnull(pret)`, nu `= 0`; iar
`pret IS NULL` da **0 linii**, ceea ce reconfirma ramura moarta.
**Write-back FACUT si verificat pe binar**, nu pe mtime: `txt2vcx.ps1` a dat `P1,E0,S1,X0` cu fidelity
check `OK`; in `omodificari.VCT` conditia noua si mesajul nou apar o data, iar cele vechi **zero**.
Editarea s-a facut **pe octeti** (`perl`, `binmode`), nu cu tool-ul Edit — cens `>0x7F` identic
inainte si dupa (`2 aa / 2 e3 / 2 fe`), zero `EF BF BD`, diff exact 2 linii. Backup:
`COMUN\clase\omodificari.pre_cantitate.bak.vc2`.
> ### DESCOPERIT LA REGRESIE — S4B FACE `test_s5_validari_articole` SA ATARNE HEADLESS
>
> **Nu e cauzat de schimbarea de mai sus** — dovedit din cod, nu presupus. Suita se opreste la cazul
> **A5** (`test_s5_validari_articole.prg:186-194`), care face `REPLACE ALL sters WITH 1 IN tvd`: cu
> toate liniile sterse, `SCAN FOR Nvl(sters,0) <> 1` are **zero iteratii**, deci verificarea de
> cantitate nici nu e atinsa. Lantul real: `lnLiniiActive = 0` -> confirmarea „toate liniile au fost
> sterse" -> mock-ul raspunde Da -> `llRet` ramane `.T.` -> intra in blocul S4b (`omodificari.vc2:14438`)
> -> cu toate liniile sterse fiecare articol-sursa iese `Adaugare`, deci `lnDivergente > 0` ->
> `AfiseazaDialogSincronizareArticole()`, **formular modal** -> headless, atarna. Cazurile A2/A3/A4 trec
> pentru ca dau `RETURN .F.` inainte de `:14438`; cazul de control A1 ajunge acolo dar n-are divergente.
>
> **De ce abia acum**: suita a rulat ultima oara **10.08 22:13**, iar S4b etapa 2 a intrat **11.08
> 09:34**, si `rec_s4b_etapa2.md` declara explicit „Netestat: nimic nu a fost rulat".
>
> **Cifre partiale ale rularii intrerupte** (12.08 21:36): 8 aserttii, **toate PASS**, inclusiv
> `cantitate<=0 -> blocheaza` — testul foloseste `REPLACE cantitate WITH 0` (`:130`), deci **schimbarea
> de azi ii pastreaza comportamentul**. Plus linia documentara „FAIL asteptat" despre `Isnull(pret)`,
> care nu e o aserttie picata. Procesul a fost oprit de garda de timp; **zero procese `vfp9.exe`** dupa.
>
> **REZOLVAT — decizia lui Marius, 12.08.2026: varianta (a), garda in cod.** Conditia de la
> `omodificari.vc2:14438` a primit `AND Used('tvd') AND m.lnLiniiActive > 0`, plus o linie de comentariu
> care explica ramura. `Used('tvd')` sta **inaintea** lui `lnLiniiActive` intentionat: VFP
> scurtcircuiteaza `AND`, deci variabila (declarata in blocul `IF ... Used('tvd')` de la `:14378`) nu se
> evalueaza cand blocul acela n-a rulat.
>
> **Write-back facut si verificat pe binar**: `P1,E0,S1,X0`, fidelity `OK`; garda apare o data in
> `omodificari.VCT`, conditia veche fara garda **zero**. Editare pe octeti, cens `>0x7F` neschimbat
> (`2 aa / 2 e3 / 2 fe`), zero `EF BF BD`. Backup: `omodificari.pre_garda.bak.vc2`.
>
> **Efectul, masurat**: `test_s5_validari_articole` trece iar integral — **35 PASS / 0 FAIL, exit 0**
> (log proaspat, 13.08 02:43). Inainte de garda atarna la cazul A5 dupa 8 aserttii.
> ### DOUA CAPCANE DE MEDIU NOI, platite pe 12-13.08 — de stiut inainte de orice rulare de regresie
>
> **1. Lansarile consecutive de `vfp9.exe` atarna.** Doua suite rulate una dupa alta in aceeasi bucla
> `for` / aceeasi comanda: a doua **atarna la pornire, inainte sa scrie orice in log**. Aceleasi suite,
> rulate cate una per comanda, merg. Se ruleaza **o suita per apel**, cu omorarea proceselor ramase
> intre ele.
>
> **2. Logul stat da cifre plauzibile si false.** Fiecare suita isi **rescrie** logul la pornire (ex.
> `test_page3_articole.prg:24`, inaintea conectarii Oracle de la `:30`). Daca rularea atarna inainte,
> logul ramane cel vechi. Combinat cu capcana 1, doua suite au raportat `20 PASS / 0 FAIL` si
> `16 PASS / 0 FAIL` **din 10.08**, la trei zile distanta, cu `exit=124`. **Verifica mereu mtime-ul
> logului fata de ceasul curent inainte sa citesti o cifra.**
>
> **3. Numararea** (reconfirmare a capcanei vechi): `grep -c "^PASS"` **subnumara** — unele linii au
> `PASS` la mijloc. Pe `test_page3_articole` a dat `10/0` in loc de `14/2`. Se numara cu
> `grep -o "PASS" <log> | wc -l`, iar unde exista linia `REZULTAT` aia e autoritara.
**REGRESIA E LA BASELINE PE TOATE CELE 10 SUITE HEADLESS**, 13.08.2026 02:43-03:07. Cifrele au fost
**recitite de orchestrator direct din loguri**, dupa ce a verificat mtime-ul fiecaruia fata de ceasul
curent — nu preluate din raportul agentului:
| suita | obtinut | baseline |
|---|---|---|
| `test_s5_validari_articole` | 35 / 0 | 35 / 0 |
| `test_page3_articole` | 14 / 2 | 14 / 2 |
| `test_adauga_linie_articol` | 20 / 0 | 20 / 0 |
| `test_adauga_linie_valuta` | 16 / 0 | 16 / 0 |
| `test_verdict_act_rul` | 26 / 0 | 26 / 0 |
| `test_incarca_vanzare_din_nota` | 5 / 0 | 5 / 0 |
| `test_efactura_readonly` | 23 / 0 | 23 / 0 |
| `test_s4b_sincronizare` | 42 / 0 | 42 / 0 |
| `test_s4b_dialog` | 35 / 0 | 35 / 0 |
| `test_s7_rotunjire` | 6 / 0 | 6 / 0 |
Singurul log cu linie `EROARE` e `test_page3_articole`: `EROARE 1925 [VERIFICA_EDITARE_GRID:366]
Unknown member COLUMN5` — **artefactul headless cunoscut** (sub `-A -T` coloanele de grid nu se
materializeaza), care produce chiar cele 2 FAIL din baseline. Nu e regresie.
**Nerulate deliberat**: suitele `test_ui_*` (cer formular vizibil pe un ecran partajat, iar schimbarile
nu ating ce verifica ele) si `test_s8_matrice_surse` (ar consuma documente de test ireversibil).
**Diff consolidat pentru review**: `docs\diff_cantitate_negativa_garda_s4b.patch` — 4 randuri schimbate
in `omodificari.vc2` (2 de logica, 2 de comentariu).
## S8 INCHIS — 12.08.2026. Matricea era rulata integral; „cele doua esecuri" erau o stare pierduta
**Matricea S8 a fost rulata pe toate cele patru documente, fiecare de doua ori, din ambele puncte de
intrare, inca din 10.08.2026.** Cifrele, din log, felie cu felie: `1048` (lista de preturi) **44/0**,
`1055` (factura din contract) **44/0**, `1052` (aviz) **26/1**, `1054` (factura din aviz) **42/2**.
Cele 8 verificari cerute de `plan_06_editare_factura.md:286-288` au verdict explicit in raport.
**Cele 3 FAIL sunt asteptate, niciunul nu e defect:**
- `1052` — garda `ReferinteDocumenteNota` **a blocat corect** intrarea ROAFACTURARE: avizul are deja o
factura emisa din el (`ACT.id_factc = 8009677` pe nota lui `1054`). Testul presupusese, mostenit din
modelul S5, ca orice document e editabil.
- `1054`, de doua ori — `Reccount(trul)=0`. Documentul **n-avea rulaje nici inainte** de editare, deci
aserttia cerea ca editarea sa *creeze* rulaje care n-au existat.
**De ce `RUL=0` e corect pe tip 4 — stabilit din cod, nu din tipar de date** (`rec_s8_rulaje_tip4.md`):
`RUL` retine exclusiv miscari pe cont de stoc. Avizul (tip 22) scoate marfa din gestiune si scrie
rulajele — pe `1052`, 4 randuri, toate pe contul `371`. Factura emisa din aviz nu mai atinge `371`:
transforma doar creanta provizorie in creanta ferma si TVA neexigibila in TVA colectata (`ACT` pe
`1054`: `4111/418` si `4428/4428`, niciun `371`). N-are, structural, ce rand de stoc sa scrie.
Editarea doar **conserva** ce exista — `trul` se incarca din `vrul_tot` (`ofacturare_editare.prg:76`)
si se scrie inapoi neconditionat (`ofacturare_comun.vc2:3821`, `OSCRIE_IN_FISIERE(0,.T.,.T.)`).
Verdictul ACT/RUL are trei stari, **toate informative** (`omodificari.vc2:13129-13139`); „nu se aplica"
e rezervat lui `tip=51`, deci tip 4 cade legitim in „divergent (informativ, nu e eroare)".
Cele trei ancore sunt **verificate la sursa de sesiunea principala**, nu preluate din raport.
**Aserttia a fost restransa** la ce voia de fapt sa apere — ca editarea nu pierde rulaje:
`test_s8_matrice_surse.prg:313-314`, `Reccount(trul)` pe nota noua `>=` cat era inainte, cu linia de
baza dusa prin `tnNrRulBaseline` de la ambele puncte de intrare. Rulare de compilare cu lista de cazuri
goala: **0 PASS / 0 FAIL, exit 0, fara `EROARE`**, zero documente consumate.
**Rularea completa pe date NU se face — decizia lui Marius, 12.08.2026.** Ar consuma inca 8 generatii
de `cod` pe cele patru documente, pentru un rezultat previzibil: relaxarea e stricta (`>= linia de
baza` in loc de `> 0`), deci muta `1054` de la 42/2 la 44/0 si lasa `1048`/`1052`/`1055` neschimbate.
**Deci logul de pe disc ramane cel al rularii de compilare, nu al matricei** — cifrele matricei se
citesc din `rec_s8_matrice.md`, iar felia `1054` asa cum a rulat inainte de relaxare e pastrata in
`COMUN\utile\Teste\editare_factura\test_s8_matrice_surse_log.1054.txt`.
> **Cum s-a pierdut starea, ca sa nu se repete.** Commit-ul `b5a7108` (curatenia din 11.08) a **sters
> `docs\cercetare\rec_s8_matrice.md`** — incadrat drept „raport de executie al lui #6, nereferit de
> nimic viu", desi e referit din `test_s8_matrice_surse.prg` — **si in acelasi commit** a scris in
> `progres.md` starea dedusa din `test_s8_matrice_surse_log.txt`. Dar logul **se rescrie la fiecare
> rulare**, deci pastra doar ultima felie (`1054`): de acolo au aparut „cele doua esecuri reale" si
> „trei tipuri de sursa sarite". Raportul e **recuperat pe disc** din `fc9c378`. Aceeasi curatenie a
> sters si `mockup_v7/v8_modificari.md`, care erau ale lui **#13**, nu ale lui #6.
>
> **Regula**: un log care se rescrie nu e sursa de stare. Starea sta in raport; daca raportul dispare,
> starea dispare cu el. Inainte de a sterge un raport, cauta-i numele in `COMUN\utile\Teste\` si in
> `docs\`, nu doar in `plan_*.md`.
## S8 — VERIFICAT PE VENDING PRODUCTIE, 10.08.2026: nu deblocheaza avizul
@@ -165,9 +413,12 @@ de `TRY/CATCH` imediat dupa ce driverul apeleaza `.do_termin()` pe `frm_alte_dat
Harness-ul si investigatia completa de cod (cu `fisier:linie`) raman pe disc in
`docs\cercetare\rec_s8_creare_variante.md` — utile daca se reia candva, dar **scopul e atins altfel**.
**Ce ramane din S8**: matricea propriu-zisa — fiecare din cele patru documente editat **de doua ori**,
o data din ROAFACTURARE si o data din registrul jurnal ROACONT, cu verificarile din
`plan_06_editare_factura.md:282-291`.
**Matricea propriu-zisa e RULATA** — fiecare din cele patru documente editat de doua ori, o data din
ROAFACTURARE si o data din registrul jurnal ROACONT, cu verificarile din
`plan_06_editare_factura.md:282-291`. Cifre si verdict: sectiunea „S8 INCHIS" de mai sus; detaliile pe
document, in `docs\cercetare\rec_s8_matrice.md`. **Documentele sunt consumate** (coduri realocate,
note vechi sterse ireversibil): `1048` -> `1140918`, `1052` -> `1140921`, `1054` -> `1140923`,
`1055` -> `1140920`.
## Firele rundei (10.08.2026, dupa-amiaza)
- `s8-creare-exec` — **executia** planului: creeaza avizul, factura din aviz si factura din contract in
@@ -297,11 +548,26 @@ Registrul Jurnal, `afisjurcom.do_modifica` (`comun.vc2:2222-2572`), **nu are nic
restrictia de luna curenta, preexistenta pentru orice nota. Deci o factura deja trimisa la ANAF se
poate edita din ROACONT, dar nu din ROAFACTURARE. Verificat de orchestrator cu `vfp_symbols.ps1`, nu
din raport: hitul `ReferinteDocumenteNota` de la `comun.vc2:2620` e in `afisjurcom.do_sterge`
(2574-2733), **alta metoda** — deci nu infirma constatarea. Variante: (1) se lasa si se documenteaza
(deja documentat); (2) garda eFactura se muta in `PregatesteArticoleFacturaEditare`, deci se aplica
doar cand documentul chiar e factura de vanzare, fara sa atinga notele obisnuite; (3) se dubleaza
explicit in `do_modifica`. **Nefacut** — `comun.vc2` e livrare inchisa, iar calea din jurnal serveste
orice tip de nota. Atinge si S8, care cere fiecare caz rulat din ambele puncte de intrare.
(2574-2733), **alta metoda** — deci nu infirma constatarea. Reconfirmat pe 12.08.2026 cu
`vfp_symbols.ps1 -Where`: in tot `comun.vc2` functia apare **o singura data**, la `:2620`, deci
`do_modifica` (2222-2572) n-o are deloc.
**INCHISA — decizia lui Marius, 12.08.2026: asimetria se accepta, nu se repara.** Nu se atinge nici
`comun.vc2`, nici `omodificari.vc2`. Motivele care sustin decizia:
- **jumatatea eFactura nu se putea dubla oricum** — decizia 42 spune explicit ca notele si rulajul se
modifica *doar* din ROACONT, deci o garda eFactura pe calea din jurnal ar fi anulat chiar decizia 42;
- **absenta lui `ReferinteDocumenteNota` din `do_modifica` e voita**, nu o scapare: garda protejeaza
incasarile legate de `id_fact` la **stergerea** notei, unde legatura chiar se rupe; la modificare,
legatura structurala trece prin `id_fact`/`id_vanzare`, niciodata prin `cod` (S6, inchis), deci
realocarea `cod`-ului nu o strica;
- **`comun.vc2` serveste orice tip de nota** din toate cele trei aplicatii — o garda acolo ar fi blocat
editarea oricarei note cu referinte de incasari, mult peste cazul semnalat.
**Ce ramane adevarat si de stiut**: pe calea din registrul jurnal nimic nu opreste editarea unui
document-sursa care are deja o factura emisa din el (dovedit pe `1052` in S8, unde intrarea
ROAFACTURARE a blocat si cea din ROACONT a scris pana la `COMMIT`). Pe reeditare fara modificari de
continut e inofensiv; o modificare reala de continut pe un asemenea document nu e oprita de nimic.
**Acceptat ca atare.**
## #6 / S7 — rotunjirea la reeditare, GATA 10.08.2026: NU E DEFECT
@@ -1986,3 +2252,62 @@ pareau sa infirme o corectie s-au dovedit, la verificare, altceva decat pareau.
textul regenerat din `<staging>\verify\*.vc2`**, nu incerca sa ghicesti ordinea.
- **`InputMask` cu literal se ghilimeleaza** (`"9.99"`, nu `9.99`) — altfel VFP il ia numeric.
Fidelity-check-ul **nu** semnaleaza asta separat.
## Runda 6 - 20.08.2026: grid Articole (nomenclatoare, zecimale, aspect), meniu, punctul 6
Livrat si aplicat, cu write-back facut si verificat prin reconversie independenta (0 linii
diferenta pe ambele fisiere):
- **`COMUN\clase\omodificari.vc2`** — cinci metode `DblClick` noi pe coloanele cu nomenclator
(`:16706`, `:16729`, `:16747`, `:16829`, `:16846`); `cPretArt` (`:12391`) si `cPretCuTvaArt`
(`:12408`) trec pe `get_mask(12,gnPPretV)`, `cPretAchizitieArt` (`:12400`) ramane pe `gnPPRET`;
cele cinci coloane cu nomenclator trec pe verde `160,255,205` (`Text1.ReadOnly` ramane `.T.`);
`cSerieArt`/`cLotArt`/`cExplicatieArt` trec pe alb `255,255,255` + `Text1.ReadOnly = .F.`
- **`COMUN\clase\ofacturare_comun.vc2:4930`** — optiunea din `xmenu` devine
`Editare \<factura (note, rulaje, articole)`
Ce s-a stabilit, ca sa nu se rediscute:
- `cExplicatieTvaArt` are `ControlSource` **expresie** (`omodificari.vc2:12467`), nu camp. Intr-un
grid VFP tastarea intr-o celula read-only declanseaza cautarea incrementala pe campul legat;
fara camp nu are pe ce face seek, deci `InteractiveChange` nu porneste niciodata acolo. `DblClick`
se sprijina pe `Thisform.pccontrol` (pus explicit in `GotFocus`, `:16722-16726`) si de aceea
acopera uniform toate cele cinci coloane.
- `gnPPret` = precizie pret **achizitie**, `gnPPretV` = precizie pret **vanzare**
(`oinit_optiuni.prg:515`). Model: `ofacturare.vc2:3490` vs `:3497`. Scrierea in Oracle a lui
`VANZARI_DETALII.PRET` foloseste `gnPPretV` (`ofacturare_stoc.prg:367`).
- Conventia de culoare (de pe pagina Rulaje a aceluiasi formular): alb = se tasteaza direct,
verde `160,255,205` = se editeaza prin dialog, gri `225,225,225` = nu se editeaza.
- Inainte de runda 6, serie/lot/explicatie **nu erau editabile pe nicio cale**: garda `.When` fusese
relaxata in runda 5, dar `Text1.ReadOnly` ramasese `.T.`, iar `but_modificaR` se activeaza doar pe
coloanele cu `GotFocus` (`:16678-16682`).
- **NU** schimba `Text1.ReadOnly` pe cele cinci coloane cu nomenclator: calea de tastare merge
tocmai fiindca sunt `.T.`.
### Punctul 6 - explicatie TVA in butonul de modificare din `frm_facturi`
Decizia finala a lui Marius: **fara recalcul**, lista **filtrata pe cota salvata a liniei**, iar
`taxcode` se sincronizeaza dupa explicatia aleasa. Varianta cu recalcul a fost propusa, aleasa
initial, apoi **respinsa** — nu se reintroduce.
- Script scris, **NERULAT** pe schema `MARIUSM_AUTO` (`ROA_CENTRAL`):
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_20_02_COMUN_PACK_FACTURARE.sql`
- Proiectarea completa: `docs\propunere_runda6_punct6_plsql.md`
- `UPDATE`-ul scrie exact trei coloane: `EXPLICATIE`, `TAXCODE`, `ID_JTVA_COLOANA`. `PROC_TVAV` nu
se atinge, totalurile nu se recalculeaza.
- Garda de neutralitate e in PL/SQL: `FACT-029` daca cota explicatiei difera de a liniei, plus
`FACT-026` (linie inexistenta), `FACT-027` (explicatie inexistenta/fara cota), `FACT-028`
(cota liniei NULL, verificata **separat**, inainte de comparatie).
- Semantica verificata pe date: `PROC_TVAV` e **multiplicator** (1.19, 1.09...), `COTA_TVA` e
**procent** (19, 9...), deci comparatia e `(COTA_TVA+100)/100`. Cele 8 cote folosite azi pe linii
active au toate explicatie corespunzatoare — cazul "lista goala" e teoretic.
- **Partea VFP nu a pornit.** Ce trebuie scris: sectiunea "Ce ramane de facut in VFP" din propunere.
`taxcode` se deriva **in VFP** prin `GetTaxCodeIdPart` si se trimite prin parametrul existent
`V_TAXCODE` — verificat ca lantul ei e inregistrat neconditionat in toate trei produsele
(`roafacturare.prg:187,189`, `roacont.prg:277,279`, `roagest.prg:239,241`), spre deosebire de
`ofacturare_editare.prg`, pe care doar ROAFACTURARE il inregistreaza.
- `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).

View File

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

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

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

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

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

@@ -92,6 +92,16 @@ 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`: