Files
roafacturare/docs/cercetare/factura_retur_document.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:17:17 +03:00

12 KiB

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):

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.