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

273 lines
18 KiB
Markdown

# Cercetare: calea VFP->Oracle la emitere si calea propusa pentru editare (#6, punctul E.4)
Sursa: interogari SELECT proaspete pe `MARIUSM_AUTO@ROA_CENTRAL` (08.08.2026), acelasi mediu si
aceeasi versiune de baza ca in `rec_s5_oracle_vanzari.md` (nu s-a schimbat nimic intre cele doua
cercetari din aceeasi zi). Numerele de linie PL/SQL de mai jos sunt dintr-un export propriu, facut
in aceasta sesiune, din `all_source` pentru `PACK_FACTURARE` (schema/pachet identic cu cel din
raportul anterior). Partea VFP e din cache-ul text `.vc2` din proiect (nu binarul).
Aceasta cercetare inchide golul lasat de `rec_s5_oracle_vanzari.md`, sectiunea E.4: cum ajung
liniile facturii in Oracle la emitere si care e calea corecta pentru editare.
## 1. Calea de azi, capat la capat
### 1.1 VFP: cine populeaza `VANZARI_DETALII_TEMP`
Raspuns: **Oracle**, prin apeluri per-linie facute din VFP, nu VFP direct prin INSERT.
- `frm_facturare_articole.do_scrie_articole` (`COMUN\clase\ofacturare.vc2:13967-14195`):
- `:13981-13999` cheama `pack_facturare.initializeaza_date_factura(...)` (antetul documentului,
tine minte parametrii in stare de sesiune pe pachet: `pack_facturare.ntip`, `nid_sucursala`,
`nid_util` etc. — folosite mai jos de `adauga_articol_factura`).
- `:14036-14115`: `SELECT crsfactura` -> `SCAN`, cate un `Scatter Name poArt MEMO` per linie, apoi
pentru fiecare linie fara rata de contract (`ELSE` la `:14051`) construieste text PL/SQL literal
(nu `{call}` cu parametri legati) si apeleaza
`pack_facturare.adauga_articol_factura(id_temp, id_articol, serie, explicatie, id_pol,
id_gestiune, pret_achizitie, pretd, id_valutad, pret, id_valuta, cu_tva, gestionabil,
cantitate, discount, cont, curs, multiplicator, id_jtva_coloana, id_part_rez, id_lucrare_rez,
pretv_orig, id_set_fact, id_ctr, gnIdUtil, taxcode, lot)` (`:14069-14091`), executat imediat
prin `goExecutor.oExecute(lcSql)` (`:14105`) — **un apel Oracle per linie de factura**, in
bucla, nu un singur apel cu tot cursorul.
- Pentru liniile din seturi: `pack_facturare.initializeaza_seturi_temp` + un apel
`pack_facturare.adauga_articol_set(...)` per linie de set (`:14117-14154`), scrie in
`VANZARI_SETURI_TEMP`.
- `frm_facturare_articole.do_scrie_factura` (`:14197-14554`): dupa ce toate liniile au fost urcate
in TEMP prin apelurile de mai sus, apeleaza finalizarea documentului —
`pack_facturare.scrie_factura2(...)` (facturi, `:14345`/`:14373`) sau
`pack_facturare.scrie_proforma(...)` (`:14286`) sau `scrie_factura_avize(...)`/
`scrie_factura_avize_retur(...)` dupa tipul documentului — **un singur apel**, fara sa mai
transmita liniile (ele sunt deja in `VANZARI_DETALII_TEMP`, populata de bucla anterioara, in
ACEEASI sesiune/tranzactie Oracle).
### 1.2 Oracle: `adauga_articol_factura` — nu doar INSERT, RECALCULEAZA din sursa originala
Verificat sursa completa a procedurii publice `pack_facturare.adauga_articol_factura` (semnatura cu
`V_ID_TEMP` ca prim parametru, cea apelata de VFP la `:14069`) — **nu** e un simplu INSERT cu
valorile primite de la VFP. Ramifica pe `pack_facturare.ntip` (starea de sesiune setata la
`initializeaza_date_factura`) si **re-deriva** `V_PRET`, `V_PROC_TVAV`, `V_ID_VALUTA`,
`V_PRETURI_CU_TVA`, `V_IN_STOC` direct din documentul-sursa:
- `ntip IN (3,21,28,42,47)` (facturare din comenzi): `SELECT ... FROM COMENZI_ELEMENTE ...`
- `ntip = 4` (facturare din avize): `SELECT ... FROM VANZARI_DETALII ...` (avizul deja scris)
- `ntip = 45` (restaurant): calcul din `JTVA_COLOANE` + `CRM_POLITICI_PRET_ART`
- `V_OPT_FACTURARE = 3` (contract): `SELECT ... FROM CTR_ARTICOLE ...`
- fallback (`ELSE`): doar cota TVA din `JTVA_COLOANE`, restul (`V_PRET`, `V_ID_VALUTA`,
`V_PRETURI_CU_TVA`, `V_IN_STOC`) preluate ca atare din parametrii transmisi de VFP.
Abia dupa acest bloc `CASE` urmeaza INSERT-ul propriu-zis:
```sql
INSERT INTO VANZARI_DETALII_TEMP
(ID_TEMP, ID_ARTICOL, SERIE, LOT, EXPLICATIA, ID_POL, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
PRET, PRET_CU_TVA, PROC_TVAV, CANTITATE, DISCOUNT_UNITAR, ID_GESTIUNE, CONT, ID_VALUTA,
CURS, MULTIPLICATOR, ID_JTVA_COLOANA, IN_STOC, ID_VANZARE_SET, ID_PART_REZ, ID_LUCRARE_REZ,
PRETV_ORIG, CUSTODIE, ID_CTR, ID_UTIL, TAXCODE)
VALUES (...)
```
(am confirmat integral corpul, INSERT-ul e ultimul bloc din procedura, imediat dupa `END CASE`).
**Consecinta directa pentru #6**: `adauga_articol_factura` NU poate fi reutilizata ca atare pentru
editare — depinde de starea de sesiune `pack_facturare.ntip`/`clistaid`/`id_ctr` care descrie
DOCUMENTUL SURSA de la emitere (comanda/aviz/contract), stare care nu exista si nu are sens la o
editare ulterioara a facturii deja emise. Orice procedura noua pentru editare trebuie sa scrie
direct in `VANZARI_DETALII` (tabela reala), nu prin acest drum.
Exista si `sterge_articol_factura(V_ID_TEMP, V_ID_UTIL)` — `DELETE FROM VANZARI_DETALII_TEMP WHERE
ID_TEMP = V_ID_TEMP` — folosita doar cat timp factura e in curs de compunere (inainte de emitere),
nu dupa.
### 1.3 Oracle: `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari` — trecerea TEMP -> real
`scrie_factura2` (PACK_FACTURARE, corpul activ, nu varianta comentata din cod):
- `:70` `pack_contafin.sterge_temp_actrul()`, seteaza `pack_facturare.ntotftva/ntottva` din
parametri (valori calculate in VFP).
- `:84` `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP` — citeste TOATA tabela
temp intr-un array PL/SQL, fara filtru pe sesiune (GTT-ul e oricum izolat per sesiune/tranzactie).
- Bucla pe `tab_detalii`, ramificata tot pe `ntip` (transfer intre subunitati, restaurant etc.) —
pentru cazul standard de factura apeleaza mai departe spre `pack_facturare.finalizeaza_factura`.
`finalizeaza_factura` (nu comentata):
```sql
pack_facturare.initializeaza_scriere_actrul(V_DATAORA);
pack_facturare.scrie_in_vanzari(V_DISCOUNT_FACTURA, V_ID_DELEGAT, V_ID_MASINA, V_ID_FACTURARE,
V_LISTARE_DETALIATA, V_DATAORA_EXP, V_ID_AGENT, V_TEXT_ADITIONAL, pack_facturare.nid_vanzare);
pack_facturare.finalizeaza_scriere_actrul();
-- update vanzari set id_fact = ... (completare id_fact dupa scrierea in contabilitate)
```
`scrie_in_vanzari` — gasit exact mecanismul care lipsea din raportul anterior:
- `INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare`
(randul parinte, un rand per document din bucla pe `tab_vanz`, tabela interna cu documentele de
scris — cazul normal e un singur document).
- `pack_facturare.scrie_cursuri(pack_facturare.nid_vanzare)`.
- **Trecerea efectiva TEMP -> real**:
```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, 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
FROM VANZARI_DETALII_TEMP
WHERE ID_COMANDA = tab_vanz(i).id_comanda AND NUMAR_ACT = tab_vanz(i).numar_act
ORDER BY ID_TEMP;
```
Cheia de potrivire intre randul nou din `VANZARI` si liniile lui din TEMP e
`(ID_COMANDA, NUMAR_ACT)` — nu `ID_TEMP` direct — pentru ca un singur apel poate scrie mai multe
documente `VANZARI` deodata (facturare pe mai multe comenzi/numere de act simultan); fiecare
document isi ia doar liniile care se potrivesc.
- Coloanele COPIATE in `VANZARI_DETALII` sunt un subset din `VANZARI_DETALII_TEMP` (16 coloane) —
`ID_TEMP`, `ID_CTR`, `ID_UTIL`, `TAXCODE`, `LOT`, `ID_VANZARE_SET`, `ID_PART_REZ`,
`ID_LUCRARE_REZ`, `PRETV_ORIG`, `CUSTODIE`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`
raman DOAR in TEMP, nu se copiaza in tabela reala (`VANZARI_DETALII` nu are coloanele `ID_TEMP`,
`NUMAR_ACT`, `ID_COMANDA` etc. — confirmat din `all_tab_columns`, vezi 2.2).
- Urmeaza blocul de agregare (`SELECT INTO` din `VANZARI_DETALII_TEMP`, deja documentat in
`rec_s5_oracle_vanzari.md` A.3-A.4) si `UPDATE VANZARI SET total_fara_tva=..., ...` cu totalurile
denormalizate.
**PK-ul `VANZARI_DETALII.ID_VANZARE_DET`** se genereaza automat: trigger `TRG_VANZARI_DET_BEFOINS`
(`BEFORE INSERT ... FOR EACH ROW`, verificat prin `dbms_metadata.get_ddl`) face exclusiv
`SELECT SEQ_VANZARI_DETALII.NEXTVAL INTO :NEW.ID_VANZARE_DET FROM DUAL` — nu seteaza alte coloane.
Deci **orice INSERT nou in `VANZARI_DETALII`** (inclusiv unul scris pentru #6, la adaugare de linie
noua la editare) primeste automat PK-ul corect fara sa fie nevoie de secventa apelata manual din
codul de editare.
## 2. Natura tabelei temp si structura
### 2.1 `VANZARI_DETALII_TEMP` — Global Temporary Table, `ON COMMIT DELETE ROWS`
Confirmat din `all_tables`:
| TABLE_NAME | TEMPORARY | DURATION |
|---|---|---|
| VANZARI | N | — |
| VANZARI_DETALII | N | — |
| VANZARI_DETALII_TEMP | **Y** | **SYS$TRANSACTION** |
| VANZARI_SETURI_TEMP | **Y** | **SYS$TRANSACTION** |
`DURATION = SYS$TRANSACTION` inseamna GTT cu `ON COMMIT DELETE ROWS` — randurile dispar la commit
(sau rollback), nu doar la sfarsit de sesiune. **Consecinta directa**: orice solutie care ar
reutiliza `VANZARI_DETALII_TEMP` pentru editare (#6) trebuie sa umple tabela SI sa consume
rezultatul in ACEEASI tranzactie — nu poate fi umpluta intr-un pas si citita in altul, ca in fluxul
de emitere unde umplere (bucla `adauga_articol_factura`) si consum (`scrie_factura2`) se intampla
deja in aceeasi conexiune/tranzactie, inainte de commit.
### 2.2 Coloane: `VANZARI_DETALII` vs `VANZARI_DETALII_TEMP`
`VANZARI_DETALII` (35 coloane, din `all_tab_columns`) — cheie primara `ID_VANZARE_DET` (NOT NULL,
generata din secventa prin trigger), FK logic `ID_VANZARE` (NOT NULL). Coloane relevante pentru
editare: `PRET` (NOT NULL), `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `STERS`
(NOT NULL), `VALIDAT`/`DATAORA_VALID`/`ID_UTIL_VALID`, `ID_UTILS`/`DATAORAS` (audit),
`DIFERENTA` (NOT NULL), `CUSTODIE`/`DESCARCAT` (NOT NULL) — ultimele patru nu au valoare implicita
vizibila in trigger, deci probabil `DEFAULT` la nivel de coloana (nu s-a verificat separat, nu era
in scope).
`VANZARI_DETALII_TEMP` (28 coloane) — **fara PK real** (`ID_TEMP` e generat in VFP, folosit doar ca
identificator temporar de linie in cadrul sesiunii curente de compunere a facturii), **fara**
`ID_VANZARE`/`ID_VANZARE_DET`/`STERS`/`VALIDAT` (nu se aplica inainte de a exista documentul
parinte). Are in schimb coloane specifice etapei de compunere, care NU exista in tabela reala:
`ID_TEMP`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`, `ID_UTIL` (vs `ID_UTILS` in cea reala).
Coloanele comune folosite de editare (S4): `ID_ARTICOL`, `PRET`, `CANTITATE`, `PRET_CU_TVA`,
`DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`, `ID_VALUTA`, `CURS` (doar in TEMP; in
`VANZARI_DETALII` nu exista `CURS` per linie — cursul e doar pe document, in `VANZARI_CURSURI`/
`VANZARI.CURS`), `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`/`EXPLICATIA` (nume diferit!), `TAXCODE`,
`LOT`.
## 3. Stergerea si adaugarea de linii
### 3.1 Stergere — mecanism EXISTENT azi doar la nivel de document intreg, NU per linie
Cautat explicit orice `UPDATE VANZARI_DETALII SET STERS` in `PACK_FACTURARE` — gasite doar doua
locuri, ambele sterg TOATE liniile unui document, niciodata o singura linie:
- `sterge_factura(V_ID_VANZARE, ...)`:
`UPDATE VANZARI_DETALII SET STERS = V_STERS, ID_UTILS = V_ID_UTIL, DATAORAS = V_DATAORA WHERE
ID_VANZARE = V_ID_VANZARE AND STERS = V_NESTERS` (V_STERS variaza 0/1 dupa tipul documentului,
cazul normal fiind 1).
- `sterge_proforma(V_ID_VANZARE, ...)`: acelasi tipar, exclusiv pe proforme.
**Nu exista azi un mecanism Oracle sau VFP de stergere per-linie in `VANZARI_DETALII`** — orice
"stergere de linie la editare" ceruta de S4/S4b e functionalitate NOUA, de adaugat la #6, nu o
reutilizare a ceva existent.
Exista insa un precedent apropiat de UPDATE punctual per linie, util ca sablon:
`modifica_explicatie_articol(V_ID_VANZARE_DET, V_EXPLICATIE, V_ID_UTIL, V_TAXCODE)` —
`UPDATE VANZARI_DETALII SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE WHERE ID_VANZARE_DET =
V_ID_VANZARE_DET` — exact modelul de "UPDATE tintit prin `goExecutor`" mentionat ca varianta in
planul S5/S6, deja folosit azi din `frm_modifica_articol_factura` (mostenire de la #7, vezi
`plan_06_editare_factura.md`).
### 3.2 Adaugare de linie noua — nu cere nimic special in plus fata de un INSERT direct
`ID_VANZARE_DET` vine automat din `SEQ_VANZARI_DETALII` prin trigger la orice `INSERT INTO
VANZARI_DETALII`, indiferent de cine face insert-ul (nu doar fluxul de emitere) — vezi 1.3. Deci
adaugarea unei linii noi la editare **nu** cere obtinerea manuala a unui ID din secventa; e suficient
un `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE, PRET_CU_TVA, ...) VALUES
(...)` (fara `ID_VANZARE_DET` in lista de coloane) intr-o procedura noua, apelata din
`finalizeaza_modificare_nota` alaturi de recalculul de totaluri propus in `rec_s5_oracle_vanzari.md`
punctul B.
## 4. Propunerea pentru S4/S5/S6 — cum trimite editarea liniile modificate in Oracle
### Optiuni comparate
**A. Refolosirea `VANZARI_DETALII_TEMP` + o "mini scrie_in_vanzari" pentru editare** — ar insemna ca
formularul de editare (S4) sa populeze TEMP la fel ca la emitere (apeluri per linie), apoi o
procedura noua sa faca INSERT-urile/UPDATE-urile in `VANZARI_DETALII` din TEMP, in aceeasi
tranzactie (impusa de `ON COMMIT DELETE ROWS`, vezi 2.1). Risc: reproduce complexitatea lui
`adauga_articol_factura` (ramificare pe `ntip`/sursa document) fara sa aiba sens la editare — acolo
nu exista comanda/aviz/contract "curent" din care sa se re-deriva pretul; ar trebui un mod nou,
simplificat, de populare a TEMP doar pentru editare, ceea ce complica inutil un cod deja incarcat.
Risc mediu-mare de regresie pe calea de emitere daca vreo modificare atinge accidental
`adauga_articol_factura`/`scrie_in_vanzari` partajate.
**B. UPDATE/INSERT/soft-DELETE punctual direct in `VANZARI_DETALII`, prin `goExecutor`, dintr-o
procedura noua dedicata editarii** — fara sa treaca deloc prin `VANZARI_DETALII_TEMP`. Model deja
existent si folosit: `modifica_explicatie_articol` (UPDATE tintit pe `ID_VANZARE_DET`). Pentru cele
trei operatii cerute de S4/S4b:
- **Editare cantitate/pret/`pret_cu_tva`** pe o linie existenta: `UPDATE VANZARI_DETALII SET
CANTITATE=..., PRET=..., PRET_CU_TVA=..., DISCOUNT_UNITAR=... WHERE ID_VANZARE_DET = :id`.
- **Stergere linie**: `UPDATE VANZARI_DETALII SET STERS=1, ID_UTILS=:util, DATAORAS=SYSDATE WHERE
ID_VANZARE_DET = :id` — acelasi tipar ca `sterge_factura`, dar tintit pe o singura linie (nou,
nu exista azi, vezi 3.1).
- **Adaugare linie noua**: `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE,
PRET_CU_TVA, DISCOUNT_UNITAR, PROC_TVAV, ID_GESTIUNE, CONT, ID_VALUTA, ID_JTVA_COLOANA, SERIE,
EXPLICATIE, TAXCODE, LOT) VALUES (...)` — PK automat din trigger (vezi 3.2).
Apoi, in ACEEASI tranzactie (deschisa deja de `do_deschide_tranzactie` in fluxul de editare a
notei, cf. `rec_s5_oracle_vanzari.md` B "Idempotenta si tranzactionalitate"), se cheama procedura
de recalcul a totalurilor propusa in `rec_s5_oracle_vanzari.md` (`recalculeaza_totaluri_vanzari`),
care citeste direct din `VANZARI_DETALII WHERE ID_VANZARE=:id AND STERS=0` — **fara nicio
dependenta de `VANZARI_DETALII_TEMP`**.
### Recomandare: **Varianta B**
Argumentul principal e riscul de regresie asupra emiterii: Varianta A ar obliga fie la extinderea
lui `adauga_articol_factura` cu o ramura noua "editare" (cod partajat cu emiterea, risc direct pe
calea critica), fie la duplicarea partiala a logicii lui in altă procedura (risc de divergenta,
exact problema pe care #8 a corectat-o deja pentru calculul de totaluri). Varianta B nu atinge deloc
`adauga_articol_factura`/`scrie_in_vanzari`/`VANZARI_DETALII_TEMP` — cod nou, izolat, apelat doar
din calea de editare (`finalizeaza_modificare_nota`), cu acelasi profil de risc "mic" motivat deja
pentru `recalculeaza_totaluri_vanzari` in `rec_s5_oracle_vanzari.md`. In plus, B se potriveste
natural cu S4b (verificare/sincronizare explicita, nu silentioasa): fiecare rand modificat/sters/
adaugat poate fi tratat ca o comanda separata, usor de enumerat utilizatorului inainte de aplicare
("linia X: cantitate 5 -> 8, pret 10 -> 12"), pe cand Varianta A ar produce un singur bloc opac de
recalcul din care nu se pot extrage usor liniile individuale schimbate.
**Observatie tehnica pentru implementare**: la editarea preturilor, NU se recalculeaza `V_PROC_TVAV`
din `JTVA_COLOANE`/comanda/contract ca la emitere (asta ar reintroduce dependenta de sursa
documentului, respinsa mai sus) — cota de TVA ramane cea deja persistata pe linie
(`VANZARI_DETALII.PROC_TVAV`), coerent cu felul in care `pret_cu_tva`/`proc_tvav` sunt deja tratate
ca date proprii ale liniei si nu recalculate la fiecare atingere (cf. #7).
## 5. Ce ramane neconfirmat / in afara scopului acestei cercetari
- Coloanele `STERS`, `VALIDAT`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT` din `VANZARI_DETALII` sunt
`NOT NULL` fara sa aiba valoare din trigger — probabil au `DEFAULT` la nivel de coloana
(`all_tab_columns.data_default` nu a fost interogat, nu era necesar pentru concluziile de mai
sus); de verificat explicit inainte de a scrie INSERT-ul de linie noua din Varianta B, ca sa nu
fie nevoie sa le populeze manual.
- Valorile implicite exacte pentru `DEFAULT` (daca exista) pe coloanele NOT NULL de mai sus.
- Comportamentul `VANZARI_SETURI_TEMP`/liniile din seturi la editare (#6 nu pare sa acopere editarea
seturilor, doar articolele individuale — de clarificat cu Marius daca seturile sunt in scope).