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
260 lines
11 KiB
Plaintext
260 lines
11 KiB
Plaintext
* test_s11_izolare_camp_1472.prg
|
|
* Proba S11 (docs\raport_s11_cauza_1472_camp.md): izoleaza EXACT campul/campurile NULL din poArt
|
|
* care duc lcSql la NULL in frm_facturare_articole2.do_scrie_articole (ofacturare.vc2:19977-20001).
|
|
* Tipar identic cu test_s10_cablare_vfp.prg (mock pe goExecutor, NIMIC scris in Oracle): doc 630
|
|
* incarcat REAL (IncarcaAntetFacturaEditare + IncarcaLiniiFacturaEditare, citire, goExecutor real),
|
|
* apoi goExecutor inlocuit cu mock INAINTE de do_scrie_articole(). Fara curatare/REPLACE ALL pe
|
|
* crsfactura la Pasul 1 - vrem NULL-urile REALE, nu ascunse.
|
|
*
|
|
* Pasul 1: enumera (AFIELDS) toate campurile din crsfactura, logheaza VARTYPE/ISNULL pe fiecare,
|
|
* pe fiecare rand.
|
|
* Pasul 2: apeleaza do_scrie_articole() cu mock, capteaza lcSql brut per apel, verifica VARTYPE.
|
|
* Pasul 3 (cauzalitate, sens 1): pe cursorul curatat DOAR pe campul/campurile gasite NULL la pasul 1
|
|
* SI nepazite in cod (candidatii: id_articol, id_jtva_coloana - singurele doua campuri din
|
|
* lista nepazita 19978-20000 care sunt si NULL-capabile in structura crsfactura, cf.
|
|
* creeaza_facturacrs, ofacturare_comun.prg:1801-1811), reruleaza - asteptam VARTYPE='C'.
|
|
* Pasul 4 (cauzalitate, sens 2): reintroduce NULL pe UN SINGUR camp cauzal, reruleaza - asteptam
|
|
* VARTYPE='X' din nou.
|
|
LPARAMETERS tnCaz
|
|
|
|
SET SAFETY OFF
|
|
SET TALK OFF
|
|
|
|
PUBLIC gcErrLog, gnPass, gnFail
|
|
PUBLIC poDate
|
|
|
|
gcErrLog = 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_s11_izolare_camp_1472_log_' + ;
|
|
STRTRAN(STRTRAN(STRTRAN(TTOC(DATETIME()), '/', ''), ':', ''), ' ', '_') + '.txt'
|
|
STRTOFILE('start ' + TRANSFORM(DATETIME()) + CHR(13) + CHR(10), gcErrLog)
|
|
|
|
ON ERROR DO test_error_handler WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
|
|
ON SHUTDOWN QUIT
|
|
|
|
DO ('D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto_roafacturare.prg') WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
|
|
IF gnHandle <= 0
|
|
STRTOFILE('EROARE: conectare esuata' + CHR(13) + CHR(10), gcErrLog, 1)
|
|
QUIT
|
|
ENDIF
|
|
gnIdUtil = 8
|
|
|
|
SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_editare.prg ADDITIVE
|
|
SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_rutare_scriere.prg ADDITIVE
|
|
|
|
*!* la nume duplicat de procedura castiga PRIMUL fisier din lista SET PROCEDURE, nu ultimul
|
|
LOCAL lcProcExistent
|
|
lcProcExistent = SET("PROCEDURE")
|
|
SET PROCEDURE TO "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\mock_amessagebox.prg", &lcProcExistent
|
|
|
|
LOCAL lcPathVechi, lcPathNou
|
|
lcPathVechi = SET("PATH")
|
|
lcPathNou = 'D:\ROA\ROAFACTURARE\COMUN\Utile\Teste\achizitie_import\stub_verificare;' + lcPathVechi
|
|
SET PATH TO &lcPathNou
|
|
|
|
PUBLIC gnMockRaspuns, gcMockUltimMesaj, gnMockUltimTip
|
|
gnMockRaspuns = 7
|
|
gcMockUltimMesaj = ''
|
|
gnMockUltimTip = 0
|
|
gnPass = 0
|
|
gnFail = 0
|
|
|
|
PUBLIC gcSqlUltim, gcVartypeUltim
|
|
gcSqlUltim = ''
|
|
gcVartypeUltim = ''
|
|
|
|
DO Izoleaza
|
|
|
|
STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), gcErrLog, 1)
|
|
STRTOFILE('end ' + TRANSFORM(DATETIME()) + CHR(13) + CHR(10), gcErrLog, 1)
|
|
|
|
QUIT
|
|
|
|
|
|
*==============================================================================================
|
|
PROCEDURE Izoleaza
|
|
LOCAL lnIdV
|
|
lnIdV = 630
|
|
|
|
LOCAL lnDiscRon, lnDiscVal
|
|
lnDiscRon = 0
|
|
lnDiscVal = 0
|
|
poDate = IncarcaAntetFacturaEditare(lnIdV, @lnDiscRon, @lnDiscVal)
|
|
DO asserteaza WITH '0a antetul documentului ' + TRANSFORM(lnIdV) + ' s-a incarcat', VARTYPE(poDate) = 'O', VARTYPE(poDate)
|
|
IF VARTYPE(poDate) <> 'O'
|
|
RETURN
|
|
ENDIF
|
|
|
|
IncarcaLiniiFacturaEditare(poDate, lnIdV)
|
|
DO asserteaza WITH '0b liniile documentului ' + TRANSFORM(lnIdV) + ' s-au incarcat', ;
|
|
USED('crsfactura') AND RECCOUNT('crsfactura') > 0, TRANSFORM(IIF(USED('crsfactura'), RECCOUNT('crsfactura'), -1))
|
|
IF !USED('crsfactura') OR RECCOUNT('crsfactura') = 0
|
|
RETURN
|
|
ENDIF
|
|
|
|
*!* curatare LOCALA (doar in RAM) doar pe ce trebuie ca sa nu strice testul de baza:
|
|
*!* fara ")" accidental in text, toate randurile pe branch-ul adauga_articol_factura
|
|
SELECT crsfactura
|
|
REPLACE ALL explicatie WITH 'test-s11-izolare', serie WITH '', lot WITH '', opt_facturare WITH 0 IN crsfactura
|
|
GO TOP IN crsfactura
|
|
|
|
*!* PASUL 1: enumerare EFECTIVA (AFIELDS), nu lista ghicita - toate campurile, toate randurile
|
|
LOCAL laFields[1], lnFields, lnI, lnRand
|
|
lnFields = AFIELDS(laFields, 'crsfactura')
|
|
STRTOFILE('--- PASUL 1: campuri NULL in crsfactura, pe fiecare rand (' + TRANSFORM(RECCOUNT('crsfactura')) + ' randuri) ---' + CHR(13) + CHR(10), gcErrLog, 1)
|
|
SELECT crsfactura
|
|
SCAN
|
|
lnRand = RECNO('crsfactura')
|
|
LOCAL lcNuleRand
|
|
lcNuleRand = ''
|
|
FOR lnI = 1 TO lnFields
|
|
IF ISNULL(EVALUATE('crsfactura.' + laFields[lnI,1]))
|
|
lcNuleRand = lcNuleRand + laFields[lnI,1] + '(' + laFields[lnI,2] + ') '
|
|
ENDIF
|
|
ENDFOR
|
|
IF EMPTY(lcNuleRand)
|
|
STRTOFILE(' rand ' + TRANSFORM(lnRand) + ': niciun camp NULL' + CHR(13) + CHR(10), gcErrLog, 1)
|
|
ELSE
|
|
STRTOFILE(' rand ' + TRANSFORM(lnRand) + ': NULL pe -> ' + lcNuleRand + CHR(13) + CHR(10), gcErrLog, 1)
|
|
ENDIF
|
|
ENDSCAN
|
|
GO TOP IN crsfactura
|
|
|
|
*!* PASUL 2: rulare BRUTA (fara nicio curatare pe campuri numerice) - reproduce defectul
|
|
DO RuleazaCuMock WITH 'PAS2-brut'
|
|
DO asserteaza WITH 'PAS2 lcSql BRUT (fara curatare) e NULL (VARTYPE X) - reproduce defectul', gcVartypeUltim = 'X', gcVartypeUltim
|
|
|
|
*!* PASUL 3: cauzalitate sens 1 - NVL DOAR pe candidatii NULL-capabili si nepaziti in cod
|
|
*!* (id_articol: ofacturare.vc2:19979; id_jtva_coloana: ofacturare.vc2:19994) - singurii doi
|
|
*!* campuri din lista nepazita 19978-20000 care sunt NULL-capabili in structura crsfactura
|
|
*!* (ofacturare_comun.prg:1801-1811 - restul candidatilor nepaziti in SQL nu sunt marcati NULL
|
|
*!* in CREATE CURSOR, deci nu pot ajunge NULL pe acest traseu, indiferent de sursa Oracle).
|
|
SELECT crsfactura
|
|
REPLACE ALL id_articol WITH NVL(id_articol,0), id_jtva_coloana WITH NVL(id_jtva_coloana,0) IN crsfactura
|
|
GO TOP IN crsfactura
|
|
DO RuleazaCuMock WITH 'PAS3-curat-id_articol-id_jtva_coloana'
|
|
DO asserteaza WITH 'PAS3 dupa NVL DOAR pe id_articol+id_jtva_coloana, lcSql devine text (VARTYPE C)', gcVartypeUltim = 'C', gcVartypeUltim
|
|
|
|
*!* PASUL 4: cauzalitate sens 2 - reintroduce NULL pe UN SINGUR camp (id_articol) pe primul rand
|
|
SELECT crsfactura
|
|
GO TOP IN crsfactura
|
|
REPLACE id_articol WITH .NULL. IN crsfactura
|
|
DO RuleazaCuMock WITH 'PAS4-regresie-doar-id_articol-null'
|
|
DO asserteaza WITH 'PAS4 cu id_articol NULL pe un rand (restul curat), lcSql redevine NULL (VARTYPE X)', gcVartypeUltim = 'X', gcVartypeUltim
|
|
|
|
*!* PASUL 5: control - NULL DOAR pe id_jtva_coloana (id_articol curat), separat de id_articol
|
|
SELECT crsfactura
|
|
GO TOP IN crsfactura
|
|
REPLACE ALL id_articol WITH NVL(id_articol,0) IN crsfactura
|
|
GO TOP IN crsfactura
|
|
REPLACE id_jtva_coloana WITH .NULL. IN crsfactura
|
|
DO RuleazaCuMock WITH 'PAS5-regresie-doar-id_jtva_coloana-null'
|
|
DO asserteaza WITH 'PAS5 cu id_jtva_coloana NULL pe un rand (restul curat), lcSql redevine NULL (VARTYPE X)', gcVartypeUltim = 'X', gcVartypeUltim
|
|
ENDPROC
|
|
|
|
|
|
*==============================================================================================
|
|
* RuleazaCuMock: apeleaza do_scrie_articole() cu goExecutor mockat, intoarce lcSql-ul (sau
|
|
* diagnosticul VARTYPE) al APELULUI cu PCOUNT()=1 (adauga_articol_factura din Scan), plus
|
|
* rollback pe conexiunea REALA dupa fiecare rulare (tranzactia ramane deschisa pe succes).
|
|
*==============================================================================================
|
|
PROCEDURE RuleazaCuMock
|
|
LPARAMETERS tcPrefix
|
|
LOCAL loForm, loMock, loExecutorReal, llReturn
|
|
|
|
loForm = CREATEOBJECT('frm_facturare_articole2')
|
|
|
|
IF TYPE('plRegenerareEditare') <> 'U'
|
|
RELEASE plRegenerareEditare
|
|
ENDIF
|
|
|
|
loExecutorReal = goExecutor
|
|
loMock = CREATEOBJECT('MockGoExecutorS11Izolare')
|
|
goExecutor = loMock
|
|
|
|
llReturn = loForm.do_scrie_articole()
|
|
|
|
goExecutor = loExecutorReal
|
|
SQLROLLBACK(gnHandle)
|
|
SQLSetProp(gnHandle, 'Transactions', 1)
|
|
|
|
gcSqlUltim = loMock.cSqlArticolCapturat
|
|
gcVartypeUltim = loMock.cVartypeArticol
|
|
|
|
STRTOFILE(tcPrefix + ' do_scrie_articole()=' + TRANSFORM(llReturn) + ' VARTYPE(tcSql pe apelul articol)=' + gcVartypeUltim + CHR(13) + CHR(10), gcErrLog, 1)
|
|
STRTOFILE(tcPrefix + ' --- lcSql capturat (apelul articol) ---' + CHR(13) + CHR(10) + TRANSFORM(gcSqlUltim) + CHR(13) + CHR(10), gcErrLog, 1)
|
|
|
|
RELEASE loForm
|
|
ENDPROC
|
|
|
|
|
|
PROCEDURE asserteaza
|
|
LPARAMETERS tcNume, tlConditie, tcValoare
|
|
|
|
IF m.tlConditie
|
|
gnPass = gnPass + 1
|
|
STRTOFILE('PASS ' + m.tcNume + CHR(13) + CHR(10), gcErrLog, 1)
|
|
ELSE
|
|
gnFail = gnFail + 1
|
|
STRTOFILE('FAIL ' + m.tcNume + ' (valoare: ' + TRANSFORM(m.tcValoare) + ')' + CHR(13) + CHR(10), gcErrLog, 1)
|
|
ENDIF
|
|
ENDPROC
|
|
|
|
|
|
PROCEDURE test_error_handler
|
|
LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
|
|
|
|
STRTOFILE('EROARE ' + TRANSFORM(tnError) + ' [' + tcProgram + ':' + TRANSFORM(tnLineNo) + '] ' + tcMessage + CHR(13) + CHR(10), gcErrLog, 1)
|
|
IF TYPE('gnHandle') = 'N' AND gnHandle > 0
|
|
SQLROLLBACK(gnHandle)
|
|
SQLSetProp(gnHandle, 'Transactions', 1)
|
|
ENDIF
|
|
gnFail = gnFail + 1
|
|
STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), gcErrLog, 1)
|
|
STRTOFILE('end (eroare) ' + TRANSFORM(DATETIME()) + CHR(13) + CHR(10), gcErrLog, 1)
|
|
QUIT
|
|
ENDPROC
|
|
|
|
|
|
*==============================================================================================
|
|
* MockGoExecutorS11Izolare: captureaza, pe FIECARE apel oExecute, PCOUNT()/VARTYPE(tcSql); retine
|
|
* separat apelul cu PCOUNT()=1 (semnatura do_scrie_articole pentru adauga_articol_factura, spre
|
|
* deosebire de apelul antetului care are 0 parametri suplimentari observabili din tcSql insusi -
|
|
* de fapt toate apelurile din do_scrie_articole trec 1 parametru; distingem prin continut: apelul
|
|
* articolului contine 'adauga_articol_factura' SAU e chiar cel NULL de pe randul cu bug-ul cautat -
|
|
* pastram DOAR ultimul apel (cel mai relevant, cel din Scan) pentru comparatie inainte/dupa.
|
|
*==============================================================================================
|
|
DEFINE CLASS MockGoExecutorS11Izolare AS Custom
|
|
cSqlArticolCapturat = ''
|
|
cVartypeArticol = ''
|
|
nApeluri = 0
|
|
cEroare = ''
|
|
|
|
FUNCTION oExecute(tcSql)
|
|
THIS.nApeluri = THIS.nApeluri + 1
|
|
LOCAL lcVartype
|
|
lcVartype = VARTYPE(m.tcSql)
|
|
*!* pastram ultimul apel care contine (in forma text) sau ar putea contine (NULL) linia de
|
|
*!* articol - primele doua apeluri din do_scrie_articole sunt garantat antetul (nu contin
|
|
*!* 'adauga_articol_factura'); din al treilea apel incolo, retinem fiecare, ultimul castiga
|
|
IF THIS.nApeluri >= 3
|
|
THIS.cSqlArticolCapturat = TRANSFORM(m.tcSql)
|
|
THIS.cVartypeArticol = m.lcVartype
|
|
ENDIF
|
|
RETURN 1
|
|
ENDFUNC
|
|
|
|
FUNCTION oExecuta(tcSql, tcCursor)
|
|
THIS.nApeluri = THIS.nApeluri + 1
|
|
IF VARTYPE(m.tcCursor) = 'C' AND !EMPTY(m.tcCursor)
|
|
IF USED(m.tcCursor)
|
|
USE IN (m.tcCursor)
|
|
ENDIF
|
|
CREATE CURSOR (m.tcCursor) (xdummy I)
|
|
ENDIF
|
|
RETURN .T.
|
|
ENDFUNC
|
|
|
|
FUNCTION oPrelucrareEroare()
|
|
RETURN 'MockGoExecutorS11Izolare: ' + THIS.cEroare
|
|
ENDFUNC
|
|
ENDDEFINE
|