Files
comun/docs/cercetare/rec_s5_proiectare_oracle.md
Marius Mutu 09ddeabb1c docs: cercetarile trans-proiect din ROAFACTURARE trec in COMUN
Paisprezece rapoarte care nu erau ale ROAFACTURARE traiau in docs\cercetare\ al
acelui proiect: valuta si curs in ofacturare, TVA calculat vs salvat plus
denormalizarea VANZARI, inventarul consumatorilor VANZARI din toata suita ROA,
integrarile de contracte/politici/nomenclator (#10, #11, #12), watchdog-ul VFP
de testare, proiectarea Oracle a scrierii din S5 si view-ul VVANZARI_ARTICOLE.

Motivul mutarii, nu doar al pastrarii: planurile #10, #11 si #12 sunt amanate,
iar indexul lor spune explicit ca se reiau din aceste rapoarte - deci sunt punct
de plecare, nu istoric. Iar tinta lor e COMUN plus ROAPRETURI/ROACONTRACTE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:31:53 +03:00

44 KiB

Cercetare S5 — proiectarea partii Oracle (plan #6, editare factura emisa)

Data: 09.08.2026. Sursa: export PROASPAT din MARIUSM_AUTO@ROA_CENTRAL (all_source), facut in aceasta sesiune, in scratchpad (nu in proiect):

  • PACK_FACTURARE.pck — 16160 linii
  • PACK_CONTAFIN.pck — 8550 linii

Toate numerele de linie de mai jos sunt din ACEST export. Raportul precedent (rec_s5_oracle_vanzari.md, 08.08.2026) declara PACK_FACTURARE.pck = 17010 linii; offset-urile procedurilor coincid totusi exact (scrie_in_vanzari la :13491, actualizeaza_vanzari la :16015), deci diferenta e de formatare a spool-ului, nu de continut. Concluziile lui A.1-A.2, C si D raman valabile pe sursa de azi si nu se repeta aici.

Doua corectii de fond fata de raportul precedent (detaliate la 3 si 4):

  1. agregarea NU citeste VANZARI_SETURI_TEMP, ci tabela persistenta VANZARI_SETURI (:13916) — deci liniile de set se pot reconstitui integral la editare;
  2. recomandarea „procedura noua apelata din interiorul finalizeaza_modificare_nota" e incompatibila cu ordinea corecta de scriere si trebuie abandonata.

1. Baza de plecare e la zi? DA (pentru ff_)

Comparatie VERSIUNE (4151 randuri) vs. D:\ROA\DATABASE\SCRIPTURI_CLAR (3030 fisiere .sql):

  • ff_ pe disc dar neaplicate in MARIUSM_AUTO: 0. Schema e la zi pe tot ce o priveste.
  • Ultimul ff_ inregistrat: ff_2026_08_08_01_COMUN_VVANZARI_ARTICOLE.sql, identic cu versiune_db.txt din radacina proiectului (2026_08_08_01).
  • Cele 26 de scripturi din 2026 care lipsesc din VERSIUNE sunt exclusiv co_ si sys_ — se aplica pe CONTAFIN_ORACLE, respectiv SYS, nu pe schema firmei; VERSIUNE din MARIUSM_AUTO contine doar 3 randuri co_, deci absenta lor e normala, nu o restanta.

Verdict: se poate construi propunerea peste sursa exportata azi.

Doua anomalii de semnalat (nu blocheaza S5)

  • ff_2026_08_08_01_COMUN_VVD_TOT.sql e inregistrat in VERSIUNE dar NU exista nicaieri pe disc (cautat recursiv in tot SCRIPTURI_CLAR). Un obiect aplicat in dev fara script salvat nu ajunge niciodata la clienti. De verificat cu Marius daca e un script de lucru abandonat sau daca lipseste din SVN.
  • Acelasi NN (2026_08_08_01) e folosit de doua scripturi ff_ (VVANZARI_ARTICOLE si VVD_TOT), contra regulii „NN e secventa unica pe zi".

2. Blocul de agregare din scrie_in_vanzari — integral

Procedura: PACK_FACTURARE.pck:13491-13956. Blocul de agregare + UPDATE VANZARI e :13762-13954, citat integral:

13762     -- Completare totaluri in vanzari din vanzari_detalii pentru a usura selectiile din fact_vfacturi
13763     begin
13764       select DISC_TVA_VAL AS DISCOUNT_TVA,
13765              VALOARE_ACHIZITIE,
13766              a.suma_fara_tva_ron - a.disc_fara_tva_ron as TOTAL_FARA_TVA,
13767              a.suma_tva_ron - a.disc_tva_ron as TOTAL_TVA,
13768              a.suma_fara_tva_ron - a.disc_fara_tva_ron + a.suma_tva_ron -
13769              a.disc_tva_ron as TOTAL_CU_TVA,
13770              a.suma_fara_tva_val - a.disc_fara_tva_val as VALVAL,
13771              a.suma_tva_val - a.disc_tva_val as TVAVAL,
13772              a.suma_fara_tva_val - a.disc_fara_tva_val + a.suma_tva_val -
13773              a.disc_tva_val as TOTVAL,
13774              id_valuta,
13775              curs,
13776              multiplicator,
13777              pack_facturare.cserie_act_incasare as SERIE_INCASAT,
13778              pack_facturare.nnumar_act_incasare as NR_INCASAT,
13779              pack_facturare.nsuma_incasare AS SUMA_INCASAT,
13780              pack_facturare.ntip_doc_incasare as TIP_INCASAT
13781         INTO lnDiscountTVA,
13782              lnValoareAchizitie,
13783              lnTotalFaraTVA,
13784              lnTotalTVA,
13785              lnTotalCuTVA,
13786              lnValVal,
13787              lnTVAVal,
13788              lnTotVal,
13789              lnIdValuta,
13790              lnCurs,
13791              lnMultiplicator,
13792              lnSerieIncasat,
13793              lnNrIncasat,
13794              lnSumaIncasat,
13795              lnTipIncasat
13796         FROM (select MAX(decode(pack_facturare.nin_valuta,
13797                                 1,
13798                                 ROUND(a1.curs * NVL(V_DISCOUNT_FACTURA, 0) /
13799                                       a1.multiplicator,
13800                                       lnPreciziePretV),
13801                                 NVL(V_DISCOUNT_FACTURA, 0))) as DISC_FARA_TVA_RON,
13802                      NVL(V_DISCOUNT_FACTURA, 0) as DISC_FARA_TVA_VAL,
13803                      pack_facturare.nin_valuta AS IN_VALUTA,
13804                      MAX(ROUND(decode(pack_facturare.nin_valuta,
13805                                       1,
13806                                       ROUND(a1.curs *
13807                                             NVL(V_DISCOUNT_FACTURA, 0) /
13808                                             a1.multiplicator,
13809                                             lnPreciziePretV),
13810                                       NVL(V_DISCOUNT_FACTURA, 0)) *
13811                                (a1.proc_tvav - 1),
13812                                lnPreciziePretV)) as DISC_TVA_RON,
13813                      MAX(ROUND(NVL(V_DISCOUNT_FACTURA, 0) *
13814                                (a1.proc_tvav - 1),
13815                                lnPreciziePretV)) as DISC_TVA_VAL,
13816                      sum(pack_facturare.calculeaza_total_fara_tva_fact(a1.pret_ron,
13817                                                                        0,
13818                                                                        1,
13819                                                                        NVL(a1.discount_unitar_ron,
13820                                                                            0),
13821                                                                        pack_facturare.ndiscount_evidentiat,
13822                                                                        a1.cantitate,
13823                                                                        a1.pret_cu_tva,
13824                                                                        a1.proc_tvav)) AS SUMA_FARA_TVA_RON,
13825                      sum(pack_facturare.calculeaza_total_tva_fact(a1.pret_ron,
13826                                                                   0,
13827                                                                   1,
13828                                                                   NVL(a1.discount_unitar_ron,
13829                                                                       0),
13830                                                                   pack_facturare.ndiscount_evidentiat,
13831                                                                   a1.cantitate,
13832                                                                   a1.pret_cu_tva,
13833                                                                   a1.proc_tvav)) as SUMA_TVA_RON,
13834                      sum(pack_facturare.calculeaza_total_fara_tva_fact(a1.pret_val,
13835                                                                        0,
13836                                                                        1,
13837                                                                        NVL(a1.discount_unitar_val,
13838                                                                            0),
13839                                                                        pack_facturare.ndiscount_evidentiat,
13840                                                                        a1.cantitate,
13841                                                                        a1.pret_cu_tva,
13842                                                                        a1.proc_tvav)) AS SUMA_FARA_TVA_VAL,
13843                      sum(pack_facturare.calculeaza_total_tva_fact(a1.pret_val,
13844                                                                   0,
13845                                                                   1,
13846                                                                   NVL(a1.discount_unitar_val,
13847                                                                       0),
13848                                                                   pack_facturare.ndiscount_evidentiat,
13849                                                                   a1.cantitate,
13850                                                                   a1.pret_cu_tva,
13851                                                                   a1.proc_tvav)) as SUMA_TVA_VAL,
13852                      sum(round(a1.cantitate * a1.pret_achizitie,
13853                                lnPrecizieCalcul)) AS VALOARE_ACHIZITIE,
13854                      max(decode(pack_facturare.nin_valuta, 1, a1.id_valuta, 0)) as id_valuta,
13855                      max(decode(pack_facturare.nin_valuta, 1, a1.curs, 1)) as curs,
13856                      max(decode(pack_facturare.nin_valuta, 1, a1.multiplicator, 1)) as multiplicator
13857                 from (select vd.id_vanzare_set,
13858                              (case
13859                                when (pack_facturare.nin_valuta = 1 or
13860                                     vd.id_valuta <>
13861                                     pack_def.GetIdMonedaNationala()) then
13862                                 ROUND(vc.curs * vd.pret / vc.multiplicator,
13863                                       lnPreciziePretV)
13864                                else
13865                                 vd.pret
13866                              end) as pret_ron,
13867                              vd.pret as pret_val,
13868                              vd.proc_tvav,
13869                              vd.cantitate,
13870                              vd.diferenta,
13871                              (case
13872                                when (pack_facturare.nin_valuta = 1 or
13873                                     vd.id_valuta <>
13874                                     pack_def.GetIdMonedaNationala()) then
13875                                 ROUND(vc.curs * vd.discount_unitar /
13876                                       vc.multiplicator,
13877                                       lnPreciziePretV)
13878                                else
13879                                 vd.discount_unitar
13880                              end) as discount_unitar_ron,
13881                              vd.discount_unitar as discount_unitar_val,
13882                              vd.id_valuta,
13883                              vd.pret_cu_tva,
13884                              vd.pret_achizitie,
13885                              vc.curs,
13886                              vc.multiplicator
13887                         from (select a.id_vanzare_set,
13888                                      a.pret,
13889                                      a.proc_tvav,
13890                                      a.cantitate,
13891                                      a.diferenta,
13892                                      a.discount_unitar,
13893                                      a.id_valuta,
13894                                      a.pret_cu_tva,
13895                                      a.pret_achizitie
13896                                 from VANZARI_DETALII_TEMP a
13897                                where nvl(a.id_vanzare_set, 0) = 0
13898                               union all
13899                               select b.id_vanzare_set,
13900                                      b.pret,
13901                                      max(c.proc_tvav) as proc_tvav,
13902                                      b.cantitate,
13903                                      0 as diferenta,
13904                                      b.discount_unitar,
13905                                      decode(pack_facturare.nin_valuta,
13906                                             0,
13907                                             pack_def.GetIdMonedaNationala(),
13908                                             c.id_valuta) as id_valuta,
13909                                      b.pret_cu_tva,
13910                                      sum(decode(b.cantitate,
13911                                                 0,
13912                                                 0,
13913                                                 c.pret_achizitie * c.cantitate /
13914                                                 b.cantitate)) as pret_achizitie
13915                                 from vanzari_detalii_temp c
13916                                 left join vanzari_seturi b
13917                                   on b.id_vanzare_set = c.id_vanzare_set
13918                                where nvl(c.id_vanzare_set, 0) <> 0
13919                                  and nvl(pack_facturare.nin_valuta, -1) > -1
13920                                group by b.id_vanzare_set,
13921                                         b.pret,
13922                                         b.cantitate,
13923                                         b.discount_unitar,
13924                                         b.pret_cu_tva,
13925                                         decode(pack_facturare.nin_valuta,
13926                                                0,
13927                                                pack_def.GetIdMonedaNationala(),
13928                                                c.id_valuta)) vd
13929                         left join vanzari_cursuri vc
13930                           on vc.id_vanzare = V_ID_VANZARE
13931                          and vd.id_valuta = vc.id_valuta) a1) a;
13932
13933       update vanzari
13934          set discount_tva      = lnDiscountTVA,
13935              valoare_achizitie = lnValoareAchizitie,
13936              total_fara_tva    = lnTotalFaraTVA,
13937              total_tva         = lnTotalTVA,
13938              total_cu_tva      = lnTotalCuTVA,
13939              valval            = lnValVal,
13940              tvaval            = lnTVAVal,
13941              totval            = lnTotVal,
13942              id_valuta         = lnIdValuta,
13943              curs              = lnCurs,
13944              multiplicator     = lnMultiplicator,
13945              serie_incasat     = lnSerieIncasat,
13946              nr_incasat        = lnNrIncasat,
13947              suma_incasat      = lnSumaIncasat,
13948              tip_incasat       = lnTipIncasat
13949        where id_vanzare = V_ID_VANZARE;
13950
13951       exception
13952         when NO_DATA_FOUND then
13953           null;
13954       end;

Variabilele locale relevante, declarate la :13501-13523:

13522     lnPrecizieCalcul   NUMBER(2) := pack_sesiune.getOptiuneFirma('PC');
13523     lnPreciziePretV    NUMBER(2) := pack_sesiune.getOptiuneFirma('PPRETV');

Inventarul dependentelor blocului

Dependenta Linii Sursa la emitere Echivalent PERSISTENT la editare Verdict
V_DISCOUNT_FACTURA 13798, 13801, 13802, 13807, 13810, 13813 parametru al procedurii VANZARI.DISCOUNT — scrisa la INSERT din exact acelasi parametru (:13621 / :13682) reconstituibil
pack_facturare.nin_valuta 13796, 13803, 13804, 13854-13856, 13859, 13872, 13905, 13919, 13925 stare de sesiune pe pachet VANZARI.IN_VALUTA (NUMBER(1), NOT NULL) — scrisa la INSERT din aceeasi variabila (:13625 / :13686) reconstituibil
pack_facturare.ndiscount_evidentiat 13821, 13830, 13839, 13848 stare de sesiune pe pachet VANZARI.DISCOUNT_EVIDENTIAT — scrisa la INSERT din aceeasi variabila (:13622 / :13683) reconstituibil
cserie_act_incasare, nnumar_act_incasare, nsuma_incasare, ntip_doc_incasare 13777-13780 stare de sesiune, setata numai in fluxul de emitere (:13102-13144; resetate la :1882-1886) NICIUNUL vezi 6
pack_def.GetIdMonedaNationala() 13861, 13874, 13907, 13927 functie pura: SELECT MIN(ID_VALUTA) FROM NOM_VALUTE WHERE STERS=0 AND MONEDA_NATIONALA=1 (PACK_DEF body :214-230) idem — fara stare fara probleme
pack_sesiune.getOptiuneFirma('PC'/'PPRETV') 13522-13523, 13853 SELECT varvalue FROM optiuni WHERE ... (PACK_SESIUNE body :119-141) — lookup pe tabela, fara stare de pachet idem fara probleme
VANZARI_DETALII_TEMP (liniile directe) 13896-13897 GTT ON COMMIT DELETE ROWS VANZARI_DETALII WHERE ID_VANZARE = :id AND STERS = 0 AND NVL(ID_VANZARE_SET,0) = 0 vezi 3
VANZARI_DETALII_TEMP (liniile de set, alias c) 13915, 13918 GTT VANZARI_DETALII ... AND NVL(ID_VANZARE_SET,0) <> 0 vezi 3
vanzari_seturi (alias b) 13916 tabela PERSISTENTA, deja ea insasi vezi 3
vanzari_cursuri vc 13929-13930 tabela persistenta, populata cu id_vanzare-ul curent la :13704 (scrie_cursuri) ea insasi, deja legata pe ID_VANZARE fara probleme

Verificat pe all_tab_columns: toate cele 9 coloane citite din VANZARI_DETALII_TEMP de agregare (id_vanzare_set, pret, proc_tvav, cantitate, diferenta, discount_unitar, id_valuta, pret_cu_tva, pret_achizitie) exista, cu acelasi nume, in VANZARI_DETALII. Singurele coloane pe care TEMP le are in plus si care nu exista in tabela reala sunt CURS, MULTIPLICATOR, EXPLICATIA, ID_COMANDA, NUMAR_ACT, ID_TEMP, ID_UTIL, IN_STOC, PRETV_ORIG, ID_GESTIUNE_DEST, ID_LUCRARE_REZ, ID_PART_REZniciuna nu e folosita de blocul de agregare (curs/multiplicator vin din vanzari_cursuri vc, nu din linie).

Concluzie: blocul se reproduce 1:1 pe surse persistente, schimband doar clauzele FROM si inlocuind cele 3 valori de stare cu cele 3 coloane din VANZARI. Nu e nevoie de nicio reformulare a formulei — inclusiv calculeaza_total_fara_tva_fact / calculeaza_total_tva_fact (PACK_FACTURARE.pck:15858-16013, semnaturi in spec la :1167-1185) raman apelate identic.

Doua observatii de detaliu:

  • a1.diferenta e selectata (:13870, :13891) dar nu e folosita nicaieri in agregarea din scrie_in_vanzari (parametrul V_DIFERENTA e 0 hard-codat la :13817, :13826, :13835, :13844). Se poate pastra pentru fidelitate sau elimina; recomand pastrarea, ca diff-ul fata de original sa ramana citibil.
  • Handler-ul WHEN NO_DATA_FOUND THEN NULL (:13951-13953) e defensiv si practic inaccesibil: subinterogarea are agregate fara GROUP BY, deci intoarce mereu exact un rand. Dar pe zero linii agregatele intorc NULL, nu 0 — la emitere cazul nu poate aparea, la editare da (vezi riscul R4).

3. Liniile din seturi — EXISTA echivalent persistent

Corectia raportului precedent. Agregarea nu citeste VANZARI_SETURI_TEMP, ci VANZARI_SETURI (PACK_FACTURARE.pck:13916), care e o tabela normala, persistenta (verificat pe all_tables: VANZARI_SETURI temporary=N; doar VANZARI_SETURI_TEMP si VANZARI_DETALII_TEMP sunt temporary=Y duration=SYS$TRANSACTION).

Trecerea TEMP -> persistent o face pack_facturare.scrie_seturi (:13993-14028), apelata la :13706, inainte de agregare: insereaza fiecare rand din VANZARI_SETURI_TEMP in VANZARI_SETURI (ID_VANZARE_SET din SEQ_VANZARI_SETURI, prin trigger TRG_VANZARI_SET_BEFOINS) si reface pointerul in VANZARI_DETALII_TEMP.ID_VANZARE_SET. De aceea agregarea de la :13915-13928 face join intre TEMP (componentele) si tabela persistenta (capul de set).

Structura VANZARI_SETURI (all_tab_columns), identica cu a TEMP-ului:

ID_VANZARE_SET  NUMBER(10)   NOT NULL   -- PK, din SEQ_VANZARI_SETURI
DENUMIRE        VARCHAR2(100)
EXPLICATIE      VARCHAR2(100)
CANTITATE       NUMBER(10,4)
UM              VARCHAR2(10)
SERIE           VARCHAR2(100)
PRET            NUMBER(20,4)
DISCOUNT_UNITAR NUMBER(20,4)
PRET_CU_TVA     NUMBER(1)    NOT NULL

Nu are ID_VANZARE si nu are STERS: legatura cu documentul e exclusiv prin VANZARI_DETALII.ID_VANZARE_SET. Deci filtrarea pe document se face tot din VANZARI_DETALII.

Precedent care confirma reconstituirea: pack_facturare.citeste_vanzari_seturi (:16619-16698) reface deja exact acest lucru pe surse persistente — VANZARI (cod, sters=0)

  • VANZARI_DETALII (sters=0, id_vanzare_set is not null) + VANZARI_CURSURI + VANZARI_SETURI, cu acelasi GROUP BY b.id_vanzare_set. Nu inventam un tipar nou.

Raspuns la intrebarea din brief: DA, recalculul poate acoperi si liniile de set, fara pierdere de informatie. Transformarea necesara in ramura de set:

from vanzari_detalii c            -- in loc de vanzari_detalii_temp c
left join vanzari_seturi b on b.id_vanzare_set = c.id_vanzare_set
where nvl(c.id_vanzare_set, 0) <> 0
  and c.id_vanzare = V_ID_VANZARE
  and c.sters = 0

Date (test, MARIUSM_AUTO): 4 randuri in VANZARI_SETURI, 4 documente cu linii de set. Nu e o dovada — validarea ramane pe cod, nu pe volum.

Ce NU se poate face din interfata (limitare de semnalat pentru S4)

View-ul VVANZARI_ARTICOLE (sursa lui IncarcaArticoleFactura, COMUN\programe\ofacturare_editare.prg:288-330) nu expune ID_VANZARE_SET si nici PRET_ACHIZITIE:

select vd.id_vanzare, vd.id_vanzare_det, vd.id_articol, vd.cantitate, vd.pret, vd.pret_cu_tva,
       vd.proc_tvav, vd.discount_unitar, vd.id_gestiune, vd.cont, vd.id_valuta,
       vd.id_jtva_coloana, vd.serie, vd.explicatie, vd.taxcode, vd.lot, vd.sters,
       na.denumire, na.codmat, ng.nume_gestiune, nv.nume_val
  from vanzari_detalii vd
  left join nom_articole na on na.id_articol = vd.id_articol
  left join nom_gestiuni ng on ng.id_gestiune = vd.id_gestiune
  left join nom_valute nv on nv.id_valuta = vd.id_valuta

Consecinte concrete:

  1. componentele de set apar in grid ca linii obisnuite, dar formularul nu poate sti ca sunt componente si nu poate edita capul de set (VANZARI_SETURI.PRET/CANTITATE), care e ceea ce intra efectiv in totaluri. Un utilizator care schimba pretul unei componente nu schimba totalul documentului — recalculul ia pretul din VANZARI_SETURI. Divergenta tacuta.
  2. PRET_ACHIZITIE lipseste din cursor, deci VFP nu-l poate rescrie la un UPDATE de linie: la scriere trebuie omis din SET, ca sa ramana valoarea existenta (altfel VALOARE_ACHIZITIE se pierde). Pentru liniile noi insa nu exista sursa — vor intra cu PRET_ACHIZITIE NULL si vor contribui cu NULL la SUM(round(cantitate * pret_achizitie)), deci VALOARE_ACHIZITIE devine NULL pe tot documentul daca fie si o singura linie noua are NULL. Vezi riscul R3.

4. Capcana de ordonare — punctul central

Ce ruleaza azi, in ce ordine

Punctul de intrare nou (deja scris in ramura curenta): COMUN\clase\ofacturare_comun.vc2, do_editare_factura, blocul de salvare:

3798   If Thisform.do_deschide_tranzactie()                      && SQLSetprop(gnHandle,"Transactions",2)
3800     lnSucces = OSCRIE_IN_FISIERE(2,.T.,.T.)                  && marcheaza STERS=1 nota veche
...
3820     lnSucces = OSCRIE_IN_FISIERE(0,.T.,.T.)                  && scrie nota noua (COD nou din SCRIE_IN_ACT)
3823     If lnSucces > 0
3824       lcSql = [begin pack_contafin.finalizeaza_modificare_nota(...); end]
3826       lnSucces = Iif(goExecutor.oExecuta(lcSql),1,-1)
3827     Endif
3828     If Thisform.do_inchide_tranzactie(Iif(lnSucces<0,2,1))    && SQLCOMMIT / SQLROLLBACK

do_deschide_tranzactie / do_inchide_tranzactie: COMUN\clase\_frm_base.vc2:251-269 si :278-302SQLSetprop(gnHandle,"Transactions",2) / Sqlcommit(gnHandle).

finalizeaza_modificare_nota (PACK_CONTAFIN.pck:8601-8651) apeleaza actualizeaza_vanzari la :8616, gardat de SELECT COUNT(*) FROM vanzari WHERE cod = tnCod > 0 (:8613-8615).

actualizeaza_vanzari (PACK_FACTURARE.pck:16015-16025):

16018     -- de modificat in caz ca il las sa stearga manual inregistrari din VANZARI_DETALII
16019     -- acum se marcheaza cu STERS = 1 doar cand se sterge toata factura
16020     UPDATE VANZARI_DETALII
16021        SET STERS = 0
16022      WHERE ID_VANZARE IN
16023            (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI);
16024     UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI;

De ce exista STERS = 0 acolo (nu e cod mort)

E perechea lui sterge_din_vanzari -> sterge_factura, care marcheaza documentul sters:

5549     UPDATE VANZARI_DETALII
5550        SET STERS = V_STERS, ID_UTILS = V_ID_UTIL, DATAORAS = V_DATAORA
5551      WHERE ID_VANZARE = V_ID_VANZARE
5552        AND STERS = V_NESTERS

Reeditarea unei note sterse trebuie sa readuca la viata si randul din VANZARI, si liniile lui. Scoaterea reset-ului ar rupe fluxul „stergi nota, apoi o reeditezi" pe toata suita ROA.

Consecinta pentru S5 — mai grava decat „stergerile se pierd"

Reset-ul e in bloc, fara nicio garda: nici AND STERS = 1 (irelevant), nici o discriminare intre „stersa ca linie" si „stersa odata cu documentul". sterge_factura marcheaza doar liniile active (AND STERS = V_NESTERS, :5552), deci o linie stearsa la o editare anterioara ramane STERS=1 — si e inviata de actualizeaza_vanzari la urmatoarea salvare a notei.

Deci daca liniile se scriu inainte de finalizeaza_modificare_nota:

  • stergerile din editarea CURENTA se pierd tacut;
  • si, independent de ce face utilizatorul acum, orice stergere facuta la o editare ANTERIOARA e anulata la fiecare salvare ulterioara — inclusiv la o salvare care nu atinge deloc articolele. Stergerea de linie nu s-ar fixa niciodata.

UPDATE-urile de pret/cantitate si INSERT-urile de linii noi ar supravietui — deci esecul e partial si asimetric, exact tipul care trece de un test superficial.

Variantele de ordonare

(a) VFP scrie detaliile inainte, actualizeaza_vanzari neatinsa. Respinsa. Motivul complet e cel de mai sus: nu doar ca stergerile din sesiunea curenta se pierd, dar mecanismul de stergere de linie devine structural imposibil, fiindca fiecare salvare a notei reseteaza tot documentul la STERS = 0. In plus, dupa actualizeaza_vanzari VANZARI.COD s-a schimbat, deci orice scriere ulterioara ancorata pe cod ar rata randul (ID_VANZARE ramane — vezi A.1 din raportul precedent, reconfirmat la :16024).

(a') VFP scrie inainte + actualizeaza_vanzari primeste o garda. Respinsa. Ar cere un discriminator „stearsa ca linie" vs „stearsa cu documentul", care nu exista in schema (VANZARI_DETALII nu are alt marcaj decat STERS/ID_UTILS/DATAORAS); orice euristica pe DATAORAS e fragila. Si e Varianta A din raportul precedent — modificare in-place a unei proceduri apelate de orice editare de nota cu cod in vanzari, din toata suita ROA.

(b) VFP scrie detaliile DUPA ce finalizeaza_modificare_nota s-a intors, apoi apeleaza pack_facturare.recalculeaza_totaluri_vanzari(id_vanzare, discount), in aceeasi tranzactie. RECOMANDATA. Verificat, nu presupus:

  • tranzactia e inca deschisa cand se intoarce finalizeaza_modificare_nota: aceasta ruleaza la ofacturare_comun.vc2:3826, iar do_inchide_tranzactie abia la :3828; tranzactia e manuala pe gnHandle (_frm_base.vc2:255), aceeasi conexiune ODBC pe care ruleaza goExecutor. Deci scrierile de dupa intra in acelasi COMMIT/ROLLBACK, fara fereastra de inconsistenta.
  • actualizeaza_vanzari ramane neatinsa — zero regresie pe ROAGEST/ROACONT.
  • PACK_CONTAFIN ramane neatins — nu se recompileaza pachetul central de scriere a documentelor.
  • reset-ul STERS = 0 ruleaza primul, iar VFP re-aplica dupa el starea autoritativa a liniilor.
  • VANZARI.COD e deja rescris cand VFP scrie — de aceea toate scrierile se ancoreaza pe ID_VANZARE (lnIdVanzare, citit din crsfacturi la ofacturare_comun.vc2:3735, stabil).

Atentie — (b) naiv are un defect. IncarcaArticoleFactura incarca doar sters = 0 (ofacturare_editare.prg:302), deci liniile sterse la o editare anterioara nu sunt in crsArticoleFactura: reset-ul le-a inviat, iar VFP nu le atinge, deci raman active. Corectia e in forma scrierii, nu in Oracle — VFP scrie o stare completa, nu un delta:

1. UPDATE VANZARI_DETALII SET STERS = 1, ID_UTILS = ?, DATAORAS = SYSDATE
    WHERE ID_VANZARE = <id> AND STERS = 0                        -- marcheaza tot
2. pentru fiecare linie pastrata din cursor, cu id_vanzare_det > 0:
   UPDATE VANZARI_DETALII SET STERS = 0, CANTITATE = ?, PRET = ?, PRET_CU_TVA = ?, ...
    WHERE ID_VANZARE_DET = <det>                                 -- invie doar ce ramane
3. pentru fiecare linie noua: INSERT INTO VANZARI_DETALII (...)  -- ID_VANZARE_DET din trigger
4. pack_facturare.recalculeaza_totaluri_vanzari(<id>, <discount>)

Idiomul „marcheaza tot, invie ce ramane" e idempotent, nu are limita de 1000 de elemente intr-un IN, nu cere lista separata de linii sterse, si pastreaza sterse liniile scoase la editari anterioare. La pasul 2, PRET_ACHIZITIE se omite din SET (nu e in cursor — vezi 3).

(c) recalcul apelat din interiorul finalizeaza_modificare_nota (recomandarea raportului precedent, sectiunea B). De abandonat. Este incompatibila cu (b): daca recalculul ruleaza inauntrul lui finalizeaza_modificare_nota, el vede liniile vechi (VFP nu le-a scris inca, pentru ca nu poate scrie inainte de reset), deci ar produce exact totalurile de dinainte de editare — tacut corecte ca formula, tacut gresite ca valoare. Nu e o varianta mai riscanta, e o varianta gresita.

(c') procedura Oracle care primeste si liniile (prin VANZARI_DETALII_TEMP sau o colectie) si face si scrierea, si recalculul. Respinsa: reintroduce calea TEMP pe care planul a exclus-o explicit (plan_06_editare_factura.md:106-114), cere o forma de parametru incomoda prin ODBC, si dubleaza scrierea per-linie pe care planul a decis-o deja in VFP, dupa modelul modifica_explicatie_articol (PACK_FACTURARE.pck:14514-14522).

Recomandare

Varianta (b), cu scrierea in forma „marcheaza tot, invie ce ramane". Argumentul decisiv nu e comoditatea, ci ca e singura ordine in care reset-ul STERS = 0 din actualizeaza_vanzari ramane inofensiv fara sa modificam procedura — iar procedura aceea e folosita azi de toata suita, pentru un scop legitim (reeditarea unei note sterse) pe care nu-l putem sacrifica.


5. Discountul de document (VANZARI.DISCOUNT)

Cine il scrie azi: nimeni, dupa emitere. Verificat pe toata schema, nu doar pe PACK_FACTURARE:

  • all_source (PACKAGE BODY/PROCEDURE/FUNCTION/TRIGGER, MARIUSM_AUTO), cautand discount\s*= exclusiv coloanele discount_unitar|discount_tva|discount_evidentiat: niciun UPDATE ... SET DISCOUNT = ... pe VANZARI. Rezultatele sunt toate pe alte tabele (PACK_COMENZI.PROC_DISCOUNT, PACK_CRM.val_discount, PACK_OFERTARE.valdiscount, ...).
  • Toate cele 8 instructiuni UPDATE VANZARI din PACK_FACTURARE (:5485, :5493, :5516, :5615, :14817, :15387, :15397, :15509) plus modifica_date_factura (:14463-14512, singura procedura de „modifica antetul facturii" existenta) ating STERS/FACTURAT/ID_FACT/AVIZE/ SERIE_ACT/NUMAR_ACT/DATA_ACT/DATA_SCADniciuna DISCOUNT.
  • In VFP: cautare pe COMUN\ dupa set discount = / vanzari set ... discountniciun rezultat.
  • Trigger-ele pe VANZARI nu-l ating: TRG_VANZARI_BEFOUPD face doar audit pe NR_ACT/SERIE_ACT/DATA_ACT/DATA_SCAD (pack_audit.verifica_val).

Deci VANZARI.DISCOUNT e scris o singura data in viata documentului, la INSERT-ul din scrie_in_vanzari (:13621 in lista de coloane, :13682 in VALUES, din V_DISCOUNT_FACTURA). Nu exista nici procedura de modificare, nici cale VFP.

Pe partea VFP valoarea e deja disponibila in memorie: cursorul tvanz are coloana discount (ofacturare_editare.prg:152, CreeazaCursorTvanzGol), populata din VANZARI de IncarcaVanzareNota (:176).

Recomandare: parametru al procedurii noi, nu UPDATE separat din VFP

recalculeaza_totaluri_vanzari(V_ID_VANZARE IN NUMBER, V_DISCOUNT IN NUMBER DEFAULT NULL)

cu semantica V_DISCOUNT IS NULL = „pastreaza valoarea curenta". Motive:

  1. discountul si totalurile nu pot diverge. Cu doua instructiuni separate din VFP, un esec pe a doua (sau o omisiune la un viitor call-site) lasa DISCOUNT nou si totaluri calculate pe cel vechi. Cu un parametru, e imposibil sa recalculezi cu un discount invechit.
  2. procedura ramane utilizabila ca recalcul pur (fara al doilea argument) de oriunde altundeva — de exemplu dintr-un script de backfill.
  3. e o instructiune ODBC in minus pe drumul critic.

Implementare: V_DISCOUNT se aplica in acelasi UPDATE vanzari final, discount = NVL(V_DISCOUNT, discount), iar valoarea folosita in agregare se citeste inainte, ca NVL(V_DISCOUNT, (select discount from vanzari where id_vanzare = V_ID_VANZARE)) — altfel agregarea ar lucra pe discountul vechi.


6. Coloanele care NU se recalculeaza — reconfirmat pe sursa proaspata

SERIE_INCASAT / NR_INCASAT / SUMA_INCASAT / TIP_INCASAT (:13777-13780, scrise la :13945-13948) vin din pack_facturare.cserie_act_incasare / nnumar_act_incasare / nsuma_incasare / ntip_doc_incasare.

Cautare exhaustiva a atribuirilor catre aceste 4 variabile in PACK_FACTURARE.pck7 rezultate, toate in fluxul de emitere:

1882-1886   cserie_act_incasare := NULL; nnumar_act_incasare := NULL;
            ntip_doc_incasare := NULL; nsuma_incasare := NULL;    (initializeaza_date_factura)
13102-13105 cserie_act_incasare := V_SERIE_ACT_INCASARE; nnumar_act_incasare := V_NUMAR_ACT_INCASARE;
            ntip_doc_incasare := nTipIncasareChitanta; nsuma_incasare := 0;
13136       nsuma_incasare := nsuma_incasare + ...
13140,13144 ntip_doc_incasare := V_TIP / nTipIncasareBonFiscal;

Sursele lor (V_SERIE_ACT_INCASARE, V_NUMAR_ACT_INCASARE) sunt parametri ai emiterii unei facturi-cu-incasare combinata. Nu exista nicio tabela din care sa fie reconstituite la o editare ulterioara — singura urma persistenta sunt chiar cele 4 coloane din VANZARI, care ar fi suprascrise cu NULL de o copiere naiva a UPDATE-ului.

Concluzie reconfirmata: procedura noua scrie 11 coloane (discount_tva, valoare_achizitie, total_fara_tva, total_tva, total_cu_tva, valval, tvaval, totval, id_valuta, curs, multiplicator), plus optional discount (vezi 5). Cele 4 de incasare raman la valoarea de la emitere.


7. Semnatura propusa si scripturile de migrare

Declaratia din PACKAGE SPEC

Se adauga imediat dupa actualizeaza_vanzari (PACK_FACTURARE.pck:1187-1188), langa procedurile surori:

  PROCEDURE recalculeaza_totaluri_vanzari(V_ID_VANZARE IN NUMBER,
                                          V_DISCOUNT   IN NUMBER DEFAULT NULL);

recalculeaza_totaluri_vanzari = 29 de caractere, sub limita de 30 a lui Oracle 10.2/11 (peste 30 ar da ORA-00972). Nu mai lungi numele.

Corpul (schita, compatibila 10.2)

Constructii folosite: SELECT INTO, UPDATE, DECODE, CASE, NVL, ROUND, MAX, SUM, LEFT JOIN, UNION ALL, GROUP BY, subinterogari inline. Nimic din tabelul de incompatibilitati din scripturi-migrare-db.md — fara LISTAGG, CONTINUE, REGEXP_COUNT, FETCH FIRST, PIVOT, secventa.NEXTVAL in atribuire.

  PROCEDURE recalculeaza_totaluri_vanzari(V_ID_VANZARE IN NUMBER,
                                          V_DISCOUNT   IN NUMBER DEFAULT NULL) IS
    lnDiscountFactura     VANZARI.DISCOUNT%TYPE;
    lnInValuta            VANZARI.IN_VALUTA%TYPE;
    lnDiscountEvidentiat  VANZARI.DISCOUNT_EVIDENTIAT%TYPE;
    lnDiscountTVA         VANZARI.DISCOUNT_TVA%TYPE;
    lnValoareAchizitie    VANZARI.VALOARE_ACHIZITIE%TYPE;
    lnTotalFaraTVA        VANZARI.TOTAL_FARA_TVA%TYPE;
    lnTotalTVA            VANZARI.TOTAL_TVA%TYPE;
    lnTotalCuTVA          VANZARI.TOTAL_CU_TVA%TYPE;
    lnValVal              VANZARI.VALVAL%TYPE;
    lnTVAVal              VANZARI.TVAVAL%TYPE;
    lnTotVal              VANZARI.TOTVAL%TYPE;
    lnIdValuta            VANZARI.ID_VALUTA%TYPE;
    lnCurs                VANZARI.CURS%TYPE;
    lnMultiplicator       VANZARI.MULTIPLICATOR%TYPE;
    lnPrecizieCalcul      NUMBER(2) := pack_sesiune.getOptiuneFirma('PC');
    lnPreciziePretV       NUMBER(2) := pack_sesiune.getOptiuneFirma('PPRETV');
  BEGIN
    SELECT NVL(V_DISCOUNT, discount), in_valuta, NVL(discount_evidentiat, 0)
      INTO lnDiscountFactura, lnInValuta, lnDiscountEvidentiat
      FROM vanzari
     WHERE id_vanzare = V_ID_VANZARE;

    -- acelasi bloc de agregare ca in scrie_in_vanzari (:13764-13931), cu trei substitutii:
    --   VANZARI_DETALII_TEMP a  -> VANZARI_DETALII a WHERE a.id_vanzare = V_ID_VANZARE AND a.sters = 0
    --   vanzari_detalii_temp c  -> vanzari_detalii  c WHERE c.id_vanzare = V_ID_VANZARE AND c.sters = 0
    --   pack_facturare.nin_valuta            -> lnInValuta
    --   pack_facturare.ndiscount_evidentiat  -> lnDiscountEvidentiat
    --   V_DISCOUNT_FACTURA                   -> lnDiscountFactura
    -- fara coloanele de incasare (serie/nr/suma/tip)
    SELECT ... INTO lnDiscountTVA, lnValoareAchizitie, lnTotalFaraTVA, lnTotalTVA,
                    lnTotalCuTVA, lnValVal, lnTVAVal, lnTotVal,
                    lnIdValuta, lnCurs, lnMultiplicator
      FROM ( ... );

    UPDATE vanzari
       SET discount          = lnDiscountFactura,
           discount_tva      = lnDiscountTVA,
           valoare_achizitie = lnValoareAchizitie,
           total_fara_tva    = lnTotalFaraTVA,
           total_tva         = lnTotalTVA,
           total_cu_tva      = lnTotalCuTVA,
           valval            = lnValVal,
           tvaval            = lnTVAVal,
           totval            = lnTotVal,
           id_valuta         = lnIdValuta,
           curs              = lnCurs,
           multiplicator     = lnMultiplicator
     WHERE id_vanzare = V_ID_VANZARE;
  END recalculeaza_totaluri_vanzari;

Idempotenta: SELECT + UPDATE ... WHERE id_vanzare = :id, fara INSERT — rularea de doua ori pe acelasi id da acelasi rezultat.

Fara handler WHEN NO_DATA_FOUND THEN NULL pe modelul originalului: la editare, un id_vanzare inexistent e o eroare reala care trebuie sa opreasca tranzactia, nu sa fie inghitita. (Primul SELECT INTO, pe cheia primara, e singurul care poate ridica NO_DATA_FOUND.)

Scripturile de migrare

Azi, 09.08.2026, in D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08 nu exista niciun script cu data de azi (ultimele sunt ..._2026_08_08_01 si ..._2026_08_08_02), deci NN porneste de la 01. NN e secventa unica pe zi, comuna tuturor prefixelor.

Un singur script, fiindca S5 atinge un singur pachet si nimic altceva:

ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql
  • prefix ff_ — pachetul sta pe schema fiecarei firme;
  • un pachet sta singur in scriptul lui: fara DDL de tabele si fara DML alaturi (nu e nevoie de niciunul — nu se adauga coloane si nu se curata date);
  • contine SPEC + BODY (SPEC-ul se schimba: declaratia noua), CRLF obligatoriu, antet de 4-5 randuri, fara select de raportare, si se incheie cu exec pack_migrare.UpdateVersiune('ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql'); + commit;;
  • versiune_db.txt din radacina proiectului se muta pe 2026_08_09_01 (fara newline final).

Nu e nevoie de un script separat pentru VVANZARI_ARTICOLE pentru S5 asa cum e proiectat aici — dar vezi intrebarea 3 de mai jos, care ar cere unul (NN = 02, si atunci inainte de cel al pachetului daca pachetul l-ar folosi; aici nu-l foloseste, deci ordinea e libera).


8. Riscuri si intrebari deschise

R1 — Componentele de set sunt editabile in grid dar nu influenteaza totalurile. (mediu) VVANZARI_ARTICOLE nu expune ID_VANZARE_SET, deci S4 nu poate distinge componentele de liniile normale; recalculul ia insa pretul/cantitatea din VANZARI_SETURI, nu din componente. Utilizatorul modifica o componenta, apasa salvare, totalul nu se schimba. Tacut. Recomandarea mea: pentru #6, adauga ID_VANZARE_SET in VVANZARI_ARTICOLE (script separat, ff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql) si fa liniile cu id_vanzare_set nenul needitabile in grid, cu o eticheta („linie din set"). Editarea seturilor e o functionalitate proprie, nu o extindere gratuita a lui S4. Alternativa minimala, daca Marius nu vrea scriptul de view: blocheaza intreaga factura care contine seturi — dar asta contrazice decizia din plan_06_editare_factura.md:183-186 („fara blocarea facturilor care le contin").

R2 — Stergerea unei componente de set. (mic, dar urat) Cu idiomul „marcheaza tot, invie ce ramane", stergerea in grid a unei componente lasa capul de set in VANZARI_SETURI si celelalte componente pe loc: totalul ramane neschimbat (vine din cap), dar VALOARE_ACHIZITIE scade (se pierde pret_achizitie-ul componentei). Divergenta partiala. Recomandare: acoperit de R1 — daca liniile de set sunt needitabile, cazul dispare.

R3 — PRET_ACHIZITIE NULL pe liniile noi anuleaza VALOARE_ACHIZITIE pe tot documentul. (mediu) SUM(round(cantitate * pret_achizitie)) cu un singur operand NULL nu da NULL pe total (SUM ignora NULL-urile), dar linia noua nu contribuie deloc — deci VALOARE_ACHIZITIE (baza de calcul a marjei) subestimeaza sistematic dupa fiecare adaugare de linie. Coloana nu e in cursorul din grid si nu are sursa la editare (la emitere vine din stoc/politica de pret). Recomandare: la INSERT-ul liniei noi, VFP scrie PRET_ACHIZITIE cu pretul de achizitie curent al articolului (acelasi lookup pe care il face adauga_articol_factura_stoc), sau, daca nu se poate determina, cu 0 explicit si o avertizare in verificarile din S4b. Nu lasa NULL tacut.

R4 — Factura fara nicio linie da totaluri NULL, nu 0. (mic) Agregatele pe zero randuri intorc NULL; la emitere cazul nu poate aparea, la editare da. Recomandare: nu modifica formula (ar diverge de original). Interzice in S4b salvarea unei facturi cu zero linii active — e oricum un document invalid.

R5 — Linie cu valuta fara rand in VANZARI_CURSURI. (mic) left join vanzari_cursuri vc on vc.id_vanzare = V_ID_VANZARE and vd.id_valuta = vc.id_valuta (:13929-13930): daca o linie ajunge cu o valuta pentru care documentul nu are curs, vc.curs e NULL si pret_ron iese NULL. CreeazaPoArticolNouTvd primeste explicit valuta documentului (tnIdValutaDoc, ofacturare_editare.prg:356), deci in fluxul proiectat nu ar trebui sa apara. NEVERIFICAT ca S4 chiar transmite acel parametru pe toate caile de adaugare. Recomandare: verificare in S4b, nu garda in Oracle.

R6 — pack_sesiune.getOptiuneFirma intoarce '' la orice eroare. (mic) PACK_SESIUNE body :128-131: WHEN OTHERS THEN lcValue := ''. Atribuit intr-un NUMBER(2), '' devine NULL, iar ROUND(x, NULL) da NULL. Comportament identic cu cel de la emitere — deci nu e o regresie introdusa de S5, dar merita stiut daca apar totaluri NULL inexplicabile. Recomandare: nimic de facut in S5; consemnat pentru depanare.

R7 — ff_2026_08_08_01_COMUN_VVD_TOT.sql aplicat in dev fara script pe disc. (de clarificat) Obiectul nu exista in all_objects sub niciun nume VVD%, deci probabil scriptul a creat altceva sau a fost anulat ulterior. Fisierul lipseste din SCRIPTURI_CLAR -> nu ajunge la clienti. Recomandare: intrebare pentru Marius, nu blocheaza S5.

Intrebari pentru Marius

  1. Liniile de set (R1) — le facem needitabile in grid, cu ID_VANZARE_SET adaugat in VVANZARI_ARTICOLE printr-un al doilea script? Recomandarea mea: da. Fara asta, editarea unei facturi cu seturi arata ca merge si nu merge.
  2. Discountul de document — parametru al procedurii (V_DISCOUNT ... DEFAULT NULL), sau UPDATE VANZARI SET DISCOUNT separat din VFP? Recomandarea mea: parametru, ca discountul si totalurile sa nu poata diverge (vezi 5).
  3. PRET_ACHIZITIE pe linia noua (R3) — se citeste din nomenclator/stoc la adaugare, sau se scrie 0 cu avertizare? Recomandarea mea: citit din nomenclator, cu 0 doar ca ultima rezerva, niciodata NULL.
  4. VALVAL/TVAVAL/TOTVAL — raman in recalcul? Recomandarea mea: da (cost zero, fac parte din acelasi SELECT; excluderea lor ar lasa facturile in valuta inconsistente). Confirmare ceruta si in raportul precedent, inca neconfirmata.
  5. ff_2026_08_08_01_COMUN_VVD_TOT.sql — script de lucru abandonat, sau lipseste din SVN?

Ce a ramas neverificat

  • R5: nu am verificat pe codul S4 in lucru ca CreeazaPoArticolNouTvd primeste efectiv valuta documentului pe toate caile de adaugare de linie.
  • Nu am rulat nimic in Oracle in afara de SELECT-uri de dictionar si de export — niciun DDL, niciun DML, niciun test de executie a procedurii propuse. Corpul propus la 7 e schita, nu cod compilat.
  • Numarul de documente cu seturi in MARIUSM_AUTO (4) e din date de test si nu constituie dovada pentru niciuna dintre afirmatiile de mai sus; toate concluziile sunt din cod.