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

229 lines
16 KiB
Markdown

# 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.