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

18 KiB

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:

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

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