Verificarile de partener nu mai depind exclusiv de raspunsul ANAF: se citeste intai cache-ul din ISTORIC_CODURI_FISCALE (mutat in CONTAFIN_ORACLE), cu provenienta afisata pe ecrane (sursa / data_sursa) si cu plasa de siguranta cand ANAF tace. Ordinea cache/ANAF e configurabila; cache-ul expira. - programe/validare.prg: verificare single si pe loturi peste cache, contract 404 separat de caderea de serviciu, expirarea cache-ului, corectii la verdictul pe firma si la etichetarea CACHE_INDISP. - programe/ocautare.prg: plasa de siguranta si banda de provenienta la cautarea de partener. - programe/oproceduri_comune.prg: VERIFICA_RTVAI_DATA citeste si cache-ul. - clase/overificari.vc2: propagarea provenientei in interfata. - utile/Teste/: harness-uri de testare headless - echivalenta cache/ANAF pe 200 de coduri reale, marginile pe inactiv si TVA la incasare, mock batch ANAF, baseline D394 si sondele de diagnostic ANAF. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JCzVWfqscv1xyo5uvJiWiW
268 lines
15 KiB
Plaintext
268 lines
15 KiB
Plaintext
* mock_batch_anaf.prg - mock pentru calea BATCH de verificare ANAF (VerificareANAF.ANAF_SincronWebService_PlatitorTva,
|
|
* COMUN\programe\validare.prg:1865-2009), fara niciun apel HTTP real. Poveste S11 (docs\stories-cache-anaf.md).
|
|
*
|
|
* ALEGERE (functie vs strat HTTP): mock la nivel de METODA, prin subclasare - NU la nivelul stratului HTTP
|
|
* de dedesubt. Metoda originala foloseste Createobject('winHTTP.winHTTPrequest.5.1') / Createobject('Microsoft.XMLHTTP')
|
|
* cu ProgID literal, direct in validare.prg - nu exista nicio proprietate/metoda suprascriabila prin care sa
|
|
* injectez un obiect fals fara sa editez validare.prg, iar inlocuirea globala a inregistrarii COM (regsvr32/
|
|
* registry) ar afecta toata masina, nu doar acest test - inacceptabil ca efect secundar, deci exclusa. In schimb,
|
|
* VerificareANAF e o clasa VFP obisnuita (Define Class VerificareANAF As Custom, validare.prg:1814), deci o
|
|
* subclasa AICI (VerificareANAFMockBatch) suprascrie DOAR ANAF_SincronWebService_PlatitorTva si MOSTENESTE
|
|
* neschimbata ParseJsonANAFv8 (validare.prg:2015) - logica REALA de parsare (toate campurile crsANAF, inclusiv
|
|
* sursa/data_sursa adaugate de S3 la Create Cursor) ramane exercitata real; doar transportul HTTP e simulat.
|
|
* Grupurile de maxim 100 coduri sunt reproduse identic cu originalul (lnReqNo=100), fara INKEY(1) intre grupuri
|
|
* (asteptarea de 1 secunda din original nu are rost intr-un test headless).
|
|
*
|
|
* GAP pentru S5 (contractul 404 pe calea batch): subclasa suprascrie INTREGUL ANAF_SincronWebService_PlatitorTva,
|
|
* deci NU exercita codul real pe care S5 il va adauga/modifica in validare.prg (gate-ul "If loHTTP.Status = 200",
|
|
* validare.prg:1968). Pentru ca S5 sa-si testeze headless codul real, are nevoie de un punct de injectie in
|
|
* validare.prg pe care NU l-am facut (nu e fisierul meu) - vezi docs\diff-S11-mock-batch.md, sectiunea "Pentru S5".
|
|
* Scenariul HTTP404_CORP_COMPLET de mai jos arata AMBELE comportamente (azi: corpul e ignorat; propus: cu
|
|
* glANAFBatchIgnoraGate404 = .T., corpul se parseaza chiar si la status<>200) - fixture de referinta pentru S5.
|
|
*
|
|
* CONFIGURARE (construita INLINE in programul principal apelant, NU intr-o PROCEDURA/FUNCTION separata - vezi
|
|
* capcana l, COMUN\docs\depanare_testare_vfp.md: CREATEOBJECT/ADDPROPERTY apelate dintr-o procedura se comporta
|
|
* gresit in acest runtime headless; obiectele de configurare trebuie construite direct in main):
|
|
* goANAFBatchMock - Collection, cheie 'C'+CUI normalizat (NormalizeazaCUI din validare.prg), valoare:
|
|
* - obiect Empty cu proprietati de raspuns (vezi lista mai jos) -> intra in "found"
|
|
* - sirul 'NOTFOUND' -> intra explicit in "notFound"
|
|
* - cheie lipsa din colectie -> tratat implicit tot ca 'NOTFOUND'
|
|
* (simuleaza raspunsul ANAF pentru un CUI pe care nu-l cunoaste)
|
|
* goANAFBatchScenariuPerGrup - Collection optionala, cheie TRANSFORM(nr. grup, 1-based), valoare = scenariu -
|
|
* suprascrie gcANAFBatchScenariu DOAR pentru acel grup (test cu >100 coduri,
|
|
* grupuri cu comportament diferit)
|
|
* gcANAFBatchScenariu - scenariul implicit pentru toate grupurile fara override in colectia de mai sus:
|
|
* 'NORMAL' (implicit daca lipseste) / 'TACERE' / 'RASPUNS_NEINTELES' /
|
|
* 'HTTP404_CORP_COMPLET'
|
|
* glANAFBatchIgnoraGate404 - .F. implicit (comportamentul de AZI) - cu .T., scenariul HTTP404_CORP_COMPLET
|
|
* parseaza corpul oricum (simuleaza fixul propus pentru S5)
|
|
*
|
|
* Proprietatile unui obiect de raspuns "gasit" (toate optionale, implicit gol/.F. cand lipsesc):
|
|
* denumire, adresa, nrRegCom, data (C, 'YYYY-MM-DD')
|
|
* scpTVA (L), dataInceputScpTVA, dataSfarsitScpTVA, dataAnulImpScpTVA (C 'YYYY-MM-DD'), mesajScpTVA (C)
|
|
* inactiv (L), dataInactivare, dataReactivare, dataPublicare, dataRadiere (C 'YYYY-MM-DD')
|
|
* tvaIncasare (L), dataInceputTvaInc, dataSfarsitTvaInc, dataActualizareTvaInc, dataPublicareTvaInc (C 'YYYY-MM-DD'), tipActTvaInc (C)
|
|
* splitTva (L), dataInceputSplitTVA, dataAnulareSplitTVA (C 'YYYY-MM-DD')
|
|
* Datele sunt STRING 'YYYY-MM-DD' (acelasi format trimis de ANAF), nu Date VFP - fara ambiguitate legata de SET DATE.
|
|
* SENSIBILITATE LA DATA (adaugat 31.07.2026): daca perechea tcPropStart/tcPropEnd e prezenta pe item,
|
|
* PropBool evalueaza boolean-ul FATA de data ceruta (tdData, parametrul catre ANAF_SincronWebService_PlatitorTva),
|
|
* nu mai intoarce un boolean fix - la fel ca API-ul ANAF real (cere "data" per CUI, validare.prg:2098).
|
|
* Fara acele proprietati de data pe item, cade pe boolean-ul static de dinainte (compatibilitate cu testele
|
|
* existente care nu seteaza date). Mock-ul ramane marginit la O SINGURA perioada per camp (nu omniscient).
|
|
* Toate 4 campuri (scpTVA/tvaIncasare/splitTva/inactiv) folosesc BETWEEN inclusiv la ambele capete (adaugat
|
|
* 01.08.2026) - doc_WS_V9.txt (documentatia oficiala ANAF) nu precizeaza daca ziua dataReactivare insasi mai
|
|
* conteaza inactiva, deci mock-ul NU copiaza aici sfarsitul exclusiv folosit de cache pentru inactiv
|
|
* (validare.prg:2675) - acea margine ramane neverificata, vezi test_margine_inactiv_frontiera.prg.
|
|
*
|
|
* CONTOR DE APELURI (esential pentru S6 - codurile din cache nu trebuie sa ajunga la ANAF):
|
|
* gnANAFBatchApeluri - numeric, o unitate per GRUP efectiv trimis (indiferent de rezultat) - echivalentul
|
|
* unui loHTTP.Send() real
|
|
* gcANAFBatchCuiuriTrimise - CSV, toate CUI-urile normalizate incluse efectiv intr-un grup trimis, in ordine -
|
|
* un CUI proaspat din cache NU trebuie sa apara niciodata aici
|
|
* Ambele se initializeaza (gnANAFBatchApeluri = 0, gcANAFBatchCuiuriTrimise = '') INLINE in programul principal,
|
|
* inainte de primul apel; mock-ul doar le incrementeaza/concateneaza (fara CREATEOBJECT, sigur din metoda de clasa).
|
|
|
|
Define Class VerificareANAFMockBatch As VerificareANAF
|
|
|
|
Procedure ANAF_SincronWebService_PlatitorTva
|
|
Lparameters tcCursorListaCIF, tdData, tcAlias
|
|
Local lcAlias, lnRecords, lnReqNo, lnGrupuri, lnGrup, lnRec1, lnRec2
|
|
Local lcCuiOriginal, lcCuiNorm, lcKey, loItem, lcScenariu, lcFound, lcNotFound, lcBody
|
|
Local loJsonObject, loEx As Exception, llNumeric, lnI
|
|
|
|
lcAlias = Iif(!Empty(m.tcAlias) And Type('tcAlias') = 'C', m.tcAlias, 'crsANAF')
|
|
Use In (Select(m.lcAlias))
|
|
|
|
lnRecords = Reccount(m.tcCursorListaCIF)
|
|
If m.lnRecords = 0
|
|
Return m.lcAlias
|
|
Endif
|
|
|
|
lnReqNo = 100
|
|
lnGrupuri = Max(Ceiling(m.lnRecords / m.lnReqNo), 1)
|
|
For lnGrup = 1 To m.lnGrupuri
|
|
lnRec1 = m.lnReqNo * (m.lnGrup - 1) + 1
|
|
lnRec2 = Min(m.lnReqNo * m.lnGrup, m.lnRecords)
|
|
|
|
lcScenariu = This.ScenariuPentruGrup(m.lnGrup)
|
|
lcFound = ''
|
|
lcNotFound = ''
|
|
|
|
Select (m.tcCursorListaCIF)
|
|
Scan For Between(Recno(), m.lnRec1, m.lnRec2)
|
|
lcCuiOriginal = Alltrim(cod_fiscal)
|
|
lcCuiNorm = NormalizeazaCUI(m.lcCuiOriginal)
|
|
|
|
llNumeric = !Empty(m.lcCuiNorm) And Len(m.lcCuiNorm) <= 10
|
|
If m.llNumeric
|
|
For lnI = 1 To Len(m.lcCuiNorm)
|
|
If !Isdigit(Substr(m.lcCuiNorm, lnI, 1))
|
|
llNumeric = .F.
|
|
Exit
|
|
Endif
|
|
Endfor
|
|
Endif
|
|
If !m.llNumeric
|
|
Loop
|
|
Endif
|
|
|
|
If Type('gnANAFBatchApeluri') = 'N'
|
|
gcANAFBatchCuiuriTrimise = gcANAFBatchCuiuriTrimise + Iif(Empty(gcANAFBatchCuiuriTrimise), '', ',') + m.lcCuiNorm
|
|
Endif
|
|
|
|
lcKey = 'C' + m.lcCuiNorm
|
|
If Type('goANAFBatchMock') = 'O' And goANAFBatchMock.GetKey(m.lcKey) > 0
|
|
loItem = goANAFBatchMock.Item(m.lcKey)
|
|
Else
|
|
loItem = 'NOTFOUND'
|
|
Endif
|
|
If Vartype(m.loItem) = 'C'
|
|
lcNotFound = lcNotFound + Iif(Empty(m.lcNotFound), '', ',') + m.lcCuiNorm
|
|
Else
|
|
lcFound = lcFound + Iif(Empty(m.lcFound), '', ',') + This.JsonRaspuns(m.loItem, m.lcCuiNorm, m.tdData)
|
|
Endif
|
|
Endscan
|
|
|
|
If Type('gnANAFBatchApeluri') = 'N'
|
|
gnANAFBatchApeluri = gnANAFBatchApeluri + 1
|
|
Endif
|
|
|
|
Do Case
|
|
Case m.lcScenariu == 'TACERE' Or m.lcScenariu == 'RASPUNS_NEINTELES'
|
|
* fara raspuns folosibil - grupul nu adauga niciun rand (ca la lcJsonString = '' in original)
|
|
Case m.lcScenariu == 'HTTP404_CORP_COMPLET' And !This.lIgnoraGate404()
|
|
* replica gate-ul de AZI (validare.prg:1968, "If loHTTP.Status = 200"): corpul exista dar e ignorat
|
|
Otherwise
|
|
* NORMAL, sau HTTP404_CORP_COMPLET cu glANAFBatchIgnoraGate404 = .T. (fixture pentru fixul propus S5)
|
|
lcBody = '{"found":[' + m.lcFound + '],"notFound":[' + m.lcNotFound + ']}'
|
|
Try
|
|
loJsonObject = nfjsonread(m.lcBody)
|
|
lcAlias = This.ParseJsonANAFv8(m.loJsonObject, m.tcAlias)
|
|
Catch To loEx
|
|
* pastreaza comportamentul original: o eroare de parsare nu opreste testul, doar grupul respectiv
|
|
Endtry
|
|
Endcase
|
|
Endfor
|
|
|
|
Return m.lcAlias
|
|
Endproc && ANAF_SincronWebService_PlatitorTva
|
|
|
|
*-- scenariul unui grup: intai override-ul per grup (goANAFBatchScenariuPerGrup), apoi implicitul global
|
|
Procedure ScenariuPentruGrup
|
|
Lparameters tnGrup
|
|
Local lcCheie
|
|
lcCheie = Transform(m.tnGrup)
|
|
If Type('goANAFBatchScenariuPerGrup') = 'O' And goANAFBatchScenariuPerGrup.GetKey(m.lcCheie) > 0
|
|
Return goANAFBatchScenariuPerGrup.Item(m.lcCheie)
|
|
Endif
|
|
If Type('gcANAFBatchScenariu') = 'C' And !Empty(gcANAFBatchScenariu)
|
|
Return gcANAFBatchScenariu
|
|
Endif
|
|
Return 'NORMAL'
|
|
Endproc
|
|
|
|
Procedure lIgnoraGate404
|
|
Return (Type('glANAFBatchIgnoraGate404') = 'L' And glANAFBatchIgnoraGate404)
|
|
Endproc
|
|
|
|
*-- serializeaza un raspuns "gasit" pentru un CUI, in forma JSON asteptata de ParseJsonANAFv8 (validare.prg:2015);
|
|
*-- campurile lipsa din obiectul de configurare primesc valori implicite neutre (siruri goale / false).
|
|
*!* 31.07.2026 marius.mutu: primeste tdData, il propaga la PropBool (booleanele devin sensibile la data ceruta)
|
|
Procedure JsonRaspuns
|
|
Lparameters toItem, tcCuiNorm, tdData
|
|
Local lcJ
|
|
lcJ = '{"date_generale":{'
|
|
lcJ = lcJ + '"cui":' + Transform(Val(m.tcCuiNorm)) + ','
|
|
lcJ = lcJ + '"data":"' + This.PropStr(m.toItem, 'data', '2024-01-01') + '",'
|
|
lcJ = lcJ + '"denumire":"' + This.PropStr(m.toItem, 'denumire', 'FIRMA TEST ' + m.tcCuiNorm) + '",'
|
|
lcJ = lcJ + '"adresa":"' + This.PropStr(m.toItem, 'adresa', 'ADRESA TEST') + '",'
|
|
lcJ = lcJ + '"nrRegCom":"' + This.PropStr(m.toItem, 'nrRegCom', '') + '"'
|
|
lcJ = lcJ + '},'
|
|
lcJ = lcJ + '"inregistrare_scop_tva":{'
|
|
lcJ = lcJ + '"scpTVA":' + This.PropBool(m.toItem, 'scpTVA', m.tdData, 'dataInceputScpTVA', 'dataSfarsitScpTVA') + ','
|
|
lcJ = lcJ + '"perioade_tva":[{'
|
|
lcJ = lcJ + '"data_inceput_ScpTVA":"' + This.PropStr(m.toItem, 'dataInceputScpTVA', '') + '",'
|
|
lcJ = lcJ + '"data_sfarsit_ScpTVA":"' + This.PropStr(m.toItem, 'dataSfarsitScpTVA', '') + '",'
|
|
lcJ = lcJ + '"data_anul_imp_ScpTVA":"' + This.PropStr(m.toItem, 'dataAnulImpScpTVA', '') + '",'
|
|
lcJ = lcJ + '"mesaj_ScpTVA":"' + This.PropStr(m.toItem, 'mesajScpTVA', '') + '"'
|
|
lcJ = lcJ + '}]'
|
|
lcJ = lcJ + '},'
|
|
lcJ = lcJ + '"inregistrare_RTVAI":{'
|
|
lcJ = lcJ + '"dataInceputTvaInc":"' + This.PropStr(m.toItem, 'dataInceputTvaInc', '') + '",'
|
|
lcJ = lcJ + '"dataSfarsitTvaInc":"' + This.PropStr(m.toItem, 'dataSfarsitTvaInc', '') + '",'
|
|
lcJ = lcJ + '"dataActualizareTvaInc":"' + This.PropStr(m.toItem, 'dataActualizareTvaInc', '') + '",'
|
|
lcJ = lcJ + '"dataPublicareTvaInc":"' + This.PropStr(m.toItem, 'dataPublicareTvaInc', '') + '",'
|
|
lcJ = lcJ + '"tipActTvaInc":"' + This.PropStr(m.toItem, 'tipActTvaInc', '') + '",'
|
|
lcJ = lcJ + '"statusTvaIncasare":' + This.PropBool(m.toItem, 'tvaIncasare', m.tdData, 'dataInceputTvaInc', 'dataSfarsitTvaInc')
|
|
lcJ = lcJ + '},'
|
|
lcJ = lcJ + '"stare_inactiv":{'
|
|
lcJ = lcJ + '"dataInactivare":"' + This.PropStr(m.toItem, 'dataInactivare', '') + '",'
|
|
lcJ = lcJ + '"dataReactivare":"' + This.PropStr(m.toItem, 'dataReactivare', '') + '",'
|
|
lcJ = lcJ + '"dataPublicare":"' + This.PropStr(m.toItem, 'dataPublicare', '') + '",'
|
|
lcJ = lcJ + '"dataRadiere":"' + This.PropStr(m.toItem, 'dataRadiere', '') + '",'
|
|
lcJ = lcJ + '"statusInactivi":' + This.PropBool(m.toItem, 'inactiv', m.tdData, 'dataInactivare', 'dataReactivare')
|
|
lcJ = lcJ + '},'
|
|
lcJ = lcJ + '"inregistrare_SplitTVA":{'
|
|
lcJ = lcJ + '"dataInceputSplitTVA":"' + This.PropStr(m.toItem, 'dataInceputSplitTVA', '') + '",'
|
|
lcJ = lcJ + '"dataAnulareSplitTVA":"' + This.PropStr(m.toItem, 'dataAnulareSplitTVA', '') + '",'
|
|
lcJ = lcJ + '"statusSplitTVA":' + This.PropBool(m.toItem, 'splitTva', m.tdData, 'dataInceputSplitTVA', 'dataAnulareSplitTVA')
|
|
lcJ = lcJ + '}'
|
|
lcJ = lcJ + '}'
|
|
Return m.lcJ
|
|
Endproc
|
|
|
|
Procedure PropStr
|
|
Lparameters toItem, tcProp, tcImplicit
|
|
If Type('toItem.' + m.tcProp) <> 'U'
|
|
Return Transform(Evaluate('toItem.' + m.tcProp))
|
|
Endif
|
|
Return m.tcImplicit
|
|
Endproc
|
|
|
|
*-- sensibil la data ceruta, ca serviciul real (ANAF trimite "data" per CUI, validare.prg:2098):
|
|
*-- daca item are perechea tcPropStart/tcPropEnd (siruri 'YYYY-MM-DD'), evalueaza tdData IN acea
|
|
*-- SINGURA perioada cunoscuta (mock-ul nu e omniscient, la fel ca ANAF v9 - o singura pereche
|
|
*-- data_inceput/data_sfarsit, nu o lista, vezi docs\handoff_cache_anaf.md); fara acele
|
|
*-- proprietati pe item, cade pe boolean-ul static dinainte (compatibil cu testele existente).
|
|
*-- BETWEEN inclusiv pe toate 4 campurile (tlSfarsitExclusiv nefolosit la niciun apel): documentatia
|
|
*-- oficiala ANAF (doc_WS_V9.txt) descrie fiecare boolean doar ca "true/false ... la data cautata",
|
|
*-- fara sa precizeze daca ziua dataReactivare insasi mai conteaza inactiva - nu e o regula
|
|
*-- documentata, deci nu se copiaza aici alegerea (exclusiva) facuta de cache in validare.prg:2675;
|
|
*-- vezi test_margine_inactiv_frontiera.prg pentru cazul care ar evidentia diferenta, daca exista.
|
|
*!* 01.08.2026 marius.mutu: PropBool nu mai copiaza sfarsitul exclusiv al cache-ului pentru inactiv (vezi docs\diff-S13-teste-echivalenta.md)
|
|
Procedure PropBool
|
|
Lparameters toItem, tcProp, tdData, tcPropStart, tcPropEnd, tlSfarsitExclusiv
|
|
Local llStatic, ldStart, ldEnd, llExclusiv
|
|
|
|
llStatic = (Vartype(m.tdData) <> 'D') Or Empty(m.tcPropStart) Or (Type('toItem.' + m.tcPropStart) = 'U')
|
|
If m.llStatic
|
|
If Type('toItem.' + m.tcProp) == 'L' And Evaluate('toItem.' + m.tcProp)
|
|
Return 'true'
|
|
Endif
|
|
Return 'false'
|
|
Endif
|
|
|
|
llExclusiv = (Vartype(m.tlSfarsitExclusiv) == 'L' And m.tlSfarsitExclusiv)
|
|
ldStart = This.ParseDataIso(Evaluate('toItem.' + m.tcPropStart))
|
|
ldEnd = Iif(Empty(m.tcPropEnd) Or Type('toItem.' + m.tcPropEnd) = 'U', {}, This.ParseDataIso(Evaluate('toItem.' + m.tcPropEnd)))
|
|
|
|
If Empty(m.ldStart)
|
|
Return 'false'
|
|
Endif
|
|
If m.tdData >= m.ldStart And (Empty(m.ldEnd) Or Iif(m.llExclusiv, m.tdData < m.ldEnd, m.tdData <= m.ldEnd))
|
|
Return 'true'
|
|
Endif
|
|
Return 'false'
|
|
Endproc
|
|
|
|
*-- 'YYYY-MM-DD' (formatul ANAF, vezi antet) -> Date VFP; sir gol/lipsa/invalid -> {} (data goala)
|
|
Procedure ParseDataIso
|
|
Lparameters tcIso
|
|
If Vartype(m.tcIso) <> 'C' Or Len(Alltrim(m.tcIso)) <> 10
|
|
Return {}
|
|
Endif
|
|
Return Date(Val(Substr(m.tcIso, 1, 4)), Val(Substr(m.tcIso, 6, 2)), Val(Substr(m.tcIso, 9, 2)))
|
|
Endproc
|
|
|
|
Enddefine && VerificareANAFMockBatch
|