#13: formular de facturare unificat - etapa curenta

Squash al branch-ului de lucru plan13-s2.

Cod: clasa noua ofacturare_util (geometrie si utilitare de formular), inrolata in
roafacturare.prg; meniul "Facturare (nou)" plat, cu reparatia literalului peste
limita VFP de 255 de caractere care impiedica compilarea metodei.

Livrare: changelog 2.11.19, marcajul de versiune DB pentru scripturile finale din
SCRIPTURI_CLAR\2026\09, inventarul livrarii in docs/livrare_13.md si registrul de
erori deschise. Documentatia de lucru a etapei (cercetare, rapoarte, handoff-uri,
snapshot-uri de scripturi si backup-uri de rollback) a fost scoasa din arbore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkyjGyrV2S7932om4kfSiK
This commit is contained in:
2026-09-09 21:30:51 +03:00
parent 87063b06a8
commit 104c24ec20
80 changed files with 5585 additions and 27852 deletions

View File

@@ -1,185 +0,0 @@
# Cercetare: factura de retur ca document de sine statator (tip 8/9) + aviz retur (24)
Corectie fata de `retur_si_lista_preturi.md`: acea cercetare a documentat corect `But_retur`
(retur de articole *in interiorul* unei facturi normale, tip 1/5/7/10 — selectie **per articol**).
Aici e documentat mecanismul **separat**: factura de retur ca document propriu (tip 8 = retur lei,
tip 9 = retur valuta), unde utilizatorul alege **facturile sursa la nivel de document**, iar linia
de articole se populeaza integral din acele facturi.
## 1. Punctul de intrare si cursorul Oracle
Tile-ul de pe ecranul principal de facturare, `Page2.Cw1` (`COMUN\clase\ofundal_facturare.vc2:882-884`):
```
PROCEDURE Page2.Cw1.do_actiune
DO facturare_lista_de_preturi IN oproceduri_facturare.prg
ENDPROC
```
`facturare_lista_de_preturi` (`COMUN\programe\oproceduri_facturare.prg:114-116`) = `Do politica.mpr`,
care ruleaza meniul shortcut generat din `Meniuri\politica.mn2:14-15,45-46`:
```
DEFINE BAR 2 OF Shortcut PROMPT "\<Retur factura in lei"
ON SELECTION BAR 2 OF Shortcut factureaza(8)
...
DEFINE BAR 9 OF Shortcut PROMPT "Re\<tur factura in valuta"
ON SELECTION BAR 9 OF Shortcut factureaza(9)
```
Deci `factureaza(8)` / `factureaza(9)` sunt apelate direct, fara `toFactura` (nu e copiere).
Comentariul din `factureaza` (`COMUN\programe\ofacturare.prg:134-135`) confirma explicit numerotarea:
```
** (25007,8) - retur factura in lei ( 25017 )
** (25008,9) - retur factura in valuta ( 25018 )
```
In `Do Case` pe `tnTip` din `factureaza` (`ofacturare.prg:306-307`):
```
Case Inlist(tnTip, 8, 9, 24) && 8,9 = facturi de retur, 24 = aviz retur
lcSqlCursor = [{call ] + gcS + [.pack_facturare.cursor_retur(?poDate.in_valuta,?poDate.listaid,?gnIdUtil)}]
```
executat prin `goExecutor.oExecute(lcSqlCursor, [crsarticole])` (`ofacturare.prg:310-311`) — acelasi
cursor `crsarticole` folosit si de `cursor_preturi`/`cursor_comanda`/`cursor_contract` pentru
celelalte tipuri. Nota: `Do Case` are inaintea acestei ramuri o ramura separata `Case m.llCopiere`
(`ofacturare.prg:268`) care foloseste `pack_facturare.cursor_retur_document(...)` — dar `llCopiere`
e `.T.` doar cand `factureaza()` primeste un al doilea parametru `toFactura` (obiect), adica la
copiere de document, nu la intrarea normala prin meniu pentru tip 8/9 (`llCopiere = (Type('toFactura')='O')`,
`ofacturare.prg:111`). Vezi si punctul 6.
`nIdTipDoc` = 5 (FACTURA, `ofacturare.prg:193`, `tnTip<21`), formularul de date antet este
`frm_date_factura` (`ofacturare.prg:222-223`, acelasi caz `tnTip<21`).
## 2. Alegerea facturilor sursa
Formularul `frm_date_factura` (`COMUN\clase\ofacturare.vc2:8482`) are metoda dedicata
`do_cauta_facturi` (`ofacturare.vc2:9173-9212`):
```
Case Empty(Nvl(poDate.id_client,0))
amessagebox("Nu ati ales clientul!",...)
Case Empty(Nvl(poDate.id_valuta,0)) And poDate.tip = 9
amessagebox("Nu ati ales valuta!",...)
OTHERWISE
lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client,poDate.in_valuta,poDate.id_valuta,.T.)
If !Empty(lcXMLFacturi) and gnButon = 1
Xmltocursor(lcXMLFacturi, "crsFacturiTemp")
...
poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")
poDate.descriere = cursor2lista("crsFacturiTemp", "numar_act", ",")
...
poDate.text_aditional = Iif(poDate.tip=8,[RETUR FACTURA ],[REFUND INVOICE FOR ]) + poDate.descriere
```
Dialogul e `caut_facturi_multiple_client` (`COMUN\programe\oproceduri_facturare.prg:2091-2121`),
un browse generic `cauta_alfa` cu titlu **"Alegeti facturile (mouse-click pe numar sau apasati SPACE)"**
(`:2104`, selectie multipla — `lnTipReturn = Iif(tlFacturiMultiple,1,0)`, apelat cu `tlFacturiMultiple=.T.`).
Criteriile SQL (`:2108-2111`):
```
lcSelect = [select serie_act,numar_act,data_act,dataora,id_vanzare from ] + gcS + [.fact_vfacturi ]
lcFiltruOriginal = [sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) ] + gcCondSucursala +
lcFiltruPart + lcFiltruValuta
```
adica **client** (`id_part`, obligatoriu ales inainte) si **valuta** (`in_valuta`/`id_valuta`, doar
daca `tnInValuta` e setat) — nu exista filtru SQL pe perioada sau serie/numar in interogare (coloanele
`Serie act, Numar act, Data, Data inreg.` sunt doar afisate/sortabile in browse-ul generic, filtrarea
pe ele e comportament generic al `cauta_alfa`, neverificat mecanismul intern). Sursa exclude explicit
tipurile de retur (`tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)`) — nu se poate face retur dintr-un retur.
**Selectie multipla**: se aduna prin `cursor2lista("crsFacturiTemp","id_vanzare",",")` intr-un
singur string CSV in `poDate.listaid` (id-urile facturilor alese), respectiv
`cursor2lista(...,"numar_act",",")` in `poDate.descriere` (afisat apoi pe antetul liniilor si in
titlul formularului de articole, `ofacturare.vc2:15022-15023`: `[ * Retur pentru facturile : ] + poDate.descriere`).
Validare obligatorie inainte de a continua (`frm_date_factura.inainte_de_do_termin`, `ofacturare.vc2:9523-9526`):
```
Case Empty(Nvl(poDate.descriere,[])) And Inlist(poDate.tip,8,9)
amessagebox("Nu ati ales factura/facturile pentru care se face returul!",48,"Atentie")
```
## 3. Popularea liniilor: gestiune si pret
`pack_facturare.cursor_retur` e un wrapper subtire peste implementarea reala
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:3943-3956`):
```
PROCEDURE cursor_retur(V_IN_VALUTA, V_LISTAID, V_ID_UTIL, V_CURSOR) IS
V_COPIERE NUMBER := 0;
V_PROFORMA NUMBER := 0;
BEGIN
pack_facturare.cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE, V_PROFORMA, V_ID_UTIL, V_CURSOR);
END;
```
`cursor_retur_document` (`:3958-4071`) selecteaza direct din `VANZARI_DETALII` (liniile facturilor
originale), filtrat pe `A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)` unde `CRS` e lista de id-uri
din `V_LISTAID` (= `poDate.listaid`, adica exact facturile alese la pasul 2, `:4059-4064`):
```sql
FROM VANZARI_DETALII A1 LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE ...
WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)
```
Coloane relevante, confirmate ca provin direct din linia facturii originale:
- **gestiune**: `nvl(A.ID_GESTIUNE, 0) as ID_GESTIUNE` (`:4034`), din `A1.ID_GESTIUNE` (`VANZARI_DETALII.ID_GESTIUNE`
al liniei originale, `:4055`) — confirma afirmatia lui Marius: gestiunea vine din factura sursa.
- **pret de achizitie**: `A.PRET_ACHIZITIE` (`:4035`), din `A1.PRET_ACHIZITIE` (`:4056`) —
preluat neschimbat din linia originala, fara recalcul de curs.
- **pret**: coloana `PRET` (`:4016-4024`) e pretul de vanzare al liniei originale, recalculat pe
cursul valutar daca moneda nu e nationala (`ROUND(A.CURS * ROUND(A.PRET,...) / A.MULTIPLICATOR, ...)`,
altfel `ROUND(A.PRET,...)`; plus `PRET_VAL` (`:4025-4030`) — valoarea in valuta straina, cand e cazul.
- `GESTIONABIL` (`:4002-4009`) pentru cazul `V_COPIERE=0` (retur, ramura efectiv folosita de
`cursor_retur`): `A.GESTIONABIL` = `NVL2(A1.ID_GESTIUNE,1,0)` (subselect intern, `:4048`) — gestionabil
doar daca linia originala avea gestiune.
Concluzie Q3: **ambele preturi** trec prin, atat cel de vanzare (`PRET`/`PRET_VAL`, ajustat pe curs)
cat si cel de achizitie (`PRET_ACHIZITIE`, neschimbat) — plus gestiunea originala (`ID_GESTIUNE`).
Numele coloanelor Oracle -> cate un camp cu acelasi nume in cursorul VFP `crsarticole` (maparea VFP
exacta camp-cu-camp nu a fost trasata pana in `crsfactura`; nu era necesara pentru raspuns).
## 4. Ce se poate face manual (`frm_facturare_articole`, `COMUN\clase\ofacturare.vc2:10968`)
- **Stergere linie**: `do_sterge` (`:14608-14693`) nu e restrictionat pe tip; pentru retur
(`Case Inlist(poDate.tip,8,9,24)`, `:14658-14659`) cantitatea stearsa se reintoarce in cursorul
sursa (`Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`) — deci
**da, se pot sterge linii aduse**, iar cantitatea redevine disponibila pentru re-adaugare.
- **Retur partial (modificare cantitate)**: coloana de cantitate din grila sursa isi schimba titlul
in "Cant. max. de returnat" pentru tip 8/9 (`:15236-15239`); validarea in `do_verifica_articol`
(`:14743-14754`, `llRetur = Inlist(poDate.tip,8,9,24)`) respinge doar cazul in care cantitatea
ceruta ar depasi maximul returnabil — utilizatorul poate introduce orice cantitate <= maxim,
deci **da, retur partial e posibil**. `do_modifica` (`:13746-13914`) trateaza explicit
`Inlist(poDate.tip,8,9,24)` la `:13788,13895` fara blocaj suplimentar.
- **Adaugare linie care NU e in facturile sursa**: `do_adauga_articol` (`:12813-13086`) preia
articolul mereu din cursorul sursa al gridului (`lcCursor = [crsarticole]`, apoi
`Select (lcCursor) / Scatter Name poArticol`, `:12843-12851`) — pentru tip 8/9 acest `crsarticole`
e chiar rezultatul `cursor_retur` de la punctul 3, deci contine **doar** liniile facturilor alese.
Nu exista pe acest formular o cale de a alege un articol din lista de preturi completa cand
`poDate.tip` e 8/9 (spre deosebire de contract/comanda unde `crsarticole` ramane lista de preturi
libera) — **nu se poate adauga o linie in afara facturilor sursa**, prin design-ul continutului
cursorului, nu printr-o validare explicita de interzicere.
## 5. Aviz de retur (tip 24)
Acelasi mecanism de baza: `Inlist(tnTip,8,9,24)` la `ofacturare.prg:306-307` (acelasi
`pack_facturare.cursor_retur`) si aceleasi ramuri `Inlist(poDate.tip,8,9,24)` in
`frm_facturare_articole` (`do_adauga_articol:12863`, `do_alege_stoc:13230`, `do_modifica:13788,13895`,
`do_verifica_articol:14743`, `do_sterge:14658`). Diferenta: la antet, `nIdTipDoc` = 6 (AVIZ, ramura
`Otherwise` la `ofacturare.prg:194-195`, pentru ca 24 nu e `<21` si nu e in `Inlist(45,48,49,51,52)`),
iar formularul de date e `frm_date_aviz` (`ofacturare.prg:225`, ramura `Otherwise`), nu
`frm_date_factura`. Punctul de intrare pentru tip 24: `emitere_aviz_clienti` cu `tnTip=7`
(`COMUN\programe\oproceduri_facturare.prg:221-222`, `factureaza(24) && Retur aviz`), apelat din
tile-ul `Page3.Cw1` -> `aviz_clienti.mpr` (neverificat detaliat, nu s-a insistat conform cerintei).
## 6. Relatia cu `But_retur`
Mecanisme **complet separate**, confirmate pe cod:
- **Vizibilitate**: `but_retur` e vizibil doar pentru tip 1/5/7/10 (`ofacturare.vc2:15127`,
`This.but_retur.Visible = .T.` in ramura `Case Inlist(poDate.tip,1,5,7,10)`) — pe un document
tip 8/9 acest buton nu exista deloc in UI (ramura lui la `Init` e alta, `:15236`).
- **Formular de alegere a facturii sursa**: `But_retur` foloseste `caut_facturi_multiple_client_articol`
(`oproceduri_facturare.prg:2124-2159`, filtrat suplimentar pe `b.id_articol = ?pnIdArticol` —
cautare per-articol, cu titlu simplu "Alegeti factura"), tip 8/9 document foloseste
`caut_facturi_multiple_client` (`:2091-2121`, fara filtru pe articol, multi-select, titlu
"Alegeti facturile ..."). Doua functii diferite, in acelasi fisier, cu semnaturi aproape identice
dar interogari SQL diferite.
- **Procedura Oracle de populare a liniilor**: documentul tip 8/9 foloseste
`pack_facturare.cursor_retur` la nivel de document intreg (populeaza `crsarticole` cu toate
liniile facturilor alese, punctul 3 de mai sus). `But_retur` nu apeleaza `cursor_retur`/
`cursor_retur_document` deloc — foloseste `pack_facturare.cursor_gestiuni_articol_retur`
(`ofacturare.vc2:13230`, per articol individual, in `do_alege_stoc`) pentru a alege
gestiunea/seria/lotul de returnat pentru articolul deja selectat din lista de preturi a facturii
normale curente.
- **Punct comun**: niciunul direct. Singurul element comun e conventia de semn a cantitatii
(`Iif(Inlist(poDate.tip,8,9,24),(-1),1)`, folosita si in `do_alege_stoc` al `But_retur`,
`:13279-13309`, si pe formularul `frm_facturare_articole2`, `:17574-17585`) si textul UI
("cant. max. de returnat"). Concluzie: sunt doua fluxuri de cod independente care ating aceeasi
clasa de formular (`frm_facturare_articole`) dar prin metode si proceduri Oracle diferite.