Squash al branch-ului de lucru plan13-s2. Clase: ofacturare (nucleul formularului unificat), ofacturare_comun, ferestre_cere_date, ocomenzi, caut_ora (lista de preturi in combo-urile de cautare), omodificari. Programe: ofacturare impartit - ofacturare_antet, ofacturare_rutare_scriere si ogrid_latimi sunt fisiere noi; oproceduri_facturare primeste discountul pe linie si cota standard de TVA cand articolul nu are cota pe politica. Documentatie: capcana SQLExec no_data_found, conventia de encoding, depanarea testelor VFP si regulile de lucru - conflictele cu modificarile venite din alte proiecte sunt rezolvate pastrand ambele parti. Loguri de rulare a testelor scoase din versionare; testele raman. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PkyjGyrV2S7932om4kfSiK
313 lines
15 KiB
Plaintext
313 lines
15 KiB
Plaintext
* test_s3b_incasare_paritate_campuri.prg
|
|
* Headless: pentru fiecare din cele patru tipuri de incasare (1=Fara incasare, 2=Chitanta,
|
|
* 3=Bon fiscal, 4=POS Card), formularul nou (frm_facturare_articole2) aduce poDate in aceeasi
|
|
* stare ca dialogul vechi (frm_alte_date) - fara sa treaca prin do_scrie_factura/Termina, deci
|
|
* fara sa scrie nimic in Oracle si fara sa emita niciun document.
|
|
* Specificatie: docs\nota_s3b_testare.md sectiunea 2b. Nu acopera lcListaIncasare/do_scrie_factura
|
|
* insusi (ar cere emitere reala sau un mock detaliat pe goExecutor - vezi sectiunea 2a/2b, ultimul
|
|
* paragraf) - asta ramane proba manuala a lui Marius, cerut si de raport_s3b_incasare.md sectiunea 10.
|
|
*
|
|
* ATENTIE la citirea rezultatelor: din cele cinci campuri cerute de specificatie (poDate.incasat,
|
|
* incasatCARD, incasatTichete, id_casa, ntip_incasare), doar poDate.incasat si poDate.id_casa sunt
|
|
* scrise efectiv de actualizeaza_tipincasare()/Init - verificat pe cod (ofacturare.vc2, ferestre_
|
|
* cere_date.vc2). incasatCARD, incasatTichete si ntip_incasare sunt scrise abia in Termina /
|
|
* inainte_de_do_termin (calea care emite, exclusa explicit din acest test - sectiunea 2a). Pentru
|
|
* aceste trei campuri parizatea verificata aici e reala, dar putin adanca: confirma doar ca NICIUNA
|
|
* din cele doua cai nu le atinge la simplul toggle al opt_incasat (raman la valoarea implicita 0 pe
|
|
* ambele cai), nu ca Termina le populeaza identic pe ambele cai.
|
|
*
|
|
* SECVENTIAL, nu simultan: frm_alte_date e creata, testata pe toate cele patru tipuri si eliberata
|
|
* COMPLET inainte sa existe frm_facturare_articole2 - niciodata doua forme vii in acelasi timp.
|
|
* Motiv: ambele forme au propriul cboTipFactura cu RowSource IDENTIC ("...into cursor cTipFacturaX",
|
|
* ofacturare.vc2:16157 si ferestre_cere_date.vc2:2388), reevaluat la fiecare apel al lui
|
|
* actualizeaza_tipincasare() (Thisform.cboTipFactura.Refresh(), prima linie a metodei pe ambele
|
|
* forme); cu ambele vii simultan in aceeasi sesiune de date (fara DataSession propriu), doua
|
|
* comboboxuri concureaza pe acelasi nume de cursor - suspectat ca varianta paralela (rulata de patru
|
|
* ori, toate agatate in acelasi punct) bloca aici, la nivel de tabel, fara sa arunce o exceptie
|
|
* catchabila prin ON ERROR. Nota cerea "acelasi poDate", nu simultaneitate - pastrata aici prin
|
|
* pastrarea aceluiasi obiect poDate intre cele doua faze, cu cele cinci campuri resetate explicit
|
|
* la starea de start intre faze (ca a doua forma sa porneasca de la aceeasi stare ca prima).
|
|
*
|
|
* Rulare: vfp9.exe -A -T "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\facturare_unificat\test_s3b_incasare_paritate_campuri.prg"
|
|
* Log: D:\ROA\ROAFACTURARE\COMUN\utile\Teste\facturare_unificat\out\test_s3b_incasare_paritate_campuri.log
|
|
|
|
SET SAFETY OFF
|
|
SET TALK OFF
|
|
SET NOTIFY OFF
|
|
PUBLIC gcLog, gnFail, gnPass
|
|
gcLog = "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\facturare_unificat\out\test_s3b_incasare_paritate_campuri.log"
|
|
IF !DIRECTORY("D:\ROA\ROAFACTURARE\COMUN\utile\Teste\facturare_unificat\out")
|
|
MD "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\facturare_unificat\out"
|
|
ENDIF
|
|
STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcLog)
|
|
gnFail = 0
|
|
gnPass = 0
|
|
ON ERROR DO AfErr WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
|
|
|
|
DO "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto_roafacturare.prg" WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
|
|
IF TYPE('gnHandle') <> 'N' OR gnHandle <= 0
|
|
DO AfLog WITH "FAIL: conexiune Oracle esuata"
|
|
DO Final
|
|
ENDIF
|
|
SET PROCEDURE TO "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\mock_amessagebox.prg" ADDITIVE
|
|
PUBLIC gcMockUltimMesaj, gnMockUltimTip
|
|
gcMockUltimMesaj = ""
|
|
gnMockUltimTip = 0
|
|
DO AfLog WITH "ENV OK, gnAn=" + TRANSFORM(gnAn) + " gnLuna=" + TRANSFORM(gnLuna)
|
|
IF !actualizeaza_optiuni_program()
|
|
DO AfLog WITH "FAIL: actualizeaza_optiuni_program()"
|
|
DO Final
|
|
ENDIF
|
|
gnScadereStoc = 0
|
|
gnIdUtil = 8
|
|
* aliniaza sucursala cu singura plaja POS/card existenta, evita blocajul Oracle pe cursor OUT
|
|
gnIdSucursala = 167
|
|
goExecutor.oExecuta([begin pack_sesiune.set_Id_Util(?gnIdUtil); end;])
|
|
DO AfLog WITH "gnScadereStoc=" + TRANSFORM(gnScadereStoc)
|
|
|
|
* gridextras.vcx e referit din ofacturare.vcx cu cale relativa, rezolvabila doar din exe/IDE -
|
|
* necesar pentru frm_facturare_articole2 (frm_alte_date nu are grid, dar SET-urile sunt ADDITIVE)
|
|
SET CLASSLIB TO "D:\ROA\ROAFACTURARE\COMUN\utile\gridextras\gridextras.vcx" ADDITIVE
|
|
SET PATH TO "D:\ROA\ROAFACTURARE\COMUN\utile\gridextras" ADDITIVE
|
|
SET CLASSLIB TO "D:\ROA\ROAFACTURARE\COMUN\clase\_cb_base.vcx" ADDITIVE
|
|
|
|
IF USED('crscursuri')
|
|
USE IN crscursuri
|
|
ENDIF
|
|
CREATE CURSOR crscursuri (curs N(12,4), nume_val C(10), multiplicator N(6))
|
|
DO creeaza_facturacrs WITH 'crsfactura'
|
|
SELECT *, CAST(NULL AS C(100)) AS gestiune FROM crsfactura INTO CURSOR crsfact_tmp READWRITE
|
|
USE IN crsfactura
|
|
SELECT * FROM crsfact_tmp INTO CURSOR crsfactura READWRITE
|
|
USE IN crsfact_tmp
|
|
|
|
DO ProbaParitate
|
|
|
|
DO Final
|
|
|
|
PROCEDURE ProbaParitate
|
|
* VARIANTA SECVENTIALA (23.08.2026): nota cerea "acelasi poDate", nu "ambele forme vii in
|
|
* acelasi timp" - simultaneitatea era detaliu de implementare, nu cerinta (confirmat de
|
|
* team-lead). Motivul schimbarii: frm_alte_date SI frm_facturare_articole2 au fiecare propriul
|
|
* cboTipFactura, cu RowSource IDENTIC ("select tip_saftname, cod from saft_tip_facturi into
|
|
* cursor cTipFacturaX" - ofacturare.vc2:16157, ferestre_cere_date.vc2:2388), refacut la fiecare
|
|
* apel al lui actualizeaza_tipincasare() (Thisform.cboTipFactura.Refresh(), prima linie a
|
|
* metodei pe ambele forme). Cu ambele forme vii simultan, in aceeasi sesiune de date (fara
|
|
* DataSession propriu pe niciuna), doua comboboxuri concureaza pe acelasi nume de cursor tinta -
|
|
* suspiciunea unei asteptari de lacat la nivel de tabel, care nu trece prin ON ERROR (nu e o
|
|
* exceptie aruncata). Aici: loVechi e creata, testata COMPLET si eliberata inainte sa existe
|
|
* loNou - niciodata doua forme vii simultan.
|
|
*
|
|
* poDate ramane ACELASI obiect intre cele doua faze (nu se recreeaza) - dar cele cinci campuri
|
|
* se retin la start si se restaureaza explicit intre faze, ca a doua forma sa porneasca de la
|
|
* aceeasi stare ca prima, nu de la ce a lasat prima in urma.
|
|
PRIVATE poDate, poGeneratorNumere, loVechi, loNou, i
|
|
LOCAL laTip[4], laNume[4], laRezVechi[4,5]
|
|
laTip[1] = 1
|
|
laTip[2] = 2
|
|
laTip[3] = 3
|
|
laTip[4] = 4
|
|
laNume[1] = "Fara incasare"
|
|
laNume[2] = "Chitanta"
|
|
laNume[3] = "Bon fiscal"
|
|
laNume[4] = "POS Card"
|
|
|
|
poDate = CREATEOBJECT("oDateFactura", 25000, 1)
|
|
poDate.nIdTipDoc = 5
|
|
poDate.totalctva = 1000
|
|
DO AfLog WITH "poDate.tip=" + TRANSFORM(poDate.tip) + " poDate.eProforma=" + TRANSFORM(poDate.eProforma) + " poDate.incasat=" + TRANSFORM(poDate.incasat)
|
|
poGeneratorNumere = CREATEOBJECT("oGeneratorNumere")
|
|
poGeneratorNumere.ResetNumere()
|
|
DO ProbaSerii WITH poDate.nIdTipDoc
|
|
poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)
|
|
|
|
* starea de start a celor cinci campuri, INAINTE de orice forma - se restaureaza intre faze
|
|
LOCAL lnStartIncasat, lnStartCard, lnStartTichete, lnStartCasa, lnStartTipInc
|
|
lnStartIncasat = poDate.incasat
|
|
lnStartCard = poDate.incasatCARD
|
|
lnStartTichete = poDate.incasatTichete
|
|
lnStartCasa = poDate.id_casa
|
|
lnStartTipInc = poDate.ntip_incasare
|
|
DO AfLog WITH "stare start poDate: incasat=" + TRANSFORM(lnStartIncasat) + " incasatCARD=" + TRANSFORM(lnStartCard) + ;
|
|
" incasatTichete=" + TRANSFORM(lnStartTichete) + " id_casa=" + TRANSFORM(lnStartCasa) + " ntip_incasare=" + TRANSFORM(lnStartTipInc)
|
|
|
|
LOCAL lnTip, lcNumeTip
|
|
|
|
* ---------- FAZA 1: doar frm_alte_date (calea veche), complet, apoi Release() ----------
|
|
DO AsiguraSaftTipFacturi
|
|
DO AfLog WITH "inainte de Createobject frm_alte_date: USED('saft_tip_facturi')=" + TRANSFORM(USED('saft_tip_facturi')) + ;
|
|
" USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
loVechi = CREATEOBJECT('frm_alte_date')
|
|
DO Assert WITH VARTYPE(loVechi) = 'O', "Createobject frm_alte_date (calea veche)"
|
|
IF VARTYPE(loVechi) <> 'O'
|
|
RETURN
|
|
ENDIF
|
|
loVechi.Visible = .F.
|
|
DO AfLog WITH "dupa Createobject frm_alte_date: USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
|
|
FOR i = 1 TO 4
|
|
lnTip = laTip[i]
|
|
lcNumeTip = laNume[i]
|
|
DO AfLog WITH "vechi [" + lcNumeTip + "]: inainte de opt_incasat.Value=, USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
loVechi.opt_incasat.Value = lnTip
|
|
DO AfLog WITH "vechi [" + lcNumeTip + "]: dupa opt_incasat.Value=, USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
laRezVechi[i,1] = poDate.incasat
|
|
laRezVechi[i,2] = poDate.incasatCARD
|
|
laRezVechi[i,3] = poDate.incasatTichete
|
|
laRezVechi[i,4] = poDate.id_casa
|
|
laRezVechi[i,5] = poDate.ntip_incasare
|
|
DO AfLog WITH "vechi [" + lcNumeTip + "]: incasat=" + TRANSFORM(laRezVechi[i,1]) + " incasatCARD=" + TRANSFORM(laRezVechi[i,2]) + ;
|
|
" incasatTichete=" + TRANSFORM(laRezVechi[i,3]) + " id_casa=" + TRANSFORM(laRezVechi[i,4]) + " ntip_incasare=" + TRANSFORM(laRezVechi[i,5])
|
|
DO AfLog WITH "vechi [" + lcNumeTip + "]: stare numere=" + StareToate()
|
|
loVechi.opt_incasat.Value = 1
|
|
DO AfLog WITH "vechi [" + lcNumeTip + "]: dezalocat, stare numere=" + StareToate()
|
|
ENDFOR
|
|
|
|
loVechi.Release()
|
|
RELEASE loVechi
|
|
DO AfLog WITH "dupa Release loVechi: USED('saft_tip_facturi')=" + TRANSFORM(USED('saft_tip_facturi')) + ;
|
|
" USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
|
|
* readuce poDate la starea de start, ca a doua forma sa porneasca identic cu prima
|
|
poDate.incasat = lnStartIncasat
|
|
poDate.incasatCARD = lnStartCard
|
|
poDate.incasatTichete = lnStartTichete
|
|
poDate.id_casa = lnStartCasa
|
|
poDate.ntip_incasare = lnStartTipInc
|
|
DO AfLog WITH "poDate resetat inainte de a doua forma: incasat=" + TRANSFORM(poDate.incasat) + " id_casa=" + TRANSFORM(poDate.id_casa)
|
|
|
|
* ---------- FAZA 2: doar frm_facturare_articole2 (calea noua) ----------
|
|
DO AsiguraSaftTipFacturi
|
|
DO AfLog WITH "inainte de Createobject frm_facturare_articole2: USED('saft_tip_facturi')=" + TRANSFORM(USED('saft_tip_facturi')) + ;
|
|
" USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
loNou = CREATEOBJECT('frm_facturare_articole2')
|
|
DO Assert WITH VARTYPE(loNou) = 'O', "Createobject frm_facturare_articole2 (calea noua)"
|
|
IF VARTYPE(loNou) <> 'O'
|
|
RETURN
|
|
ENDIF
|
|
loNou.Visible = .F.
|
|
DO AfLog WITH "dupa Createobject frm_facturare_articole2: USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
|
|
FOR i = 1 TO 4
|
|
lnTip = laTip[i]
|
|
lcNumeTip = laNume[i]
|
|
DO AfLog WITH "nou [" + lcNumeTip + "]: inainte de opt_incasat.Value=, USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
loNou.seteaza_mod_incasare(lnTip)
|
|
DO Assert WITH loNou.nmod_incasare() = lnTip, "[" + lcNumeTip + "] nmod_incasare() reflecta seteaza_mod_incasare(" + TRANSFORM(lnTip) + ")"
|
|
DO AfLog WITH "nou [" + lcNumeTip + "]: dupa opt_incasat.Value=, USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
DO AfLog WITH "nou [" + lcNumeTip + "]: incasat=" + TRANSFORM(poDate.incasat) + " incasatCARD=" + TRANSFORM(poDate.incasatCARD) + ;
|
|
" incasatTichete=" + TRANSFORM(poDate.incasatTichete) + " id_casa=" + TRANSFORM(poDate.id_casa) + " ntip_incasare=" + TRANSFORM(poDate.ntip_incasare)
|
|
DO Assert WITH poDate.incasat = laRezVechi[i,1], "[" + lcNumeTip + "] poDate.incasat identic (vechi=" + TRANSFORM(laRezVechi[i,1]) + " nou=" + TRANSFORM(poDate.incasat) + ")"
|
|
DO Assert WITH poDate.incasatCARD = laRezVechi[i,2], "[" + lcNumeTip + "] poDate.incasatCARD identic"
|
|
DO Assert WITH poDate.incasatTichete = laRezVechi[i,3], "[" + lcNumeTip + "] poDate.incasatTichete identic"
|
|
DO Assert WITH poDate.id_casa = laRezVechi[i,4], "[" + lcNumeTip + "] poDate.id_casa identic"
|
|
DO Assert WITH poDate.ntip_incasare = laRezVechi[i,5], "[" + lcNumeTip + "] poDate.ntip_incasare identic"
|
|
|
|
loNou.seteaza_mod_incasare(1)
|
|
DO Assert WITH loNou.nmod_incasare() = 1, "[" + lcNumeTip + "] nmod_incasare() reflecta seteaza_mod_incasare(1) (dezalocare)"
|
|
DO AfLog WITH "nou [" + lcNumeTip + "]: dezalocat, stare numere=" + StareToate()
|
|
ENDFOR
|
|
|
|
DO AfLog WITH "stare finala numere (asteptat nimic alocat): " + StareToate()
|
|
DO Assert WITH !EsteAlocat(3) AND !EsteAlocat(16) AND !EsteAlocat(26), "nimic ramas alocat la finalul rularii"
|
|
|
|
loNou.Release()
|
|
RELEASE loNou
|
|
DO AfLog WITH "dupa Release loNou: USED('saft_tip_facturi')=" + TRANSFORM(USED('saft_tip_facturi')) + ;
|
|
" USED('cTipFacturaX')=" + TRANSFORM(USED('cTipFacturaX'))
|
|
ENDPROC
|
|
|
|
PROCEDURE AsiguraSaftTipFacturi
|
|
* cboTipFactura (RowSourceType=3) citeste cursorul local saft_tip_facturi ca pe un tabel la
|
|
* instantiere (ofacturare.vc2:16157 - frm_facturare_articole2, ferestre_cere_date.vc2:2388 -
|
|
* frm_alte_date). Structura copiata din update_saft_tip_facturi (updateserver.prg:669-691),
|
|
* fara apel la server - cursor local, dummy, cu randul gol si indexul pe cod ca in original.
|
|
IF !USED('saft_tip_facturi')
|
|
CREATE CURSOR saft_tip_facturi (cod N(3) NULL, tip_saftname C(200) NULL)
|
|
INSERT INTO saft_tip_facturi (cod, tip_saftname) VALUES (NULL, '')
|
|
INDEX ON cod TAG cod
|
|
ENDIF
|
|
ENDPROC
|
|
|
|
FUNCTION EsteAlocat
|
|
LPARAMETERS tnTip
|
|
LOCAL lcCursor
|
|
IF poGeneratorNumere.paPlaje(tnTip, 6)
|
|
lcCursor = poGeneratorNumere.paPlaje(tnTip, 8)
|
|
IF EMPTY(NVL(lcCursor, [])) OR !USED(lcCursor)
|
|
RETURN .F.
|
|
ENDIF
|
|
RETURN RECCOUNT(lcCursor) > 0
|
|
ELSE
|
|
RETURN poGeneratorNumere.paPlaje(tnTip, 1) <> 0
|
|
ENDIF
|
|
ENDFUNC
|
|
|
|
FUNCTION StareNumar
|
|
LPARAMETERS tnTip
|
|
LOCAL lcCursor, lnCount
|
|
IF poGeneratorNumere.paPlaje(tnTip, 6)
|
|
lcCursor = poGeneratorNumere.paPlaje(tnTip, 8)
|
|
IF EMPTY(NVL(lcCursor, [])) OR !USED(lcCursor)
|
|
lnCount = 0
|
|
ELSE
|
|
lnCount = RECCOUNT(lcCursor)
|
|
ENDIF
|
|
RETURN "buffer:" + TRANSFORM(lnCount)
|
|
ELSE
|
|
RETURN "array:" + TRANSFORM(poGeneratorNumere.paPlaje(tnTip, 1))
|
|
ENDIF
|
|
ENDFUNC
|
|
|
|
FUNCTION StareToate
|
|
RETURN "3=" + StareNumar(3) + "|16=" + StareNumar(16) + "|26=" + StareNumar(26)
|
|
ENDFUNC
|
|
|
|
PROCEDURE ProbaSerii
|
|
LPARAMETERS tnIdTipDoc
|
|
LOCAL lnOk, laErr[1], i, lcSql
|
|
PRIVATE pnIdTipDoc, pnRezultat
|
|
pnIdTipDoc = tnIdTipDoc
|
|
pnRezultat = 0
|
|
lcSql = "{call " + gcS + ".pack_serii_numere.verifica_cursor_serii(?pnIdTipDoc,?gnIdUtil,?gnIdSucursala,?@pnRezultat)}"
|
|
FOR i = 1 TO 3
|
|
lnOk = SQLEXEC(gnHandle, lcSql, "crsprobaserii")
|
|
IF lnOk > 0
|
|
DO AfLog WITH "proba serii OK la incercarea " + TRANSFORM(i) + ", pnRezultat=" + TRANSFORM(pnRezultat) + " randuri=" + TRANSFORM(RECCOUNT("crsprobaserii"))
|
|
USE IN (SELECT("crsprobaserii"))
|
|
EXIT
|
|
ELSE
|
|
AERROR(laErr)
|
|
DO AfLog WITH "proba serii ESEC la incercarea " + TRANSFORM(i) + ": " + TRANSFORM(laErr[1]) + " / " + TRANSFORM(laErr[2]) + " / " + TRANSFORM(laErr[5])
|
|
ENDIF
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
PROCEDURE Assert
|
|
LPARAMETERS tlCond, tcMsg
|
|
IF tlCond
|
|
gnPass = gnPass + 1
|
|
DO AfLog WITH "PASS: " + tcMsg
|
|
ELSE
|
|
gnFail = gnFail + 1
|
|
DO AfLog WITH "FAIL: " + tcMsg
|
|
ENDIF
|
|
ENDPROC
|
|
|
|
PROCEDURE AfLog
|
|
LPARAMETERS tcMsg
|
|
STRTOFILE(tcMsg + CHR(13) + CHR(10), gcLog, 1)
|
|
ENDPROC
|
|
|
|
PROCEDURE AfErr
|
|
LPARAMETERS tnErr, tcMsg, tcProg, tnLine
|
|
gnFail = gnFail + 1
|
|
DO AfLog WITH "ERROR " + TRANSFORM(tnErr) + " [" + tcProg + ":" + TRANSFORM(tnLine) + "] " + tcMsg
|
|
ENDPROC
|
|
|
|
PROCEDURE Final
|
|
DO AfLog WITH "TOTAL pass=" + TRANSFORM(gnPass) + " fail=" + TRANSFORM(gnFail)
|
|
DO AfLog WITH "END " + TTOC(DATETIME())
|
|
QUIT
|
|
ENDPROC
|