diff --git a/utile/Teste/storno/extract_fixture_3795.prg b/utile/Teste/storno/extract_fixture_3795.prg
new file mode 100644
index 0000000..49c11b8
--- /dev/null
+++ b/utile/Teste/storno/extract_fixture_3795.prg
@@ -0,0 +1,49 @@
+* extract_fixture_3795.prg
+* Extrage xml-ul original al randului ANAF_EFACTURA.ID = 3795 (MARIUSM_AUTO) prin
+* GetXmlOriginalEFactura, pentru anonimizare si salvare ca fixtura de test (storno diacritice reale).
+* Rulare: vfp9.exe -A -T "D:\ROA\ROACONT\COMUN\utile\Teste\storno\extract_fixture_3795.prg"
+* Iesire: D:\ROA\ROACONT\COMUN\utile\Teste\storno\_raw_3795.xml + _raw_3795_log.txt
+
+SET SAFETY OFF
+SET TALK OFF
+CLOSE DATABASES
+
+PUBLIC gcUILog
+gcUILog = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\_raw_3795_log.txt"
+STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
+
+ON ERROR DO PrErrLocal WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
+ON SHUTDOWN QUIT
+
+TRY
+ DO "D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg" WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
+ IF gnHandle <= 0
+ DO PrLogLocal WITH 'FAIL: conectare Oracle esuata'
+ QUIT
+ ENDIF
+
+ LOCAL lcXml, lcMesaj, llSucces
+ llSucces = GetXmlOriginalEFactura(3795, @lcXml, @lcMesaj)
+ DO PrLogLocal WITH 'GetXmlOriginalEFactura(3795) = ' + TRANSFORM(llSucces) + ' mesaj=[' + m.lcMesaj + '] len=' + TRANSFORM(LEN(m.lcXml))
+ IF m.llSucces
+ STRTOFILE(m.lcXml, "D:\ROA\ROACONT\COMUN\utile\Teste\storno\_raw_3795.xml")
+ DO PrLogLocal WITH 'salvat _raw_3795.xml'
+ ENDIF
+CATCH TO loEx
+ DO PrLogLocal WITH 'EXCEPTIE: ' + loEx.Message + ' (' + TRANSFORM(loEx.LineNo) + ') ' + loEx.Procedure
+ENDTRY
+
+QUIT
+
+PROCEDURE PrLogLocal
+ LPARAMETERS tcMsg
+ LOCAL lcL
+ SET SAFETY OFF
+ lcL = IIF(FILE(gcUILog), FILETOSTR(gcUILog), '')
+ STRTOFILE(lcL + TRANSFORM(tcMsg) + CHR(13) + CHR(10), gcUILog)
+ENDPROC
+
+PROCEDURE PrErrLocal
+ LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
+ DO PrLogLocal WITH 'EROARE ' + TRANSFORM(tnError) + ' ' + TRANSFORM(tcMessage) + ' in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLineNo)
+ENDPROC
diff --git a/utile/Teste/storno/mock_amessagebox_storno.prg b/utile/Teste/storno/mock_amessagebox_storno.prg
new file mode 100644
index 0000000..0351394
--- /dev/null
+++ b/utile/Teste/storno/mock_amessagebox_storno.prg
@@ -0,0 +1,27 @@
+* mock_amessagebox_storno.prg - mock AMESSAGEBOX specific testului E2E storno
+* (test_storno_e2e.prg). Raspunde diferit dupa continutul mesajului: 1 (OK) la gate-ul
+* initial "genereaza XML-ul de stornare", 6 (Da) la confirmarea "Trimiteti la ANAF",
+* altfel gnMockRaspuns implicit. Logheaza TOATE apelurile in PUBLIC gaAmsgCalls/gnAmsgCalls,
+* utile ca sa vedem daca un guard neasteptat (CIF/luna blocata) a oprit fluxul.
+FUNCTION amessagebox
+ LPARAMETERS tcMessage, tnDialogBoxType, tcTitle, tcFont, tnTimeOut, tnTimeoutValue
+ LOCAL lnRaspuns
+ DO CASE
+ CASE 'genereaza XML-ul de stornare' $ tcMessage
+ lnRaspuns = 1
+ CASE 'Trimiteti la ANAF' $ tcMessage
+ lnRaspuns = 6
+ OTHERWISE
+ lnRaspuns = IIF(TYPE('gnMockRaspuns') = 'N', gnMockRaspuns, 6)
+ ENDCASE
+ IF TYPE('gnAmsgCalls') = 'N'
+ gnAmsgCalls = gnAmsgCalls + 1
+ DIMENSION gaAmsgCalls[gnAmsgCalls, 2]
+ gaAmsgCalls[gnAmsgCalls, 1] = TRANSFORM(tcMessage)
+ gaAmsgCalls[gnAmsgCalls, 2] = lnRaspuns
+ ENDIF
+ IF TYPE('gcUILog') = 'C'
+ DO PrLogLocal WITH 'MOCK amessagebox: [' + LEFT(TRANSFORM(tcMessage), 100) + '] -> ' + TRANSFORM(lnRaspuns)
+ ENDIF
+ RETURN lnRaspuns
+ENDFUNC
diff --git a/utile/Teste/storno/mock_anaf_server_storno.prg b/utile/Teste/storno/mock_anaf_server_storno.prg
new file mode 100644
index 0000000..8b60952
--- /dev/null
+++ b/utile/Teste/storno/mock_anaf_server_storno.prg
@@ -0,0 +1,55 @@
+* mock_anaf_server_storno.prg - MOCK pentru AnafeFacturaServer, STRICT pentru testul E2E
+* storno eFactura (test_storno_e2e.prg). ZERO apeluri reale la ANAF: RefreshTokenAuto /
+* TrimiteFacturaXML / SaveFile / Xml2PdfAnaf doar logheaza apelul in PUBLIC gaMockApeluri /
+* gnMockApeluri (declarate de test INAINTE de a instantia acest mock) si intorc raspunsuri
+* canonice, fara retea si fara disc (in afara de SaveFile, care doar logheaza).
+DEFINE CLASS MockAnafServerStorno AS Custom
+ cCodFiscal = ''
+ lTest = .T.
+ cToken = 'MOCK-TOKEN-STORNO'
+ cRefreshToken = 'MOCK-REFRESH-STORNO'
+ dTokenGendate = {}
+ dTokenExpdate = {}
+
+ PROCEDURE RegMock
+ LPARAMETERS tcMetoda, tcP2, tcP3
+ gnMockApeluri = gnMockApeluri + 1
+ DIMENSION gaMockApeluri[gnMockApeluri, 3]
+ gaMockApeluri[gnMockApeluri, 1] = tcMetoda
+ gaMockApeluri[gnMockApeluri, 2] = TRANSFORM(tcP2)
+ gaMockApeluri[gnMockApeluri, 3] = TRANSFORM(tcP3)
+ IF TYPE('gcUILog') = 'C'
+ DO PrLogLocal WITH 'MOCK ' + tcMetoda + ' p2=[' + TRANSFORM(tcP2) + '] p3=[' + TRANSFORM(tcP3) + ']'
+ ENDIF
+ ENDPROC
+
+ PROCEDURE RefreshTokenAuto
+ This.RegMock('RefreshTokenAuto', '', '')
+ ENDPROC
+
+ PROCEDURE TrimiteFacturaXML
+ LPARAMETERS tcFile, tlValidare, tlValidareOnline, tlNuTrimiteEFacturaANAF, tlExtern, tlB2C
+ LOCAL loReturn
+ This.RegMock('TrimiteFacturaXML', tcFile, TRANSFORM(tlB2C))
+ loReturn = CREATEOBJECT('Empty')
+ ADDPROPERTY(loReturn, 'lSucces', .T.)
+ ADDPROPERTY(loReturn, 'cIdIncarcare', 'MOCK-ID-STORNO-12345')
+ ADDPROPERTY(loReturn, 'cMesaj', '')
+ ADDPROPERTY(loReturn, 'cResponse', '')
+ ADDPROPERTY(loReturn, 'nStatus', 200)
+ ADDPROPERTY(loReturn, 'cFile', TRANSFORM(tcFile))
+ RETURN loReturn
+ ENDPROC
+
+ PROCEDURE SaveFile
+ LPARAMETERS tcFile, tlVariable, tcDirectory, tcFileName
+ This.RegMock('SaveFile', tcFile, TRANSFORM(tcDirectory) + '|' + TRANSFORM(tcFileName))
+ RETURN .T.
+ ENDPROC
+
+ PROCEDURE Xml2PdfAnaf
+ LPARAMETERS tcXml, tcFile, tlVariable
+ This.RegMock('Xml2PdfAnaf', tcFile, '')
+ RETURN tcFile
+ ENDPROC
+ENDDEFINE
diff --git a/utile/Teste/storno/storno_fixture_b2c_cnp.xml b/utile/Teste/storno/storno_fixture_b2c_cnp.xml
new file mode 100644
index 0000000..4333bc8
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_b2c_cnp.xml
@@ -0,0 +1 @@
+TEST-B2C-012026-05-08380RONRO99999901VATFURNIZOR TEST SAJ40/9999/2020PERSOANA FIZICA TEST199010112345619.00100.0019.00S19.0VAT100.00100.00119.00119.0011.0100.00TEST-B2CS19.0VAT100.00
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_creditnote_minimal.xml b/utile/Teste/storno/storno_fixture_creditnote_minimal.xml
new file mode 100644
index 0000000..4866757
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_creditnote_minimal.xml
@@ -0,0 +1 @@
+TEST-CN-012026-05-02381RONRO99999901VATFURNIZOR TEST SAJ40/9999/2020CLIENT TEST SRLJ40/8888/19999.5050.009.50S19.0VAT50.0050.0059.5059.5011.050.00TEST-CNS19.0VAT50.00
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_diacritice_reale.xml b/utile/Teste/storno/storno_fixture_diacritice_reale.xml
new file mode 100644
index 0000000..d7e7945
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_diacritice_reale.xml
@@ -0,0 +1 @@
+urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1TEST-3795-012026-03-252026-03-28380RONRO99999901Str. Test nr. 1SECTOR6060787RO-BRORO99999901VATFURNIZOR TEST SAJ40/9999/2020Capital social 200 RON+40210000000RO99999902Str. Client nr. 2Constanţa900189RO-CTRORO99999902VATCLIENT TEST SRLJ40/8888/199948RO00AAAA0000000000000001CONT RON48RO00TREZ0000000000000002CONT RONfalse95Discount DVCR1VAT21TYPE108.43S21.0VATtrueTVTransportation by vendor10.74S21.0VAT18.1886.5918.18S21.0VAT84.2886.59104.778.4310.74104.771Produs2.084.28Piesa auto test diacriticeTEST-001S21.0VAT42.15
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_factura_negativa.xml b/utile/Teste/storno/storno_fixture_factura_negativa.xml
new file mode 100644
index 0000000..b1554da
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_factura_negativa.xml
@@ -0,0 +1 @@
+TEST-NEG-012026-05-05380RONRO99999901VATFURNIZOR TEST SAJ40/9999/2020CLIENT TEST SRLJ40/8888/1999-19.00-100.00-19.00S19.0VAT-100.00-100.00-119.00-119.001-1.0-100.00TEST-NEGS19.0VAT100.00
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_fara_partytaxscheme.xml b/utile/Teste/storno/storno_fixture_fara_partytaxscheme.xml
new file mode 100644
index 0000000..ff4cb59
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_fara_partytaxscheme.xml
@@ -0,0 +1 @@
+TEST-NOPTS-012026-05-09380RONFURNIZOR NEPLATITOR TVA SRLRO99999903CLIENT TEST SRLJ40/8888/19990.00100.000.00OVAT100.00100.00100.00100.0011.0100.00TEST-NOPTSOVAT100.00
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_paymentterms.xml b/utile/Teste/storno/storno_fixture_paymentterms.xml
new file mode 100644
index 0000000..76eab54
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_paymentterms.xml
@@ -0,0 +1 @@
+TEST-PT-012026-05-07380RONRO99999901VATFURNIZOR TEST SAJ40/9999/2020CLIENT TEST SRLJ40/8888/199930 zile119.0019.00100.0019.00S19.0VAT100.00100.00119.00119.0011.0100.00TEST-PTS19.0VAT100.00
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_prefix_n3.xml b/utile/Teste/storno/storno_fixture_prefix_n3.xml
new file mode 100644
index 0000000..e10ed3d
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_prefix_n3.xml
@@ -0,0 +1 @@
+urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1TEST-N3-012026-04-10380RONRO99999901VATFURNIZOR TEST SAJ40/9999/2020CLIENT TEST SRLJ40/8888/199919.00100.0019.00S19.0VAT100.00100.00119.00119.0011.0100.00TEST-N3S19.0VAT100.00
\ No newline at end of file
diff --git a/utile/Teste/storno/storno_fixture_sume_zero.xml b/utile/Teste/storno/storno_fixture_sume_zero.xml
new file mode 100644
index 0000000..f17b804
--- /dev/null
+++ b/utile/Teste/storno/storno_fixture_sume_zero.xml
@@ -0,0 +1 @@
+TEST-ZERO-012026-05-06380RONRO99999901VATFURNIZOR TEST SAJ40/9999/2020CLIENT TEST SRLJ40/8888/1999false95-0.00S19.0VAT0.000.000.00S0.0VAT0.000.000.00-0.000.0010.00.00TEST-ZEROS0.0VAT0.00
\ No newline at end of file
diff --git a/utile/Teste/storno/test4_storno_efactura_anaf_test.prg b/utile/Teste/storno/test4_storno_efactura_anaf_test.prg
new file mode 100644
index 0000000..74bbb7d
--- /dev/null
+++ b/utile/Teste/storno/test4_storno_efactura_anaf_test.prg
@@ -0,0 +1,227 @@
+* test4_storno_efactura_anaf_test.prg
+* Test 4 (docs\plan_storno_efactura.md): upload real pe ANAF TEST al unui storno + reincarcarea
+* facturii corecte cu acelasi numar ca originalul. Sursa: MARIUSM_AUTO.anaf_efactura (test=1).
+* Maxim 4 incarcari (aprobat); interzisa orice trimitere spre /prod/.
+* Validarea online (ValidareEFacturaANAF) e SARITA intentionat: xmlefactura.prg:1275,1278
+* construieste URL-ul hardcodat pe /prod/, indiferent de context (fara proprietate lTest pe
+* ExportEFactura) - decizie aprobata, ANAF valideaza oricum la upload.
+*
+* Rulare: vfp9.exe -A -T "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test4_storno_efactura_anaf_test.prg"
+* Iesire: test4_storno_efactura_anaf_test_log.txt (acelasi folder)
+
+SET SAFETY OFF
+SET TALK OFF
+SET DELETED ON
+SET EXACT ON
+SET CENTURY ON
+SET DATE DMY
+SET NULLDISPLAY TO ''
+CLOSE DATABASES
+
+PUBLIC gcUILog, gcOutDir
+gcOutDir = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\out\"
+IF !DIRECTORY(gcOutDir)
+ MD (gcOutDir)
+ENDIF
+gcUILog = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test4_storno_efactura_anaf_test_log.txt"
+STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
+
+ON ERROR DO PrErrLocal WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
+ON SHUTDOWN QUIT
+
+PUBLIC lnUploads
+lnUploads = 0
+
+TRY
+ DO "D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg" WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
+ IF gnHandle <= 0
+ DO PrLogLocal WITH 'FAIL: conectare Oracle esuata (gnHandle=' + TRANSFORM(gnHandle) + ')'
+ QUIT
+ ENDIF
+ DO PrLogLocal WITH 'mediu initializat, gnHandle=' + TRANSFORM(gnHandle)
+
+ IF !('nfjsonread' $ LOWER(SET("Procedure")))
+ SET PROCEDURE TO 'D:\ROA\ROACONT\COMUN\utile\nfjson\nfjsonread.prg' ADDITIVE
+ ENDIF
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 1: selectie sursa (MARIUSM_AUTO.anaf_efactura, test=1, factura_emisa=1, FACTURA TRIMISA) ==='
+ LOCAL lcSql, lnIdSursa, lcIdIncarcareSursa
+ lnIdSursa = 0
+ lcSql = "select id, id_incarcare from anaf_efactura"
+ lcSql = lcSql + " where factura_emisa=1 and test=1 and tip_mesaj_raspuns='FACTURA TRIMISA'"
+ lcSql = lcSql + " and detalii_zip is not null order by id desc"
+ IF !goExecutor.oExecuta(lcSql, "cSursa")
+ DO PrLogLocal WITH 'FAIL: select sursa: ' + ALLTRIM(NVL(goExecutor.cEroare,''))
+ QUIT
+ ENDIF
+ IF RECCOUNT('cSursa') = 0
+ DO PrLogLocal WITH 'FAIL: nicio sursa disponibila in MARIUSM_AUTO (factura_emisa=1, test=1, FACTURA TRIMISA, cu detalii_zip)'
+ QUIT
+ ENDIF
+ SELECT cSursa
+ GO TOP
+ lnIdSursa = id
+ lcIdIncarcareSursa = ALLTRIM(NVL(id_incarcare, ''))
+ USE IN (SELECT('cSursa'))
+ DO PrLogLocal WITH 'sursa aleasa: id=' + TRANSFORM(lnIdSursa) + ' id_incarcare_original=' + lcIdIncarcareSursa
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 2: GetXmlOriginalEFactura + StornoEFacturaXML ==='
+ LOCAL lcXmlOriginal, lcMsgGet, llRetGet
+ llRetGet = GetXmlOriginalEFactura(lnIdSursa, @lcXmlOriginal, @lcMsgGet)
+ IF !llRetGet
+ DO PrLogLocal WITH 'FAIL: GetXmlOriginalEFactura: ' + lcMsgGet
+ QUIT
+ ENDIF
+ DO PrLogLocal WITH 'GetXmlOriginalEFactura OK, len=' + TRANSFORM(LEN(lcXmlOriginal))
+
+ LOCAL lcFileOriginal, lcFileStorno, lcMsgStorno, llRetStorno
+ LOCAL laMod[1,4]
+ lcFileOriginal = gcOutDir + 'test4_original_' + TRANSFORM(lnIdSursa) + '.xml'
+ lcFileStorno = gcOutDir + 'test4_storno_' + TRANSFORM(lnIdSursa) + '.xml'
+ STRTOFILE(lcXmlOriginal, lcFileOriginal)
+
+ llRetStorno = StornoEFacturaXML(lcXmlOriginal, '', 'Stornare factura test 4 ANAF TEST', lcFileStorno, @laMod, @lcMsgStorno)
+ IF !llRetStorno
+ DO PrLogLocal WITH 'FAIL: StornoEFacturaXML: ' + lcMsgStorno
+ QUIT
+ ENDIF
+ DO PrLogLocal WITH 'StornoEFacturaXML OK, fisier=' + lcFileStorno
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 3: pregatire AnafeFacturaServer pe TEST (token + CIF), garda /test/ ==='
+ LOCAL loAnaf As 'AnafeFacturaServer'
+ loAnaf = CREATEOBJECT('AnafeFacturaServer', .F., '', gcS, '')
+ loAnaf.lTest = .T.
+ loAnaf.RefreshTokenAuto()
+
+ DO PrLogLocal WITH 'CIF=' + loAnaf.cCodFiscal + ' lTest=' + TRANSFORM(loAnaf.lTest) + ' tokenExp=' + DTOC(loAnaf.dTokenExpdate) + ' tokenGol=' + TRANSFORM(EMPTY(loAnaf.cToken))
+
+ * garda: nicio trimitere daca lTest nu e .T., tokenul e gol sau expirat - fara presupuneri
+ IF !loAnaf.lTest
+ DO PrLogLocal WITH 'BLOCAT: lTest nu e .T. - opresc, fara nicio trimitere'
+ QUIT
+ ENDIF
+ IF EMPTY(loAnaf.cToken)
+ DO PrLogLocal WITH 'BLOCAT: token gol - opresc, fara nicio trimitere'
+ QUIT
+ ENDIF
+ IF loAnaf.dTokenExpdate <= DATE()
+ DO PrLogLocal WITH 'BLOCAT: token expirat (' + DTOC(loAnaf.dTokenExpdate) + ') - opresc, fara nicio trimitere'
+ QUIT
+ ENDIF
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 4: upload 1/2 - storno pe TEST ==='
+ LOCAL loRetStorno
+ IF !loAnaf.lTest
+ DO PrLogLocal WITH 'BLOCAT (garda finala): lTest nu mai e .T. inainte de upload storno'
+ QUIT
+ ENDIF
+ loRetStorno = loAnaf.TrimiteFacturaXML(lcFileStorno, .F., .F., .F., .F., .F.)
+ lnUploads = lnUploads + 1
+ DO PrLogLocal WITH 'upload storno: lSucces=' + TRANSFORM(loRetStorno.lSucces) + ' status=' + TRANSFORM(loRetStorno.nStatus) + ' idIncarcare=' + ALLTRIM(NVL(loRetStorno.cIdIncarcare,'')) + ' mesaj=[' + ALLTRIM(NVL(loRetStorno.cMesaj,'')) + ']'
+ DO PrLogLocal WITH 'raspuns brut storno: ' + ALLTRIM(NVL(loRetStorno.cResponse,''))
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 5: upload 2/2 - reincarcare XML original (acelasi numar) pe TEST ==='
+ LOCAL loRetReincarcare
+ IF !loAnaf.lTest
+ DO PrLogLocal WITH 'BLOCAT (garda finala): lTest nu mai e .T. inainte de reincarcare'
+ QUIT
+ ENDIF
+ loRetReincarcare = loAnaf.TrimiteFacturaXML(lcFileOriginal, .F., .F., .F., .F., .F.)
+ lnUploads = lnUploads + 1
+ DO PrLogLocal WITH 'upload reincarcare: lSucces=' + TRANSFORM(loRetReincarcare.lSucces) + ' status=' + TRANSFORM(loRetReincarcare.nStatus) + ' idIncarcare=' + ALLTRIM(NVL(loRetReincarcare.cIdIncarcare,'')) + ' mesaj=[' + ALLTRIM(NVL(loRetReincarcare.cMesaj,'')) + ']'
+ DO PrLogLocal WITH 'raspuns brut reincarcare: ' + ALLTRIM(NVL(loRetReincarcare.cResponse,''))
+
+ DO PrLogLocal WITH '=== PAS 6: storno CreditNote - NEEXECUTAT: nicio sursa CreditNote emisa in MARIUSM_AUTO.anaf_efactura (test=1) ==='
+ DO PrLogLocal WITH '=== PAS 7: storno B2C - NEEXECUTAT: nicio sursa cu CNP 13 cifre in MARIUSM_AUTO.anaf_efactura (test=1) ==='
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 8: verificare stare incarcari (stareMesaj, /test/) ==='
+ IF !EMPTY(ALLTRIM(NVL(loRetStorno.cIdIncarcare,'')))
+ DO VerificaStare WITH loAnaf, ALLTRIM(loRetStorno.cIdIncarcare), 'storno'
+ ELSE
+ DO PrLogLocal WITH 'storno: fara id_incarcare, nu se poate verifica starea (vezi mesajul de upload de mai sus)'
+ ENDIF
+ IF !EMPTY(ALLTRIM(NVL(loRetReincarcare.cIdIncarcare,'')))
+ DO VerificaStare WITH loAnaf, ALLTRIM(loRetReincarcare.cIdIncarcare), 'reincarcare'
+ ELSE
+ DO PrLogLocal WITH 'reincarcare: fara id_incarcare, nu se poate verifica starea (vezi mesajul de upload de mai sus)'
+ ENDIF
+
+ DO PrLogLocal WITH 'TOTAL incarcari efectuate: ' + TRANSFORM(lnUploads) + ' (limita aprobata: 4)'
+ DO PrLogLocal WITH 'TOTAL: test terminat fara exceptie'
+CATCH TO loEx
+ DO PrLogLocal WITH 'EXCEPTIE: ' + loEx.Message + ' (' + TRANSFORM(loEx.LineNo) + ') ' + loEx.Procedure
+ENDTRY
+
+QUIT
+
+**********************************************************
+* interogheaza stareMesaj pe /test/ pentru un id_incarcare, cu reincercari, fara sa scrie nimic
+PROCEDURE VerificaStare
+ LPARAMETERS toAnaf, tcIdIncarcare, tcLabel
+
+ LOCAL loHTTP, lcServer, lnStatus, lcResponse, lnIncercare, llGata, loJson, lcStare
+ LOCAL loEx As Exception
+ llGata = .F.
+
+ FOR lnIncercare = 1 TO 5
+ lcServer = 'https://api.anaf.ro/test/FCTEL/rest/stareMesaj?id_incarcare=' + tcIdIncarcare
+ TRY
+ loHTTP = CREATEOBJECT('winHTTP.winHTTPrequest.5.1')
+ loHTTP.Open('GET', lcServer, .F.)
+ loHTTP.setRequestHeader('Authorization', 'Bearer ' + toAnaf.cToken)
+ loHTTP.Send()
+ lnStatus = loHTTP.Status
+ lcResponse = loHTTP.ResponseText
+ CATCH TO loEx
+ lnStatus = 0
+ lcResponse = ''
+ ENDTRY
+
+ DO PrLogLocal WITH '[' + tcLabel + '] stareMesaj incercare ' + TRANSFORM(lnIncercare) + '/5 id_incarcare=' + tcIdIncarcare + ' status=' + TRANSFORM(lnStatus) + ' raspuns=' + lcResponse
+
+ lcStare = ''
+ TRY
+ loJson = nfjsonread(lcResponse)
+ IF TYPE('loJson.stare') = 'C'
+ lcStare = LOWER(ALLTRIM(loJson.stare))
+ ENDIF
+ CATCH
+ lcStare = ''
+ ENDTRY
+
+ IF INLIST(lcStare, 'ok', 'nok')
+ llGata = .T.
+ DO PrLogLocal WITH '[' + tcLabel + '] stare finala ANAF: ' + lcStare
+ EXIT
+ ENDIF
+
+ IF lnIncercare < 5
+ INKEY(20, 'H')
+ ENDIF
+ ENDFOR
+
+ IF !llGata
+ DO PrLogLocal WITH '[' + tcLabel + '] stare neconcludenta dupa 5 incercari (posibil inca in prelucrare la ANAF) - vezi ultimul raspuns de mai sus'
+ ENDIF
+ENDPROC
+
+**********************************************************
+PROCEDURE PrLogLocal
+ LPARAMETERS tcMsg
+ LOCAL lcL
+ SET SAFETY OFF
+ lcL = IIF(FILE(gcUILog), FILETOSTR(gcUILog), '')
+ STRTOFILE(lcL + TTOC(DATETIME()) + ' ' + TRANSFORM(tcMsg) + CHR(13) + CHR(10), gcUILog)
+ENDPROC
+
+**********************************************************
+PROCEDURE PrErrLocal
+ LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
+ DO PrLogLocal WITH 'EROARE ' + TRANSFORM(tnError) + ' ' + TRANSFORM(tcMessage) + ' in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLineNo)
+ENDPROC
diff --git a/utile/Teste/storno/test4b_retry_storno_si_poll.prg b/utile/Teste/storno/test4b_retry_storno_si_poll.prg
new file mode 100644
index 0000000..b9db3fb
--- /dev/null
+++ b/utile/Teste/storno/test4b_retry_storno_si_poll.prg
@@ -0,0 +1,143 @@
+* test4b_retry_storno_si_poll.prg
+* Continuare test4 (docs\plan_storno_efactura.md): PAS 4 (upload storno) a esuat cu eroare
+* tehnica ANAF (Cod: 1814, fara id_incarcare) la prima incercare - reincercare (upload 3/4
+* aprobate) + continuare poll stareMesaj pe /test/ pentru storno (nou) si reincarcare
+* (id_incarcare=5036305973, "in prelucrare" la ultima verificare).
+* Refoloseste fisierul deja generat: out\test4_storno_1127.xml (nu regenereaza StornoEFacturaXML).
+*
+* Rulare: vfp9.exe -A -T "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test4b_retry_storno_si_poll.prg"
+* Iesire: test4b_retry_storno_si_poll_log.txt (acelasi folder)
+
+SET SAFETY OFF
+SET TALK OFF
+SET DELETED ON
+SET EXACT ON
+SET CENTURY ON
+SET DATE DMY
+SET NULLDISPLAY TO ''
+CLOSE DATABASES
+
+PUBLIC gcUILog, gcOutDir
+gcOutDir = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\out\"
+gcUILog = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test4b_retry_storno_si_poll_log.txt"
+STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
+
+ON ERROR DO PrErrLocal WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
+ON SHUTDOWN QUIT
+
+TRY
+ DO "D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg" WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
+ IF gnHandle <= 0
+ DO PrLogLocal WITH 'FAIL: conectare Oracle esuata (gnHandle=' + TRANSFORM(gnHandle) + ')'
+ QUIT
+ ENDIF
+ DO PrLogLocal WITH 'mediu initializat, gnHandle=' + TRANSFORM(gnHandle)
+
+ IF !('nfjsonread' $ LOWER(SET("Procedure")))
+ SET PROCEDURE TO 'D:\ROA\ROACONT\COMUN\utile\nfjson\nfjsonread.prg' ADDITIVE
+ ENDIF
+
+ LOCAL lcFileStorno
+ lcFileStorno = gcOutDir + 'test4_storno_1127.xml'
+ IF !FILE(lcFileStorno)
+ DO PrLogLocal WITH 'FAIL: nu exista fisierul storno de la rularea anterioara: ' + lcFileStorno
+ QUIT
+ ENDIF
+
+ LOCAL loAnaf As 'AnafeFacturaServer'
+ loAnaf = CREATEOBJECT('AnafeFacturaServer', .F., '', gcS, '')
+ loAnaf.lTest = .T.
+ loAnaf.RefreshTokenAuto()
+ DO PrLogLocal WITH 'CIF=' + loAnaf.cCodFiscal + ' lTest=' + TRANSFORM(loAnaf.lTest) + ' tokenExp=' + DTOC(loAnaf.dTokenExpdate) + ' tokenGol=' + TRANSFORM(EMPTY(loAnaf.cToken))
+
+ IF !loAnaf.lTest OR EMPTY(loAnaf.cToken) OR loAnaf.dTokenExpdate <= DATE()
+ DO PrLogLocal WITH 'BLOCAT: garda lTest/token nu trece - opresc, fara nicio trimitere'
+ QUIT
+ ENDIF
+
+ DO PrLogLocal WITH '=== PAS 4 (reincercare, upload 3/4 aprobate): storno pe TEST ==='
+ LOCAL loRetStorno
+ loRetStorno = loAnaf.TrimiteFacturaXML(lcFileStorno, .F., .F., .F., .F., .F.)
+ DO PrLogLocal WITH 'upload storno (reincercare): lSucces=' + TRANSFORM(loRetStorno.lSucces) + ' status=' + TRANSFORM(loRetStorno.nStatus) + ' idIncarcare=' + ALLTRIM(NVL(loRetStorno.cIdIncarcare,'')) + ' mesaj=[' + ALLTRIM(NVL(loRetStorno.cMesaj,'')) + ']'
+ DO PrLogLocal WITH 'raspuns brut storno (reincercare): ' + ALLTRIM(NVL(loRetStorno.cResponse,''))
+
+ IF !EMPTY(ALLTRIM(NVL(loRetStorno.cIdIncarcare,'')))
+ DO VerificaStare WITH loAnaf, ALLTRIM(loRetStorno.cIdIncarcare), 'storno-reincercare'
+ ELSE
+ DO PrLogLocal WITH 'storno-reincercare: fara id_incarcare, nu se poate verifica starea'
+ ENDIF
+
+ DO PrLogLocal WITH '=== continuare poll reincarcare (id_incarcare=5036305973, era "in prelucrare") ==='
+ DO VerificaStare WITH loAnaf, '5036305973', 'reincarcare'
+
+ DO PrLogLocal WITH 'TOTAL: test terminat fara exceptie'
+CATCH TO loEx
+ DO PrLogLocal WITH 'EXCEPTIE: ' + loEx.Message + ' (' + TRANSFORM(loEx.LineNo) + ') ' + loEx.Procedure
+ENDTRY
+
+QUIT
+
+**********************************************************
+PROCEDURE VerificaStare
+ LPARAMETERS toAnaf, tcIdIncarcare, tcLabel
+
+ LOCAL loHTTP, lcServer, lnStatus, lcResponse, lnIncercare, llGata, loJson, lcStare
+ LOCAL loEx As Exception
+ llGata = .F.
+
+ FOR lnIncercare = 1 TO 6
+ lcServer = 'https://api.anaf.ro/test/FCTEL/rest/stareMesaj?id_incarcare=' + tcIdIncarcare
+ TRY
+ loHTTP = CREATEOBJECT('winHTTP.winHTTPrequest.5.1')
+ loHTTP.Open('GET', lcServer, .F.)
+ loHTTP.setRequestHeader('Authorization', 'Bearer ' + toAnaf.cToken)
+ loHTTP.Send()
+ lnStatus = loHTTP.Status
+ lcResponse = loHTTP.ResponseText
+ CATCH TO loEx
+ lnStatus = 0
+ lcResponse = ''
+ ENDTRY
+
+ DO PrLogLocal WITH '[' + tcLabel + '] stareMesaj incercare ' + TRANSFORM(lnIncercare) + '/6 id_incarcare=' + tcIdIncarcare + ' status=' + TRANSFORM(lnStatus) + ' raspuns=' + lcResponse
+
+ lcStare = ''
+ TRY
+ loJson = nfjsonread(lcResponse)
+ IF TYPE('loJson.stare') = 'C'
+ lcStare = LOWER(ALLTRIM(loJson.stare))
+ ENDIF
+ CATCH
+ lcStare = ''
+ ENDTRY
+
+ IF INLIST(lcStare, 'ok', 'nok')
+ llGata = .T.
+ DO PrLogLocal WITH '[' + tcLabel + '] stare finala ANAF: ' + lcStare
+ EXIT
+ ENDIF
+
+ IF lnIncercare < 6
+ INKEY(25, 'H')
+ ENDIF
+ ENDFOR
+
+ IF !llGata
+ DO PrLogLocal WITH '[' + tcLabel + '] stare neconcludenta dupa 6 incercari (posibil inca in prelucrare la ANAF) - vezi ultimul raspuns de mai sus'
+ ENDIF
+ENDPROC
+
+**********************************************************
+PROCEDURE PrLogLocal
+ LPARAMETERS tcMsg
+ LOCAL lcL
+ SET SAFETY OFF
+ lcL = IIF(FILE(gcUILog), FILETOSTR(gcUILog), '')
+ STRTOFILE(lcL + TTOC(DATETIME()) + ' ' + TRANSFORM(tcMsg) + CHR(13) + CHR(10), gcUILog)
+ENDPROC
+
+**********************************************************
+PROCEDURE PrErrLocal
+ LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
+ DO PrLogLocal WITH 'EROARE ' + TRANSFORM(tnError) + ' ' + TRANSFORM(tcMessage) + ' in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLineNo)
+ENDPROC
diff --git a/utile/Teste/storno/test4c_descarcare_raspuns.prg b/utile/Teste/storno/test4c_descarcare_raspuns.prg
new file mode 100644
index 0000000..0be2ad7
--- /dev/null
+++ b/utile/Teste/storno/test4c_descarcare_raspuns.prg
@@ -0,0 +1,109 @@
+* test4c_descarcare_raspuns.prg
+* Continuare test4: descarca raspunsul ANAF (nok) pentru reincarcarea originalului cu acelasi
+* numar (id_descarcare=3059138503), ca sa se vada mesajul exact de respingere. Doar GET/descarcare
+* si dezarhivare locala - nu scrie nimic in Oracle, nu conteaza ca incarcare.
+*
+* Rulare: vfp9.exe -A -T "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test4c_descarcare_raspuns.prg"
+
+SET SAFETY OFF
+SET TALK OFF
+CLOSE DATABASES
+
+PUBLIC gcUILog, gcOutDir
+gcOutDir = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\out\"
+gcUILog = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test4c_descarcare_raspuns_log.txt"
+STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
+
+ON ERROR DO PrErrLocal WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
+ON SHUTDOWN QUIT
+
+TRY
+ DO "D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg" WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
+ IF gnHandle <= 0
+ DO PrLogLocal WITH 'FAIL: conectare Oracle esuata'
+ QUIT
+ ENDIF
+
+ LOCAL loAnaf As 'AnafeFacturaServer'
+ loAnaf = CREATEOBJECT('AnafeFacturaServer', .F., '', gcS, '')
+ loAnaf.lTest = .T.
+ loAnaf.RefreshTokenAuto()
+ IF !loAnaf.lTest OR EMPTY(loAnaf.cToken)
+ DO PrLogLocal WITH 'BLOCAT: garda lTest/token nu trece'
+ QUIT
+ ENDIF
+
+ DO DescarcaSiLogheaza WITH loAnaf, '3059138503', 'reincarcare-nok'
+ DO DescarcaSiLogheaza WITH loAnaf, '3059138527', 'storno-reincercare-ok'
+
+ DO PrLogLocal WITH 'TOTAL: test terminat fara exceptie'
+CATCH TO loEx
+ DO PrLogLocal WITH 'EXCEPTIE: ' + loEx.Message + ' (' + TRANSFORM(loEx.LineNo) + ') ' + loEx.Procedure
+ENDTRY
+
+QUIT
+
+**********************************************************
+PROCEDURE DescarcaSiLogheaza
+ LPARAMETERS toAnaf, tcIdDescarcare, tcLabel
+
+ LOCAL loHTTP, lcServer, lnStatus, lcZip, lcDir, lcMesaj, llSucces
+ LOCAL loEx As Exception
+
+ lcServer = 'https://api.anaf.ro/test/FCTEL/rest/descarcare?id=' + tcIdDescarcare
+ TRY
+ loHTTP = CREATEOBJECT('winHTTP.winHTTPrequest.5.1')
+ loHTTP.Open('GET', lcServer, .F.)
+ loHTTP.setRequestHeader('Authorization', 'Bearer ' + toAnaf.cToken)
+ loHTTP.Send()
+ lnStatus = loHTTP.Status
+ CATCH TO loEx
+ DO PrLogLocal WITH '[' + tcLabel + '] descarcare eroare: ' + loEx.Message
+ RETURN
+ ENDTRY
+
+ DO PrLogLocal WITH '[' + tcLabel + '] descarcare id=' + tcIdDescarcare + ' status=' + TRANSFORM(lnStatus) + ' content-type=' + loHTTP.getResponseHeader('Content-Type')
+
+ IF lnStatus <> 200
+ DO PrLogLocal WITH '[' + tcLabel + '] raspuns brut (non-200): ' + loHTTP.ResponseText
+ RETURN
+ ENDIF
+
+ lcZip = gcOutDir + 'test4_descarcare_' + tcIdDescarcare + '.zip'
+ STRTOFILE(loHTTP.ResponseBody, lcZip)
+
+ lcDir = ''
+ lcMesaj = ''
+ llSucces = MyUnzip(lcZip, @lcDir, @lcMesaj, .T.)
+ IF !llSucces
+ DO PrLogLocal WITH '[' + tcLabel + '] MyUnzip esuat: ' + lcMesaj + ' (zip pastrat: ' + lcZip + ')'
+ RETURN
+ ENDIF
+
+ DO PrLogLocal WITH '[' + tcLabel + '] dezarhivat in ' + lcDir
+
+ LOCAL lcFisier, laFisiere[1], lnI, lnFisiere
+ lnFisiere = ADIR(laFisiere, ADDBS(lcDir) + '*.*')
+ FOR lnI = 1 TO lnFisiere
+ lcFisier = ADDBS(lcDir) + laFisiere[lnI, 1]
+ DO PrLogLocal WITH '[' + tcLabel + '] fisier: ' + laFisiere[lnI,1] + ' (' + TRANSFORM(laFisiere[lnI,2]) + ' octeti)'
+ IF LOWER(JUSTEXT(lcFisier)) = 'xml'
+ DO PrLogLocal WITH '[' + tcLabel + '] continut: ' + FILETOSTR(lcFisier)
+ ENDIF
+ ENDFOR
+ENDPROC
+
+**********************************************************
+PROCEDURE PrLogLocal
+ LPARAMETERS tcMsg
+ LOCAL lcL
+ SET SAFETY OFF
+ lcL = IIF(FILE(gcUILog), FILETOSTR(gcUILog), '')
+ STRTOFILE(lcL + TTOC(DATETIME()) + ' ' + TRANSFORM(tcMsg) + CHR(13) + CHR(10), gcUILog)
+ENDPROC
+
+**********************************************************
+PROCEDURE PrErrLocal
+ LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
+ DO PrLogLocal WITH 'EROARE ' + TRANSFORM(tnError) + ' ' + TRANSFORM(tcMessage) + ' in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLineNo)
+ENDPROC
diff --git a/utile/Teste/storno/test_storno_e2e.prg b/utile/Teste/storno/test_storno_e2e.prg
new file mode 100644
index 0000000..0f5b0f1
--- /dev/null
+++ b/utile/Teste/storno/test_storno_e2e.prg
@@ -0,0 +1,411 @@
+* test_storno_e2e.prg
+* Test E2E HEADLESS pentru "Generare XML storno si trimitere la ANAF" din borderoul eFactura:
+* exerseaza codul REAL din anaf_efactura.vcx (metoda do_storno_efactura + clasa
+* frm_storno_efactura), nu copii ale lui. Vezi docs\handoff_storno_efactura.md.
+*
+* Ruleaza direct pe binarul real din proiect (COMUN\CLASE\anaf_efactura.vcx, cu write-back):
+* vfp9.exe -A -T "...\test_storno_e2e.prg"
+*
+* ZERO trimiteri ANAF: Thisform.oAnaf.AnafeFacturaServer e inlocuit cu MockAnafServerStorno
+* (mock_anaf_server_storno.prg) imediat dupa ce forma reala citeste cCodFiscal/lTest de la
+* serverul real. cmdValideaza (ValidareEFacturaANAF, apel HTTP real, hardcodat /prod/ -
+* xmlefactura.prg:1275,1278) NU e apelat; se ocoleste cu SetStare(1) direct.
+
+SET SAFETY OFF
+SET TALK OFF
+SET DELETED ON
+SET EXACT ON
+SET CENTURY ON
+SET DATE DMY
+SET NULLDISPLAY TO ''
+CLOSE DATABASES
+
+PUBLIC gcUILog, gcTestDir, gcTesteDir
+PUBLIC gnPass, gnFail, gnMockRaspuns, gnAmsgCalls, gnMockApeluri
+PUBLIC loAnaf, loForm, loMockSrv, loStornoForm, lnLastErrorNo, lcLastErrorMsg
+DECLARE gaAmsgCalls[1,2]
+DECLARE gaMockApeluri[1,3]
+gnPass = 0
+gnFail = 0
+gnAmsgCalls = 0
+gnMockApeluri = 0
+gnMockRaspuns = 6
+lnLastErrorNo = 0
+lcLastErrorMsg = ''
+
+gcTestDir = ADDBS(JUSTPATH(SYS(16)))
+gcTesteDir = ADDBS(JUSTPATH(JUSTPATH(SYS(16))))
+gcUILog = gcTestDir + 'test_storno_e2e_log_real.txt'
+STRTOFILE('START ' + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
+
+ON ERROR DO PrErrLocal WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
+ON SHUTDOWN QUIT
+
+* mock-urile INAINTEA test_init_env_auto: la nume duplicat de procedura castiga fisierul
+* cautat PRIMUL (vezi mock_amessagebox.prg / capcana testare-ui-vfp.md)
+SET PROCEDURE TO (gcTestDir + 'mock_amessagebox_storno.prg') ADDITIVE
+SET PROCEDURE TO (gcTestDir + 'mock_anaf_server_storno.prg') ADDITIVE
+
+TRY
+ DO (gcTesteDir + 'test_init_env_auto.prg') WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
+ IF gnHandle <= 0
+ DO PrLogLocal WITH 'FAIL: conectare Oracle esuata (gnHandle=' + TRANSFORM(gnHandle) + ')'
+ QUIT
+ ENDIF
+ DO PrLogLocal WITH 'mediu initializat, gnHandle=' + TRANSFORM(gnHandle) + ' gcS=' + gcS
+
+ * gnId_Prg_Owner: setat de roacont.prg la pornirea reala a aplicatiei (=0). test_init_env_auto.prg
+ * NU il seteaza pentru ROACONT - do_storno_efactura il foloseste direct (is_luna_blocata),
+ * fara TYPE()-check => "variable not found" daca lipseste (capcana de mediu, nu bug de aplicatie).
+ PUBLIC gnId_Prg_Owner
+ gnId_Prg_Owner = 0
+
+ * gnEFACTURA_*/gcEFACTURA_*: memvari globale create dinamic de optiuni_firma() (oinit_optiuni.prg,
+ * apelata din ocomenzi.vc2:6675 la login real) din tabela de optiuni (v_optiuni, macro-substitutie
+ * PUBLIC gn&lcvarname.). test_init_env_auto.prg:290 apeleaza ACELASI loader - nu setez manual,
+ * doar diagnostichez ce a produs, pentru cele 11 legate de controale in pgfeFactura.Page4
+ * (vc2:4952-5201).
+ LOCAL i, laEFVars[11], lcEFDiag
+ laEFVars[1] = 'gnEFACTURA_ZIP_DATABASE'
+ laEFVars[2] = 'gnEFACTURA_SAVE'
+ laEFVars[3] = 'gnEFACTURA_SAVE_ANAF_PDF'
+ laEFVars[4] = 'gnEFACTURA_VALIDARE_ONLINE'
+ laEFVars[5] = 'gcEFACTURA_NETWORK_PATH'
+ laEFVars[6] = 'gcEFACTURA_RESPONSESPATH'
+ laEFVars[7] = 'gcEFACTURA_SAVE_CLIENTI_FILE'
+ laEFVars[8] = 'gcEFACTURA_SAVE_CLIENTI_PATH'
+ laEFVars[9] = 'gcEFACTURA_SAVE_FURNIZORI_FILE'
+ laEFVars[10] = 'gcEFACTURA_SAVE_FURNIZORI_PATH'
+ laEFVars[11] = 'gcEFACTURA_SERVER_PATH'
+ FOR i = 1 TO 11
+ lcEFDiag = laEFVars[i] + '=TYPE[' + TYPE(laEFVars[i]) + ']'
+ IF TYPE(laEFVars[i]) != 'U'
+ lcEFDiag = lcEFDiag + ' val=[' + TRANSFORM(EVALUATE(laEFVars[i])) + ']'
+ ENDIF
+ DO PrLogLocal WITH lcEFDiag
+ ENDFOR
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 0: sursa reala MARIUSM_AUTO.anaf_vefactura_trimis id=1127 (FACTURA TRIMISA) ==='
+ LOCAL lnIdSursa, lnTestSursa, ldDataSursa, llLunaBlocataDiag
+ lnIdSursa = 0
+ * numar_act/data_act nu exista pe tabela anaf_efactura (are xnumar_act/xdata_act) - doar pe
+ * view-ul anaf_vefactura_trimis (verificat cu DESC pe MARIUSM_AUTO@ROA_CENTRAL).
+ IF !goExecutor.oExecuta("select id, numar_act, data_act, test, tip_mesaj_raspuns from anaf_vefactura_trimis where id = 1127", "cSursa")
+ DO PrLogLocal WITH 'FAIL: select sursa id=1127: ' + ALLTRIM(NVL(goExecutor.cEroare,''))
+ QUIT
+ ENDIF
+ IF RECCOUNT('cSursa') = 0
+ DO PrLogLocal WITH 'FAIL: id=1127 nu exista in MARIUSM_AUTO.anaf_vefactura_trimis'
+ QUIT
+ ENDIF
+ SELECT cSursa
+ lnIdSursa = id
+ lnTestSursa = NVL(test, 0)
+ ldDataSursa = data_act
+ DO PrLogLocal WITH 'sursa: id=' + TRANSFORM(lnIdSursa) + ' numar=[' + ALLTRIM(NVL(numar_act, '')) + '] data=' + DTOC(ldDataSursa) + ' test=' + TRANSFORM(lnTestSursa) + ' tip_mesaj=' + ALLTRIM(NVL(tip_mesaj_raspuns, ''))
+ USE IN (SELECT('cSursa'))
+
+ llLunaBlocataDiag = IS_LUNA_BLOCATA(gcS, gnId_Prg_Owner, YEAR(ldDataSursa), MONTH(ldDataSursa))
+ DO PrLogLocal WITH 'diagnostic is_luna_blocata(' + TRANSFORM(YEAR(ldDataSursa)) + '/' + TRANSFORM(MONTH(ldDataSursa)) + ')=' + TRANSFORM(llLunaBlocataDiag) + ' (informativ - daca e .T., formularul de storno nu va aparea deloc)'
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 1: cursoarele borderoului (copie fluxul real ANAFeFactura.vizeFactura, anaf_efactura.prg:2867+) ==='
+ PUBLIC poFacturiEmise, poFacturiPrimite, poFacturiTrimise, poFacturiDetalii
+ LOCAL lcData1, lcSchema, lcSelect, lcFiltru, lcFiltruOriginal, lcOrder, lcgroup, llModParam, llAfiseaza
+ lcData1 = '01' + PADL(INT(gnLuna), 2, '0') + ALLTRIM(STR(gnAn))
+
+ * FACTURI PRIMITE (goale - nu sunt necesare pentru storno, dar cursorul trebuie sa existe:
+ * lipsa lui a lasat formularul cu controale incomplete intr-o sesiune anterioara - vezi
+ * testare-ui-vfp.md, capcana j)
+ TEXT TO lcSchema NOSHOW
+ales N(1), id N(20), id_fact N(20), data_act D, data_scad D, numar_act C(30), xfurnizor C(200), xclient C(200), id_incarcare C(36), id_descarcare c(36) null, tip_mesaj_raspuns C(50) null,mesaj_raspuns C(250) null, data_raspuns D null, trimis N(1) null, data_trimis T null, cod_fiscal_emitent C(30) null, cod_fiscal_beneficiar C(30) null, detalii M null, total_fara_tva N(16,4), total_tva N(16,4), total_tva_ron N(16,4), total_cu_tva N(16,4), discount_fara_tva N(16,4), taxe_fara_tva N(16,4), valoare_fara_tva N(16,4), total_de_plata N(16,4), nume_valuta C(5), test N(1) null, jtotctva N(16,4), descriere M null, detalii_plata M null, diferenta N(16,4), procesat N(1), tip_persoana N(1) null, descarcat N(1) null, filepath c(250) null, procesat_server N(1) null, creditnote N(1)
+ ENDTEXT
+ TEXT TO lcSelect NOSHOW
+SELECT 0 as ales, id, id_fact, data_act, data_scad, numar_act, xfurnizor, xclient, id_incarcare, id_descarcare, tip_mesaj_raspuns, mesaj_raspuns, data_raspuns, NVL(trimis,0) as trimis, data_trimis, cod_fiscal_emitent, cod_fiscal_beneficiar, '' as detalii, total_fara_tva, total_tva, total_tva_ron, total_cu_tva, discount_fara_tva, taxe_fara_tva, valoare_fara_tva, total_de_plata, nume_valuta, test, jtotctva, descriere, detalii_plata, decode(creditnote,0,1,-1) * NVL(total_cu_tva, 0.00)-NVL(jtotctva, 0.00) as diferenta, procesat, tip_persoana, descarcat, filepath, procesat_server, creditnote FROM anaf_vefactura_primit
+ ENDTEXT
+ lcOrder = [data_act,numar_act,data_raspuns]
+ lcgroup = []
+ lcFiltru = [1=2]
+ lcFiltruOriginal = []
+ llModParam = .T.
+ llAfiseaza = .F.
+ gencursor('poFacturiPrimite', 'crsFacturiPrimite', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
+ poFacturiPrimite.ca_baza1.afisare()
+ SELECT crsFacturiPrimite
+ GO TOP
+
+ * FACTURI TRIMISE - filtru tintit pe sursa reala (id=1127), nu pe luna curenta
+ TEXT TO lcSchema NOSHOW
+ales N(1), id N(20), id_fact N(20), data_act D, data_scad D, numar_act C(30), xfurnizor C(200), xclient C(200), id_incarcare C(36), id_descarcare c(36) null, mesaj_trimis C(250) null, tip_mesaj_raspuns C(50) null,mesaj_raspuns C(250) null, data_raspuns D null, trimis N(1) null, data_trimis T null, cod_fiscal_emitent C(30) null, cod_fiscal_beneficiar C(30) null, detalii M null, total_fara_tva N(16,4), total_tva N(16,4), total_tva_ron N(16,4), total_cu_tva N(16,4), discount_fara_tva N(16,4), taxe_fara_tva N(16,4), valoare_fara_tva N(16,4), total_de_plata N(16,4), nume_valuta C(5), test N(1) null, jtotctva N(16,4), descriere M null, detalii_plata M null, diferenta N(16,4), procesat N(1), tip_persoana N(1) null, descarcat N(1) null, filepath c(250) null, procesat_server N(1) null, creditnote N(1)
+ ENDTEXT
+ TEXT TO lcSelect NOSHOW
+SELECT 0 as ales, id, id_fact, data_act, data_scad, numar_act, xfurnizor, xclient, id_incarcare, id_descarcare, mesaj_trimis, tip_mesaj_raspuns, mesaj_raspuns, data_raspuns, NVL(trimis,0) as trimis, data_trimis, cod_fiscal_emitent, cod_fiscal_beneficiar, '' as detalii, total_fara_tva, total_tva, total_tva_ron, total_cu_tva, discount_fara_tva, taxe_fara_tva, valoare_fara_tva, total_de_plata, nume_valuta, test, jtotctva, descriere, detalii_plata, decode(creditnote,0,1,-1) * NVL(total_cu_tva, 0.00)-NVL(jtotctva, 0.00) as diferenta, procesat, tip_persoana, descarcat, filepath, procesat_server, creditnote FROM anaf_vefactura_trimis
+ ENDTEXT
+ lcOrder = [data_act,numar_act,data_trimis,data_raspuns]
+ lcgroup = []
+ lcFiltru = [id = 1127]
+ lcFiltruOriginal = []
+ llModParam = .T.
+ llAfiseaza = .F.
+ gencursor('poFacturiTrimise', 'crsFacturiTrimise', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
+ poFacturiTrimise.ca_baza1.afisare()
+ SELECT crsFacturiTrimise
+ GO TOP
+ DO TVerdict WITH RECCOUNT('crsFacturiTrimise') = 1 AND NVL(crsFacturiTrimise.Id, 0) = lnIdSursa, 'PRECONDITIE crsFacturiTrimise populat cu id=1127 (flux real gencursor)', 'reccount=' + TRANSFORM(RECCOUNT('crsFacturiTrimise')) + ' id curent=' + TRANSFORM(NVL(crsFacturiTrimise.Id, 0))
+
+ * FACTURI EMISE (goale)
+ TEXT TO lcSchema NOSHOW
+ales N(1), id N(20), id_fact N(20), id_vanzare N(10), data_act D, numar_act N(14), in_valuta N(1) null, curs N(10,4) null, valval N(16,2), tvaval N(16,2),totval N(16,2), total_fara_tva N(16,2), total_tva N(16,2), total_cu_tva N(16,2), nume_valuta C(5) null, client C(100), id_client N(10), cod_fiscal C(30), tip_persoana N(1) null, trimis N(1) null, data_trimis T null, stare C(100) null, id_incarcare C(36) null, mesaj_trimis C(250) null, id_descarcare c(36) null, tip_mesaj_raspuns C(50) null,mesaj_raspuns C(250) null, data_raspuns D null, cod_fiscal_emitent C(30) null, cod_fiscal_beneficiar C(30) null, detalii M null, xfurnizor C(200), xclient C(200), xdata_act D, xnumar_act C(30), xtotal_fara_tva N(16,4), xtotal_tva N(16,4), xtotal_tva_ron N(16,4), xtotal_cu_tva N(16,4), xdiscount_fara_tva N(16,4), xtaxe_fara_tva N(16,4), xvaloare_fara_tva N(16,4), xtotal_de_plata N(16,4), xnume_valuta C(5), test N(1) null, id_util N(10), utilizator C(100), jtotctva N(16,4), diferenta N(16,4), procesat N(1), procesat_server N(1), creditnote N(1)
+ ENDTEXT
+ TEXT TO lcSelect NOSHOW
+SELECT 0 as ales, id, id_fact, id_vanzare, data_act, numar_act, in_valuta, curs, valval, tvaval, totval, total_fara_tva, total_tva,total_cu_tva, nume_valuta, client, id_client, cod_fiscal, NVL(tip_persoana,1) as tip_persoana, NVL(trimis,0) as trimis, data_trimis, stare, id_incarcare, mesaj_trimis, id_descarcare, tip_mesaj_raspuns, mesaj_raspuns, data_raspuns, cod_fiscal_emitent, cod_fiscal_beneficiar, '' as detalii, xfurnizor, xclient, xdata_act, xnumar_act, xtotal_fara_tva, xtotal_tva, xtotal_tva_ron, xtotal_cu_tva, xdiscount_fara_tva, xtaxe_fara_tva, xvaloare_fara_tva, xtotal_de_plata, xnume_valuta, test, id_util, utilizator, jtotctva, decode(creditnote,0,1,-1) * ROUND(NVL(xtotal_cu_tva , 0.00) * decode(in_valuta,1,curs,1), 2)-NVL(jtotctva, 0.00) as diferenta, procesat, procesat_server,creditnote FROM anaf_vefactura_emis
+ ENDTEXT
+ lcOrder = [data_act,numar_act]
+ lcgroup = []
+ lcFiltru = [1=2]
+ lcFiltruOriginal = []
+ llModParam = .T.
+ llAfiseaza = .F.
+ gencursor('poFacturiEmise', 'crsFacturiEmise', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
+ poFacturiEmise.ca_baza1.afisare()
+
+ * DETALII (goale)
+ TEXT TO lcSchema NOSHOW
+id N(20), id_efactura N(20), nr N(5), articol V(250), descriere V(250), detalii M, cantitate N(18,6), um V(20), pret N(20,6), proctva N(7,2), valoarefaratva N(20,6), discountfaratva N(20,6)
+ ENDTEXT
+ TEXT TO lcSelect NOSHOW
+select id, id_efactura, nr, articol, descriere, detalii, cantitate, um, pret, proctva, valoarefaratva, discountfaratva from anaf_vefactura_detalii
+ ENDTEXT
+ lcOrder = [nr]
+ lcgroup = []
+ lcFiltru = [1=2]
+ lcFiltruOriginal = []
+ llModParam = .T.
+ llAfiseaza = .F.
+ gencursor('poFacturiDetalii', 'crsDetaliiFacturi', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
+ poFacturiDetalii.ca_baza1.afisare()
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 2: forma REALA anaf_efactura (modeless) + mock pe AnafeFacturaServer ==='
+ LOCAL lcCifReal
+ loAnaf = CREATEOBJECT('ANAFeFactura')
+ lcCifReal = ALLTRIM(loAnaf.AnafeFacturaServer.cCodFiscal)
+ DO PrLogLocal WITH 'AnafeFacturaServer real (INAINTE de inlocuire): cCodFiscal=[' + lcCifReal + '] lTest=' + TRANSFORM(loAnaf.AnafeFacturaServer.lTest)
+
+ loMockSrv = NEWOBJECT('MockAnafServerStorno', gcTestDir + 'mock_anaf_server_storno.prg')
+ loMockSrv.cCodFiscal = lcCifReal
+ loMockSrv.lTest = (lnTestSursa = 1)
+ loAnaf.AnafeFacturaServer = loMockSrv
+ DO PrLogLocal WITH 'AnafeFacturaServer INLOCUIT cu MockAnafServerStorno (ZERO retea): cCodFiscal=[' + loAnaf.AnafeFacturaServer.cCodFiscal + '] lTest=' + TRANSFORM(loAnaf.AnafeFacturaServer.lTest) + ' Class=' + loAnaf.AnafeFacturaServer.Class
+CATCH TO loExOuter
+ DO PrLogLocal WITH 'EXCEPTIE NEPRINSA: ' + loExOuter.Message + ' (' + TRANSFORM(loExOuter.LineNo) + ') ' + loExOuter.Procedure
+ gnFail = gnFail + 1
+ DO PrLogLocal WITH 'REZULTAT FINAL: PASS=' + TRANSFORM(gnPass) + ' FAIL=' + TRANSFORM(gnFail)
+ QUIT
+ENDTRY
+
+* ABATERE: instantiat prin frmtest_anaf.scx (subclasa goala peste anaf_efactura.vcx, stub de test,
+* vezi frmtest_anaf.sc2 in acest folder) in loc de CREATEOBJECT direct pe clasa din .vcx - sfatul
+* lui Marius: instantierea prin forma .scx arata calea COMPLETA a controlului in mesajul de eroare,
+* nu doar numele de baza "_checkbox1". Afara din orice TRY, ca eroarea sa treaca prin ON ERROR
+* (logata cu AERROR, executia continua) in loc sa opreasca testul.
+* CREATEOBJECT('form1', ) a dat "Class definition FORM1 is not found" - .scx foloseste alt
+* mecanism de incarcare decat .vcx; DO FORM e calea nativa pentru clase de forma.
+DO FORM (gcTestDir + 'frmtest_anaf.scx') NAME loForm NOSHOW WITH loAnaf
+IF VARTYPE(loForm) != 'O'
+ DO PrLogLocal WITH 'FAIL: DO FORM frmtest_anaf nu a returnat obiect, VARTYPE=' + VARTYPE(loForm)
+ QUIT
+ENDIF
+DO PrLogLocal WITH 'loForm creat dupa eroarea 177 (abatere de mediu, consemnata): VARTYPE=O, Class=' + loForm.Class
+
+TRY
+ DO PrLogLocal WITH 'ClassLibrary anaf_efactura = implicit, via SET CLASSLIB (COMUN\CLASE\anaf_efactura.vcx, binar real cu write-back)'
+ * ABATERE: loForm.Show() NU se apeleaza (nu e necesar - do_storno_efactura foloseste doar
+ * Thisform.oAnaf si crsFacturiTrimise).
+ loForm.WindowType = 0
+ DO PrLogLocal WITH 'BORDEROU creat (nu afisat - vezi abaterea de mai sus): Class=' + loForm.Class
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== PAS 3: driver Timer pe _SCREEN (Show(1) in do_storno_efactura e modal si pompeaza mesaje Windows) ==='
+ IF TYPE('_SCREEN.tmrDrvStorno') = 'U'
+ _SCREEN.ADDOBJECT('tmrDrvStorno', 'Timer')
+ ENDIF
+ _SCREEN.tmrDrvStorno.Interval = 200
+ PUBLIC loDriverStorno
+ loDriverStorno = CREATEOBJECT('DriverStorno')
+ BINDEVENT(_SCREEN.tmrDrvStorno, 'Timer', loDriverStorno, 'Executa')
+ _SCREEN.tmrDrvStorno.Enabled = .T.
+
+ DO PrLogLocal WITH '=== PAS 4: This.do_storno_efactura() - exact ce apeleaza optiunea de meniu 10 (anaf_efactura.vc2:5829) ==='
+ DO PrLogLocal WITH 'ON(ERROR) inainte de do_storno_efactura: [' + ON('ERROR') + ']'
+ loForm.do_storno_efactura()
+
+ _SCREEN.tmrDrvStorno.Enabled = .F.
+ TRY
+ UNBINDEVENTS(_SCREEN.tmrDrvStorno)
+ CATCH
+ ENDTRY
+
+ DO TVerdict WITH loDriverStorno.lDone, 'driverul a ajuns la final (a tratat forma de storno pana la capat)', 'nState final=' + TRANSFORM(loDriverStorno.nState) + ' tick-uri=' + TRANSFORM(loDriverStorno.nTicks)
+
+ LOCAL i
+ IF !loDriverStorno.lDone OR loDriverStorno.nState < 4
+ FOR i = 1 TO gnAmsgCalls
+ DO PrLogLocal WITH 'AMESSAGEBOX vazut: [' + gaAmsgCalls[i, 1] + '] -> ' + TRANSFORM(gaAmsgCalls[i, 2])
+ ENDFOR
+ ENDIF
+
+ DO PrLogLocal WITH 'TOTAL apeluri mock server: ' + TRANSFORM(gnMockApeluri)
+ FOR i = 1 TO gnMockApeluri
+ DO PrLogLocal WITH ' mock[' + TRANSFORM(i) + '] ' + gaMockApeluri[i, 1] + ' | ' + gaMockApeluri[i, 2] + ' | ' + gaMockApeluri[i, 3]
+ ENDFOR
+
+ TRY
+ loForm.Release()
+ CATCH
+ ENDTRY
+
+ DO PrLogLocal WITH 'REZULTAT FINAL: PASS=' + TRANSFORM(gnPass) + ' FAIL=' + TRANSFORM(gnFail)
+CATCH TO loExOuter
+ DO PrLogLocal WITH 'EXCEPTIE NEPRINSA: ' + loExOuter.Message + ' (' + TRANSFORM(loExOuter.LineNo) + ') ' + loExOuter.Procedure
+ gnFail = gnFail + 1
+ DO PrLogLocal WITH 'REZULTAT FINAL: PASS=' + TRANSFORM(gnPass) + ' FAIL=' + TRANSFORM(gnFail)
+ENDTRY
+
+QUIT
+
+**********************************************************
+PROCEDURE TVerdict
+ LPARAMETERS tlCond, tcLabel, tcDetaliu
+ IF tlCond
+ gnPass = gnPass + 1
+ DO PrLogLocal WITH 'PASS ' + tcLabel + ' | ' + TRANSFORM(tcDetaliu)
+ ELSE
+ gnFail = gnFail + 1
+ DO PrLogLocal WITH 'FAIL ' + tcLabel + ' | ' + TRANSFORM(tcDetaliu)
+ ENDIF
+ENDPROC
+
+**********************************************************
+PROCEDURE PrLogLocal
+ LPARAMETERS tcMsg
+ LOCAL lcL
+ SET SAFETY OFF
+ lcL = IIF(FILE(gcUILog), FILETOSTR(gcUILog), '')
+ STRTOFILE(lcL + TTOC(DATETIME()) + ' ' + TRANSFORM(tcMsg) + CHR(13) + CHR(10), gcUILog)
+ENDPROC
+
+**********************************************************
+PROCEDURE PrErrLocal
+ LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
+ LOCAL laErrDetail[1], i, lcDetail
+ lnLastErrorNo = tnError
+ lcLastErrorMsg = TRANSFORM(tcMessage)
+ DO PrLogLocal WITH 'EROARE ' + TRANSFORM(tnError) + ' ' + TRANSFORM(tcMessage) + ' in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLineNo)
+ AERROR(laErrDetail)
+ lcDetail = ''
+ FOR i = 1 TO ALEN(laErrDetail, 1)
+ lcDetail = lcDetail + TRANSFORM(i) + '=[' + TRANSFORM(NVL(laErrDetail[i], '')) + '] '
+ ENDFOR
+ DO PrLogLocal WITH 'AERROR: ' + lcDetail
+ENDPROC
+
+**********************************************************
+FUNCTION ExistaApelMock
+ LPARAMETERS tcMetoda
+ LOCAL i
+ FOR i = 1 TO gnMockApeluri
+ IF gaMockApeluri[i, 1] == tcMetoda
+ RETURN .T.
+ ENDIF
+ ENDFOR
+ RETURN .F.
+ENDFUNC
+
+**********************************************************
+* Driver pentru dialogul modal Show(1) al frm_storno_efactura (deschis din interiorul
+* do_storno_efactura). Vezi COMUN\docs\testare-ui-vfp.md, sectiunea "Dialoguri modale (Show(1))",
+* varianta B: Timer pe _SCREEN, detectie prin _SCREEN.Forms (nu _SCREEN.ActiveForm - nu merge
+* headless).
+DEFINE CLASS DriverStorno AS Custom
+ nState = 0
+ nTicks = 0
+ lDone = .F.
+
+ PROCEDURE Executa
+ LOCAL loStorno, i, llRet
+ LOCAL loExD
+ This.nTicks = This.nTicks + 1
+ IF This.nTicks = 1
+ DO PrLogLocal WITH 'ON(ERROR) in interiorul DriverStorno.Executa (primul tick): [' + ON('ERROR') + ']'
+ ENDIF
+ TRY
+ DO CASE
+ CASE This.nState = 0
+ loStorno = .NULL.
+ FOR i = 1 TO _SCREEN.FormCount
+ IF UPPER(_SCREEN.Forms(i).Class) == 'FRM_STORNO_EFACTURA'
+ loStorno = _SCREEN.Forms(i)
+ EXIT
+ ENDIF
+ ENDFOR
+ IF ISNULL(loStorno)
+ IF This.nTicks > 150
+ DO PrLogLocal WITH 'DRIVER FAIL: frm_storno_efactura nu a aparut in ~30s (nState=0)'
+ gnFail = gnFail + 1
+ This.lDone = .T.
+ _SCREEN.tmrDrvStorno.Enabled = .F.
+ ENDIF
+ RETURN
+ ENDIF
+ loStornoForm = loStorno
+ DO PrLogLocal WITH 'DRIVER: frm_storno_efactura gasita dupa ' + TRANSFORM(This.nTicks) + ' tick-uri, Class=' + loStorno.Class
+ DO TVerdict WITH lnLastErrorNo = 0, 'fara eroare pana la afisarea formei stornului', 'lnLastErrorNo=' + TRANSFORM(lnLastErrorNo) + ' [' + lcLastErrorMsg + ']'
+ DO TVerdict WITH RECCOUNT('crsStornoDetalii') > 0, 'grid crsStornoDetalii populat', 'reccount=' + TRANSFORM(RECCOUNT('crsStornoDetalii'))
+ DO TVerdict WITH 'TEST' $ UPPER(loStorno.Caption), 'Caption contine TEST', 'caption=[' + loStorno.Caption + ']'
+ DO TVerdict WITH UPPER(ALLTRIM(loStorno.lblStare.Caption)) == 'NEVALIDAT', 'stare initiala Nevalidat', 'stare=[' + loStorno.lblStare.Caption + ']'
+ DO TVerdict WITH FILE(loStorno.pcFisierStorno), 'fisier storno exista pe disc', 'fisier=[' + loStorno.pcFisierStorno + ']'
+ This.nState = 1
+
+ CASE This.nState = 1
+ loStorno = loStornoForm
+ loStorno.pcNumarNou = ALLTRIM(loStorno.pcNumarNou) + 'X'
+ loStorno.pcMotiv = 'Motiv test E2E'
+ llRet = loStorno.RegenereazaXml()
+ DO TVerdict WITH llRet, 'RegenereazaXml dupa schimbare numar/motiv', 'numarnou=[' + loStorno.pcNumarNou + '] return=' + TRANSFORM(llRet)
+ DO TVerdict WITH RECCOUNT('crsStornoDetalii') > 0, 'grid populat dupa regenerare', 'reccount=' + TRANSFORM(RECCOUNT('crsStornoDetalii'))
+ This.nState = 2
+
+ CASE This.nState = 2
+ loStorno = loStornoForm
+ loStorno.SetStare(1)
+ DO TVerdict WITH UPPER(ALLTRIM(loStorno.lblStare.Caption)) == 'VALID', 'SetStare(1) -> Valid (ocolire ValidareEFacturaANAF, retea reala, cf. instructiuni)', 'stare=[' + loStorno.lblStare.Caption + ']'
+ loStorno.cmdTrimite.Click()
+ DO TVerdict WITH UPPER(ALLTRIM(loStorno.lblStare.Caption)) == 'TRIMIS', 'dupa cmdTrimite.Click -> Trimis', 'stare=[' + loStorno.lblStare.Caption + ']'
+ DO TVerdict WITH ExistaApelMock('TrimiteFacturaXML'), 'mock a primit TrimiteFacturaXML', ''
+ This.nState = 3
+
+ CASE This.nState = 3
+ loStorno = loStornoForm
+ TRY
+ loStorno.Release()
+ CATCH TO loExD
+ DO PrLogLocal WITH 'DRIVER: eroare la Release: ' + loExD.Message
+ ENDTRY
+ This.lDone = .T.
+ This.nState = 4
+ _SCREEN.tmrDrvStorno.Enabled = .F.
+ ENDCASE
+ CATCH TO loExD
+ DO PrLogLocal WITH 'DRIVER EXCEPTIE nState=' + TRANSFORM(This.nState) + ': ' + loExD.Message + ' linia ' + TRANSFORM(loExD.LineNo)
+ gnFail = gnFail + 1
+ This.lDone = .T.
+ _SCREEN.tmrDrvStorno.Enabled = .F.
+ TRY
+ loStornoForm.Release()
+ CATCH
+ ENDTRY
+ ENDTRY
+ ENDPROC
+ENDDEFINE
diff --git a/utile/Teste/storno/test_storno_efactura.prg b/utile/Teste/storno/test_storno_efactura.prg
new file mode 100644
index 0000000..e55a3d3
--- /dev/null
+++ b/utile/Teste/storno/test_storno_efactura.prg
@@ -0,0 +1,449 @@
+* test_storno_efactura.prg
+* Teste StornoEFacturaXML/GetXmlOriginalEFactura (COMUN\programe\anaf_efactura.prg) si
+* rutarea CN/FCN + B2C din SendEfactura/ValidareEFacturaANAF (verificare fara retea).
+* Fixturi in COMUN\utile\Teste\storno\ (anonimizate, cu diacritice reale la fixtura din id 3795).
+*
+* Rulare: vfp9.exe -A -T "D:\ROA\ROACONT\COMUN\utile\Teste\storno\test_storno_efactura.prg"
+* Iesire in D:\ROA\ROACONT\COMUN\utile\Teste\storno\test_storno_efactura_log.txt
+
+SET SAFETY OFF
+SET TALK OFF
+SET DELETED ON
+SET EXACT ON
+SET CENTURY ON
+SET DATE DMY
+SET NULLDISPLAY TO ''
+CLOSE DATABASES
+
+PUBLIC gcUILog, gcFixDir, gcOutDir
+gcFixDir = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\"
+gcOutDir = "D:\ROA\ROACONT\COMUN\utile\Teste\storno\out\"
+IF !DIRECTORY(gcOutDir)
+ MD (gcOutDir)
+ENDIF
+gcUILog = gcFixDir + "test_storno_efactura_log.txt"
+STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
+
+ON ERROR DO PrErrLocal WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
+ON SHUTDOWN QUIT
+
+PUBLIC lnPass, lnFail
+lnPass = 0
+lnFail = 0
+
+TRY
+ DO "D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg" WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT'
+ IF gnHandle <= 0
+ DO PrLogLocal WITH 'FAIL: conectare Oracle esuata (gnHandle=' + TRANSFORM(gnHandle) + ')'
+ QUIT
+ ENDIF
+ DO PrLogLocal WITH 'mediu initializat, gnHandle=' + TRANSFORM(gnHandle) + ' gnAn=' + TRANSFORM(gnAn) + ' gnLuna=' + TRANSFORM(gnLuna)
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== TEST 0: functiile noi sunt incarcate (SET PROCEDURE anaf_efactura.prg din init) ==='
+ LOCAL lcMsg0, llRet0
+ LOCAL laMod0[1,4]
+ llRet0 = StornoEFacturaXML('', '', '', gcOutDir + 't0.xml', @laMod0, @lcMsg0)
+ DO Assert WITH !llRet0 AND 'gol' $ LOWER(lcMsg0), 'StornoEFacturaXML e apelabila si valideaza XML gol', 'retur=' + TRANSFORM(llRet0) + ' mesaj=[' + lcMsg0 + ']'
+
+ *=========================================================================
+ DO PrLogLocal WITH '=== TEST 1 + 1b + 2: round-trip dubla negare + diacritice + noduri neatinse (fixtura reala id 3795) ==='
+ DO TestRoundTrip WITH 'storno_fixture_diacritice_reale.xml', 'diacritice_reale', .T.
+
+ DO PrLogLocal WITH '=== TEST 1 + 2: round-trip (fixtura simpla cu reduceri/majorari pe document) ==='
+ DO TestRoundTrip WITH 'storno_fixture_diacritice_reale.xml', 'diacritice_reale_bis', .F.
+
+ DO PrLogLocal WITH '=== TEST 1 + 1c: round-trip + namespace corect pe prefixe custom n1/n2/n3 ==='
+ DO TestRoundTrip WITH 'storno_fixture_prefix_n3.xml', 'prefix_n3', .F.
+ DO TestNamespaceN3
+
+ DO PrLogLocal WITH '=== TEST 1: round-trip pe CreditNote minimal ==='
+ DO TestRoundTrip WITH 'storno_fixture_creditnote_minimal.xml', 'creditnote_minimal', .F.
+
+ DO PrLogLocal WITH '=== TEST 1: round-trip pe factura deja negativa (D13: permisa) ==='
+ DO TestRoundTrip WITH 'storno_fixture_factura_negativa.xml', 'factura_negativa', .F.
+
+ DO PrLogLocal WITH '=== TEST 1: sume zero / -0.00 raman neschimbate (nu apar in taModificari) ==='
+ DO TestSumeZero
+
+ DO PrLogLocal WITH '=== TEST 1: cbc:Amount in PaymentTerms NU se neaga (doar Amount/BaseAmount sub AllowanceCharge) ==='
+ DO TestPaymentTermsNeatins
+
+ DO PrLogLocal WITH '=== TEST: motiv cu caractere speciale (& si <) scris corect prin DOM, nu concatenare ==='
+ DO TestMotivSpecial
+
+ DO PrLogLocal WITH '=== TEST: numar nou cu slash pastrat exact (scris prin DOM) ==='
+ DO TestNumarCuSlash
+
+ DO PrLogLocal WITH '=== TEST 3b: blocaje fara retea (in interiorul StornoEFacturaXML) ==='
+ DO TestBlocaje
+
+ DO PrLogLocal WITH '=== TEST: fixtura B2C (CNP 13 cifre) si furnizor fara PartyTaxScheme - parsare OK, pregatite pentru Lane 2/C ==='
+ DO TestRoundTrip WITH 'storno_fixture_b2c_cnp.xml', 'b2c_cnp', .F.
+ DO TestRoundTrip WITH 'storno_fixture_fara_partytaxscheme.xml', 'fara_partytaxscheme', .F.
+
+ DO PrLogLocal WITH '=== TEST: rutare CN/FCN si B2C (logica SendEfactura/ValidareEFacturaANAF, fara apel retea) ==='
+ DO TestRutareCNSiB2C
+
+ DO PrLogLocal WITH '=== TEST 3: validare online ANAF TEST - neexecutat (fara token de test verificat; risc cunoscut SET STEP ON anaf_efactura.prg:717 cu token expirat) ==='
+
+ DO PrLogLocal WITH '=== TEST 4: upload real ANAF - INTERZIS pentru Lane 1, neexecutat ==='
+
+ DO PrLogLocal WITH 'TOTAL: PASS=' + TRANSFORM(lnPass) + ' FAIL=' + TRANSFORM(lnFail)
+ DO PrLogLocal WITH 'TOTAL: test terminat fara exceptie'
+CATCH TO loEx
+ DO PrLogLocal WITH 'EXCEPTIE: ' + loEx.Message + ' (' + TRANSFORM(loEx.LineNo) + ') ' + loEx.Procedure
+ENDTRY
+
+QUIT
+
+**********************************************************
+* neaga de doua ori (storno al storno-ului) si verifica identitatea: valorile noi din a doua
+* trecere trebuie sa fie exact valorile originale din prima trecere (textual, fara rotunjire).
+* Foloseste taModificari (nu re-parseaza XML-ul), deci verifica direct contractul functiei.
+PROCEDURE TestRoundTrip
+ LPARAMETERS tcFixtura, tcLabel, tlVerificaDiacritice
+
+ LOCAL lcXml, lcFile1, lcFile2, lcMsg1, lcMsg2, llRet1, llRet2
+ LOCAL laMod1[1,4], laMod2[1,4]
+
+ IF !FILE(gcFixDir + tcFixtura)
+ DO Assert WITH .F., '[' + tcLabel + '] fixtura exista', gcFixDir + tcFixtura
+ RETURN
+ ENDIF
+ lcXml = FILETOSTR(gcFixDir + tcFixtura)
+ lcFile1 = gcOutDir + tcLabel + '_storno1.xml'
+ lcFile2 = gcOutDir + tcLabel + '_storno2.xml'
+
+ llRet1 = StornoEFacturaXML(m.lcXml, '', 'Stornare factura test ' + m.tcLabel, m.lcFile1, @laMod1, @lcMsg1)
+ DO Assert WITH llRet1, '[' + tcLabel + '] StornoEFacturaXML (prima trecere) reuseste', 'mesaj=[' + lcMsg1 + ']'
+ IF !llRet1
+ RETURN
+ ENDIF
+ DO Assert WITH FILE(m.lcFile1), '[' + tcLabel + '] fisierul storno1 exista pe disc', m.lcFile1
+
+ IF m.tlVerificaDiacritice
+ LOCAL lcBrutOriginal, lcBrutStorno
+ lcBrutOriginal = FILETOSTR(gcFixDir + tcFixtura)
+ lcBrutStorno = FILETOSTR(m.lcFile1)
+ *-- Constan[C5 A3]a (U+0163, "t cu virgula") pastrat identic pe octeti in storno
+ DO Assert WITH ('Constan' + CHR(0xC5) + CHR(0xA3) + 'a') $ m.lcBrutStorno, '[' + tcLabel + '] 1b: diacritice C5 A3 (U+0163) pastrate pe octeti in storno1', 'len=' + TRANSFORM(LEN(m.lcBrutStorno))
+ ENDIF
+
+ llRet2 = StornoEFacturaXML(FILETOSTR(m.lcFile1), 'ORIG-TEST-' + tcLabel, '', m.lcFile2, @laMod2, @lcMsg2)
+ DO Assert WITH llRet2, '[' + tcLabel + '] StornoEFacturaXML (a doua trecere, pe storno1) reuseste', 'mesaj=[' + lcMsg2 + ']'
+ IF !llRet2
+ RETURN
+ ENDIF
+
+ *-- filtreaza doar randurile de sume/cantitati (tip 2 total, tip 3 linie), fara antet (tip 1)
+ LOCAL lnI, lnJ1, lnJ2
+ DIMENSION laF1[1], laF2[1]
+ lnJ1 = 0
+ FOR lnI = 1 TO ALEN(laMod1, 1)
+ IF laMod1[lnI, 4] = 2 OR laMod1[lnI, 4] = 3
+ lnJ1 = lnJ1 + 1
+ DIMENSION laF1[lnJ1, 2]
+ laF1[lnJ1, 1] = laMod1[lnI, 2]
+ laF1[lnJ1, 2] = laMod1[lnI, 3]
+ ENDIF
+ ENDFOR
+ lnJ2 = 0
+ FOR lnI = 1 TO ALEN(laMod2, 1)
+ IF laMod2[lnI, 4] = 2 OR laMod2[lnI, 4] = 3
+ lnJ2 = lnJ2 + 1
+ DIMENSION laF2[lnJ2, 2]
+ laF2[lnJ2, 1] = laMod2[lnI, 2]
+ laF2[lnJ2, 2] = laMod2[lnI, 3]
+ ENDIF
+ ENDFOR
+
+ DO Assert WITH lnJ1 = lnJ2 AND lnJ1 > 0, '[' + tcLabel + '] 1: numarul de noduri numerice negate e acelasi la dus si intors', 'trecere1=' + TRANSFORM(lnJ1) + ' trecere2=' + TRANSFORM(lnJ2)
+
+ IF lnJ1 = lnJ2 AND lnJ1 > 0
+ LOCAL llToateOk
+ llToateOk = .T.
+ FOR lnI = 1 TO lnJ1
+ *-- valoarea noua din trecerea 1 (negata) trebuie sa fie valoarea originala din trecerea 2
+ IF ALLTRIM(laF1[lnI, 2]) <> ALLTRIM(laF2[lnI, 1])
+ llToateOk = .F.
+ ENDIF
+ *-- valoarea noua din trecerea 2 (negata a doua oara) trebuie sa fie exact valoarea originala din trecerea 1
+ IF ALLTRIM(laF1[lnI, 1]) <> ALLTRIM(laF2[lnI, 2])
+ llToateOk = .F.
+ ENDIF
+ ENDFOR
+ DO Assert WITH llToateOk, '[' + tcLabel + '] 1: dubla negare da exact valorile originale (textual)', ''
+ ENDIF
+
+ *-- 2: un nod static, neatins de negare, ramane identic intre original si storno1
+ LOCAL loDOMOrig, loDOMStorno, loNodOrig, loNodStorno
+ loDOMOrig = CREATEOBJECT('MSXML2.DOMDocument.6.0')
+ loDOMOrig.Async = .F.
+ loDOMOrig.setProperty('SelectionLanguage', 'XPath')
+ loDOMOrig.LoadXML(m.lcXml)
+ loDOMStorno = CREATEOBJECT('MSXML2.DOMDocument.6.0')
+ loDOMStorno.Async = .F.
+ loDOMStorno.setProperty('SelectionLanguage', 'XPath')
+ loDOMStorno.Load(m.lcFile1)
+
+ loNodOrig = loDOMOrig.documentElement.selectSingleNode("//*[local-name()='RegistrationName']")
+ loNodStorno = loDOMStorno.documentElement.selectSingleNode("//*[local-name()='RegistrationName']")
+ IF !ISNULL(m.loNodOrig) AND !ISNULL(m.loNodStorno)
+ DO Assert WITH loNodOrig.Text == loNodStorno.Text, '[' + tcLabel + '] 2: RegistrationName (nod neatins) identic in storno', '[' + loNodOrig.Text + '] vs [' + loNodStorno.Text + ']'
+ ENDIF
+
+ *-- ID radacinii trebuie sa fie exact numarul cerut (implicit + '-S')
+ LOCAL loIdOrig, loIdStorno
+ loIdOrig = loDOMOrig.documentElement.selectSingleNode("*[local-name()='ID']")
+ loIdStorno = loDOMStorno.documentElement.selectSingleNode("*[local-name()='ID']")
+ IF !ISNULL(m.loIdOrig) AND !ISNULL(m.loIdStorno)
+ DO Assert WITH ALLTRIM(loIdStorno.Text) == ALLTRIM(loIdOrig.Text) + '-S', '[' + tcLabel + '] numar storno implicit = numar original + -S', '[' + loIdStorno.Text + ']'
+ ENDIF
+
+ *-- BillingReference catre originalul (numarul VECHI, dinainte de schimbare)
+ LOCAL loRef
+ loRef = loDOMStorno.documentElement.selectSingleNode("//*[local-name()='BillingReference']/*[local-name()='InvoiceDocumentReference']/*[local-name()='ID']")
+ IF !ISNULL(m.loRef) AND !ISNULL(m.loIdOrig)
+ DO Assert WITH ALLTRIM(loRef.Text) == ALLTRIM(loIdOrig.Text), '[' + tcLabel + '] BillingReference contine numarul original', '[' + loRef.Text + ']'
+ ELSE
+ DO Assert WITH .F., '[' + tcLabel + '] BillingReference/InvoiceDocumentReference/ID gasit', ''
+ ENDIF
+
+ *-- Nota scrisa (a fost trimisa nevida la trecerea 1)
+ LOCAL loNote
+ loNote = loDOMStorno.documentElement.selectSingleNode("*[local-name()='Note']")
+ IF !ISNULL(m.loNote)
+ DO Assert WITH 'Stornare factura test' $ loNote.Text, '[' + tcLabel + '] Nota scrisa pe document', '[' + loNote.Text + ']'
+ ELSE
+ DO Assert WITH .F., '[' + tcLabel + '] Nota (cbc:Note) gasita dupa storno', ''
+ ENDIF
+ENDPROC
+
+**********************************************************
+* namespace-ul nodurilor NOI (BillingReference/ID, Note) trebuie sa fie cel din radacina
+* originalului (n1/CommonBasicComponents-2, n2/CommonAggregateComponents-2), nu prefix implicit
+PROCEDURE TestNamespaceN3
+ LOCAL lcFile1, loDOM, loRef, loNote
+ LOCAL lcUriCbc, lcUriCac
+ lcUriCbc = 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
+ lcUriCac = 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'
+ lcFile1 = gcOutDir + 'prefix_n3_storno1.xml'
+ IF !FILE(m.lcFile1)
+ DO Assert WITH .F., '[prefix_n3] 1c: fisierul storno1 exista (rulat de TestRoundTrip)', m.lcFile1
+ RETURN
+ ENDIF
+
+ loDOM = CREATEOBJECT('MSXML2.DOMDocument.6.0')
+ loDOM.Async = .F.
+ loDOM.setProperty('SelectionLanguage', 'XPath')
+ loDOM.Load(m.lcFile1)
+
+ loRef = loDOM.documentElement.selectSingleNode("//*[local-name()='BillingReference']")
+ IF !ISNULL(m.loRef)
+ DO Assert WITH loRef.namespaceURI == lcUriCac, '[prefix_n3] 1c: BillingReference in namespace cac corect', loRef.namespaceURI
+ ELSE
+ DO Assert WITH .F., '[prefix_n3] 1c: BillingReference gasit', ''
+ ENDIF
+
+ LOCAL loRefId
+ loRefId = loDOM.documentElement.selectSingleNode("//*[local-name()='BillingReference']/*[local-name()='InvoiceDocumentReference']/*[local-name()='ID']")
+ IF !ISNULL(m.loRefId)
+ DO Assert WITH loRefId.namespaceURI == lcUriCbc, '[prefix_n3] 1c: ID din InvoiceDocumentReference in namespace cbc corect', loRefId.namespaceURI
+ ELSE
+ DO Assert WITH .F., '[prefix_n3] 1c: BillingReference/InvoiceDocumentReference/ID gasit', ''
+ ENDIF
+
+ loNote = loDOM.documentElement.selectSingleNode("*[local-name()='Note']")
+ IF !ISNULL(m.loNote)
+ DO Assert WITH loNote.namespaceURI == lcUriCbc, '[prefix_n3] 1c: Note in namespace cbc corect', loNote.namespaceURI
+ ELSE
+ DO Assert WITH .F., '[prefix_n3] 1c: Note gasita', ''
+ ENDIF
+ENDPROC
+
+**********************************************************
+* -0.00 si 0.00 nu trebuie sa apara deloc in taModificari (raman neschimbate, cf. spec)
+PROCEDURE TestSumeZero
+ LOCAL lcXml, lcFile, lcMsg, llRet
+ LOCAL laMod[1,4]
+ lcXml = FILETOSTR(gcFixDir + 'storno_fixture_sume_zero.xml')
+ lcFile = gcOutDir + 'sume_zero_storno1.xml'
+ llRet = StornoEFacturaXML(m.lcXml, '', '', m.lcFile, @laMod, @lcMsg)
+ DO Assert WITH llRet, '[sume_zero] StornoEFacturaXML reuseste', 'mesaj=[' + lcMsg + ']'
+ IF !llRet
+ RETURN
+ ENDIF
+
+ LOCAL lnI, llGasitZero
+ llGasitZero = .F.
+ FOR lnI = 1 TO ALEN(laMod, 1)
+ IF (laMod[lnI, 4] = 2 OR laMod[lnI, 4] = 3) AND ('0.00' $ laMod[lnI, 2] OR '0,00' $ laMod[lnI, 2])
+ IF VAL(STRTRAN(ALLTRIM(laMod[lnI, 2]), '-', '')) = 0
+ llGasitZero = .T.
+ ENDIF
+ ENDIF
+ ENDFOR
+ DO Assert WITH !llGasitZero, '[sume_zero] nicio valoare zero (inclusiv -0.00) nu apare in taModificari', 'randuri=' + TRANSFORM(ALEN(laMod,1))
+
+ *-- verificare directa pe fisier: -0.00 din AllowanceCharge/Amount ramane neschimbat
+ LOCAL lcOut
+ lcOut = FILETOSTR(m.lcFile)
+ DO Assert WITH '-0.00' $ m.lcOut, '[sume_zero] -0.00 (AllowanceCharge/Amount) prezent neschimbat in storno', ''
+ENDPROC
+
+**********************************************************
+* cbc:Amount din cac:PaymentTerms nu are parintele AllowanceCharge -> nu se neaga
+PROCEDURE TestPaymentTermsNeatins
+ LOCAL lcXml, lcFile, lcMsg, llRet
+ LOCAL laMod[1,4]
+ lcXml = FILETOSTR(gcFixDir + 'storno_fixture_paymentterms.xml')
+ lcFile = gcOutDir + 'paymentterms_storno1.xml'
+ llRet = StornoEFacturaXML(m.lcXml, '', '', m.lcFile, @laMod, @lcMsg)
+ DO Assert WITH llRet, '[paymentterms] StornoEFacturaXML reuseste', 'mesaj=[' + lcMsg + ']'
+ IF !llRet
+ RETURN
+ ENDIF
+
+ LOCAL loDOM, loPT
+ loDOM = CREATEOBJECT('MSXML2.DOMDocument.6.0')
+ loDOM.Async = .F.
+ loDOM.setProperty('SelectionLanguage', 'XPath')
+ loDOM.Load(m.lcFile)
+ loPT = loDOM.documentElement.selectSingleNode("//*[local-name()='PaymentTerms']/*[local-name()='Amount']")
+ IF !ISNULL(m.loPT)
+ DO Assert WITH ALLTRIM(loPT.Text) == '119.00', '[paymentterms] cbc:Amount din PaymentTerms ramane neatins (119.00)', '[' + loPT.Text + ']'
+ ELSE
+ DO Assert WITH .F., '[paymentterms] nodul PaymentTerms/Amount gasit', ''
+ ENDIF
+ENDPROC
+
+**********************************************************
+* Nota cu '&' si '<' scrisa prin .Text (DOM face escape automat) - XML-ul ramane valid
+PROCEDURE TestMotivSpecial
+ LOCAL lcXml, lcFile, lcMsg, llRet, lcNota
+ LOCAL laMod[1,4]
+ lcXml = FILETOSTR(gcFixDir + 'storno_fixture_diacritice_reale.xml')
+ lcFile = gcOutDir + 'motiv_special_storno1.xml'
+ lcNota = 'Marfa < prag & retur client'
+ llRet = StornoEFacturaXML(m.lcXml, '', m.lcNota, m.lcFile, @laMod, @lcMsg)
+ DO Assert WITH llRet, '[motiv_special] StornoEFacturaXML reuseste cu motiv & si <', 'mesaj=[' + lcMsg + ']'
+ IF !llRet
+ RETURN
+ ENDIF
+
+ LOCAL loDOM, loNote
+ loDOM = CREATEOBJECT('MSXML2.DOMDocument.6.0')
+ loDOM.Async = .F.
+ loDOM.setProperty('SelectionLanguage', 'XPath')
+ loDOM.Load(m.lcFile)
+ DO Assert WITH loDOM.parseError.errorcode = 0, '[motiv_special] xml-ul rezultat e valid (parseError=0)', TRANSFORM(loDOM.parseError.errorcode)
+ loNote = loDOM.documentElement.selectSingleNode("*[local-name()='Note']")
+ IF !ISNULL(m.loNote)
+ DO Assert WITH loNote.Text == m.lcNota, '[motiv_special] textul notei intact dupa reincarcare (& si < pastrate ca text)', '[' + loNote.Text + ']'
+ ELSE
+ DO Assert WITH .F., '[motiv_special] Note gasita', ''
+ ENDIF
+ENDPROC
+
+**********************************************************
+* numar cu '/' (documente cu numerotare pe serie/numar) scris prin .Text, fara conflict XML
+PROCEDURE TestNumarCuSlash
+ LOCAL lcXml, lcFile, lcMsg, llRet, lcNumar
+ LOCAL laMod[1,4]
+ lcXml = FILETOSTR(gcFixDir + 'storno_fixture_diacritice_reale.xml')
+ lcFile = gcOutDir + 'numar_slash_storno1.xml'
+ lcNumar = 'FF/2026/S'
+ llRet = StornoEFacturaXML(m.lcXml, m.lcNumar, '', m.lcFile, @laMod, @lcMsg)
+ DO Assert WITH llRet, '[numar_slash] StornoEFacturaXML reuseste cu numar continand /', 'mesaj=[' + lcMsg + ']'
+ IF !llRet
+ RETURN
+ ENDIF
+
+ LOCAL loDOM, loId
+ loDOM = CREATEOBJECT('MSXML2.DOMDocument.6.0')
+ loDOM.Async = .F.
+ loDOM.setProperty('SelectionLanguage', 'XPath')
+ loDOM.Load(m.lcFile)
+ loId = loDOM.documentElement.selectSingleNode("*[local-name()='ID']")
+ IF !ISNULL(m.loId)
+ DO Assert WITH loId.Text == m.lcNumar, '[numar_slash] ID radacinii = numarul cu / exact', '[' + loId.Text + ']'
+ ELSE
+ DO Assert WITH .F., '[numar_slash] ID gasit', ''
+ ENDIF
+ENDPROC
+
+**********************************************************
+* blocaje interne StornoEFacturaXML, fara nicio conexiune de retea
+PROCEDURE TestBlocaje
+ LOCAL lcMsg, llRet
+ LOCAL laMod[1,4]
+
+ llRet = StornoEFacturaXML('', '', '', gcOutDir + 'blocaj_gol.xml', @laMod, @lcMsg)
+ DO Assert WITH !llRet AND 'gol' $ LOWER(lcMsg), '3b: XML gol -> blocare cu mesaj clar', '[' + lcMsg + ']'
+
+ llRet = StornoEFacturaXML(' eroare de parsare', '[' + lcMsg + ']'
+
+ llRet = StornoEFacturaXML('1', '', '', gcOutDir + 'blocaj_radacina.xml', @laMod, @lcMsg)
+ DO Assert WITH !llRet AND 'Invoice/CreditNote' $ lcMsg, '3b: radacina ne-Invoice/CreditNote -> blocare cu mesaj clar', '[' + lcMsg + ']'
+
+ llRet = StornoEFacturaXML('2026-01-01', '', '', gcOutDir + 'blocaj_lipsa.xml', @laMod, @lcMsg)
+ DO Assert WITH !llRet AND 'Lipseste' $ lcMsg, '3b: lipseste ID/IssueDate/Supplier -> blocare cu mesaj clar', '[' + lcMsg + ']'
+
+ LOCAL lcXmlBadNum
+ lcXmlBadNum = STRTRAN(FILETOSTR(gcFixDir + 'storno_fixture_diacritice_reale.xml'), '18.18', 'ABC', 1, 1)
+ DO Assert WITH 'ABC' $ lcXmlBadNum, '3b: pregatire fixtura cu valoare nenumerica (substitutie reusita)', ''
+ llRet = StornoEFacturaXML(m.lcXmlBadNum, '', '', gcOutDir + 'blocaj_nenumeric.xml', @laMod, @lcMsg)
+ DO Assert WITH !llRet AND 'nenumeric' $ LOWER(lcMsg) AND 'TaxAmount' $ lcMsg, '3b: valoare nenumerica -> blocare, mesajul numeste nodul', '[' + lcMsg + ']'
+
+ DO PrLogLocal WITH ' (CIF strain / luna blocata: verificari la pasul 2 in frm_storno_efactura, Lane 2/C - nu exista inca cod de testat)'
+ENDPROC
+
+**********************************************************
+* rutarea CN/FCN (standard=CN la upload, FCN la validare) si detectia B2C (CNP 13 cifre) -
+* aceeasi expresie ca in SendEfactura/ValidareEFacturaANAF, verificata fara HTTP
+PROCEDURE TestRutareCNSiB2C
+ LOCAL lcInv, lcCn, lcB2c
+ lcInv = FILETOSTR(gcFixDir + 'storno_fixture_diacritice_reale.xml')
+ lcCn = FILETOSTR(gcFixDir + 'storno_fixture_creditnote_minimal.xml')
+ lcB2c = FILETOSTR(gcFixDir + 'storno_fixture_b2c_cnp.xml')
+
+ DO Assert WITH IIF('CreditNote' $ lcInv, 'CN', 'UBL') == 'UBL', 'rutare: Invoice -> standard=UBL / validare FACT1', ''
+ DO Assert WITH IIF('CreditNote' $ lcCn, 'CN', 'UBL') == 'CN', 'rutare: CreditNote -> standard=CN', ''
+ DO Assert WITH IIF('CreditNote' $ lcCn, 'FCN', 'FACT1') == 'FCN', 'rutare: CreditNote -> validare FCN', ''
+ DO Assert WITH IIF('CreditNote' $ lcInv, 'FCN', 'FACT1') == 'FACT1', 'rutare: Invoice -> validare FACT1', ''
+
+ LOCAL lcCustomer, lcPartyLegal, lcCodFiscalClient
+ lcCustomer = STREXTRACT(lcB2c, '', '', 1, 1)
+ lcPartyLegal = STREXTRACT(lcCustomer, '', '', 1, 1)
+ lcCodFiscalClient = ALLTRIM(STREXTRACT(lcPartyLegal, '', '', 1, 1))
+ DO Assert WITH LEN(lcCodFiscalClient) = 13, 'B2C: CNP de 13 cifre detectat din fixtura (llB2C ar deveni .T.)', 'cnp=[' + lcCodFiscalClient + '] len=' + TRANSFORM(LEN(lcCodFiscalClient))
+ENDPROC
+
+**********************************************************
+PROCEDURE Assert
+ LPARAMETERS tlCond, tcTitlu, tcDetaliu
+ IF tlCond
+ lnPass = lnPass + 1
+ DO PrLogLocal WITH ' OK ' + tcTitlu + IIF(EMPTY(tcDetaliu), '', ' (' + tcDetaliu + ')')
+ ELSE
+ lnFail = lnFail + 1
+ DO PrLogLocal WITH ' FAIL ' + tcTitlu + IIF(EMPTY(tcDetaliu), '', ' (' + tcDetaliu + ')')
+ ENDIF
+ENDPROC
+
+**********************************************************
+PROCEDURE PrLogLocal
+ LPARAMETERS tcMsg
+ LOCAL lcL
+ SET SAFETY OFF
+ lcL = IIF(FILE(gcUILog), FILETOSTR(gcUILog), '')
+ STRTOFILE(lcL + TRANSFORM(tcMsg) + CHR(13) + CHR(10), gcUILog)
+ENDPROC
+
+**********************************************************
+PROCEDURE PrErrLocal
+ LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo
+ DO PrLogLocal WITH 'EROARE ' + TRANSFORM(tnError) + ' ' + TRANSFORM(tcMessage) + ' in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLineNo)
+ENDPROC