Files
roafacturare/docs/cercetare/legatura_linie_retur.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

16 KiB

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

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

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:

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

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