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
32 KiB
S5 — calea de scriere VFP: unde se agata scrierea in VANZARI_DETALII
Cercetare read-only, 09.08.2026. Nu s-a modificat niciun fisier si nu s-a rulat nimic care sa scrie in Oracle.
Surse. Partea VFP: versiunile text .vc2 din arbore (COMUN\clase\*.vc2), verificate ca fiind
la zi — mtime identic cu al binarelor (omodificari.vcx/.vc2 = 09.08.2026 18:53,
ofacturare_comun = 08.08.2026 23:26, comun = 09.08.2026 09:45). Atributia clasa/metoda pentru
fiecare linie citata e din vfp_symbols.ps1 -Where. Atentie: alti agenti lucreaza in paralel pe
omodificari.vc2 — numerele de linie din acest raport sunt un instantaneu 09.08.2026 ~21:15.
Briefingul dadea inainte_de_do_termin la :13357-13549; azi e la :14249-14441.
Partea Oracle: surse PL/SQL de pe disc — COMUN\docs\PACK_CONTAFIN.pck (03.08.2026) si
D:\ROA\ROAACNPRO\PACK_FACTURARE.pck (10.06.2026, copie mai veche). Corpurile citate mai jos
coincid caracter cu caracter cu ce raporta rec_s5_oracle_vanzari.md dintr-un export proaspat
(08.08.2026), deci sunt de incredere; nu s-a facut un export nou in aceasta sesiune.
1. Lantul complet de salvare, ambele puncte de intrare
1.1 ROAFACTURARE — frm_facturi.do_editare_factura (COMUN\clase\ofacturare_comun.vc2:3715-3869)
| Pas | Linie | Ce face |
|---|---|---|
| garzi | :3716-3767 |
lactiv3, glLunaInchisa, sters=1, proforma, luna curenta, ReferinteDocumenteNota, EsteInEFactura |
| incarcare nota | :3769 |
IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.) -> tact/trul/trul_obinv |
| incarcare articole | :3793 |
IncarcaArticoleFactura(m.lnIdVanzare, 'crsArticoleFactura') — inainte de Createobject, ca Load() sa lege gridul pe cursor plin |
| formular | :3796-3797 |
Createobject([frm_modific2024], lnIdSet) + .Show() — modal (WindowType = 1, omodificari.vc2:6892) |
| confirmare | :3799 |
If buton = 1 |
| tranzactie ON | :3800 |
Thisform.do_deschide_tranzactie() |
| stergere nota veche | :3802 |
lnSucces = OSCRIE_IN_FISIERE(2,.T.,.T.) |
| rescriere cursoare | :3807-3820 |
tact->actactan, trul->RUL_TEMP, trul_obinv->RUL_TEMP_OBINV |
| scriere nota noua | :3821 |
lnSucces = OSCRIE_IN_FISIERE(0,.T.,.T.) |
| finalizare | :3824-3826 |
begin pack_contafin.finalizeaza_modificare_nota(...); end; prin goExecutor.oExecuta; lnSucces = Iif(..., 1, -1) |
| tranzactie OFF | :3828 |
Thisform.do_inchide_tranzactie(Iif(lnSucces<0, 2, 1)) |
| reafisare | :3830 |
Thisform.do_cauta() |
| curatenie | :3837-3860 |
inchide actactan, tact, rul_temp, trul, rul_temp_obinv, trul_obinv, crsJtvaTemp, crsArticoleFactura — NU inchide tvd/tvanz |
1.2 ROACONT / registru jurnal — afisjurcom.do_modifica (COMUN\clase\comun.vc2:2222-2569)
| Pas | Linie | Ce face |
|---|---|---|
| garzi | :2230-2290 |
lactiv3, glLunaInchisa, luna curenta, id_set 30000-30009, nota de inventar |
| incarcare nota | :2352-2426 |
acelasi SQL ca IncarcaCursoareModificareNota (cod duplicat, nu apel) |
| incarcare articole | :2435-2437 |
IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) -> PregatesteArticoleFacturaEditare('tact') |
| formular | :2439, :2445 |
Createobject([frm_modific2024], lnIdSet) + .Show() |
| confirmare | :2447 |
If buton=1 |
| tranzactie ON | :2451 |
Thisform.do_deschide_tranzactie() |
| stergere nota veche | :2454 |
oscrie_in_fisiere(2,.T.,llRul) (sarit daca nota era deja stearsa, :2456) |
| rescriere cursoare | :2463-2481 |
idem |
| scriere nota noua | :2482 |
oscrie_in_fisiere(0,.T.,llRul) |
| finalizare | :2487-2489 |
finalizeaza_modificare_nota prin goExecutor.oExecuta |
| tranzactie OFF | :2538 |
Thisform.do_inchide_tranzactie(Iif(lnSucces<0, 2, 1)) |
| curatenie | :2546-2566 |
inchide aceleasi cursoare + crsArticoleFactura; NU tvd/tvanz |
Punctul de intrare 2 e reachable din ROAFACTURARE, nu doar din ROACONT: viz_act
(COMUN\programe\ooperatii_comune.prg:115-126) instantiaza AFISJURcom, iar
ooperatii_comune.prg e inregistrat in Programe\roafacturare.prg:218.
ofacturare_editare.prg e inregistrat numai in ROAFACTURARE
(Programe\roafacturare.prg:214 — singura potrivire in tot D:\ROA), deci in ROACONT/ROAGEST
garda "OFACTURARE_EDITARE" $ Set("Procedure") e falsa, pagina 3 nu apare
(omodificari.vc2:14683) si nu exista nimic de scris. Codul nou trebuie sa fie no-op acolo, nu doar
inofensiv.
1.3 Raspunsul explicit: DA, tranzactia e inca deschisa dupa ce finalizeaza_modificare_nota se intoarce
In ambele puncte de intrare do_inchide_tranzactie vine dupa apelul PL/SQL
(ofacturare_comun.vc2:3826 -> :3828; comun.vc2:2489 -> :2538). Intre ele nu se executa
nimic. Contractul, verificat in sursa (COMUN\clase\_frm_base.vc2:252-302):
do_deschide_tranzactie()—SQLSetprop(gnHandle,"Transactions",2); intoarce.T./.F.;do_inchide_tranzactie(tnTip)—tnTip = 1->Sqlcommit(gnHandle), orice altceva ->Sqlrollback(gnHandle); apoi revine peTransactions = 1; intoarce.T./.F..
Deci fereastra :3826-3828 / :2489-2538 e exact locul de agatare: aceeasi conexiune, aceeasi
tranzactie manuala, COMMIT/ROLLBACK inca nedat.
Capcana de contract, preexistenta, de care sa nu depinda codul nou: garda de commit e
Iif(lnSucces<0, 2, 1), deci lnSucces = 0 comite. Codul nou trebuie sa puna explicit
lnSucces = -1 la esec, nu 0.
1.4 Contractele functiilor pe care se sprijina agatarea (deschise, nu presupuse)
goExecutor.oExecuta(...)(COMUN\programe\oproceduri_comune.prg:121-158): intoarce logic.T./.F., si afiseaza singuramessagebox(This.oPrelucrareEroare(), 16, "Eroare")la esec (:153-156). Apelantul nu mai trebuie sa afiseze nimic.goExecutor.oExecute(...)(:173-...): intoarce numericCT_SUCCES/CT_INSUCCES(:218-220), nu numar de randuri. Nu le confunda.OSCRIE_IN_FISIERE(tnScrie_Sterge, tlModificare, tlRul, ...)(COMUN\programe\oscrie_in_fisiere.prg:14): numeric,>0= succes;-1la esecuri de precon- ditie (:68-81),-5la verificarea de stoc (:104). Parametrul 1:0= scriere,2= stergere._frmbase.do_termin(_frm_base.vc2:363-376): singura poarta care punebuton = 1/gnButon = 1, si o face doar dacathis.inainte_de_do_termin()intoarce.T..frm_modific2024nu suprascriedo_termin(nu apare in lista de metode proprii), deci mosteneste asta.
2. Starea purtata de cursorul tvd (si de tvanz)
2.1 Coloanele lui tvd
tvd se creeaza in frm_modific2024.Load (omodificari.vc2:14554-14591): pe ramura
ROAFACTURARE prin CreeazaCursorTvdGol() -> CreeazaCursorArticoleGol('tvd')
(COMUN\programe\ofacturare_editare.prg:263-282), pe ramura ROACONT/ROAGEST printr-un
CREATE CURSOR duplicat literal in clasa (omodificari.vc2:14571) — doua definitii care trebuie
tinute sincron manual.
Se umple din VVANZARI_ARTICOLE prin IncarcaArticoleFactura
(ofacturare_editare.prg:288-323), cu where v.id_vanzare = <id> and v.sters = 0
(:300) — deci la incarcare toate randurile au sters = 0.
| Coloana | Provenienta | Editabila in grid |
|---|---|---|
id_vanzare, id_vanzare_det |
view | nu |
id_articol |
view | nu |
cantitate |
view | DA — Column5, ReadOnly = .F. (:12366-12374) |
pret |
view | DA — Column6, ReadOnly = .F. (:12375-12383) |
pret_cu_tva (flag 0/1) |
view | DA — Column7 checkbox, ReadOnly = .F., Sparse = .F. (:12384-12391) |
proc_tvav |
view | nu (Column8.ReadOnly = .T.) |
discount_unitar |
view | nu (Column9.ReadOnly = .T.) |
id_gestiune, cont, id_valuta, id_jtva_coloana, serie, explicatie, taxcode, lot, sters |
view | nu (cont, id_gestiune, id_valuta, id_jtva_coloana, sters nici macar nu au coloana in grid) |
denumire, codmat, nume_gestiune, nume_val |
join-uri din view | nu |
in_stoc |
nu e in view — join separat pe NOM_ARTICOLE in SQL-ul din :299 |
nu |
lmodificat (L) |
calculat, .F. la incarcare (:316) |
— |
valoare (N 14,2) |
calculat la incarcare (:316-319) si recalculat la fiecare editare |
nu (Column14.ReadOnly = .T.) |
Definitia view-ului: docs\cercetare\ff_view_articole_vanzare.sql (21 de coloane, valori brute,
fara conversie valutara; filtrul STERS lasat pe seama apelantului).
Doar 3 coloane sunt editabile: cantitate, pret, pret_cu_tva. Tot restul e read-only in
grid; explicatie/taxcode se schimba doar pe cealalta cale, frm_modifica_articol_factura
(punctul 4).
2.2 Cum se distinge o linie modificata / stearsa / adaugata
- Modificata:
tvd.lmodificat = .T., per linie, nu global. Se pune in exact trei locuri:frm_modific2024.calculeaza_valori_articol(:13397-13409, linia:13407REPLACE lmodificat WITH .T., valoare WITH m.lnValoare) — apelata dinValid-ul cantitatii (:16428-16433),Valid-ul pretului (:16439-16444) siInteractiveChange-ul checkboxuluipret_cu_tva(:16450-16458);cmdStergeArticol.Click(:16423);AdaugaLinieTvdDinArticol(:13119).Valid-urile compara cuThisform.oldvalue(setat inWhen), deci o retastare a aceleiasi valori nu marcheaza linia. Nu existalmodificatla nivel de formular.
- Stearsa:
tvd.sters = 1.cmdStergeArticol.Click(:16417-16426) comuta (REPLACE sters WITH IIF(Nvl(sters,0) = 1, 0, 1)), deci stergerea e reversibila pana la salvare; linia ramane vizibila, grizata prinDynamicForeColor = "IIF(tvd.sters=1,RGB(150,150,150),...)"pe toate cele 14 coloane. Toate randurile incarcate pornesc de lasters = 0, decisters = 1intvdinseamna intotdeauna "sters in sesiunea curenta". - Adaugata:
tvd.id_vanzare_det = 0, pus explicit deAdaugaLinieTvdDinArticol(:13108). Randul vine dinfrm_articol_facturaprinpoArticol(cmdAdaugaArticol.Click,:16374-16415), cuid_vanzare=This.nIdVanzaresi conversie RON->valuta documentului pe ramuratip_valuta = 0(:13097-13105). Combinatiaid_vanzare_det = 0 AND sters = 1e posibila (linie adaugata si apoi stearsa in aceeasi sesiune) si trebuie ignorata la scriere.
2.3 Valorile VECHI: NU se pastreaza nicaieri
Confirmat prin cautare: nu exista niciun cursor de instantaneu (tvd_orig, crsArticoleOrig,
tvanz_orig, nDiscountVechi etc.) in cod de productie — singura potrivire e intr-un test
(COMUN\utile\Teste\editare_factura\test_ui_bara_totaluri.prg:86). tvd poarta doar valorile
curente plus flagul lmodificat; valoarea dinainte de editare se pierde in momentul in care
utilizatorul o schimba.
Consecinte:
- scrierea nu poate fi restransa la "coloanele efectiv schimbate" — se scriu toate cele trei
coloane editabile plus
valoare, pe randurile culmodificat = .T.; - cerinta S4b de a enumera utilizatorului "linia X: cantitate 5 -> 8" nu e realizabila azi;
cere un cursor de instantaneu luat imediat dupa
IncarcaArticoleFactura(ex.SELECT id_vanzare_det, cantitate, pret, pret_cu_tva FROM tvd INTO CURSOR tvd_initial). Nu e implementat; e lucrare in plus, nu detaliu.
2.4 tvanz si discountul de document
tvanz se creeaza tot in Load (:14574-14582), prin CreeazaCursorTvanzGol()
(ofacturare_editare.prg:148-154) sau prin CREATE CURSOR duplicat (:14581, cu 3 coloane mai
putin — in_valuta/id_valuta/nume_val lipsesc pe ramura ROACONT). Se umple in Show()
prin IncarcaVanzareDinNota('tact') (:14684), care cauta randul din VANZARI incercand toate
tripletele distincte (cod, nract, serie_act, dataact) din tact
(ofacturare_editare.prg:203-258). Coloane: id_vanzare, tip, discount, total_fara_tva, total_tva, total_cu_tva, curs, multiplicator, in_valuta, id_valuta, nume_val.
discounte editabil direct:txtDiscountArtareControlSource = "tvanz.discount",ReadOnly = .F.(omodificari.vc2:12825-12836).Valid-ul lui cheama doarActualizeazaBaraTotaluri()(:16460-16462).- Nu exista
lmodificatpetvanzsi nici valoare veche salvata. Nu se poate sti daca utilizatorul a atins discountul. Singura optiune fara lucrare in plus: scrie discountul neconditionat cand pagina a fost activa (UPDATE VANZARI SET DISCOUNT = ...e idempotent). Daca se vrea "doar daca s-a schimbat", trebuie retinuta valoarea initiala la incarcare. tvanz.total_cu_tvae afisat ca "Total salvat" (txtTotalSalvatArt,ControlSource = "tvanz.total_cu_tva", ReadOnly,:12895-12907) — e valoarea din baza, nu una recalculata.- Totalurile calculate stau in proprietati de formular, nu in cursor:
nTotalLiniiRon,nTotalNetRon(ActualizeazaBaraTotaluri,:12934-12976),nTotalActRon,nTotalRulRon,lRulAjustat(ActualizeazaVerdictActRul,:12978-13079). Ele dispar odata cu formularul.
2.5 Cursoarele supravietuiesc formularului
frm_modific2024 nu are proprietatea DataSession (nicio potrivire in tot omodificari.vc2),
deci ruleaza in sesiunea de date implicita: tvd si tvanz, create in Load(), raman deschise
dupa This.Release din do_termin. Niciunul dintre cei doi apelanti nu le inchide
(ofacturare_comun.vc2:3837-3860, comun.vc2:2546-2566). Asta face agatarea posibila — dupa
Omodif.Show(), apelantul citeste tvd/tvanz direct.
Corolar: obiectul Omodif e Released, deci nu se pot citi proprietatile lui
(lAreArticoleVanzari, nIdVanzare) dupa Show(). Semnalul "pagina de articole a fost activa"
trebuie dedus din cursoare: Used('tvanz') AND Reccount('tvanz') = 1 AND Used('tvd'), iar
id_vanzare se ia din tvanz.id_vanzare.
3. Unde se agata scrierea — si ce ordonare rezista capcanei
3.1 Capcana, verificata in sursa
pack_contafin.finalizeaza_modificare_nota (COMUN\docs\PACK_CONTAFIN.pck:8601-8649):
lnCodNou := pack_contafin.get_cod();
SELECT COUNT(*) INTO lnEInVanzari FROM vanzari WHERE cod = tnCod;
IF lnEInVanzari > 0 THEN
pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou);
END IF;
UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod = tnCod;
...
pack_facturare.actualizeaza_vanzari (D:\ROA\ROAACNPRO\PACK_FACTURARE.pck:15951-15961):
-- de modificat in caz ca il las sa stearga manual inregistrari din VANZARI_DETALII
-- acum se marcheaza cu STERS = 1 doar cand se sterge toata factura
UPDATE VANZARI_DETALII SET STERS = 0
WHERE ID_VANZARE IN (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI);
UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI;
Pentru o factura, COUNT(*) > 0 e intotdeauna adevarat, deci resetul STERS = 0 ruleaza la
FIECARE editare de nota. ID_VANZARE nu se schimba niciodata (se schimba doar COD), deci o
cheie ID_VANZARE capturata inainte ramane valida dupa.
3.2 Doua consecinte, nu una
- Evidenta: o linie marcata
STERS = 1de VFP inainte definalizeaza_modificare_notae reactivata tacut. Deci scrierea trebuie sa fie dupa. - Mai putin evidenta, si mai grava: la o editare ulterioara a aceleiasi facturi, resetul
reactiveaza si liniile sterse in sesiuni anterioare.
tvdse incarca doar custers = 0(ofacturare_editare.prg:300), deci VFP nici nu stie ca liniile alea exista si nu le-ar re-marca. Rezultat: o linie stearsa luna trecuta reapare la prima re-editare a facturii, si intra si in recalculul de totaluri (care citesteSTERS = 0). Asta nu e o problema azi, pentru ca azi nu exista stergere per linie — devine problema exact prin #6.
3.3 Ordonarea care rezista
Punctul de agatare, pentru ambele puncte de intrare, e imediat dupa apelul
finalizeaza_modificare_nota si inainte de do_inchide_tranzactie, gardat de lnSucces > 0:
- ROAFACTURARE: intre
ofacturare_comun.vc2:3827(Endif-ul apelului) si:3828; - registru jurnal: intre
comun.vc2:2490(Endif-ul apelului) si:2538.
Secventa completa, o singura tranzactie manuala:
do_deschide_tranzactie()
OSCRIE_IN_FISIERE(2, ...) -- sterge nota veche
OSCRIE_IN_FISIERE(0, ...) -- scrie nota noua
finalizeaza_modificare_nota(...) -- realiniaza COD, RESETEAZA STERS = 0 pe toate liniile
>>> ScrieArticoleFacturaEditate(...) -- NOU: liniile + discountul
>>> recalculeaza_totaluri_vanzari(id_vanzare) -- NOU (S5 Oracle)
do_inchide_tranzactie(Iif(lnSucces < 0, 2, 1))
Iar in interiorul helperului, ordinea operatiilor:
INSERTpentru liniile noi (id_vanzare_det = 0 AND Nvl(sters,0) <> 1) — intai, ca ID-urile lor sa poata fi excluse la pasul 3; PK-ul vine din trigger, nu se cere din secventa (rec_cale_vanzari_detalii.md3.2);UPDATEpentru liniile existente atinse (id_vanzare_det > 0 AND lmodificat AND Nvl(sters,0) <> 1);- stergere ca diferenta de multimi, nu ca lista de linii sterse:
UPDATE VANZARI_DETALII SET STERS = 1, ID_UTILS = :util, DATAORAS = SYSDATE WHERE ID_VANZARE = :id AND STERS = 0 AND ID_VANZARE_DET NOT IN (<ID-urile active din tvd>). Formularea asta e cea care rezista: ea nu enumera ce s-a sters acum, ci impune ca multimea activa din baza sa fie exact multimea activa dintvd. Repara automat si consecinta (2) din 3.2 — liniile inviate de reset redevin sterse — si e idempotenta. Pentru asta,<ID-urile active>trebuie sa includa si ID-urile randurilor tocmai inserate (de aici ordinea INSERT-inainte), sau, mai simplu, pasul 3 se restrange la... AND ID_VANZARE_DET NOT IN (<ID-urile > 0 active din tvd>) AND ID_VANZARE_DET NOT IN (<ID-urile inserate acum>). Daca lista de ID-uri inserate e greu de obtinut din VFP, alternativa e sa se ruleze pasul 3 inainte de INSERT — atunci listaNOT INcontine doar ID-uri preexistente si randurile noi nu sunt inca in tabela, deci nu pot fi atinse. Recomandare: pasul 3 primul, apoi INSERT, apoi UPDATE. Mai simplu si fara nevoia deRETURNING. UPDATE VANZARI SET DISCOUNT = :discount WHERE ID_VANZARE = :id(dintvanz.discount).
Recalculul de totaluri se muta din finalizeaza_modificare_nota in apelantul VFP.
rec_s5_oracle_vanzari.md (B) propunea recalculeaza_totaluri_vanzari apelata din interiorul
lui finalizeaza_modificare_nota, dupa actualizeaza_vanzari. Cu ordonarea de mai sus asta nu
mai merge: la momentul acela liniile nu sunt inca scrise si VANZARI.DISCOUNT inca are valoarea
veche, deci totalurile s-ar calcula pe date invechite. Apelul trebuie facut din VFP, ca pas separat,
dupa helper. Efectul secundar e favorabil si corespunde motivatiei originale a Variantei B:
recalculul devine strict opt-in pe calea de editare de factura, nu ruleaza pentru notele
ROACONT/ROAGEST care nu ating sumele — ceva ce finalizeaza_modificare_nota oricum nu putea
distinge.
3.4 Ordonari respinse
| Varianta | De ce nu |
|---|---|
Scriere inainte de finalizeaza_modificare_nota |
STERS = 1 sters de resetul din actualizeaza_vanzari; INSERT/UPDATE ar supravietui, dar stergerea nu — comportament pe jumatate, cel mai prost caz |
Scriere din inainte_de_do_termin (in formular) |
ruleaza inainte de do_deschide_tranzactie (_frm_base.vc2:364 -> ofacturare_comun.vc2:3800), deci in autocommit, in afara tranzactiei; la esec ulterior al notei, liniile raman scrise |
Scriere dupa do_inchide_tranzactie |
tranzactie separata; commit partial daca a doua esueaza |
Modificarea lui actualizeaza_vanzari sa nu mai reseteze STERS |
cod partajat cu toata suita ROA (apelat pentru orice nota cu cod in vanzari); riscul respins deja de plan |
4. Modelul existent: pack_facturare.modifica_explicatie_articol
Oracle (D:\ROA\ROAACNPRO\PACK_FACTURARE.pck:14464-14472) — corpul complet:
PROCEDURE modifica_explicatie_articol(V_ID_VANZARE_DET IN NUMBER,
V_EXPLICATIE IN VARCHAR2,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL) is
BEGIN
UPDATE VANZARI_DETALII
SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE
WHERE ID_VANZARE_DET = V_ID_VANZARE_DET;
END modifica_explicatie_articol;
Detaliu de contract, contra-intuitiv: V_ID_UTIL e primit si complet ignorat — nu se scriu
ID_UTILS/DATAORAS. Procedura nu e un model bun pentru partea de audit; e model doar pentru
forma apelului si pentru granularitatea "un UPDATE tintit pe ID_VANZARE_DET".
Apelantul VFP — frm_modifica_articol_factura.inainte_de_do_termin
(COMUN\clase\ofacturare_comun.vc2:5230-5241), integral:
PROCEDURE inainte_de_do_termin
Local llReturn
If amessagebox("Doriti sa salvati modificarile?",4+32,"Confirmare salvare explicatie") = 6
lcSql = [begin pack_facturare.modifica_explicatie_articol(] + Alltrim(Str(poRec.id_vanzare_det)) + [,] + ;
['] + Nvl(Alltrim(OracleSpecialCharacters(poRec.explicatie)),'') + [',?gnIdUtil,?poRec.taxcode); end;]
llReturn = goExecutor.oExecuta(lcSql)
Else
llReturn = .F.
Endif
Return llReturn
ENDPROC
Deschis din frm_facturi.do_modifica_explicatie (ofacturare_comun.vc2:4639-4656):
Scatter Name poRec Memo din crsDetalii, Createobject("frm_modifica_articol_factura"),
.Show(), apoi actualizeaza_grid2() daca gnButon = 1.
Ce se preia din model:
- bloc
begin ... ; end;construit ca text, cu numerele injectate prinAlltrim(Str(...))si sirurile prin?-binding sau literal cu ghilimele simple; OracleSpecialCharacters(...)obligatoriu pe orice sir care ajunge literal in SQL;- tratarea erorii = niciuna in apelant:
goExecutor.oExecutaintoarce.T./.F.si afiseaza singur mesajul; apelantul doar propaga booleanul.
Ce NU se preia: apelul asta ruleaza in autocommit, in afara oricarei tranzactii manuale
(do_modifica_explicatie nu deschide tranzactie). Helperul nou ruleaza in interiorul tranzactiei
deschise de apelant si nu are voie sa dea COMMIT/ROLLBACK — se opreste la primul esec si lasa
apelantul sa faca rollback prin do_inchide_tranzactie(2).
5. inainte_de_do_termin (omodificari.vc2:14249-14441)
Numerotare: briefingul dadea :13357-13549; azi metoda e la :14249-14441 (aproximativ 90% din
corp — :14299-14437 — e cod comentat, ramas din versiunea veche).
Ce valideaza azi (codul viu, :14252-14296):
| Linie | Verificare |
|---|---|
:14252-14253 |
SELECT tact + SET FILTER TO (curata filtrul de grid inainte de salvare) |
:14257-14259 |
completeaza id_set gol pe tact, trul, trul_obinv |
:14265-14271 |
pentru id_set 99998 / 90024 sare peste verificarea de conturi |
:14273-14277 |
verificare_note_contabile('tact', ...) — analitice si parteneri (in oOperatii_comune) |
:14281-14290 |
avertisment 4426-4428 / 4428-4427 introdusa fara optiunea dedicata (doar din 2013), cu confirmare Da/Nu |
:14291-14293 |
This.VerificaAvertizareExigibilizareTVA() |
:14296 |
RETURN m.llRet |
Nu atinge deloc tvd sau tvanz. Nicio validare pe pagina de articole.
Recomandare: da, aici trebuie adaugata validarea paginii de articole — e singura poarta inaintea
lui gnButon = 1 (_frm_base.vc2:364), deci singurul loc care poate opri o salvare inainte ca
apelantul sa deschida tranzactia. Validari care merita:
- cantitate 0 sau negativa pe o linie activa (
Nvl(sters,0) <> 1) — o linie cu cantitate 0 se scrie inVANZARI_DETALIIcu valoare 0 si strica totalurile fara sa fie vizibila ca eroare; id_articolnul sau 0 pe o linie activa — se poate produce doar prinAdaugaLinieTvdDinArticolcu unpoArticolincomplet, darINSERT-ul ar cadea pe FK abia in Oracle, dupa ce nota a fost deja rescrisa, deci cu ROLLBACK pe tot;pretnul pe o linie activa —VANZARI_DETALII.PRETeNOT NULL(rec_cale_vanzari_detalii.md2.2);- zero linii active cand documentul are rand in
VANZARI— utilizatorul a sters tot; cerea o confirmare explicita, nu o salvare tacuta care goleste factura.
Trei conditii obligatorii pentru adaugare:
- gardata pe
"OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Used('tvd'), altfel se rupe registrul jurnal din ROACONT/ROAGEST; RETURN .F.blocheaza salvarea — se foloseste doar pentru erori reale; pentru situatii discutabile, tiparul existent din:14286-14288(mesaj cu 4+32 si continuare la "Da");- plasata inaintea lui
RETURN m.llRetde la:14296, nu in blocul comentat de dedesubt.
Fara cod aici, doar constatarea si recomandarea, cum s-a cerut.
6. Contractul minim al helperului nou
Locul: COMUN\programe\ofacturare_editare.prg — acolo stau deja IncarcaArticoleFactura,
IncarcaVanzareDinNota, CreeazaPoArticolNouTvd, si fisierul e inregistrat doar in ROAFACTURARE
(Programe\roafacturare.prg:214), ceea ce da automat no-op-ul in ROACONT/ROAGEST.
FUNCTION ScrieArticoleFacturaEditate
LPARAMETERS tnIdVanzare, tcAliasArticole, tcAliasVanzare
Parametri
tnIdVanzare—ID_VANZAREal documentului (dintvanz.id_vanzare). Nu se ia dintvd, ca sa functioneze si candtvda ramas gol.tcAliasArticole— implicit'tvd'(simetric cuIncarcaArticoleFactura, care primeste aliasul destinatie; permite testarea pe un cursor construit in test).tcAliasVanzare— implicit'tvanz', pentrudiscount.
Preconditii (nu le verifica, le documenteaza):
- tranzactie manuala deja deschisa de apelant (
do_deschide_tranzactiea intors.T.); finalizeaza_modificare_notaa rulat deja cu succes — deciactualizeaza_vanzarisi-a facut resetulSTERS = 0, iar helperul scrie peste el;goExecutorconectat.
Ce scrie, in ordinea din 3.3:
UPDATE VANZARI_DETALII SET STERS = 1, ID_UTILS = <gnIdUtil>, DATAORAS = SYSDATE WHERE ID_VANZARE = :id AND STERS = 0 AND ID_VANZARE_DET NOT IN (<ID-urile > 0 active din alias>)— un singur statement, formulat ca diferenta de multimi (motivarea in 3.3);INSERT INTO VANZARI_DETALII (...)per linie cuid_vanzare_det = 0 AND Nvl(sters,0) <> 1, faraID_VANZARE_DETin lista de coloane (triggerTRG_VANZARI_DET_BEFOINS);UPDATE VANZARI_DETALII SET CANTITATE = ..., PRET = ..., PRET_CU_TVA = ..., ID_UTILS = ..., DATAORAS = SYSDATE WHERE ID_VANZARE_DET = :id_detper linie cuid_vanzare_det > 0 AND lmodificat AND Nvl(sters,0) <> 1;UPDATE VANZARI SET DISCOUNT = :disc WHERE ID_VANZARE = :iddin<tcAliasVanzare>.discount.
PROC_TVAV nu se recalculeaza — ramane cota deja persistata pe linie
(rec_cale_vanzari_detalii.md, "Observatie tehnica"). DISCOUNT_UNITAR nu e editabil in grid
(punctul 2.1), deci se scrie doar la INSERT, nu la UPDATE.
Ce intoarce: logic. .T. = tot a mers sau nu era nimic de facut; .F. = primul esec, si se
opreste acolo. No-op cu .T. cand tnIdVanzare <= 0, cand aliasul de articole nu e deschis, sau
cand aliasul de vanzare nu are exact un rand — asa apelul poate sta neconditionat in ambele puncte
de intrare, fara garda duplicata la apelant.
Exceptie de la "nimic de facut = .T.": Reccount(tcAliasArticole) = 0 cu tnIdVanzare > 0 nu
e no-op, e "s-au sters toate liniile" — pasul 1 marcheaza tot documentul. Validarea din punctul 5
(confirmare la zero linii active) e ce trebuie sa impiedice cazul accidental.
Cum semnaleaza eroarea: prin valoarea de retur, atat. Nu afiseaza mesaj — goExecutor.oExecuta
o face deja (oproceduri_comune.prg:153-156). Nu da COMMIT/ROLLBACK, nu inchide cursoare, nu
schimba workarea curenta (o salveaza cu Select() si o restaureaza, ca IncarcaVanzareDinNota).
La apelant:
If lnSucces > 0
lnSucces = Iif(ScrieArticoleFacturaEditate(...), 1, -1)
Endif
— -1, nu 0, ca sa se prinda in Iif(lnSucces<0, 2, 1) de la do_inchide_tranzactie
(capcana din 1.3).
Motivare a formei: un singur punct de scriere, apelat identic din ambele puncte de intrare
(altfel logica se dubleaza in ofacturare_comun.vc2 si in comun.vc2, iar comun.vc2 e atins de
toata suita); parametrizat pe alias, ca testul headless sa-l poata rula pe cursoare construite
manual, fara formular; contract boolean, identic cu goExecutor.oExecuta si cu
frm_modifica_articol_factura.inainte_de_do_termin, deci fara conventie noua de erori in cod.
De verificat inainte de a scrie INSERT-ul (ramas deschis din rec_cale_vanzari_detalii.md
punctul 5, NEVERIFICAT si aici): coloanele NOT NULL fara valoare din trigger —
STERS, VALIDAT, DIFERENTA, CUSTODIE, DESCARCAT — au sau nu DEFAULT la nivel de coloana
(all_tab_columns.data_default). Daca nu au, trebuie enumerate explicit in INSERT.
7. Ce ramane netestabil headless
Testabil headless (vfp9.exe -A -T), pe cursoare construite in test:
ScrieArticoleFacturaEditatecu ungoExecutormock: se verifica textul SQL generat pentru fiecare din cele 3+1 categorii, ordinea statement-elor, si comportarea la.F.din mock;- clasificarea liniilor din
tvd(modificata / stearsa / adaugata / adaugata-si-stearsa) — logica pura pe cursor; AdaugaLinieTvdDinArticolsicalculeaza_valori_articol(au deja teste:COMUN\utile\Teste\editare_factura\test_adauga_linie_articol.prg,test_adauga_linie_valuta.prg);- validarile noi din
inainte_de_do_termin, apelate direct pe o instanta de formular.
Netestabil headless, cu motivul:
- Coloanele gridului
grdArticoleFactura. Sub-A -Tcoloanele nu se materializeaza (ColumnCountsiRecordSourcecitite acolo sunt artefacte); pentru ele exista deja harnessul cu UI vizibil (test_ui_grid_articole.prg,test_ui_sterge_linie.prg,test_ui_fix_editabil_subtotal.prg). - Interactiunea reala cu gridul —
Valid/When/InteractiveChangepecCantitateArt,cPretArt,cPretCuTvaArt(:16428-16458) depind de focus si deThisform.oldvalue; se pot apela metodele direct, dar asta nu testeaza traseul care punelmodificat. - Dialogul modal
frm_articol_facturadeschis dincmdAdaugaArticol.Click(:16407-16408,loDlg.Show(1)) — blocheaza headless. De aceeaAdaugaLinieTvdDinArticola fost deja separata deClick(comentariul de la:13082-13083); se testeaza doar partea separata. - Ordonarea fata de
actualizeaza_vanzari— inima acestei cercetari. Nu se poate verifica decat pe Oracle real:finalizeaza_modificare_notatrebuie sa ruleze efectiv ca sa se vada resetulSTERS = 0, iar apoi ca helperul il corecteaza. Cere un test tranzactional (do_deschide_tranzactie-> pasii ->SELECTde verificare ->Sqlrollback), care scrie temporar in baza. Nu s-a rulat aici. - Regresia liniilor sterse in sesiuni anterioare (3.2, consecinta 2) — cere doua editari succesive ale aceleiasi facturi, deci doua tranzactii, deci scriere reala.
amessageboxdingoExecutor.oExecutala esec Oracle — blocheaza headless; testele care forteaza un esec trebuie sa mochezeoExecuta, altfel raman agatate.- Comportamentul in ROACONT/ROAGEST (pagina absenta, helper neincarcat) — nu se poate testa din
ROAFACTURARE, unde
ofacturare_editare.prge mereu inSET("PROCEDURE"). Se poate aproxima verificand ca garda"OFACTURARE_EDITARE" $ Upper(Set("Procedure"))exista in fiecare punct nou, dar nu e acelasi lucru cu o rulare reala.
Rezumat al lucrurilor de decis inainte de implementare
- Recalculul de totaluri se muta din
finalizeaza_modificare_notain VFP (3.3) — abatere de larec_s5_oracle_vanzari.mdB, impusa de ordonare. De confirmat cu Marius. - Stergerea se scrie ca diferenta de multimi, nu ca lista de linii sterse (3.3, pasul 1) — asta e ce repara si regresia din 3.2(2).
- Discountul se scrie neconditionat cat timp nu exista valoare initiala salvata pe
tvanz(2.4); alternativa e un instantaneu la incarcare. - S4b "enumera vechi -> nou" nu are azi de unde lua valorile vechi (2.3) — cere un cursor de instantaneu, lucrare in plus fata de ce exista.
- De verificat
DEFAULT-urile pe coloaneleNOT NULLdinVANZARI_DETALIIinainte de a scrieINSERT-ul (punctul 6, NEVERIFICAT).