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