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
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 liniiPACK_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):
- agregarea NU citeste
VANZARI_SETURI_TEMP, ci tabela persistentaVANZARI_SETURI(:13916) — deci liniile de set se pot reconstitui integral la editare; - 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 inMARIUSM_AUTO: 0. Schema e la zi pe tot ce o priveste.- Ultimul
ff_inregistrat:ff_2026_08_08_01_COMUN_VVANZARI_ARTICOLE.sql, identic cuversiune_db.txtdin radacina proiectului (2026_08_08_01). - Cele 26 de scripturi din 2026 care lipsesc din
VERSIUNEsunt exclusivco_sisys_— se aplica peCONTAFIN_ORACLE, respectivSYS, nu pe schema firmei;VERSIUNEdinMARIUSM_AUTOcontine doar 3 randurico_, 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.sqle inregistrat inVERSIUNEdar NU exista nicaieri pe disc (cautat recursiv in totSCRIPTURI_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 scripturiff_(VVANZARI_ARTICOLEsiVVD_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_REZ — niciuna 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.diferentae selectata (:13870,:13891) dar nu e folosita nicaieri in agregarea dinscrie_in_vanzari(parametrulV_DIFERENTAe0hard-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 faraGROUP BY, deci intoarce mereu exact un rand. Dar pe zero linii agregatele intorcNULL, nu0— 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 acelasiGROUP 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:
- 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 dinVANZARI_SETURI. Divergenta tacuta. PRET_ACHIZITIElipseste din cursor, deci VFP nu-l poate rescrie la unUPDATEde linie: la scriere trebuie omis dinSET, ca sa ramana valoarea existenta (altfelVALOARE_ACHIZITIEse pierde). Pentru liniile noi insa nu exista sursa — vor intra cuPRET_ACHIZITIENULL si vor contribui cu NULL laSUM(round(cantitate * pret_achizitie)), deciVALOARE_ACHIZITIEdevine 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-302 — SQLSetprop(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 laofacturare_comun.vc2:3826, iardo_inchide_tranzactieabia la:3828; tranzactia e manuala pegnHandle(_frm_base.vc2:255), aceeasi conexiune ODBC pe care ruleazagoExecutor. Deci scrierile de dupa intra in acelasiCOMMIT/ROLLBACK, fara fereastra de inconsistenta. actualizeaza_vanzariramane neatinsa — zero regresie pe ROAGEST/ROACONT.PACK_CONTAFINramane neatins — nu se recompileaza pachetul central de scriere a documentelor.- reset-ul
STERS = 0ruleaza primul, iar VFP re-aplica dupa el starea autoritativa a liniilor. VANZARI.CODe deja rescris cand VFP scrie — de aceea toate scrierile se ancoreaza peID_VANZARE(lnIdVanzare, citit dincrsfacturilaofacturare_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), cautanddiscount\s*=exclusiv coloanelediscount_unitar|discount_tva|discount_evidentiat: niciunUPDATE ... SET DISCOUNT = ...peVANZARI. Rezultatele sunt toate pe alte tabele (PACK_COMENZI.PROC_DISCOUNT,PACK_CRM.val_discount,PACK_OFERTARE.valdiscount, ...).- Toate cele 8 instructiuni
UPDATE VANZARIdinPACK_FACTURARE(:5485, :5493, :5516, :5615, :14817, :15387, :15397, :15509) plusmodifica_date_factura(:14463-14512, singura procedura de „modifica antetul facturii" existenta) atingSTERS/FACTURAT/ID_FACT/AVIZE/SERIE_ACT/NUMAR_ACT/DATA_ACT/DATA_SCAD— niciunaDISCOUNT. - In VFP: cautare pe
COMUN\dupaset discount =/vanzari set ... discount— niciun rezultat. - Trigger-ele pe
VANZARInu-l ating:TRG_VANZARI_BEFOUPDface doar audit peNR_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:
- 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
DISCOUNTnou si totaluri calculate pe cel vechi. Cu un parametru, e imposibil sa recalculezi cu un discount invechit. - procedura ramane utilizabila ca recalcul pur (fara al doilea argument) de oriunde altundeva — de exemplu dintr-un script de backfill.
- 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.pck — 7 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
selectde raportare, si se incheie cuexec pack_migrare.UpdateVersiune('ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql');+commit;; versiune_db.txtdin radacina proiectului se muta pe2026_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
- Liniile de set (R1) — le facem needitabile in grid, cu
ID_VANZARE_SETadaugat inVVANZARI_ARTICOLEprintr-un al doilea script? Recomandarea mea: da. Fara asta, editarea unei facturi cu seturi arata ca merge si nu merge. - Discountul de document — parametru al procedurii (
V_DISCOUNT ... DEFAULT NULL), sauUPDATE VANZARI SET DISCOUNTseparat din VFP? Recomandarea mea: parametru, ca discountul si totalurile sa nu poata diverge (vezi 5). PRET_ACHIZITIEpe linia noua (R3) — se citeste din nomenclator/stoc la adaugare, sau se scrie0cu avertizare? Recomandarea mea: citit din nomenclator, cu0doar ca ultima rezerva, niciodata NULL.VALVAL/TVAVAL/TOTVAL— raman in recalcul? Recomandarea mea: da (cost zero, fac parte din acelasiSELECT; excluderea lor ar lasa facturile in valuta inconsistente). Confirmare ceruta si in raportul precedent, inca neconfirmata.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
CreeazaPoArticolNouTvdprimeste 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.