Files
comun/utile/Teste/editare_factura/test_s5_rollback_real.prg
Marius Mutu 1c42ae0af4 #6 editare factura emisa: S5 - scrierea sumelor editate in Oracle
La salvarea unei facturi deja emise, modificarile facute liniilor de articole
se scriu acum real in VANZARI_DETALII, in aceeasi tranzactie cu restul salvarii,
si totalurile documentului se recalculeaza din liniile efectiv salvate.

- ofacturare_editare.prg: helper nou ScrieArticoleFacturaEditate. Idiomul e
  "marcheaza tot sters, invie ce ramane", nu delta, pentru ca
  actualizeaza_vanzari face UPDATE ... SET STERS = 0 pe tot documentul, fara
  garda; o scriere delta ar lasa liniile sterse sa reinvie la fiecare salvare.
  Cursor gol = no-op, ca o eroare la incarcare sa nu goleasca factura.
- comun.vc2, ofacturare_comun.vc2: agatarea apelului dupa
  finalizeaza_modificare_nota si inainte de inchiderea tranzactiei, in ambele
  puncte de intrare.
- omodificari.vc2: coloana Pret achizitie in grid, editabila doar pe liniile
  noi; liniile de set devin needitabile, cu marcaj; cinci validari la salvare.
- teste: cinci suite noi (validari, grid UI, scriere reala, discount/valuta,
  rollback, al doilea punct de intrare) si asteptari actualizate in
  test_page3_articole.
- docs: oracle_export.md - exportul all_source cere linesize 32767, altfel
  rupe liniile lungi prin mijlocul identificatorilor; scripturi-migrare-db.md -
  UpdateVersiune adauga singur extensia .sql.

Cere scripturile ff_2026_08_09_01 (PACK_FACTURARE) si ff_2026_08_09_02
(VVANZARI_ARTICOLE).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-10 01:37:12 +03:00

471 lines
21 KiB
Plaintext

* test_s5_rollback_real.prg
* Inchide golul "calea de ROLLBACK la esec partial" din docs\livrare_s5.md, sectiunea 5: pana acum
* niciun test nu provocase deliberat un esec in mijlocul ScrieArticoleFacturaEditate (ofacturare_editare.prg)
* pe date reale - comportamentul de rollback era verificat doar static, prin simetria codului.
*
* PARTEA M (mock, fara Oracle) - completeaza test_s5_validari_articole.prg/B3 (care opreste esecul
* la a DOUA comanda, adica in mijlocul buclei UPDATE a liniilor pastrate): aici esecul e la PRIMA
* comanda (chiar update-ul "marcheaza tot sters", primul pas al helperului) - dovedeste ca daca
* pasul 1 esueaza, functia se opreste imediat, FARA nicio comanda de UPDATE/INSERT/recalcul.
*
* PARTEA R (real, Oracle) - NU apeleaza ScrieArticoleFacturaEditate direct (vezi ATENTIE mai jos),
* ci reproduce EXACT aceeasi secventa SQL pe care helperul ar genera-o pentru un caz cu esec la
* INSERT (id_articol inexistent, FK_VANZARE_DET002), prin SQLEXEC brut, in tranzactia manuala
* deja folosita de test_s5_scriere_reala.prg. Dovedeste, pe id_vanzare = 1049:
* 1) pasii 1-2 ai helperului (marcheaza tot sters, invie+actualizeaza liniile pastrate) chiar
* produc starea asteptata NECOMISA, verificata pe aceeasi conexiune;
* 2) o linie noua VALIDA se INSEREaza cu succes, tot necomis - proba de executie PARTIALA reala;
* 3) o linie noua cu id_articol inexistent arunca eroarea Oracle reala (ORA-02291) - exact
* punctul unde helperul s-ar opri (EXIT din SCAN al pasului 3, recalculeaza_totaluri_vanzari
* NU s-ar mai apela);
* 4) tranzactia se inchide cu ROLLBACK (tiparul apelantilor: lnSucces=Iif(...,1,-1), apoi
* MyInchideTranzactie(Iif(lnSucces<0,2,1)));
* 5) verificat INDEPENDENT prin sqlplus, dupa terminarea procesului VFP: VANZARI_DETALII pentru
* 1049 are EXACT acelasi continut (id_vanzare_det/sters/cantitate/pret/pret_achizitie) ca
* inainte de test - nimic din pasii 1-3 nu a supravietuit.
*
* ATENTIE - GASIT IN TIMPUL ACESTUI TEST, NU REPARAT (cod de productie interzis pentru acest agent):
* apelul goExecutor.oExecuta(<sql invalid>) - EXACT calea Oracle folosita de ScrieArticoleFacturaEditate
* pentru toate comenzile ei - AGATA procesul VFP headless la o eroare Oracle REALA (nu simulata),
* sub tranzactie manuala (SQLSetprop Transactions=2), chiar daca Oracle a raspuns deja cu eroarea
* (sesiunea Oracle arata INACTIVE/"SQL*Net message from client", 0 sesiuni blocante, nicio fereastra
* de dialog Windows - EnumWindows arata doar fereastra principala). Reprodus de doua ori izolat, cu
* un script minimal (~15 linii, test_repro_hang_fk.prg, sters dupa investigatie), inclusiv fara nicio
* legatura cu ScrieArticoleFacturaEditate - deci NU e un bug al helperului, ci al executorului comun
* (COMUN\programe\oproceduri_comune.prg, clasa oexecutor, PROCEDURE oExecute). SQLEXEC brut (fara
* goExecutor) pe ACEEASI comanda intoarce eroarea instant (lnRes=-1, AERROR populat) - vezi
* test_repro_hang_fk_raw.prg (sters dupa investigatie). Suspiciune, neconfirmata: ramurile de eroare
* din oExecute apeleaza necondiotionat goLog.Log() cand Type('goLog')='O' - posibil sa incerce o
* comanda Oracle proprie pe aceeasi conexiune, aflata deja sub tranzactie manuala. Consecinta pentru
* livrare: comportamentul REAL al aplicatiei (nu doar al helperului izolat) la un esec Oracle propriu-zis
* in acest lant de scriere NU a putut fi verificat capat-la-capat prin apelul functiei de productie -
* doar prin reproducerea manuala a secventei ei SQL. De raportat separat echipei, nu de reparat aici.
*
* Harness identic cu test_s5_scriere_reala.prg: do_deschide/inchide_tranzactie reproduse inline dupa
* _frm_base.vc2:252-302. Nu atinge cod de aplicatie. QUIT la final.
SET SAFETY OFF
SET TALK OFF
LOCAL lcLog
lcLog = 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_s5_rollback_real_log.txt'
STRTOFILE('start ' + TTOC(DATETIME()) + CHR(13) + CHR(10), lcLog)
PUBLIC gcErrLog, gcTestLog, gnPass, gnFail
gcErrLog = lcLog
gcTestLog = lcLog
gnPass = 0
gnFail = 0
ON ERROR DO test_error_handler WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\utile\Teste\mock_amessagebox.prg ADDITIVE
DO ('D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto.prg') WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
IF gnHandle <= 0
STRTOFILE('EROARE: conectare esuata' + CHR(13) + CHR(10), lcLog, 1)
QUIT
ENDIF
SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_editare.prg ADDITIVE
PUBLIC gnIdVanzareTest
gnIdVanzareTest = 1049
DO parte_m_mock WITH lcLog
DO parte_r_real WITH lcLog, gnIdVanzareTest
STRTOFILE(CHR(13) + CHR(10) + 'REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), lcLog, 1)
STRTOFILE('done ' + TTOC(DATETIME()) + CHR(13) + CHR(10), lcLog, 1)
QUIT
PROCEDURE asserteaza
LPARAMETERS tcNume, tlConditie, tcValoare
IF m.tlConditie
gnPass = gnPass + 1
STRTOFILE('PASS ' + m.tcNume + ' [' + m.tcValoare + ']' + CHR(13) + CHR(10), gcTestLog, 1)
ELSE
gnFail = gnFail + 1
STRTOFILE('FAIL ' + m.tcNume + ' [' + m.tcValoare + ']' + CHR(13) + CHR(10), gcTestLog, 1)
ENDIF
ENDPROC
*-- executa SELECT-ul prin goExecutor (cai care CITESC - fara risc, vezi ATENTIE din antet) si
*-- intoarce codul de succes; cursorul vechi se inchide intai
PROCEDURE ExecSel
LPARAMETERS tcSql, tcCursor
LOCAL lnQ
IF USED(tcCursor)
USE IN (tcCursor)
ENDIF
lnQ = goExecutor.oExecute(tcSql, tcCursor)
RETURN lnQ
ENDPROC
*-- reproduce _frm_base.vc2:252-265 (do_deschide_tranzactie), fara sa instantieze niciun formular
PROCEDURE MyDeschideTranzactie
LOCAL llReturn, lnSuccesLocal
IF TYPE('goExecutor') = 'O'
goExecutor.oExecuta([select * from dual])
ENDIF
lnSuccesLocal = SQLSetprop(gnHandle, "Transactions", 2)
llReturn = IIF(lnSuccesLocal < 0, .F., .T.)
RETURN llReturn
ENDPROC
*-- reproduce _frm_base.vc2:279-302 (do_inchide_tranzactie), fara sa instantieze niciun formular
PROCEDURE MyInchideTranzactie
LPARAMETERS tnTip
LOCAL llReturn, lnSuccesLocal
IF tnTip = 1
lnSuccesLocal = SQLCOMMIT(gnHandle)
ELSE
lnSuccesLocal = SQLROLLBACK(gnHandle)
ENDIF
IF lnSuccesLocal < 0
llReturn = .F.
ELSE
lnSuccesLocal = SQLSetprop(gnHandle, "Transactions", 1)
llReturn = IIF(lnSuccesLocal < 0, .F., .T.)
ENDIF
RETURN llReturn
ENDPROC
*=====================================================================================
*-- PARTEA M: mock pe goExecutor - esec chiar la PRIMA comanda (pasul 1, "marcheaza tot sters")
*=====================================================================================
PROCEDURE parte_m_mock
LPARAMETERS tcLog
STRTOFILE(CHR(13) + CHR(10) + '========== PARTEA M - esec mock la prima comanda (pasul 1) ==========' + CHR(13) + CHR(10), tcLog, 1)
LOCAL loExecutorReal
loExecutorReal = goExecutor
*-- obiectul mock se construieste O SINGURA DATA, inline in program - vezi capcana l, testare-ui-vfp.md
goExecutor = CREATEOBJECT('dummyexecutorrollback')
goExecutor.nFailAtCall = 1
IF USED('crstvdrb')
USE IN crstvdrb
ENDIF
CreeazaCursorArticoleGol('crstvdrb')
SELECT crstvdrb
APPEND BLANK
REPLACE id_vanzare_det WITH 701, id_articol WITH 1, cantitate WITH 1, pret WITH 1, sters WITH 0 IN crstvdrb
IF USED('crstvanzrb')
USE IN crstvanzrb
ENDIF
CREATE CURSOR crstvanzrb (id_vanzare I, discount N(18,4) NULL)
INSERT INTO crstvanzrb (id_vanzare, discount) VALUES (99999, NULL)
LOCAL llRet
llRet = ScrieArticoleFacturaEditate(99999, 'crstvdrb', 'crstvanzrb')
DO asserteaza WITH 'M1: esec la prima comanda (pasul 1) -> functia intoarce .F. imediat', llRet == .F., TRANSFORM(llRet)
DO asserteaza WITH 'M2: esec la prima comanda -> NICIO alta comanda incercata (1 comanda total, nu update/insert/recalcul)', ;
goExecutor.nApeluri == 1, 'nApeluri=' + TRANSFORM(goExecutor.nApeluri)
goExecutor = loExecutorReal
IF USED('crstvdrb')
USE IN crstvdrb
ENDIF
IF USED('crstvanzrb')
USE IN crstvanzrb
ENDIF
IF USED('crsMockSqlRb')
USE IN crsMockSqlRb
ENDIF
ENDPROC
*=====================================================================================
*-- PARTEA R: aceeasi secventa SQL pe care ScrieArticoleFacturaEditate ar genera-o pentru un caz cu
*-- esec la INSERT, prin SQLEXEC brut (vezi ATENTIE din antetul fisierului - goExecutor.oExecuta pe
*-- o comanda care esueaza real agata procesul, deci NU e folosit aici pentru comenzile de scriere)
*=====================================================================================
PROCEDURE parte_r_real
LPARAMETERS tcLog, tnIdVanzare
LOCAL lcSql, lnQ, lnRes, lnArtLipsa, lnArtValid
LOCAL lnDet1, lnDet2, lnDet3, lnCant1Inainte, lnPret1
LOCAL lnNrLiniiInainte, lcSnapshotInainte, lcSnapshotDupa
LOCAL lnCodInainte, lnCodDupa, lnNrLiniiInTranz, lnStersDet1InTranz
LOCAL lnSuccesFinal, lnNrLiniiDupa
STRTOFILE(CHR(13) + CHR(10) + '========== PARTEA R - esec Oracle real (FK id_articol), rollback (id_vanzare=' + ;
TRANSFORM(tnIdVanzare) + ') ==========' + CHR(13) + CHR(10), tcLog, 1)
*-- id_articol garantat inexistent - verificat inainte de scriere, nu presupus
lnArtLipsa = 999999999
lnQ = ExecSel([select count(*) as nr from nom_articole where id_articol = ] + TRANSFORM(lnArtLipsa), 'v_chk')
DO asserteaza WITH 'R0: id_articol folosit pentru esec chiar NU exista in NOM_ARTICOLE', ;
lnQ > 0 AND v_chk.nr = 0, 'nr=' + IIF(lnQ > 0, TRANSFORM(v_chk.nr), 'eroare citire')
IF USED('v_chk')
USE IN v_chk
ENDIF
*-- starea INAINTE, citita direct din Oracle
lcSql = [select cod from vanzari where id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnQ = ExecSel(lcSql, 'v_vz0')
IF lnQ <= 0 OR RECCOUNT('v_vz0') <> 1
DO asserteaza WITH 'R0: VANZARI citit', .F., 'lnQ=' + TRANSFORM(lnQ)
RETURN
ENDIF
lnCodInainte = v_vz0.cod
USE IN v_vz0
lcSql = [select id_vanzare_det, id_articol, nvl(sters,0) as sters, nvl(cantitate,0) as cantitate, ] + ;
[nvl(pret,0) as pret, nvl(pret_achizitie,0) as pret_achizitie ] + ;
[from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ order by id_vanzare_det]
lnQ = ExecSel(lcSql, 'v_lin0')
IF lnQ <= 0
DO asserteaza WITH 'R0: VANZARI_DETALII citit', .F., 'lnQ=' + TRANSFORM(lnQ)
RETURN
ENDIF
lnNrLiniiInainte = RECCOUNT('v_lin0')
lcSnapshotInainte = ''
lnDet1 = 0
lnDet2 = 0
lnDet3 = 0
SELECT v_lin0
SCAN
IF sters = 0
DO CASE
CASE lnDet1 = 0
lnDet1 = id_vanzare_det
lnCant1Inainte = cantitate
lnPret1 = pret
CASE lnDet2 = 0
lnDet2 = id_vanzare_det
CASE lnDet3 = 0
lnDet3 = id_vanzare_det
ENDCASE
ENDIF
lcSnapshotInainte = lcSnapshotInainte + TRANSFORM(id_vanzare_det) + ':' + TRANSFORM(sters) + ':' + ;
TRANSFORM(cantitate) + ':' + TRANSFORM(pret) + ':' + TRANSFORM(pret_achizitie) + '|'
ENDSCAN
GO TOP IN v_lin0
lnArtValid = v_lin0.id_articol
USE IN v_lin0
STRTOFILE(' INAINTE: cod=' + TRANSFORM(lnCodInainte) + ' linii=' + TRANSFORM(lnNrLiniiInainte) + ;
' pastrate=' + TRANSFORM(lnDet1) + '/' + TRANSFORM(lnDet2) + '/' + TRANSFORM(lnDet3) + ;
' snapshot=[' + lcSnapshotInainte + ']' + CHR(13) + CHR(10), tcLog, 1)
DO asserteaza WITH 'R0: documentul are cel putin 3 linii active (pastrate)', lnDet1 > 0 AND lnDet2 > 0 AND lnDet3 > 0, ;
TRANSFORM(lnDet1) + '/' + TRANSFORM(lnDet2) + '/' + TRANSFORM(lnDet3)
IF lnDet1 = 0 OR lnDet2 = 0 OR lnDet3 = 0
RETURN
ENDIF
*-- lantul de scriere, tiparul exact al apelantilor: pornire tranzactie -> comenzi -> esec -> ROLLBACK
lnSuccesFinal = 1
TRY
IF !MyDeschideTranzactie()
DO asserteaza WITH 'R1: tranzactia s-a deschis', .F., 'SQLSetprop a esuat'
RETURN
ENDIF
*-- PASUL 1 al helperului: marcheaza tot sters (ofacturare_editare.prg:489-490)
lcSql = [update vanzari_detalii set sters = 1, id_utils = ] + TRANSFORM(gnIdUtil) + [, dataoras = sysdate ] + ;
[where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ and sters = 0]
lnRes = SQLEXEC(gnHandle, lcSql)
STRTOFILE(' PAS1 marcheaza tot sters => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1)
IF lnRes < 0
lnSuccesFinal = -1
ENDIF
*-- PASUL 2 al helperului: invie+actualizeaza liniile pastrate (:498-501) - cele 3 identificate mai sus,
*-- prima cu cantitatea dublata (ca sa se poata verifica direct efectul UPDATE-ului)
IF lnSuccesFinal > 0
lcSql = [update vanzari_detalii set sters = 0, cantitate = ] + TRANSFORM(lnCant1Inainte * 2) + ;
[, pret = ] + TRANSFORM(lnPret1) + [, pret_cu_tva = 0, id_utils = ] + TRANSFORM(gnIdUtil) + ;
[, dataoras = sysdate where id_vanzare_det = ] + TRANSFORM(lnDet1) + [ and id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnRes = SQLEXEC(gnHandle, lcSql)
STRTOFILE(' PAS2 update linie pastrata det=' + TRANSFORM(lnDet1) + ' => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1)
IF lnRes < 0
lnSuccesFinal = -1
ENDIF
ENDIF
IF lnSuccesFinal > 0
lcSql = [update vanzari_detalii set sters = 0, id_utils = ] + TRANSFORM(gnIdUtil) + [, dataoras = sysdate ] + ;
[where id_vanzare_det = ] + TRANSFORM(lnDet2) + [ and id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnRes = SQLEXEC(gnHandle, lcSql)
STRTOFILE(' PAS2 update linie pastrata det=' + TRANSFORM(lnDet2) + ' => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1)
IF lnRes < 0
lnSuccesFinal = -1
ENDIF
ENDIF
IF lnSuccesFinal > 0
lcSql = [update vanzari_detalii set sters = 0, id_utils = ] + TRANSFORM(gnIdUtil) + [, dataoras = sysdate ] + ;
[where id_vanzare_det = ] + TRANSFORM(lnDet3) + [ and id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnRes = SQLEXEC(gnHandle, lcSql)
STRTOFILE(' PAS2 update linie pastrata det=' + TRANSFORM(lnDet3) + ' => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1)
IF lnRes < 0
lnSuccesFinal = -1
ENDIF
ENDIF
*-- proba intermediara: starea NECOMISA dupa pasii 1-2, pe aceeasi conexiune
IF lnSuccesFinal > 0
lcSql = [select count(*) as nr from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ and sters = 0]
lnQ = ExecSel(lcSql, 'v_activ')
lnNrLiniiInTranz = IIF(lnQ > 0, v_activ.nr, -1)
USE IN v_activ
lcSql = [select nvl(sters,0) as sters, cantitate from vanzari_detalii where id_vanzare_det = ] + TRANSFORM(lnDet1)
lnQ = ExecSel(lcSql, 'v_det1')
lnStersDet1InTranz = IIF(lnQ > 0, v_det1.sters, -1)
STRTOFILE(' IN TRANZACTIE dupa pasii 1-2: linii active=' + TRANSFORM(lnNrLiniiInTranz) + ' det=' + TRANSFORM(lnDet1) + ;
' sters=' + TRANSFORM(lnStersDet1InTranz) + ' cantitate=' + IIF(lnQ > 0, TRANSFORM(v_det1.cantitate), '?') + CHR(13) + CHR(10), tcLog, 1)
USE IN v_det1
DO asserteaza WITH 'R2: dupa pasii 1-2, cele 3 linii pastrate sunt active (sters=0), NECOMIS', ;
lnNrLiniiInTranz = 3, 'linii active in tranzactie=' + TRANSFORM(lnNrLiniiInTranz)
DO asserteaza WITH 'R3: linia pastrata are cantitatea actualizata deja, NECOMIS (proba de executie partiala)', ;
lnStersDet1InTranz = 0, 'sters=' + TRANSFORM(lnStersDet1InTranz)
ENDIF
*-- PASUL 3 al helperului: INSERT linii noi (:519-530) - prima VALIDA, trebuie sa reuseasca
IF lnSuccesFinal > 0
lcSql = [insert into vanzari_detalii (id_vanzare, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, ] + ;
[id_gestiune, pret_achizitie, id_utils, dataoras) values (] + TRANSFORM(tnIdVanzare) + [,] + TRANSFORM(lnArtValid) + ;
[,1,33.33,0,1.21,-1000,44.44,] + TRANSFORM(gnIdUtil) + [,sysdate)]
lnRes = SQLEXEC(gnHandle, lcSql)
STRTOFILE(' PAS3 insert linie noua VALIDA (id_articol=' + TRANSFORM(lnArtValid) + ') => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1)
IF lnRes < 0
lnSuccesFinal = -1
ENDIF
ENDIF
*-- proba intermediara: linia noua e deja acolo, NECOMIS
IF lnSuccesFinal > 0
lcSql = [select count(*) as nr from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnQ = ExecSel(lcSql, 'v_cnt')
lnNrLiniiInTranz = IIF(lnQ > 0, v_cnt.nr, -1)
USE IN v_cnt
STRTOFILE(' IN TRANZACTIE dupa insertul valid: total linii=' + TRANSFORM(lnNrLiniiInTranz) + ' (erau ' + TRANSFORM(lnNrLiniiInainte) + ')' + CHR(13) + CHR(10), tcLog, 1)
DO asserteaza WITH 'R4: linia noua VALIDA a fost inserata deja, NECOMIS (proba de executie partiala)', ;
lnNrLiniiInTranz = lnNrLiniiInainte + 1, 'linii=' + TRANSFORM(lnNrLiniiInTranz)
ENDIF
*-- PASUL 3, a doua linie: id_articol inexistent - trebuie sa ARUNCE ORA-02291 si sa opreasca aici
*-- (exact ce ar face SCAN-ul helperului: EXIT la primul esec, fara recalculeaza_totaluri_vanzari)
IF lnSuccesFinal > 0
lcSql = [insert into vanzari_detalii (id_vanzare, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, ] + ;
[id_gestiune, pret_achizitie, id_utils, dataoras) values (] + TRANSFORM(tnIdVanzare) + [,] + TRANSFORM(lnArtLipsa) + ;
[,1,1,0,1.21,-1000,1,] + TRANSFORM(gnIdUtil) + [,sysdate)]
lnRes = SQLEXEC(gnHandle, lcSql)
STRTOFILE(' PAS3 insert linie noua INVALIDA (id_articol=' + TRANSFORM(lnArtLipsa) + ') => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1)
IF lnRes < 0
LOCAL laErrRb(1)
AERROR(laErrRb)
STRTOFILE(' eroare Oracle: ' + TRANSFORM(laErrRb(2)) + CHR(13) + CHR(10), tcLog, 1)
DO asserteaza WITH 'R5: insertul invalid arunca eroarea asteptata (FK_VANZARE_DET002 / ORA-02291)', ;
'FK_VANZARE_DET002' $ TRANSFORM(laErrRb(2)) OR 'ORA-02291' $ TRANSFORM(laErrRb(2)), TRANSFORM(laErrRb(2))
lnSuccesFinal = -1
ELSE
DO asserteaza WITH 'R5: insertul invalid a esuat asa cum era asteptat', .F., 'lnRes=' + TRANSFORM(lnRes) + ' (nu a esuat!)'
ENDIF
ENDIF
*-- pasul 4 al helperului (recalculeaza_totaluri_vanzari) NU se apeleaza aici - exact ca in helper,
*-- care s-ar opri la EXIT-ul din SCAN inainte sa ajunga la el
*-- tiparul apelantilor: lnSucces=Iif(...,1,-1) -> MyInchideTranzactie(Iif(lnSucces<0,2,1))
IF MyInchideTranzactie(IIF(lnSuccesFinal < 0, 2, 1))
STRTOFILE(' tranzactie inchisa cu ' + IIF(lnSuccesFinal < 0, 'ROLLBACK', 'COMMIT') + CHR(13) + CHR(10), tcLog, 1)
ELSE
STRTOFILE(' FAIL la inchiderea tranzactiei' + CHR(13) + CHR(10), tcLog, 1)
ENDIF
CATCH TO loExc
STRTOFILE(' EXCEPTIE: ' + TRANSFORM(loExc.ErrorNo) + ' [' + loExc.Message + '] ' + ;
loExc.Procedure + ':' + TRANSFORM(loExc.LineNo) + ' -> ROLLBACK' + CHR(13) + CHR(10), tcLog, 1)
lnSuccesFinal = -99
MyInchideTranzactie(2)
ENDTRY
DO asserteaza WITH 'R6: secventa s-a oprit la insertul invalid (lnSuccesFinal<0)', lnSuccesFinal < 0, 'lnSuccesFinal=' + TRANSFORM(lnSuccesFinal)
DO asserteaza WITH 'R7: tranzactia s-a inchis cu ROLLBACK (nu COMMIT)', lnSuccesFinal < 0, 'lnSuccesFinal=' + TRANSFORM(lnSuccesFinal)
*-- verificarea de FOND: dupa rollback, documentul e EXACT cum era - pe aceeasi conexiune (prima
*-- linie de aparare); verificarea INDEPENDENTA, prin sqlplus dupa QUIT, e cea care conteaza
lcSql = [select cod from vanzari where id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnQ = ExecSel(lcSql, 'v_vz1')
lnCodDupa = IIF(lnQ > 0, v_vz1.cod, -1)
USE IN v_vz1
DO asserteaza WITH 'R8: dupa ROLLBACK, VANZARI.COD e neschimbat', lnCodDupa = lnCodInainte, TRANSFORM(lnCodInainte) + ' -> ' + TRANSFORM(lnCodDupa)
lcSql = [select count(*) as nr from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare)
lnQ = ExecSel(lcSql, 'v_cnt1')
lnNrLiniiDupa = IIF(lnQ > 0, v_cnt1.nr, -1)
USE IN v_cnt1
DO asserteaza WITH 'R9: dupa ROLLBACK, numarul de linii ale documentului a revenit la valoarea initiala', ;
lnNrLiniiDupa = lnNrLiniiInainte, 'dupa=' + TRANSFORM(lnNrLiniiDupa) + ' inainte=' + TRANSFORM(lnNrLiniiInainte)
lcSql = [select id_vanzare_det, nvl(sters,0) as sters, nvl(cantitate,0) as cantitate, ] + ;
[nvl(pret,0) as pret, nvl(pret_achizitie,0) as pret_achizitie ] + ;
[from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ order by id_vanzare_det]
lnQ = ExecSel(lcSql, 'v_lin1')
lcSnapshotDupa = ''
IF lnQ > 0
SELECT v_lin1
SCAN
lcSnapshotDupa = lcSnapshotDupa + TRANSFORM(id_vanzare_det) + ':' + TRANSFORM(sters) + ':' + ;
TRANSFORM(cantitate) + ':' + TRANSFORM(pret) + ':' + TRANSFORM(pret_achizitie) + '|'
ENDSCAN
ENDIF
IF USED('v_lin1')
USE IN v_lin1
ENDIF
STRTOFILE(' DUPA ROLLBACK: snapshot=[' + lcSnapshotDupa + ']' + CHR(13) + CHR(10), tcLog, 1)
DO asserteaza WITH 'R10: dupa ROLLBACK, snapshot-ul liniilor (det:sters:cant:pret:pret_ach) e IDENTIC cu cel de dinaintea testului', ;
lcSnapshotDupa == lcSnapshotInainte, 'dupa=[' + lcSnapshotDupa + ']'
ENDPROC
PROCEDURE test_error_handler
LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
STRTOFILE('EROARE ' + TRANSFORM(tnError) + ' [' + tcProgram + ':' + TRANSFORM(tnLineNo) + '] ' + tcMessage + CHR(13) + CHR(10), gcErrLog, 1)
ENDPROC
*-- mock de goExecutor pentru PARTEA M - esueaza la apelul cu numarul nFailAtCall (implicit 1 = chiar
*-- prima comanda); jurnalizeaza in crsMockSqlRb, separat de crsMockSql (folosit de alta suita)
DEFINE CLASS dummyexecutorrollback AS Custom
cEroare = ''
nFailAtCall = 1
nApeluri = 0
PROCEDURE oExecuta
LPARAMETERS tcSql, tcCursor
LOCAL lnUrmator
This.nApeluri = This.nApeluri + 1
IF !USED('crsMockSqlRb')
CREATE CURSOR crsMockSqlRb (nr I, sql M)
ENDIF
SELECT crsMockSqlRb
lnUrmator = RECCOUNT('crsMockSqlRb') + 1
INSERT INTO crsMockSqlRb (nr, sql) VALUES (m.lnUrmator, m.tcSql)
IF !EMPTY(m.tcCursor)
IF USED(m.tcCursor)
USE IN (m.tcCursor)
ENDIF
CREATE CURSOR (m.tcCursor) (id_temp N(1))
ENDIF
IF This.nFailAtCall > 0 AND This.nApeluri == This.nFailAtCall
This.cEroare = 'esec simulat de test la apelul ' + TRANSFORM(This.nApeluri)
RETURN .F.
ENDIF
RETURN .T.
ENDPROC
ENDDEFINE