# Cercetare: legatura "linie de retur -> linie/factura originala" se persista undeva? ## Verdict (10 randuri) **NU se persista la nivel de linie. PARTIAL la nivel de document, si numai cand a fost aleasa o singura factura sursa.** Cursorul Oracle care populeaza grila de retur pentru N.1 (documente tip 8/9/24, `pack_facturare.cursor_retur_document`, `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3949-4062`) **nu selecteaza deloc** `A1.ID_VANZARE` sau `A1.ID_VANZARE_DET` din `VANZARI_DETALII` — legatura cu factura/linia sursa se pierde chiar in query-ul care aduce datele in VFP, inainte sa existe vreo sansa sa fie afisata. INSERT-ul final in `VANZARI_DETALII` (`scrie_in_vanzari`, documentat deja in `rec_cale_vanzari_detalii.md:105-113`) nu are nicio coloana de tip sursa. Exista in schimb un link **la nivel de document intreg**: `VANZARI_CORESP` (`TIP=3`, `ID_VANZARE_FACT`=documentul de retur, `ID_VANZARE_AVIZ`=fiecare factura sursa aleasa) — dar cand utilizatorul alege mai multe facturi sursa deodata (selectie multipla, suportata explicit de dialog), acest link iti da **multimea** de facturi posibile, nu factura exacta a liniei. Pentru N.2 (`But_retur`, per articol) nu exista niciun document nou scris separat — returul e o linie in factura normala curenta, iar "sursa" e verificata doar la nivel de stoc (`RUL`/`STOC` filtrat pe `COD` legat de `VANZARI.ID_VANZARE`), nu persista ca atribut al liniei noi. Concluzie pentru S4f: **se livreaza fara coloana de provenienta pe linie**; cel mult se poate afisa, cand e un singur document sursa, "factura de retur X provine din factura Y" la nivel de document (din `VANZARI_CORESP`), nu per linie. ## 1. Ce face Oracle cu `poDate.listaid` Doua cai, in functie de mecanism: **N.1 (document, tip 8/9/24)**: `poDate.listaid` = CSV de `id_vanzare` (facturile alese la pasul de cautare multipla, `ofacturare.vc2:9200`: `poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")`). Ajunge la Oracle ca parametru `V_LISTAID` in `pack_facturare.cursor_retur(V_IN_VALUTA, V_LISTAID, V_ID_UTIL, V_CURSOR)` (`ff_...sql:3934-3947`), care e doar un wrapper peste `cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE=0, V_PROFORMA, V_ID_UTIL, V_CURSOR)` (`:3949-4062`). Acolo `V_LISTAID` devine CTE-ul `CRS` (`:3962-3964`): ```sql WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR))) ``` folosit doar ca filtru: `WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)` (`:4054-4055`). **Filtru, nu persistare** — dupa acest `WHERE`, `ID_VANZARE` nu mai apare deloc in lista de coloane a `SELECT`-ului extern (`:3965-4028`: `ID_C` (=`ROWNUM`, sintetic), `ID_ARTICOL`, `LOT`, `SERIE`, `ID_POL`, preturi, `GESTIONABIL`, `CANTITATE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`... dar nu `ID_VANZARE` si nu `ID_VANZARE_DET`). Cursorul primit de VFP in `crsarticole` nu are, deci, nicio coloana care sa spuna din ce factura/linie vine randul. Separat, la scrierea efectiva a documentului de retur, `scrie_corespondente_vanzari(3)` (`:14834-14836`, apelata din `finalizeaza_factura`) foloseste tot `pack_facturare.clistaid` (= acelasi `poDate.listaid`, tinut in variabila de sesiune a pachetului) ca sa scrie in `VANZARI_CORESP` (`:15481-15516`): ```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,','))); ``` Asta scrie **cate un rand per factura sursa aleasa** (nu per linie), cu noul document de retur ca `ID_VANZARE_FACT` si fiecare sursa ca `ID_VANZARE_AVIZ` (numele coloanei e generic, reutilizat si pentru perechi aviz-factura, `TIP=1/2`, vezi `sterge_factura:5452-5457,5582-5585`). **N.2 (`But_retur`, per articol)**: `poDate.listaid` = `"ID_ARTICOL:ID_VANZARE"` (una sau mai multe perechi CSV, `ofacturare.vc2:12888`: `thisform.cListaIdArticoleRetur = ... + STR(poArticol.id_articol) + ':' + poDate.listaid`, trimis la Oracle la `:13978`). In `pack_facturare` (verificat in ramura `WHEN V_CANTE < 0 and pack_facturare.clistaid is not null and instr(pack_facturare.clistaid, ':') > 0`, `ff_...sql:8142-8212`) e folosit ca filtru pentru calculul cantitatii disponibile de retur din rulaj (`RUL`), NU ca sa scrie o legatura: ```sql AND A.COD IN (SELECT COD FROM VANZARI WHERE ID_VANZARE IN (SELECT id_vanzare FROM (SELECT CAST(GETWORDNUM(id_articol_id_vanzare,1,':') AS NUMBER(20,0)) id_articol, CAST(GETWORDNUM(id_articol_id_vanzare,2,':') AS NUMBER(20,0)) id_vanzare FROM (SELECT x AS id_articol_id_vanzare FROM table(cast(CHARC2COLLECTION(pack_facturare.clistaid,',') AS char_tab)))) WHERE id_articol = V_ID_ARTICOL)) ``` Foloseste `listaid` doar ca sa restranga `RUL` la miscarile de stoc (`A.COD`) legate de factura aleasa, ca sa calculeze **cantitatea inca disponibila in gestiune din acea vanzare** pentru articolul respectiv (`tab_stoc`, tip=1 in acel `SELECT`). Nu se scrie nicio linie noua de legatura in vreun tabel — e folosit exclusiv ca filtru intr-un calcul de disponibil. ## 2. Coloana de provenienta pe `VANZARI_DETALII` Nu exista. DDL-ul complet nu e disponibil in acest repo (nu exista `CREATE TABLE VANZARI_DETALII` in `docs/`; confirmat deja de cercetari anterioare — `docs/cercetare/discount_verificare2.md:104-111`, `COMUN\docs\cercetare\rec_tva_vanzari.md:19`), dar structura efectiva a fost verificata pe schema live in `docs/cercetare/rec_cale_vanzari_detalii.md:157-169` (interogare `all_tab_columns`, 08.08.2026): 35 de coloane, PK `ID_VANZARE_DET` (generat prin trigger `TRG_VANZARI_DET_BEFOINS` din `SEQ_VANZARI_DETALII`), FK logic `ID_VANZARE`. Lista coloanelor relevante citata acolo: `ID_ARTICOL`, `PRET`, `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`, `ID_VALUTA`, `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`, `TAXCODE`, `LOT`, `STERS`, `VALIDAT`, `DATAORA_VALID`, `ID_UTIL_VALID`, `ID_UTILS`, `DATAORAS`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT`, `PRETD`, `ID_VALUTAD`, `PRETV_ORIG`, `ID_CTR`, `ID_RATA`. **Nicio coloana** de tipul `ID_VANZARE_SURSA`, `ID_DETALIU_SURSA`, `ID_FACT_SURSA`, `ID_VANZARE_RETUR`, `ID_ORIGINAL` sau orice self-referinta catre o alta linie `VANZARI_DETALII`. Cautat explicit acele siruri in tot exportul `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (~28000 linii) si in `COMUN\docs\` — zero potriviri (vezi comanda de mai jos, sectiunea "Ramas de verificat"). Confirmarea independenta finala si cea mai directa: INSERT-ul care trece liniile din `VANZARI_DETALII_TEMP` in `VANZARI_DETALII` la finalizarea oricarui document (inclusiv retur, pentru ca `scrie_in_vanzari` e comun tuturor tipurilor) are lista de coloane explicita (`rec_cale_vanzari_detalii.md:105-113`): ```sql INSERT /*+ APPEND */ INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD, PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA) SELECT pack_facturare.nid_vanzare, ID_ARTICOL, ... FROM VANZARI_DETALII_TEMP WHERE ... ``` Nicio coloana sursa in lista. Cum `VANZARI_DETALII_TEMP` insasi e populata pentru retur din `cursor_retur_document` (care, cf. punctul 1, nu aduce `ID_VANZARE`/`ID_VANZARE_DET` sursa), legatura e pierduta cu doi pasi inainte de a ajunge la acest INSERT — nu doar "nu se scrie", ci "nu mai exista in date la momentul scrierii". ## 3. Tabel separat de legatura **Da, exista, dar la nivel de document, nu de linie**: `VANZARI_CORESP(ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP, STERS)`. Folosit pentru mai multe perechi de corespondenta, disambiguizate prin `TIP`: - `TIP=1`: factura scrisa dintr-un aviz (`scrie_corespondente_vanzari(1)`, la facturare din aviz, `:14826`); - `TIP=2`: aviz de retur (`scrie_corespondente_vanzari(2)`, `:14830`, la `ntip=24`); - `TIP=3`: **factura de retur** (`scrie_corespondente_vanzari(3)`, `:14834-14836`, la `ntip in (8,9)`) — exact cazul cerut de S4f. Verificarile de stergere din `sterge_factura` (`:5450-5494`) confirma semantica: interogheaza `VANZARI_CORESP WHERE ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3` pentru "exista facturi de retur pe aceasta factura?" — deci pentru o factura normala, `ID_VANZARE_AVIZ` (nume generic, refolosit) e ea insasi, iar `ID_VANZARE_FACT` gasit prin acea interogare e factura(le) de retur emise pe baza ei. Invers, pentru un document de retur dat, `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE ID_VANZARE_FACT = :id_retur AND TIP = 3` da **toate** facturile sursa alese la emiterea acelui retur (poate fi mai multe, cf. selectie multipla din `caut_facturi_multiple_client`, `docs/cercetare/factura_retur_document.md:67-85`). **Limita exacta**: cand pe un document de retur exista o singura factura sursa in `VANZARI_CORESP`, "factura originala" e determinata fara ambiguitate pentru **toate** liniile documentului de retur (pentru ca nu exista alt candidat). Cand exista mai multe (utilizatorul a bifat 2+ facturi la cautare), `VANZARI_CORESP` da multimea, dar nu se poate spune care linie de retur vine din care factura din multime — informatia care ar face diferenta (ID_VANZARE per linie in cursorul de populare) a fost deja aruncata la pasul 1/2. Nu exista niciun tabel `VANZARI_RETUR`, `RETUR_DETALII` sau `LEGATURI_DOCUMENTE`; cautate explicit in `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si in `COMUN\docs\` — zero potriviri. `DOCUMENTE.AVIZE` (coloana denormalizata pe `VANZARI`, nu tabel separat) e completata redundant tot din `VANZARI_CORESP` (`scrie_corespondente_vanzari:15505-15514`, `UPDATE VANZARI SET AVIZE = ...`) — tine text afisabil (serie+numar avize), nu un id structurat, si nu e populata pentru `TIP=3` (doar pentru avize, vezi apelul unic la acel `UPDATE` in corpul procedurii, comun tuturor `V_TIP`-urilor dar cu sens practic doar pentru avize-spre-factura). ## 4. Cum calculeaza serverul maximul returnabil **Raspunsul e diferit pe cele doua mecanisme, si niciunul nu se sprijina pe o legatura persistata linie-la-linie:** **N.1 (document)**: NU exista un calcul de "cat s-a mai returnat deja". Coloana pe care utilizatorul o vede ca "Cant. max. de returnat" (`ofacturare.vc2:15238,15243` — doar schimbare de caption pe capul de coloana, nu logica noua) e pur si simplu `CANTITATE` a liniei originale, asa cum vine din `cursor_retur_document` (`A1.CANTITATE`, `:4036`, filtrat doar pe `A1.STERS = 0`, fara nicio agregare cu alte retururi anterioare pe aceeasi linie). Validarea din `do_verifica_articol` (`:14743-14754`) nu face niciun calcul suplimentar de disponibil — blocheaza doar cazul `tnCantitate >= 0` cand esti in mod retur (semnul gresit), nu o depasire de maxim istoric. **Consecinta directa**: daca acelasi utilizator emite doua facturi de retur separate pe aceeasi factura sursa, a doua interogare `cursor_retur_document` va aduce din nou linia originala cu `CANTITATE` **intreaga**, neredusa de primul retur — nimic in cod nu scade sau marcheaza cat s-a returnat deja la acest nivel. Acesta e cel mai clar indiciu ca legatura linie-la-linie *nu* e tinuta nicaieri pentru N.1: daca ar fi fost tinuta, ar fi trebuit folosita exact aici, ca sa capeze cantitatea, si nu e. **N.2 (`But_retur`)**: exista un calcul real de disponibil, dar e un calcul de **stoc/rulaj**, nu de "cat s-a returnat pe acea linie". Ramura din `pack_facturare` citata la punctul 1 (`ff_...sql:8142-8212`) calculeaza cantitatea inca prezenta in `RUL` (miscari de stoc) care a venit din vanzarea aleasa (`RUL.COD` legat prin `VANZARI.COD`, filtrat pe `id_articol:id_vanzare` din `listaid`), plus `STOC`/`RUL_TEMP` pentru cazul general. E un calcul valid de "poti scoate din gestiune atat cat inca exista acolo cu provenienta asta", sprijinit pe legatura persistata `RUL.COD -> VANZARI.COD` (asta e adevarata "legatura care exista" pentru N.2) — dar e o legatura la nivelul miscarii de stoc, nu un contor "cantitate returnata" pe `VANZARI_DETALII`, si nu se translateaza in nicio coloana de provenienta pe linia noua scrisa. ## 5. Ce se poate afisa efectiv - **Numarul/seria facturii originale la nivel de document de retur, cand exista o singura factura sursa**: SE POATE, din `VANZARI_CORESP` (`TIP=3`) join `VANZARI` pe `ID_VANZARE_AVIZ`. Interogare de rulat pe baza vie (nu verificata aici, doar formulata): ```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; ``` Daca randul e unic, se poate afisa "provine din factura X" **pe capul documentului de retur** (nu pe linie — toate liniile ar arata aceeasi sursa, pentru ca e singura posibila). - **Cand documentul de retur are 2+ facturi sursa** (selectie multipla permisa de dialog): se poate afisa lista de facturi sursa posibile (tot din `VANZARI_CORESP`), dar NU care linie vine din care factura din lista — informatia nu exista. - **Numarul facturii originale pe fiecare linie individuala de retur**: NU SE POATE, in niciun caz — nici cand exista un singur document sursa (pentru ca "linie cu linie" nu inseamna nimic diferit de "documentul cu documentul" atunci, dar afirmatia stricta "aceasta linie de retur vine din linia Y a facturii" nu poate fi demonstrata din date, doar presupusa cand exista un singur candidat), si sigur nu cand exista mai multe facturi sursa sau cand linia e libera (permisa explicit de decizia 22). - Pentru N.2 (retur in factura normala, tip 1/5/7/10): nu exista deloc "document de retur" separat de arata provenienta — linia de retur e o linie normala (cu cantitate negativa) in factura curenta; singura urma a sursei e `RUL.COD`/`VANZARI.COD` folosita tranzitoriu la calculul de disponibil, nu persistata pe linia noua din `VANZARI_DETALII`. ## Verdict pentru plan (S4f) **NU se persista legatura linie-la-linie.** Pentru documentul de retur (N.1), se poate afisa "factura sursa" **la nivel de document**, din `VANZARI_CORESP` (TIP=3), **doar cand exista exact o singura factura sursa aleasa** — cazul cu selectie multipla da doar multimea, fara atribuire per linie. Pe linia individuala de retur nu se poate afisa nimic verificabil din date, in niciun caz. Recomandare pentru S4f: se livreaza fara coloana de provenienta pe linie (cf. deciziei deja scrise in plan, "nu se inventeaza"); daca se vrea un gest minim, singura afisare sustenabila cu date e un text de tip "Retur pentru factura: X" pe **capul** documentului (deja exista, cf. `factura_retur_document.md:85`: `poDate.text_aditional = ... + poDate.descriere`, unde `poDate.descriere` e lista serie+numar a facturilor alese) — nu pe linie, si nu nou, e deja acolo. ## Ramas de verificat pe baza vie - Nu s-a interogat live daca `VANZARI_CORESP.TIP=3` e scris consecvent pentru fiecare factura de retur emisa istoric (posibil sa existe date vechi scrise inainte ca aceasta ramura sa existe, sau prin alt cod neexaminat aici) — de rulat: ```sql SELECT COUNT(*) FROM VANZARI v WHERE v.TIP IN (8,9) AND v.STERS = 0 AND NOT EXISTS (SELECT 1 FROM VANZARI_CORESP c WHERE c.ID_VANZARE_FACT = v.ID_VANZARE AND c.TIP = 3); ``` Daca da >0, exista facturi de retur fara nicio legatura de document persistata, nici macar cea partiala descrisa mai sus. - Nu s-a verificat live cate din facturile de retur existente au >1 factura sursa in `VANZARI_CORESP` (adica cate din documentele reale ar cadea in cazul "multime, nu atribuire") — utila pentru a decide cat de des s-ar aplica de fapt afisarea propusa la punctul 5. - Nu s-a gasit (si nici nu era in scop) un mecanism separat pentru avize de retur custodie (`tip=50`, marcat "in lucru" in `tipuri_documente_facturare.md`) — daca S4f ajunge sa acopere si acel tip, de recercetat separat. - Subagentul de research auxiliar lansat pentru confirmarea independenta a definitiei `caut_facturi_multiple_client`/`caut_facturi_multiple_client_articol` nu a returnat un raport utilizabil (rezultat gol la finalizare); definitiile au fost gasite si citite direct in aceasta sesiune, in `COMUN\programe\oproceduri_facturare.prg:2091-2160`, deci nu blocheaza verdictul, dar nu a adaugat nimic peste ce e deja in acest raport.