#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
This commit is contained in:
470
utile/Teste/editare_factura/test_s5_rollback_real.prg
Normal file
470
utile/Teste/editare_factura/test_s5_rollback_real.prg
Normal file
@@ -0,0 +1,470 @@
|
||||
* 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
|
||||
Reference in New Issue
Block a user