Files
roafacturare/docs/cercetare/s4f_retur_formular_unificat.md
2026-09-09 22:19:22 +03:00

728 lines
52 KiB
Markdown

# S4f — Returul in formularul unificat
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
scriere Oracle — doar `SELECT`), pentru povestea **S4f** din
`docs\plan_13_unificare_formular_facturare.md:2287-2330` (decizia 17, proiectata in sectiunea N,
`:1574-1655`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si
`COMUN\programe\ofacturare_editare.prg` — doar citite, cand au aparut in cautari.
Surse de adevar deja stabilite, citate nu reinvestigate: `docs\cercetare\legatura_linie_retur.md`,
`docs\cercetare\factura_retur_document.md`, `docs\cercetare\retur_si_lista_preturi.md`,
`docs\cercetare\s4b_bara_butoane_meniu.md`.
## Verdict (esenta, 10 randuri)
**Corectat dupa avertismentul S4e** (`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si
7): mecanismul `APPEND FROM` din decizia 16 (`ofacturare.prg:454-473`) **e respins pentru orice
cursor care serveste si drept registru citit de `do_sterge`**, `crsarticole` fiind exact acel cursor
pe documentele de retur (`Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate -
poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) — riscul e coliziune de
`id_c` intre `cursor_retur_document` si `cursor_preturi`, ambele numerotate `ROWNUM` independent.
Sectiunea 5 de mai jos e rescrisa pe solutia validata de S4e: **liniile libere nu intra deloc in
`crsarticole`**, se adauga direct in `crsfactura` prin `APPEND BLANK` + `combosql` (tiparul deja in
productie la `frm_facturare_articole2.But_nou1.do_adauga`), iar campul de distinctie e
**`crsfactura.id_c`** (`0` implicit pe o linie libera, valoare pe care Oracle n-o produce niciodata
pentru `id_c`), nu o coloana lipsa in `crsarticole`.
Partea grea nu e ce parea: N.1 (documentul de retur, tip 8/9/24) merge deja cap-coada — alegere
multipla de facturi, populare din `cursor_retur`, gestiune si pret de achizitie mostenite, stergere
si retur partial. Munca reala e in trei bucati inguste. **(1) Nu se muta dialogul de alegere a
facturilor** in bara de butoane a gridului — s-a verificat pe cod ca alegerea ramane la nivelul
antetului (sectiunea pliata), exact cum recomanda deja `s4b_bara_butoane_meniu.md` §9.3/tabelul
"puncte marunte", randul h; ce se muta in meniul "Adauga articole" e **rezultatul** alegerii (liniile
deja aduse), ca "Adauga tot din facturile alese" / "Alege liniile de returnat…", simetric cu
comanda/contract/avize. **(2)** Ridicarea lui `But_retur` la nivel de document e cod nou real, nu o
mutare de buton — azi alegerea e per-articol si niciodata scrisa ca legatura persistata.
**(3)** Liniile libere pe retur folosesc reteta S4e ("`APPEND BLANK` pe `crsfactura`, niciodata pe
`crsarticole`"), **plus** un gol nou de proiectat, gasit in aceasta sesiune si necunoscut de S4e (care
nu are dialog de gestiune pe comanda/avize): mecanismul de alegere a gestiunii
(`frm_articol_gest_factura`, `.lRetur=.T.`) **suprascrie pretul de vanzare cu pretul din stoc**
(`ofacturare.vc2:4094-4103`) — corect pentru o linie mostenita dintr-o factura sursa, dar gresit
pentru o linie libera, a carei pret de vanzare trebuie sa vina din lista de preturi (decizia 16), nu
din costul de stoc; in plus, acel dialog se intra azi doar dintr-un `poArticol` scatter-uit din
`crsarticole` (`do_adauga_articol`/`do_alege_stoc`), pe care o linie libera din `crsfactura` nu-l are
— e nevoie de un punct de intrare nou in dialog, nu doar de reparat pretul. Vezi sectiunea 5 si
riscurile R1/R7.
---
## 1. Inventarul celor doua cai de retur de azi
### 1.1 N.1 — factura de retur ca document (tip 8, 9, aviz 24)
| Pas | Ce face | `fisier:linie` |
|---|---|---|
| Intrare | Tile `Page2.Cw1` -> `politica.mpr` -> `factureaza(8)`/`factureaza(9)` din meniul shortcut | `COMUN\clase\ofundal_facturare.vc2:882-884`, `Meniuri\politica.mn2:14-15,45-46` |
| Antet | `nIdTipDoc=5`, formular `frm_date_factura`, aviz retur (24) foloseste `frm_date_aviz` cu `nIdTipDoc=6` | `COMUN\programe\ofacturare.prg:192-196,222-226` |
| Alegere facturi sursa | `frm_date_factura.do_cauta_facturi` -> `caut_facturi_multiple_client(id_client, in_valuta, id_valuta, .T.)` -> `cauta_alfa(..., tnTipReturn=1)`, selectie multipla, exclude tipurile de retur din sursa | `COMUN\clase\ofacturare.vc2:9173-9212`; `COMUN\programe\oproceduri_facturare.prg:2091-2121` |
| Validare obligatorie | Fara alegere, antetul nu se poate inchide (`inainte_de_do_termin`) | `ofacturare.vc2:9523-9526` |
| Populare linii | `pack_facturare.cursor_retur(V_IN_VALUTA,V_LISTAID,V_ID_UTIL)` -> `cursor_retur_document(...,V_COPIERE=0,...)`, `SELECT` direct din `VANZARI_DETALII`, filtrat pe `ID_VANZARE IN (V_LISTAID)` | `ofacturare.prg:306-307`; `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3943-3956,3958-4071` |
| Gestiune + pret achizitie | `ID_GESTIUNE`/`PRET_ACHIZITIE` copiate neschimbate din linia originala; `GESTIONABIL = NVL2(A1.ID_GESTIUNE,1,0)` | `ff_...sql:4034-4035,4055-4056,4048` |
| Stergere linie | Permisa, cantitatea reintra in cursorul sursa | `ofacturare.vc2:14608-14693`, ramura `:14658-14659` |
| Retur partial | Permis, validat fata de maximul = `CANTITATE` a liniei originale, fara agregare cu retururi anterioare | `ofacturare.vc2:14743-14754`, `ff_...sql:4036` |
| Adaugare libera | **Nu se poate azi** — `do_adauga_articol` ia articolul mereu din `crsarticole`, care pe tip 8/9/24 **este** rezultatul lui `cursor_retur` | `ofacturare.vc2:12843-12851` |
### 1.2 N.2 — `But_retur` per-articol, in factura normala (tip 1, 5, 7, 10)
| Pas | Ce face | `fisier:linie` |
|---|---|---|
| Vizibilitate | Doar pe tip 1/5/7/10; pe documentele de retur (8/9/24) butonul nu exista in UI | `ofacturare.vc2:15122-15127` |
| Declansare | `caction=do_retur` -> `do_adauga_articol(.F.,.F.,.T.)` | `cmd_butoane.vc2:324-338`; `ofacturare.vc2:13963-13965` |
| Alegere factura sursa | **Per articol**, la fiecare adaugare: `caut_facturi_multiple_client_articol(id_client,in_valuta,id_valuta,.F.,id_articol)` -> `cauta_alfa` cu titlu simplu "Alegeti factura" (nu selectie multipla) | `oproceduri_facturare.prg:2124-2159` |
| Acumulare | `thisform.cListaIdArticoleRetur` acumuleaza CSV de perechi `id_articol:id_vanzare`, cate una per articol adaugat; `poDate.listaid` e folosit **transient** (salvat in `lnListaIdOld` si restaurat imediat dupa) doar cat dureaza dialogul per-articol | `ofacturare.vc2:12882-12892` |
| Gestiune | **Se alege**, nu se mosteneste — `pack_facturare.cursor_gestiuni_articol_retur`, prin `do_alege_stoc(...,tlRetur=.T.)` -> `frm_articol_gest_factura` | `ofacturare.vc2:13230,13353-13385` |
| Pret | **Suprascris cu pretul din stoc** (`pretv`), nu cel din lista de preturi — vezi sectiunea 5 | `ofacturare.vc2:4094-4103` |
| Semn cantitate | Explicit `poArticol.cantitate = (-1) * cantitate` cand `Thisform.lRetur` | `ofacturare.vc2:4103` |
| Trimitere Oracle | `poDate.listaid = thisform.cListaIdArticoleRetur` la scriere finala | `ofacturare.vc2:13977-13979` |
| Legatura persistata | **Nicio legatura scrisa** — `listaid` e folosit doar ca filtru in calculul de disponibil pe rulaj (`RUL`), nu scrie nimic in `VANZARI_CORESP` sau pe linia noua | `ff_...sql:8142-8212` |
### 1.3 Ce au in comun si unde diverg
Comun: excluderea returului dintr-un retur (acelasi filtru `tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)`
in ambele functii de cautare), conventia de semn negativ pe cantitate, `frm_articol_gest_factura`
ca formular de alegere a gestiunii pentru linia gestionabila.
Diverg pe tot restul: N.1 alege facturile **o singura data, la nivel de document, inainte** ca
articolele sa existe; N.2 alege **per articol, in orice moment**, fara limita de cate ori. N.1
mosteneste gestiune+pret_achizitie fara alegere; N.2 le cere explicit. N.1 scrie
`VANZARI_CORESP(TIP=3)`; N.2 nu scrie nimic persistat. Zero cod comun intre cele doua cai —
`ofacturare.vc2:12813-12900` (N.2, `do_adauga_articol`) si `ofacturare.prg:306-311` (N.1,
`cursor_retur`) nu se ating.
---
## 2. `do_cauta_facturi` / `cauta_alfa(..., tnTipReturn=1)` — contractul exact
### 2.1 Cine il cheama si cand
Pe `frm_date_factura` exista un control generic de cautare, `Ct_clb_altele` (clasa
`ct_clb_cautare`), a carui iconita de lupa e legata de `cprocedura = thisform.do_cauta_altele`
(`ofacturare.vc2:8721-8729`). `do_cauta_altele` e un dispecer pe `nid_tip`:
```
Case Inlist(Thisform.nid_tip,8,9)
Thisform.do_cauta_facturi()
```
(`ofacturare.vc2:8953-8954`)
**Secventa confirmata pe cod** (`factureaza()`, `ofacturare.prg:187-311`): `frm_date_factura` se
creeaza si se afiseaza (`.Show()`, `:230-235`) **inainte** ca `Do Case tnTip` de la `:266-308` sa
ruleze `cursor_retur`, si mult inainte ca `lcObject = [frm_facturare_articole]` sa fie ales
(`:395`). Executia continua abia dupa ce `ofrmceredate` (antetul) se elibereaza (`:248`), iar
`pnButon` (setat la inchiderea antetului) e verificat imediat dupa. Deci **azi dialogul de alegere
a facturilor ruleaza pe formularul de antet, complet inainte ca formularul de articole sa existe** —
`crsarticole` se incarca o singura data, cu `poDate.listaid` deja fixat.
Control observat, semnificativ pentru sectiunea 3: `Ct_clb_altele` are
`cconditie = EMPTY(NVL(poDate.listaid,''))` (`ofacturare.vc2:8722`) — sugereaza ca iconita de
cautare e activa doar cat timp nimic nu a fost inca ales; **neverificat** insa exact ce proprietate
citeste `cconditie` (clasa `ct_clb_cautare` nu a fost gasita in text in acest repo, posibil intr-un
`.vcx` neconvertit sau in `COMUNROA`), deci nu se poate confirma daca astazi re-alegerea e blocata
explicit sau doar descurajata vizual. De tratat ca ipoteza, nu ca fapt confirmat.
### 2.2 Contractul functiei
`caut_facturi_multiple_client(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple)`
(`oproceduri_facturare.prg:2091-2121`):
- **Intrare**: client (obligatoriu — dialogul nu porneste fara el), valuta (doar daca `tnInValuta`),
`tlFacturiMultiple=.T.` la acest apel -> `lnTipReturn=1` in `cauta_alfa`.
- **Sursa**: `select serie_act,numar_act,data_act,dataora,id_vanzare from fact_vfacturi where
sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) [+ conditie sucursala] [+ client] [+ valuta]`
— fara filtru SQL pe perioada/serie (filtrarea pe acele coloane e comportament generic al
`cauta_alfa`, nu parametru dedicat).
- **Iesire**: XML cu randurile bifate (`ales=1`), convertit local prin `Xmltocursor(...,
"crsFacturiTemp")`; **nimic nu ajunge la Oracle in acest pas**.
- **Populare aditiva a rezultatului in VFP**: `poDate.listaid = cursor2lista("crsFacturiTemp",
"id_vanzare", ",")` — **inlocuieste** intreg continutul (`=`, nu `+=`); daca dialogul se apeleaza a
doua oara azi, a doua rulare suprascrie complet prima (dar azi asta nu se intampla niciodata pentru
ca dialogul ruleaza o singura data, inainte de orice alta actiune pe document — vezi 2.1).
- **`cauta_alfa(...,tnTipReturn=1)`** (`COMUN\programe\cauta_alfa.prg:16-260`, mecanismul generic
folosit peste tot in suita): adauga singur coloana `ales` (`cauta_alfa.prg:124`), titlu explicit
"Alegeti ... (mouse-click pe numar sau apasati SPACE)", filtrare interactiva pe coloanele afisate.
Nu are mecanism de dezactivare a unei optiuni individuale, nici submeniu — doar bifare/debifare.
### 2.3 Varianta per-articol
`caut_facturi_multiple_client_articol(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol)`
(`oproceduri_facturare.prg:2124-2159`) — **aceeasi functie de baza**, cu doua diferente: filtru
suplimentar `b.id_articol = ?pnIdArticol` (join pe `vanzari_detalii`) si `tlFacturiMultiple=.F.` la
apelul din `do_adauga_articol` (`:12884`), deci `lnTipReturn=0` -> selectie **simpla**, nu multipla.
Titlu simplu "Alegeti factura". Rezultatul e un obiect cu un singur `id_vanzare`, nu un cursor.
---
## 3. Proiectarea punctului 1 — alegerea facturilor sursa ca sursa in meniul de adaugare
**Ce NU se schimba**: alegerea propriu-zisa a facturilor sursa (dialogul cu bifare multipla) ramane
la nivelul antetului — in formularul unificat, sectiunea pliata (decizia 7). Verificat impotriva
tabelului "puncte marunte" din plan (`plan_13...md`, randul h): *„Alege facturile de returnat…"
ramane doar la antet in etapa I; mutarea (in bara de butoane) e o poveste separata* — si confirmat
independent de tabelul de meniu al lui `s4b_bara_butoane_meniu.md` §3: pe randul "retur (8,9,24)",
meniul "Adauga articole" propus contine **"Adauga tot din facturile alese" · "Alege liniile de
returnat…" · "Cauta in lista de preturi…" · "Alege din nomenclator…"** — nu si "Alege facturile de
returnat…". **Punctul 1 al lui S4f nu contrazice asta**: ce se muta in meniul de adaugare e
rezultatul alegerii deja facute la antet (liniile din facturile alese), simetric cu comanda/
contract/avize, nu dialogul de alegere a facturilor insusi.
**Ce SE schimba, si e proiectarea reala**: azi secventa e rigida — antet modal -> `cursor_retur` ->
formular de articole (sectiunea 2.1). In formularul unificat exista un singur formular, mereu
deschis; sectiunea de antet (pliata) si gridul de articole coexista. Trei consecinte de proiectat:
1. **Cand se declanseaza `cursor_retur_document`?** Nu mai poate fi legat de "inchiderea antetului"
(nu mai exista acel eveniment). Trebuie legat de **confirmarea dialogului de alegere** (butonul de
cautare din sectiunea pliata, echivalentul lui `Ct_clb_altele`/`do_cauta_facturi`): imediat ce
utilizatorul bifeaza facturile si confirma, `poDate.listaid` se seteaza si `cursor_retur_document`
ruleaza pe loc, populand/repopuland `crsarticole`. Documentul poate porni cu tipul deja pe 8/9/24
(ales din combo-ul de tip document) inainte ca vreo factura sa fie aleasa — grid-ul de articole
ramane gol pana la prima alegere, exact ca azi cand `Reccount(crsarticole)=0` da mesajul "Nu exista
articole pentru optiunile alese!" (`ofacturare.prg:325`).
2. **Poate opera la orice moment, nu doar o data.** Spre deosebire de azi (dialogul ruleaza o singura
data, inainte ca gridul sa existe), in formularul unificat butonul din sectiunea pliata e
apasabil oricand documentul e deschis. Trebuie decis explicit comportamentul la a doua apasare —
vezi punctul urmator.
3. **Alegere aditiva sau inlocuitoare, la a doua apasare.** Azi `poDate.listaid = ...` **inlocuieste**
(nu `+=`), dar azi nu conteaza pentru ca nu se poate apasa a doua oara dupa ce gridul exista. In
formularul unificat, daca utilizatorul a ales deja factura A, a adaugat cateva linii din ea in
`crsfactura`, si apoi vrea sa adauge si din factura B, doua variante:
- **(a) Inlocuire** — a doua alegere reseteaza `poDate.listaid`/`crsarticole` la noul set ales;
liniile deja mutate in `crsfactura` raman (crsfactura e independent de crsarticole dupa mutare),
dar orice linie inca needitata din factura A dispare din `crsarticole` fara avertisment. Simplu
de implementat (identic cu azi), dar surprinzator pentru operator.
- **(b) Aditiv** — a doua alegere **extinde** `poDate.listaid` cu facturile noi (evitand
duplicate) si ruleaza `cursor_retur_document` filtrat doar pe facturile noi, cu
`INSERT INTO crsarticole` (nu `SELECT INTO`), exact reteta deja propusa de
`s4b_bara_butoane_meniu.md` §9.3, optiunea 2 — cere cod VFP nou (bucla de filtrare +
`INSERT`), dar zero cod nou in `pack_facturare` (aceeasi procedura, apelata de mai multe ori).
Consistent cu filozofia deciziei 16 ("sursa umple documentul, nu il inchide").
**Recomandare**: varianta (b), aditiv — e coerenta cu tratamentul lista-de-preturi (S4e) si cu
comanda/contract (unde alegerea suplimentara nu sterge ce exista deja), si evita pierderea tacuta
de linii needitate. **Ramane decizie de confirmat de Marius** — nici plan-ul, nici
`s4b_bara_butoane_meniu.md` nu au tranzat-o explicit (§9.5, punctul 2 acolo o listeaza tot ca
deschisa).
**Consecinta pentru validarea de antet**: garda de azi (`inainte_de_do_termin`, `descriere` obligatoriu
pentru tip 8/9, `ofacturare.vc2:9523-9526`) nu mai are un moment natural "inainte de a inchide
antetul" — se muta la garda echivalenta din `Termina` (aceeasi verificare, alt declansator).
---
## 4. Proiectarea punctului 2 — ridicarea `But_retur` la nivel de document
**Ce se pastreaza din calea per-articol**: alegerea gestiunii ramane per-articol, prin
`cursor_gestiuni_articol_retur` si `frm_articol_gest_factura` — decizia 22/N.3 spune explicit ca
gestiunea si pretul de achizitie **se aleg**, nu se mostenesc, pentru orice linie fara factura
sursa unica precisa (si N.2 nu are niciodata o factura "sursa unica a documentului", are cel mult
factura aleasa pentru articolul curent). Punctul 2 nu schimba asta.
**Ce se schimba**: alegerea facturii sursa nu mai e per-articol (un dialog `cauta_alfa` cu selectie
simpla la fiecare `do_retur`), ci **la nivel de document**, dupa modelul N.1 — un singur dialog cu
selectie multipla (`caut_facturi_multiple_client`, acelasi ca la N.1, dar filtrat pe articolul
curent doar daca ramane necesar; de decis daca filtrul pe articol dispare complet odata cu ridicarea
la document). Consecinta directa: `poDate.listaid` (sau un camp nou, vezi mai jos) ar fi populat o
singura data pentru intreg documentul, nu reconstruit per articol prin `caut_facturi_multiple_client_articol`.
**Capcana de proiectare, gasita pe cod**: `poDate.listaid` e deja **partajat** intre N.1 si N.2 azi —
N.2 il suprascrie transient (`lnListaIdOld = poDate.listaid` ... `poDate.listaid = m.lnListaIdOld`,
`ofacturare.vc2:12882,12892`) doar cat dureaza alegerea per-articol, apoi il restaureaza. Daca N.2
ridicat la document incepe sa scrie **permanent** in `poDate.listaid` (ca N.1), cele doua mecanisme
intra in conflict pe acelasi camp — un document normal (tip 1/5/7/10) cu retur ridicat la nivel de
document ar avea `poDate.listaid` populat cu facturile de retur alese, camp care pe alte tipuri
(comanda/contract/avize) e deja folosit cu alt sens (id-uri de comanda/contract/aviz sursa, vezi
`ofacturare.prg:266-308`). **Nu e conflict pe tip 1/5/7/10** — acele tipuri nu folosesc azi
`poDate.listaid` pentru altceva (sunt populate cu `cursor_preturi`, care nu ia `V_LISTAID` ca
parametru) — deci reutilizarea e sigura tehnic, dar merita un camp cu nume propriu
(`poDate.listaid_retur` sau similar) ca sa nu se ambiguizeze semantic ce inseamna `listaid` pe un
document normal. **De decis la implementare**, nu blocant.
**Ce arata in bara de butoane / meniu (S4b)**: tabelul din `s4b_bara_butoane_meniu.md` §3 nu are
azi un rand pentru "retur intr-o factura normala" — doar pentru documentele de retur propriu-zise
(8,9,24). Ridicarea lui N.2 la nivel de document introduce o optiune noua in meniul "Adauga
articole" **pentru tipurile 1,5,7,10**: "Retur de articole…" (deja anticipata in tabelul §3 al
raportului S4b, coloana lista de preturi: "Cauta in lista de preturi… · Alege din nomenclator… ·
**Retur de articole…**"). Aceasta optiune deschide dialogul de alegere multipla la nivel de document
(prima data), apoi pe alegerile ulterioare acelasi comportament aditiv/inlocuitor discutat la
sectiunea 3 se aplica identic.
**Ce dispare**: `caut_facturi_multiple_client_articol` (filtrul per-articol) ramane cod mort daca
alegerea trece integral la nivel de document — sau se pastreaza ca alegere secundara optionala
("mai am nevoie de o factura sursa doar pentru articolul X", nefolosita azi in acest scenariu). Nu
e clar din decizia 17/N.3 daca trebuie eliminata explicit sau doar nu mai e calea principala — **de
decis de Marius** (risc R3).
---
## 5. Proiectarea punctului 3 — liniile libere pe document de retur
**Corectie fata de premisa initiala a brief-ului** ("prin acelasi `APPEND FROM` ca la S4e"): S4e a
gasit ca reteta literala a deciziei 16 (`ofacturare.prg:454-473`) **nu e sigura** pe niciun tip unde
`crsarticole` e citit ca registru de `do_sterge`/`do_scrie_factura`, si a documentat asta explicit
pentru retur, in tabelul sau de la §7:
> "**retur ca document (8,9,24)** | `crsarticole` = registru citit de `do_scrie_factura`? **Nu** —
> cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | `do_sterge` ajusteaza
> `crsarticole` pe `id_c`? **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With
> cantitate - poArticol.cantitate For id_c = poArticol.id_c` (`:14658-14659`, semn opus, urmareste
> maximul returnabil, nu inchiderea) | Concluzie: **risc de coliziune `id_c` ramane, dar fara
> poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un
> document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii
> reale — de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f."
> (`s4e_lista_preturi_pe_sursa.md`, §7, randul "retur ca document")
Solutia S4e (verificata acolo pe comanda/avize) se aplica identic aici — sectiunile de mai jos sunt
rescrise pe ea, nu pe `APPEND FROM`.
### 5.1 Mecanismul corect: `APPEND BLANK` pe `crsfactura`, niciodata pe `crsarticole`
**Liniile libere nu intra in `crsarticole`.** Se adauga direct in `crsfactura`, prin acelasi gest deja
in productie la `frm_facturare_articole2.But_nou1.do_adauga` (`ofacturare.vc2:17118-17122`):
```
PROCEDURE do_adauga
Select crsFactura
APPEND BLANK
this.grd_factura.SetFocus()
ENDPROC
```
urmat de editare inline prin `combosql` pe celula de cod/denumire (mecanismul construit de S4/S4e
pentru cautarea filtrata pe server, `pack_facturare.cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`).
Optiunea de meniu "Cauta in lista de preturi…" (deja in tabelul S4b §3, pe randul retur) declanseaza
exact acest gest — **nu** un `cursor_retur_document` suplimentar si **nu** un `APPEND FROM` peste
`crsarticole`.
### 5.2 Cum se distinge o linie libera de una din factura sursa
**Campul corect e `crsfactura.id_c`, nu o coloana pe `crsarticole`.** O linie mostenita ajunge in
`crsfactura` prin `do_adauga_articol`, care face `Select (lcCursor) / Scatter Name poArticol` pe
randul ales din `crsarticole` (populat de `cursor_retur_document`, cu `id_c = ROWNUM`, pornind de la
1) si copiaza acel `id_c` in randul nou din `crsfactura`. O linie libera, adaugata prin `APPEND BLANK`
direct pe `crsfactura` (5.1), **nu trece niciodata prin acest `Scatter`** — `id_c` ramane la valoarea
implicita a campului (`N(20)`, fara `Null`, deci `0`, `creeaza_facturacrs`,
`COMUN\programe\ofacturare_comun.prg:1775-1785`, citat si verificat de S4e §3 punctul 3). **Oracle nu
produce niciodata `id_c = 0`** (`ROWNUM` incepe la 1) — deci `crsfactura.id_c = 0` e semnalul curat,
fara camp nou, care distinge o linie libera de una mostenita. Acelasi tipar e deja in productie pentru
linia de discount (`ofacturare.vc2:14531-14537`, `Append Blank` fara `id_c` setat).
**Consecinta directa, fara cod suplimentar**: `do_sterge`, ramura de retur
(`Case Inlist(poDate.tip,8,9,24) => Select crsarticole / Replace cantitate With cantitate -
poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) cauta in `crsarticole`
un rand cu `id_c = poArticol.id_c`. Pentru o linie libera, `poArticol.id_c = 0`, iar `crsarticole` nu
are niciodata un rand cu acel `id_c` — `REPLACE ... FOR` devine **no-op prin constructie**. Stergerea
unei linii libere de pe un document de retur **nu atinge** maximul returnabil al nici unei linii
mostenite — exact ce cerea brief-ul la punctul 5, si exact acelasi mecanism (nu doar aceeasi idee) ca
solutia S4e pentru comanda.
### 5.3 Pretul de vanzare — riscul gasit (R1, ramane valabil)
**Nu e doar despre gestiune/pret de achizitie, si nu se rezolva prin mutarea in `crsfactura`.** Cand
o linie ajunge la `do_alege_stoc` cu `Inlist(poDate.tip,8,9,24)` adevarat (orice linie pe un document
de retur, indiferent de origine — conditia e pe `poDate.tip`, nu pe un flag de linie), se deschide
`frm_articol_gest_factura`. In acel formular, la schimbarea coloanei de gestiune
(`grd_gestiuni.RowColChange`), daca `Thisform.lRetur` e adevarat:
```
If Thisform.lRetur
poArticol.pretftva = pretv && pret DIN STOC, nu din lista de preturi
...
poArticol.cantitate = (-1) * cantitate
Endif
```
(`ofacturare.vc2:4094-4103`)
Pentru o linie **mostenita** (N.1), asta e corect prin constructie — returnezi marfa la pretul cu
care a fost vanduta. Pentru o linie **libera** (decizia 22 — articol niciodata vandut clientului, deci
fara "pret de retur" de mostenit), suprascrierea cu pretul de stoc e o **eroare de pret**: linia
libera ar iesi cu costul de stoc in loc de pretul din lista de preturi ales prin `combosql` la 5.1.
**Solutie propusa, actualizata**: `frm_articol_gest_factura` primeste un al doilea semnal, distinct de
`tlRetur` ("esti pe flux de retur"), care sa insemne "aceasta linie are factura sursa" — derivat acum
din `crsfactura.id_c <> 0` (5.2), nu dintr-o coloana lipsa in `crsarticole`. Ramura de suprascriere
pret (`:4094-4103`) se activeaza doar cand acest al doilea semnal e adevarat.
### 5.4 Golul nou, nu tratat de S4e: punctul de intrare in dialogul de gestiune
**S4e nu are acest gol pentru ca nu are dialog de gestiune pe comanda/avize** — liniile libere de
acolo se completeaza integral prin `combosql` (pret, TVA, valuta) fara sa mai treaca prin niciun
dialog de alegere a gestiunii. Pe retur, decizia 22 cere explicit ca gestiunea si pretul de achizitie
sa **se aleaga** pentru o linie libera — mecanismul care stie sa faca asta e
`cursor_gestiuni_articol_retur` + `frm_articol_gest_factura`, dar acel dialog se intra azi **doar**
din `do_alege_stoc`, apelat de `do_adauga_articol` pe baza unui `poArticol` scatter-uit dintr-un rand
**deja incarcat in `crsarticole`** (`ofacturare.vc2:12843-12851,12891`). O linie libera adaugata prin
`APPEND BLANK` pe `crsfactura` (5.1) **nu are** un asemenea rand sursa — exact acelasi gol pe care
S4e l-a semnalat, netratat, pentru validarea de cantitate pe comanda (`s4e_lista_preturi_pe_sursa.md`
§6: *"`do_verifica_articol` cere un `poArticol` scatter-uit dintr-un cursor sursa deja incarcat... o
linie libera... nu are un asemenea rand sursa"*).
**Consecinta pentru S4f, mai stricta decat pe comanda**: pe comanda/avize (S4e), lipsa unui `poArticol`
de sursa afecteaza doar validarea de cantitate (un gol de acoperit, dar linia se poate scrie si fara
gestiune aleasa — comanda/avize nu cer gestiune diferita de cea din lista de preturi). Pe retur, lipsa
aceluiasi `poArticol` blocheaza **si** alegerea gestiunii, care e obligatorie prin decizia 22. Trebuie
proiectat un punct de intrare nou in `frm_articol_gest_factura`/`do_alege_stoc`, care sa porneasca de
la randul **deja inserat in `crsfactura`** (dupa `combosql`, cu `id_articol` si cantitate deja
completate) in loc de la un rand din `crsarticole` — practic o a doua cale de apel, cu acelasi dialog
la capat, dar `poArticol` construit ad-hoc din `crsfactura` in loc de `Scatter` din `crsarticole`.
Aceeasi problema structurala, aceeasi solutie propusa (poArticol construit ad-hoc) ca varianta (b) de
la S4e §6 pentru `do_verifica_articol` — de unificat la implementare daca se poate, nu de rezolvat de
doua ori independent.
### 5.5 Maximul returnabil — nu se aplica, si acum e clar de ce
Confirmat structural: azi maximul returnabil pe N.1 **nu e un calcul de "cat s-a mai returnat"**, e
pur si simplu `CANTITATE` a liniei originale (`ff_...sql:4036`, `ofacturare.vc2:15236-15239`,
`do_verifica_articol:14743-14754`) — validarea respinge doar semnul gresit (`tnCantitate >= 0` in mod
retur), nu o depasire fata de un istoric. **Pentru o linie libera, intrebarea nici nu se pune in
termenii de azi**: `do_verifica_articol`, ca si dialogul de gestiune (5.4), asteapta un `poArticol`
din `crsarticole` — o linie libera nu are unul, deci nu poate trece prin comparatia de maxim existenta
nici macar din greseala. Validarea proprie a liniei libere (cantitate/stoc, construita ad-hoc, 5.4)
nu are nimic de comparat cu un "maxim returnabil" — verifica doar disponibilul de stoc curent, ca pe
o linie normala de lista de preturi (acelasi gol deschis de S4e §6 pentru comanda, aceeasi solutie).
---
## 6. Efectul asupra `VANZARI_CORESP`
**Scrierea nu depinde de continutul liniilor, ci de alegerea facuta la antet.**
`scrie_corespondente_vanzari(3)` (`ff_...sql:14834-14836`, apelata din `finalizeaza_factura`) scrie
direct din `pack_facturare.clistaid` (variabila de sesiune, = `poDate.listaid` trimis la
`initializeaza_date_factura`), **independent** de ce a ajuns efectiv in `VANZARI_DETALII` la
finalizare:
```sql
INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)
SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3
FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,',')));
```
(`ff_...sql:15481-15516`, citat integral in `legatura_linie_retur.md:45-53`)
**Consecinta directa pentru liniile libere**: daca documentul de retur contine si linii libere (din
lista de preturi), corespondenta **tot se scrie**, si scrie exact aceleasi facturi alese la antet —
liniile libere nu adauga si nu scot randuri din `VANZARI_CORESP`, pentru ca sursa e `poDate.listaid`
(fixat la antet), nu continutul `crsfactura`/`VANZARI_DETALII`. Un document de retur cu 3 linii
mostenite din factura A si 2 linii libere scrie tot un singur rand in `VANZARI_CORESP`
(`ID_VANZARE_FACT`=documentul nou, `ID_VANZARE_AVIZ`=factura A, `TIP=3`) — corespondenta descrie
"acest document a folosit ca sursa factura A", nu "aceste linii vin din factura A".
**Ce se scrie cand nu s-a ales nicio factura sursa**: nu e un caz posibil pentru documentul de retur
insusi — validarea de antet (`inainte_de_do_termin`, `descriere` obligatoriu, sectiunea 3) impune cel
putin o factura aleasa inainte ca documentul sa poata continua. Deci pentru N.1/S4f (documente 8/9/24)
`clistaid` nu e niciodata gol la scriere. **Pentru N.2 ridicat la document (punctul 2)** raspunsul e
diferit: acolo documentul e de tip 1/5/7/10, iar `poDate.listaid`/campul nou echivalent (sectiunea 4)
nu e obligatoriu — un document normal fara nicio linie de retur nu are ce sa scrie. **Intrebare
deschisa, verificata partial**: daca `scrie_corespondente_vanzari(3)` e apelata azi doar cand
`ntip in (8,9)` (confirmat de `legatura_linie_retur.md:113`: *"TIP=3: factura de retur
(scrie_corespondente_vanzari(3), :14834-14836, la ntip in (8,9))"*), atunci **ridicarea lui N.2 la
nivel de document NU va scrie automat `VANZARI_CORESP`** — documentul ramane tip 1/5/7/10, gardat de
`ntip`, nu de continutul `listaid`. Linia exacta a acestui `IF`/`CASE` in exportul curent
(`ff_2026_08_09_01...sql`) **nu a fost re-verificata caracter cu caracter** in aceasta sesiune (citata
din raportul sursa, care a folosit un export anterior cu alta numerotare) — de reverificat la
implementare, dar concluzia structurala (gating pe `ntip`, nu pe `listaid`) e solida.
**Decizie de proiectare care rezulta**: ridicarea lui N.2 la nivel de document (punctul 2) **nu
capata**, prin simpla ridicare, o corespondenta persistata in `VANZARI_CORESP` — comportamentul
raman identic cu azi (nicio legatura scrisa), *cu exceptia* cazului in care Marius decide explicit ca
merita cod Oracle nou (extinderea gardei `ntip in (8,9)` la un al treilea semnal, ex. "are `listaid`
populat"). Decizia 35 (un singur cod de scriere contabila, cod Oracle nou doar cand strict necesar)
face ca varianta "fara schimbare in pack_facturare" sa fie implicita — **de confirmat cu Marius**
(risc R4), nu de presupus.
---
## 7. Semnul cantitatii / al valorii pe retur
**Doua mecanisme diferite, verificate separat pe cod:**
- **N.1 (documente 8/9/24)**: cantitatea vine deja cu semnul corect direct din
`cursor_retur_document` — `CANTITATE` a liniei originale (pozitiva ca valoare de referinta, dar
interpretata cu semn de retur prin `poDate.tip`); semnul efectiv de scriere in `VANZARI_DETALII`
e controlat de codul Oracle care insereaza documentul de retur (nu urmarit aici, in afara
perimetrului VFP), iar in UI validarea `do_verifica_articol` (`llRetur = Inlist(poDate.tip,8,9,24)`)
respinge `tnCantitate >= 0` in mod retur — deci utilizatorul introduce cantitatea ca valoare
pozitiva ("cat returnez"), iar semnul negativ e o conventie aplicata la nivel de tip de document,
nu per linie.
- **N.2 (`But_retur`)**: semnul negativ **e aplicat explicit in cod VFP**, in
`frm_articol_gest_factura` — `poArticol.cantitate = (-1) * cantitate` (`ofacturare.vc2:4103`),
declansat de `Thisform.lRetur` (adevarat cand `tlRetur` a fost trimis la `Init`, indiferent de
`poDate.tip`). Aici semnul **nu** vine din `poDate.tip` (documentul e tip 1/5/7/10, pozitiv prin
design), ci e o negatie explicita facuta pentru ca acea linie anume e un retur intr-un document
altfel normal.
**Implicatie pentru liniile libere pe document de retur (S4f punctul 3)**: pentru ca documentul
intreg e tip 8/9/24, orice linie — libera sau mostenita — trece prin acelasi tratament de tip de
document (semnul negativ e o proprietate a **documentului**, nu a liniei, in acest caz). Nu exista
risc de "linie libera cu semn pozitiv gresit" atata timp cat validarea ramane gatata pe `poDate.tip`
si nu pe originea liniei — de verificat explicit ca ramura noua a validarii (5.4, sarirea peste
maximul returnabil) **nu** atinge si sare peste verificarea de semn, care trebuie sa ramana activa
identic pe linii libere si mostenite.
**Implicatie pentru N.2 ridicat la document (punctul 2)**: aici semnul **ramane** o proprietate de
linie (nu de document, pentru ca documentul e tip normal 1/5/7/10) — mecanismul de la
`ofacturare.vc2:4103` se pastreaza neschimbat, declansat de acelasi `tlRetur`/`Thisform.lRetur`,
indiferent daca alegerea facturii sursa se face per-articol (ca azi) sau la nivel de document
(punctul 2). Nimic de schimbat aici — doar de confirmat ca noul flux (alegere la document) tot
seteaza `tlRetur=.T.` la apelul catre `do_alege_stoc`/`frm_articol_gest_factura` pentru fiecare linie
de retur adaugata.
---
## 8. Unde se aseaza informatia de provenienta in UI
**Inchis pe cod, cf. `legatura_linie_retur.md`**: legatura linie-la-linie nu se poate afisa in niciun
caz — nici `cursor_retur_document` nu aduce `ID_VANZARE`/`ID_VANZARE_DET` in `crsarticole`
(`ff_...sql:3965-4028`, coloanele lipsesc din SELECT), nici `INSERT`-ul final in `VANZARI_DETALII`
nu are coloana de sursa (`PACK_FACTURARE:13705-13757`, listata explicit, fara camp de provenienta).
**Ce se poate afisa, verificat**: la nivel de **document**, cand exact o singura factura sursa a fost
aleasa, `VANZARI_CORESP(TIP=3)` da fara ambiguitate factura sursa:
```sql
SELECT v.serie_act, v.numar_act
FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ
WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0;
```
(`legatura_linie_retur.md:174-178`, interogare formulata, neverificata pe date live in acel raport)
**Propunere concreta de text si conditie** (in romana, pentru sectiunea pliata / antetul
formularului unificat):
- **O singura factura sursa aleasa**: eticheta fixa de tip "Provine din factura: **{serie} {numar}**"
langa campul de descriere existent (`poDate.text_aditional`, deja populat azi cu
"RETUR FACTURA {numere}" — `factura_retur_document.md:65`), afisata read-only, imediat sub sau langa
campul de tip document.
- **Mai multe facturi sursa alese**: eticheta devine "Provine din facturile: **{lista serie+numar}**"
— aceeasi sursa de date (`poDate.descriere`, deja populata cu lista, nu doar `VANZARI_CORESP`),
**fara** sa se sugereze vreo distributie pe linii.
- **Niciodata pe linie** — nici coloana in grid, nici tooltip per rand care sa pretinda o sursa
exacta. Pentru liniile libere in particular, nu exista nimic de afisat (nu au sursa) — nu se
introduce o valoare "N/A" vizibila care ar sugera ca alte linii AU o sursa unica atunci cand de
fapt (cu selectie multipla) nici ele nu o au cu certitudine.
- **Conditie tehnica**: textul se construieste din `poDate.descriere` (deja in memorie, fara interogare
suplimentara) — nu necesita interogarea `VANZARI_CORESP` de mai sus decat daca se doreste
reafisarea la redeschiderea unui document deja emis (regenerare, etapa II) — caz in care
interogarea de mai sus e necesara pentru ca `poDate.descriere` nu mai exista in memorie.
---
## 9. Pasi de implementare ordonati
1. **Confirmarea comportamentului aditiv/inlocuitor la alegerea facturilor sursa** (sectiunea 3,
punctul 3) — decizie de produs, nu cod. *Gata cand:* Marius a ales (a) sau (b); fara asta pasii 2-3
nu se pot implementa fara risc de refacere.
*Verificabil:* niciunul (decizie, nu cod).
2. **Butonul/actiunea de alegere a facturilor sursa, mutat in sectiunea pliata a formularului
unificat**, pastrand `do_cauta_facturi`/`caut_facturi_multiple_client` neschimbate — doar
punctul de declansare se muta din antetul modal in sectiunea pliata a formularului unic, si
`cursor_retur_document` se declanseaza la confirmarea dialogului, nu la inchiderea unui formular
separat (sectiunea 3, punctele 1-2).
*Gata cand:* pe un document nou de tip 8/9/24, deschis in formularul unificat, alegerea facturilor
sursa din sectiunea pliata populeaza `crsarticole` cu exact aceleasi randuri ca azi (gestiune, pret
achizitie, pret vanzare identice), verificabil prin comparatie directa a cursorului.
3. **Meniul "Adauga articole" pe tipurile 8/9/24**, cu optiunile "Adauga tot din facturile alese" /
"Alege liniile de returnat…" peste `crsarticole` deja populat (reteta comuna S4b, sectiunile 4-5
ale acelui raport) — fara alegere de facturi in acest meniu (sectiunea 3).
*Gata cand:* meniul contine exact aceste doua optiuni pe tip 8/9/24, plus "Cauta in lista de
preturi…"/"Alege din nomenclator…" (punctul urmator).
4. **Liniile libere — `APPEND BLANK` pe `crsfactura`, semnalul de distinctie pe `id_c`** (sectiunile
5.1-5.2): optiunea de meniu "Cauta in lista de preturi…" pe tip 8/9/24 declanseaza
`Select crsFactura / APPEND BLANK` + `combosql`, exact tiparul S4e (`ofacturare.vc2:17118-17122`)
— **nu** `APPEND FROM` peste `crsarticole`. `id_c` ramane implicit (`0`) pe randul nou.
*Gata cand:* dupa adaugarea unei linii libere pe un document de test, `crsfactura.id_c = 0` pe acel
rand si `crsarticole` ramane neschimbat (acelasi `Reccount`/continut ca inainte de adaugare).
5. **Punct de intrare nou in dialogul de gestiune, pornind de la randul din `crsfactura`** (sectiunea
5.4) — `frm_articol_gest_factura`/`do_alege_stoc` primesc o a doua cale de apel, cu `poArticol`
construit ad-hoc din randul deja inserat in `crsfactura` (dupa `combosql`), nu din `Scatter` pe un
rand din `crsarticole`. Fara acest pas, o linie libera nu poate ajunge deloc la alegerea gestiunii
ceruta de decizia 22.
*Gata cand:* pe o linie libera adaugata pe un document de retur, dialogul de gestiune se deschide si
returneaza `ID_GESTIUNE`/`PRET_ACHIZITIE` alese de operator, fara sa fi trecut prin `crsarticole`.
6. **Fixarea pretului de vanzare pe liniile libere** (sectiunea 5.3, riscul R1) — ramura de
suprascriere din `frm_articol_gest_factura` (`ofacturare.vc2:4094-4103`) primeste al doilea semnal
("are sursa" vs. "e libera"), derivat din `crsfactura.id_c <> 0` (pasul 4), si nu suprascrie
`pretftva` cand linia e libera.
*Gata cand:* o linie libera adaugata pe un document de retur pastreaza pretul din lista de preturi
dupa alegerea gestiunii, verificabil comparand `poArticol.pretftva` inainte si dupa
`RowColChange` in dialogul de gestiune.
7. **Validarea de cantitate/stoc pe liniile libere** (sectiunea 5.5) — mecanism nou, nu
`do_verifica_articol` ca atare (care cere un `poArticol` din `crsarticole`); verifica disponibilul
de stoc curent, fara nicio comparatie cu un maxim returnabil. Unificabil cu golul echivalent
deschis de S4e §6 pentru comanda/avize, daca implementarea alege aceeasi varianta (`poArticol`
construit ad-hoc, parametru explicit).
*Gata cand:* pe o linie libera, orice cantitate <= stocul curent trece validarea, cu mesaj propriu
(nu "cantitate maxima de returnat"); pe o linie mostenita, comportamentul de azi ramane neschimbat.
8. **Ridicarea `But_retur` la nivel de document (punctul 2)** — dialog de alegere multipla la
deschiderea primei linii de retur pe un document normal (1/5/7/10), populare cu un camp nou sau
reutilizarea controlata a lui `poDate.listaid` (sectiunea 4), pastrand calea per-articol existenta
ca fallback sau eliminand-o explicit (decizie separata, risc R3).
*Gata cand:* pe o factura normala, alegerea facturilor sursa se face o singura data, iar liniile de
retur adaugate ulterior (mai multe articole) folosesc acel set fara sa redeschida dialogul de
cautare la fiecare articol.
9. **`VANZARI_CORESP` pentru N.2 ridicat la document** (sectiunea 6) — de decis daca se adauga cod
Oracle nou sau ramane fara corespondenta persistata (risc R4); implementat doar dupa decizia lui
Marius.
*Gata cand:* comportamentul ales (cu sau fara corespondenta) e implementat consistent si testat pe
un document cu retur ridicat la nivel de document si mai multe facturi sursa.
10. **Textul de provenienta la antet** (sectiunea 8) — afisare read-only din `poDate.descriere`,
condusa de numarul de facturi alese (0/1/multiple).
*Gata cand:* eticheta arata corect in toate trei cazurile (nicio factura inca, o factura, mai
multe), fara sa apara pe grid/linie.
*Depinde de:* S2 (formular unificat de baza), S4e (mecanismul `APPEND BLANK` + `combosql` pentru
liniile libere pe document cu sursa, NU `APPEND FROM`) — exact ca in plan (`plan_13...md:2330`),
corectat fata de reteta literala a deciziei 16.
---
## 10. Cum se verifica
**Proba din plan** (`plan_13...md:2327-2329`): *"un document de retur deschis in formularul unificat
aduce liniile facturilor alese cu aceleasi valori ca azi — inclusiv gestiunea si pretul de
achizitie —, permite stergere si cantitate partiala, iar pe o factura normala se poate face retur
alegand facturile o singura data."*
Pasi concreti:
1. **Paritate N.1**: pe date de test, emite un document de retur (tip 8) pe calea veche (formularul
actual) si retine `crsarticole` rezultat (gestiune, pret achizitie, pret vanzare, cantitate per
linie). Repeta aceeasi alegere de facturi sursa in formularul unificat si compara `crsarticole`
camp cu camp — trebuie sa fie identic, pentru ca sursa Oracle (`cursor_retur_document`) nu se
schimba, doar punctul VFP care o declanseaza.
2. **Stergere si retur partial**: pe formularul unificat, sterge o linie mostenita si verifica ca
cantitatea reintra in `crsarticole` (acelasi test ca azi, `do_sterge` neschimbat); introdu o
cantitate mai mica decat maximul pe o linie mostenita si verifica validarea.
3. **Linie libera**: adauga o linie din lista de preturi pe documentul de retur deschis (prin
`APPEND BLANK` + `combosql`, nu `APPEND FROM`); verifica (a) `crsfactura.id_c = 0` pe acel rand si
`crsarticole` neschimbat; (b) pretul de vanzare = pretul din lista de preturi, nu din stoc; (c)
gestiunea/pretul de achizitie sunt cerute prin dialogul de gestiune (intrat pe calea noua, 5.4), nu
completate automat; (d) cantitatea nu e limitata de niciun maxim istoric, doar de stocul curent;
(e) semnul cantitatii ramane negativ (consistent cu documentul); (f) stergerea liniei libere nu
modifica `crsarticole` (verifica direct: `Reccount`/continut identic inainte si dupa stergere).
4. **`VANZARI_CORESP` neschimbat de liniile libere**: dupa emiterea documentului din pasul 3,
interogheaza:
```sql
SELECT ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP FROM VANZARI_CORESP
WHERE ID_VANZARE_FACT = :id_document_nou AND TIP = 3;
```
— trebuie sa arate exact facturile alese la antet, indiferent de cate linii libere s-au adaugat.
5. **N.2 ridicat la document**: pe o factura normala, adauga doua linii de retur pentru articole
diferite folosind acelasi set de facturi sursa ales o singura data (nu redeschide dialogul la
fiecare articol); verifica semnul cantitatii (negativ) si absenta oricarei scrieri in
`VANZARI_CORESP` (daca decizia de la punctul 8/risc R4 pastreaza comportamentul de azi).
6. **Provenienta**: deschide un document de retur cu o singura factura sursa — verifica textul de
antet; alege doua facturi sursa — verifica ca textul listeaza ambele si nu implica o sursa unica.
## 11. Ce nu se poate testa headless
Reluat din capcanele deja confirmate pe acest proiect (`s4b_bara_butoane_meniu.md` §8), plus
specificul retur:
- **`cauta_alfa` cu `tnTipReturn=1`** — dialog modal (`.Show(1)`), blocheaza thread-ul UI; testarea
automata a bifarii multiple nu se poate face prin injectare de input pe aceasta masina (partajata,
memoria `masina-partajata-fara-input-real`). Se verifica indirect: apeland direct
`caut_facturi_multiple_client`/`cursor_retur_document` cu parametri de test si citind cursorul
rezultat, ocolind UI-ul.
- **`frm_articol_gest_factura`** — acelasi tip de dialog modal; verificarea suprascrierii de pret
(risc R1, sectiunea 5.3) si a noului punct de intrare pornit din `crsfactura` (sectiunea 5.4) se
poate face doar citind codul metodei `RowColChange`/noua metoda de intrare si, separat, apeland
direct logica de calcul cu date de test simulate — nu prin click real in grid.
- **Coloanele griduri** (`grd_articole`, gridul dialogului de alegere) — capcana deja cunoscuta:
`ColumnCount=0` sub `-A -T`; verificare vizuala necesita harness UI vizibil, nu headless.
(memoria `grid-coloane-nu-se-materializeaza-headless`)
- **Textul de provenienta din sectiunea pliata** (sectiunea 8) — verificabil headless ca **continut
al proprietatii** (`Caption`/`Value` a controlului), nu ca "arata bine" vizual.
- **Secventa reala de deschidere a dialogului din sectiunea pliata** (declansarea la click, nu doar
codul din spatele ei) — necesita harness UI vizibil pentru captura de interactiune.
**Ce se poate verifica headless, direct**: continutul `crsarticole`/`crsfactura` dupa apeluri directe
ale rutinelor (`cursor_retur_document`, `do_adauga_articol`, `do_verifica_articol`, `APPEND BLANK` pe
`crsfactura`) cu parametri de test; valoarea `crsfactura.id_c` pe randurile noi (semnalul de la 5.2,
`0` pe liniile libere, valoare reala pe cele mostenite); continutul real din
`VANZARI_CORESP`/`VANZARI_DETALII` dupa emiterea unui document de test pe schema Oracle (doar
`SELECT`, verificare post-factum).
---
## 12. Riscuri si ce ramane de decis de Marius
**R1 — Pretul de vanzare pe liniile libere se suprascrie cu pretul de stoc, daca nu se trateaza
explicit** (sectiunea 5.3). Cel mai concret risc gasit in aceasta cercetare, netratat in plan pana
acum. **Recomandare**: al doilea semnal ("linie cu sursa" vs. "linie libera") pe langa `tlRetur`,
derivat din `crsfactura.id_c <> 0` (5.2), care sa dezactiveze suprascrierea de pret doar pentru
liniile libere. Necesita o mica modificare in `frm_articol_gest_factura` (nu in `pack_facturare`).
**R7 — Punctul de intrare in dialogul de gestiune pentru o linie libera nu exista azi** (sectiunea
5.4). Gol nou, mai strict decat echivalentul lui pe comanda/avize (S4e §6, doar validare de
cantitate) — pe retur blocheaza si alegerea gestiunii/pretului de achizitie, obligatorie prin decizia
22. `do_alege_stoc`/`frm_articol_gest_factura` se intra azi doar dintr-un `poArticol` scatter-uit din
`crsarticole`; o linie libera (`APPEND BLANK` pe `crsfactura`, 5.1) nu are asa ceva. **Recomandare**:
a doua cale de apel, cu `poArticol` construit ad-hoc din randul `crsfactura` deja completat prin
`combosql`, unificata daca se poate cu solutia aleasa pentru golul echivalent de validare (R7 aici,
S4e §6/varianta b acolo) — un singur mecanism de "construieste `poArticol` fara sursa in `crsarticole`",
nu doua independente.
**R2 — Alegere aditiva sau inlocuitoare la a doua apasare a dialogului de facturi sursa** (sectiunea
3, punctul 3). Nu e tratat de nicio decizie existenta. **Recomandare**: aditiv, consistent cu
filozofia deciziei 16, dar cere cod VFP nou (bucla de filtrare pe facturi noi + `INSERT INTO
crsarticole`).
**R3 — Ce se intampla cu `caut_facturi_multiple_client_articol` (alegerea per-articol) dupa ridicarea
lui `But_retur` la nivel de document.** Ramane ca optiune secundara sau se elimina? Nefolosit oriunde
altundeva in cod (verificat implicit — apelul e doar din `do_adauga_articol`, ramura `tlRetur`).
**Recomandare**: se elimina, pentru ca pastrarea ambelor cai ar insemna doua UX-uri pentru aceeasi
actiune, exact riscul pe care unificarea vrea sa il elimine.
**R4 — `VANZARI_CORESP` pentru N.2 ridicat la document.** Gardat azi pe `ntip in (8,9)`, nu pe
continutul `listaid` — **reconfirmat independent** in aceasta sesiune, pe exact acelasi export
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:14834-14836`): `WHEN pack_facturare.ntip in (8, 9) THEN
pack_facturare.scrie_corespondente_vanzari(3);`, in interiorul unui `CASE` fara alta conditie pe
`listaid`. Daca ramane asa, un document normal
cu retur ridicat la document nu va avea nicio legatura persistata catre facturile sursa alese — exact
ca azi cu `But_retur` per-articol, deci nu e o regresie, dar nici un castig. **Recomandare**: se lasa
neschimbat (fara cod Oracle nou), consistent cu decizia 35 (economie de cod de intretinut) — de
confirmat explicit cu Marius, pentru ca punctul 2 al S4f ("ridicarea la nivel de document") ar putea
crea asteptarea implicita ca acum exista si o legatura persistata, cand de fapt nu exista.
**R5 — `Ct_clb_altele.cconditie` — semantica exacta neconfirmata** (sectiunea 2.1). Daca acest camp
chiar dezactiveaza azi re-alegerea dupa prima selectie, design-ul nou (sectiunea 3) trebuie sa decida
explicit sa **elimine** aceasta restrictie (pentru varianta aditiva) — altfel comportamentul nou ar
fi mai permisiv decat parea sa fie azi, fara sa fi fost o decizie constienta. **De verificat la
implementare**, cand clasa `ct_clb_cautare` devine accesibila (posibil in `COMUNROA`, in afara acestui
repo).
**R6 — Semnul cantitatii pe liniile libere, verificare explicita ceruta de brief** (sectiunea 7): nu
e un risc nou — analiza arata ca semnul e o proprietate a documentului (tip 8/9/24), nu a liniei,
deci liniile libere il mostenesc automat corect. Se listeaza aici doar ca sa fie explicit ca punctul
a fost verificat, nu omis.
---
## Handoff
Cercetare incheiata fara sa fie nevoie de predare de context — un subagent de verificare
(`ab0be8e27f0fb97f3`, Explore/general-purpose) a esuat de doua ori sa returneze rezultate, apoi a
raspuns complet la a treia incercare (reluat prin `SendMessage`), confirmand independent 4 puncte deja
scrise pe cod propriu in raport: (1) `do_cauta_facturi` e declansat explicit de user, prin
`Ct_clb_altele`/`caut_ora.vc2:787-798,839-844` -> `do_cauta_altele` -> `do_cauta_facturi`
(`ofacturare.vc2:8940-8961,9173`), inainte de orice deschidere a `frm_facturare_articole`; (2)
`scrie_corespondente_vanzari(3)` e gardat exact pe `ntip in (8,9)`
(`ff_...sql:14834-14836`) — folosit sa inchid definitiv R4; (3)
`thisform.cListaIdArticoleRetur` poate acumula `ID_VANZARE` diferit per articol
(`ofacturare.vc2:12882-12892`); (4) `poDate.listaid` e acelasi camp la N.1 si N.2, dar cu rol
tranzitoriu la N.2 (salvat/restaurat) si definitiv doar la `do_scrie_articole`
(`ofacturare.vc2:13977-13979`). Niciuna din aceste confirmari nu a schimbat vreo concluzie a
raportului, doar le-a intarit sursa.
**Corectie aplicata dupa livrare**, la avertismentul `team-lead` (bazat pe
`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si 7): sectiunea 5 (liniile libere) a
fost **rescrisa integral** — reteta `APPEND FROM` peste `crsarticole` (premisa initiala a brief-ului)
e inlocuita cu mecanismul validat de S4e (`APPEND BLANK` pe `crsfactura` + `combosql`, niciodata pe
`crsarticole`), campul de distinctie devine `crsfactura.id_c = 0` (nu absenta `PRET_ACHIZITIE` pe
`crsarticole`), si s-a adaugat un gol nou, negasit de S4e (care nu are dialog de gestiune pe
comanda/avize): punctul de intrare in `frm_articol_gest_factura` pentru o linie fara rand sursa in
`crsarticole` (risc R7, nou). Verdictul de la inceputul raportului, pasii de implementare (9),
verificarea (10) si sectiunile headless (11) au fost actualizate in consecinta. Restul raportului
(sectiunile 1-4, 6-8, 12 minus R1/R7) ramane neschimbat fata de livrarea initiala.
Fisierul SQL folosit: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(cel mai recent din director). Nicio editare de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun
commit, nicio scriere Oracle.