diff --git a/COMUN/clase/anaf_efactura.vct b/COMUN/clase/anaf_efactura.vct index 9eaab63..e5e1ec5 100644 Binary files a/COMUN/clase/anaf_efactura.vct and b/COMUN/clase/anaf_efactura.vct differ diff --git a/COMUN/clase/anaf_efactura.vcx b/COMUN/clase/anaf_efactura.vcx index eefba79..05c4f17 100644 Binary files a/COMUN/clase/anaf_efactura.vcx and b/COMUN/clase/anaf_efactura.vcx differ diff --git a/COMUN/programe/anaf_efactura.prg b/COMUN/programe/anaf_efactura.prg index bff3d04..0a6fb1d 100644 --- a/COMUN/programe/anaf_efactura.prg +++ b/COMUN/programe/anaf_efactura.prg @@ -500,21 +500,200 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat Endproc && SaveToken +*!* 07.07.2026 +*!* newToken - flux automat OAuth2: state + sesiune PHP + polling pick.php +*!* implementare unica in AnafeFacturaServer; ANAFeFactura deleaga +*!* kill switch ANAF_TOKEN_AUTOFLOW (1=automat, 0=vechi), garda headless + * Intoarce tokenul de acces la ANAF (eFactura) daca este salvat sau obtine un nou token -* Se apeleaza din formularul anaf_token.do_executa +* Se apeleaza din formularul anaf_token.do_executa (prin ANAFeFactura.newToken) Function newToken - Local lcTokenURL + LOCAL lnAutoFlowVal, lcAutoFlowStr, lcTokenURL, lcState, lcStatePrefix + LOCAL lcBuf1, lcBuf2, lni, lnAscByte, lcHexChars + LOCAL lcBrowserUrl, lcPickUrl, lcExitCode + LOCAL loHTTP, lnStatus, lcResponse, loJson + LOCAL lnStartSec, lnElapsed, lnStep, lnKey + LOCAL loExHTTP, loExJson, loExSave - This.Log('Introduceti in calculator tokenul USB cu semnatura electronica' + LF + ; - 'Se va deschide pagina ANAF de generare token. Introduceti PIN-ul pentru semnatura electronica.' + LF + ; - 'Copiati codurile "Token" si "Refresh Token" afisate si completati-le in ' + m.gcGeneralIniFile) + * --- Garda headless: server/ROAEFACTURA (lServer=.T.) sau _SCREEN invizibil --- + * Pastreaza EXACT comportamentul vechi al AnafeFacturaServer.newToken + IF This.lServer OR !_SCREEN.Visible + This.Log('Introduceti in calculator tokenul USB cu semnatura electronica' + LF + ; + 'Se va deschide pagina ANAF de generare token. Introduceti PIN-ul pentru semnatura electronica.' + LF + ; + 'Copiati codurile "Token" si "Refresh Token" afisate si completati-le in ' + m.gcGeneralIniFile) + lcTokenURL = This.cTokenUrl + open_default_app(m.gcGeneralIniFile) + open_default_app(m.lcTokenURL) + RETURN + ENDIF - lcTokenURL = This.cTokenUrl + * --- Kill switch: default 1 la NULL/inexistent/Oracle indisponibil --- + lnAutoFlowVal = 1 + TRY + lcAutoFlowStr = ALLTRIM(NVL(citeste_optiune("ANAF_TOKEN_AUTOFLOW"), '')) + IF !EMPTY(lcAutoFlowStr) + lnAutoFlowVal = INT(VAL(lcAutoFlowStr)) + ENDIF + CATCH + lnAutoFlowVal = 1 + ENDTRY - open_default_app(m.gcGeneralIniFile) - open_default_app(m.lcTokenURL) + lcTokenURL = ALLTRIM(NVL(This.cTokenUrl, '')) + IF EMPTY(lcTokenURL) + lcTokenURL = 'https://romfast.ro/oauth2/' + ENDIF - Endfunc + * --- Kill switch OFF (0) -> comportament vechi nemodificat --- + IF lnAutoFlowVal <> 1 + AMESSAGEBOX('Introduceti in calculator tokenul USB cu semnatura electronica' + CHR(13) + CHR(10) + ; + 'Se va deschide pagina ANAF de generare token. Introduceti PIN-ul pentru semnatura electronica.' + CHR(13) + CHR(10) + ; + 'Copiati codurile "Token" si "Refresh Token" afisate si completati-le in ROA.', 0 + 64, _Screen.Caption) + open_default_app(m.lcTokenURL) + RETURN + ENDIF + + * --- Modal initial OK/Anulare --- + IF aMESSAGEBOX('Se va genera un token nou eFactura.' + CHR(13) + ; + '1. Introduceti tokenul USB cu semnatura electronica in calculator.' + CHR(13) + ; + '2. Se va deschide pagina ANAF in browser: selectati certificatul dvs. si introduceti PIN-ul.' + CHR(13) + ; + '3. Reveniti in aplicatie - tokenul se preia automat.' + CHR(13) + CHR(13) + ; + 'Continuati?', 1 + 64, _Screen.Caption) <> 1 + RETURN + ENDIF + + IF RIGHT(lcTokenURL, 1) <> '/' + lcTokenURL = lcTokenURL + '/' + ENDIF + + * --- State anti-CSRF: 2 GUID-uri CoCreateGuid -> 64 hex (SYS(2015) e predictibil) --- + TRY + DECLARE INTEGER CoCreateGuid IN ole32 STRING @pguid + CATCH + * deja declarata in aceasta sesiune VFP + ENDTRY + lcBuf1 = REPLICATE(CHR(0), 16) + lcBuf2 = REPLICATE(CHR(0), 16) + CoCreateGuid(@lcBuf1) + CoCreateGuid(@lcBuf2) + lcBuf1 = lcBuf1 + lcBuf2 && 32 octeti aleatori = 256 biti entropie + lcHexChars = '0123456789abcdef' + lcState = '' + FOR lni = 1 TO 32 + lnAscByte = ASC(SUBSTR(lcBuf1, lni, 1)) + lcState = lcState + SUBSTR(lcHexChars, INT(lnAscByte/16)+1, 1) + SUBSTR(lcHexChars, MOD(lnAscByte,16)+1, 1) + ENDFOR + lcStatePrefix = LEFT(lcState, 8) && doar prefixul apare in log, niciodata tokenuri + + lcBrowserUrl = lcTokenURL + '?state=' + lcState + lcPickUrl = lcTokenURL + 'pick.php' + This.Log('[' + lcStatePrefix + '] start flux automat token') + + * --- Lansare browser; open_default_app (oexport.prg) intoarce .T./.F. (ShellExecute > 32) --- + IF !open_default_app(lcBrowserUrl) + AMESSAGEBOX('Nu s-a putut deschide browserul. Se continua cu fluxul manual: se va deschide pagina care afiseaza codurile pentru copiere.', 0 + 64, _Screen.Caption) + This.Log('[' + lcStatePrefix + '] esec lansare browser -> fallback manual') + open_default_app(lcTokenURL) && fara ?state -> pagina afiseaza codurile (retro-compat) + RETURN + ENDIF + + * --- Polling pick.php la 2s: timeout 10 minute, Esc = anulare --- + lcExitCode = '' + lnStartSec = SECONDS() + DO WHILE .T. + lnElapsed = SECONDS() - lnStartSec + IF lnElapsed < 0 + lnElapsed = lnElapsed + 86400 && trecere miezul noptii + ENDIF + IF lnElapsed >= 600 + lcExitCode = 'timeout' + EXIT + ENDIF + WAIT WINDOW 'Asteptare autorizare ANAF... Continuati in fereastra browserului: selectati certificatul si introduceti PIN-ul. (Esc = anulare)' NOWAIT + + lnStatus = 0 + lcResponse = '' + TRY + loHTTP = CREATEOBJECT('WinHttp.WinHttpRequest.5.1') + loHTTP.SetTimeouts(5000, 5000, 5000, 5000) + loHTTP.Open('POST', lcPickUrl, .F.) + loHTTP.SetRequestHeader('Content-Type', 'application/x-www-form-urlencoded') + loHTTP.Send('state=' + lcState) + lnStatus = loHTTP.Status + lcResponse = loHTTP.ResponseText + CATCH TO loExHTTP + * eroare retea tranzitorie -> continuam polling pana la timeout + ENDTRY + + IF lnStatus = 200 + loJson = NULL + TRY + loJson = nfjsonread(lcResponse) + CATCH TO loExJson + This.Log('[' + lcStatePrefix + '] eroare parsare JSON: ' + loExJson.Message) + ENDTRY + IF TYPE('loJson') = 'O' AND !ISNULL(loJson) + IF TYPE('loJson.error') <> 'U' + lcExitCode = 'anaf_error' + EXIT + ENDIF + IF TYPE('loJson.access_token') <> 'U' AND TYPE('loJson.refresh_token') <> 'U' + * Tokenuri primite - populare proprietati (fara valori de tokenuri in log) + This.cToken = loJson.access_token + This.cRefreshToken = loJson.refresh_token + This.dTokenGendate = DATE() + IF TYPE('loJson.expires_in') <> 'U' + This.dTokenExpdate = DATE() + INT(VAL(TRANSFORM(loJson.expires_in)) / 86400) + ELSE + This.dTokenExpdate = DATE() + 90 + ENDIF + lcExitCode = 'success' + EXIT + ENDIF + * {"status":"pending"} sau alt continut -> continuam polling + ENDIF + ENDIF + + * asteptare 2s cu verificare Esc la fiecare 0.5s + lnStep = 0 + DO WHILE lnStep < 4 + lnKey = INKEY(0.5) + IF lnKey = 27 && Esc + lcExitCode = 'esc' + EXIT + ENDIF + lnStep = lnStep + 1 + ENDDO + IF lcExitCode = 'esc' + EXIT + ENDIF + ENDDO + WAIT CLEAR + This.Log('[' + lcStatePrefix + '] iesire: ' + lcExitCode) + + DO CASE + CASE lcExitCode = 'success' + TRY + This.SaveToken() + AMESSAGEBOX('S-a generat token-ul eFactura, valabil pana la ' + DTOC(This.dTokenExpdate), 0 + 64, _Screen.Caption) + CATCH TO loExSave + * tokenul ramane in This.cToken; la reluarea operatiei va fi necesar PIN-ul din nou + AMESSAGEBOX('Tokenul a fost generat, dar salvarea in baza de date a esuat: ' + loExSave.Message + CHR(13) + ; + 'Reluati operatia Token nou sau contactati suportul ROA.', 0 + 48, _Screen.Caption) + ENDTRY + + CASE lcExitCode = 'esc' + AMESSAGEBOX('Ati anulat asteptarea. Daca ati introdus deja PIN-ul, autorizarea respectiva va fi ignorata; la reluare va trebui sa introduceti PIN-ul din nou.', 0 + 64, _Screen.Caption) + + CASE lcExitCode = 'timeout' + AMESSAGEBOX('Nu s-a primit tokenul in 10 minute. Se deschide pagina pentru completare manuala; puteti si relua operatia Token nou.', 0 + 48, _Screen.Caption) + open_default_app(lcTokenURL) + + CASE lcExitCode = 'anaf_error' + AMESSAGEBOX('Eroare la autorizarea ANAF: ' + TRANSFORM(NVL(loJson.error, '')) + '. Reluati operatia sau folositi completarea manuala (se deschide pagina care afiseaza codurile).', 0 + 48, _Screen.Caption) + open_default_app(lcTokenURL) + ENDCASE + + Endfunc && newToken * Actualizeaza data expirarii tokenului ANAF eFactura * Se apeleaza din formularul anaf_token.do_executa @@ -523,10 +702,11 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat * tcMesaj (OUT) Local loHTTP As 'winHTTP.winHTTPrequest.5.1' - Local lcRefreshToken, lcResponse, lcURL, llSucces, loJson - llSucces = .F. + Local lcRefreshToken, lcResponse, lcURL, llSucces, loJson, loEx as Exception + llSucces = .T. lcRefreshToken = Alltrim(This.cRefreshToken) tcMesaj = '' + SET STEP ON If Empty(m.lcRefreshToken) tcMesaj = 'Completati Refresh Token' This.Log(m.tcMesaj) @@ -539,12 +719,21 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat loHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") loHTTP.Send([refresh_token=] + m.lcRefreshToken) - CATCH - loHTTP = Createobject('MSXML2.XMLHTTP.6.0') - loHTTP.Open('POST', lcURL, .F.) - loHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") - loHTTP.Send([refresh_token=] + m.lcRefreshToken) + CATCH TO loEx + llSucces = .F. ENDTRY + + IF !m.llSucces OR TYPE('loHTTP') <> 'O' + TRY + loHTTP = Createobject('MSXML2.XMLHTTP.6.0') + loHTTP.Open('POST', lcURL, .F.) + loHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") + loHTTP.Send([refresh_token=] + m.lcRefreshToken) + CATCH TO loEx + llSucces = .F. + AMESSAGEBOX(loEx.Message,0,_screen.Caption) + ENDTRY + ENDIF llSucces = (TYPE('loHTTP') = 'O') IF m.llSucces @@ -578,7 +767,7 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat Endif ELSE tcMesaj = 'Nu s-a regenerat token-ul! Status = ' + Transform(loHTTP.Status) + CRLF + m.lcResponse - This.Log(m.tcMesaj, 0 + 48, _Screen.Caption) + This.Log(m.tcMesaj) Endif Endif @@ -2154,7 +2343,7 @@ end; ENDIF IF m.llSucces - * Transformam raspunsul JSON într-un obiect FoxPro + * Transformam raspunsul JSON intr-un obiect FoxPro TRY loJson = nfjsonread(m.lcResponse) CATCH TO loEx @@ -2167,7 +2356,7 @@ end; * Verificam daca exista mesaje disponibile IF TYPE("loJson.mesaje") <> "U" - * Daca avem mesaje, le salvam într-un array + * Daca avem mesaje, le salvam intr-un array *!* laMesaje = loJson.mesaje *!* lnMesajeCount = ALEN(laMesaje, 1) @@ -2697,7 +2886,10 @@ select id, id_efactura, nr, articol, descriere, detalii, cantitate, um, pret, pr If m.tlShow Or Empty(m.lcToken) Or m.ldTokenExpDate <= Date() loFrmToken = Createobject("anaf_token", This) loFrmToken.Show(1) - If m.lcToken <> loServer.cToken + *!* 07.07.2026 + *!* dupa fluxul automat (newToken) datele sunt deja setate corect din expires_in + *!* (dTokenGendate = azi); suprascrierea cu +90 ramane doar pentru completarea manuala + If m.lcToken <> loServer.cToken And loServer.dTokenGendate <> Date() loServer.dTokenGendate = Date() loServer.dTokenExpdate = Date() + 90 Endif @@ -2719,15 +2911,23 @@ select id, id_efactura, nr, articol, descriere, detalii, cantitate, um, pret, pr Endproc && SaveToken +*!* 07.07.2026 +*!* newToken - deleaga la AnafeFacturaServer.newToken (implementare unica FR-7) +*!* pattern identic cu refreshToken: loServer = This.AnafeFacturaServer + * Intoarce tokenul de acces la ANAF (eFactura) daca este salvat sau obtine un nou token * Se apeleaza din formularul anaf_token.do_executa Function newToken - AMESSAGEBOX('Introduceti in calculator tokenul USB cu semnatura electronica' + Chr(13) + Chr(10) + ; - 'Se va deschide pagina ANAF de generare token. Introduceti PIN-ul pentru semnatura electronica.' + Chr(13) + Chr(10) + ; - 'Copiati codurile "Token" si "Refresh Token" afisate si completati-le in ROA.', 0 + 64, _Screen.Caption) - - lcURL = [https://romfast.ro/oauth2/] - open_default_app(m.lcURL) + LOCAL loServer + loServer = This.AnafeFacturaServer + * Asigura cTokenUrl populat din optiunile firmei (citit de GetToken din citeste_optiune) + loServer.GetToken() + loServer.newToken() + * Propagare proprietati actualizate catre ANAFeFactura (consistent cu getToken) + This.token = loServer.cToken + This.refresh_token = loServer.cRefreshToken + This.token_gendate = loServer.dTokenGendate + This.token_expdate = loServer.dTokenExpdate Endfunc * Actualizeaza data expirarii tokenului ANAF eFactura diff --git a/COMUN/programe/oexport.prg b/COMUN/programe/oexport.prg index 3efbb0e..dc40418 100644 --- a/COMUN/programe/oexport.prg +++ b/COMUN/programe/oexport.prg @@ -299,9 +299,12 @@ Define Class oExportConfig As wwConfig Endfunc && Report2File ************************************** INCEPUT : open_default_app + *!* 07.07.2026 + *!* open_default_app - intoarce .T./.F. dupa rezultatul ShellExecute (>32 = succes) + *!* apelantii existenti care ignora rezultatul nu sunt afectati Procedure OPEN_DEFAULT_APP Parameters tcNumeFisier - Local lcActiune + Local lcActiune, lnResult Declare Integer ShellExecute In shell32.Dll ; Integer hndWin, ; @@ -311,8 +314,9 @@ Define Class oExportConfig As wwConfig String cDir, ; Integer nShowWin lcActiune = "open" - ShellExecute(0, lcActiune, tcNumeFisier, "", "", 1) + lnResult = ShellExecute(0, lcActiune, tcNumeFisier, "", "", 1) + Return (m.lnResult > 32) Endproc && open_default_app ************************************** SFARSIT : open_default_app ************************************** INCEPUT : getExcelMask diff --git a/roacont.PJT b/roacont.PJT index 3d9e549..76be185 100644 Binary files a/roacont.PJT and b/roacont.PJT differ diff --git a/roacont.PJX b/roacont.PJX index 2947321..856b423 100644 Binary files a/roacont.PJX and b/roacont.PJX differ diff --git a/roacont.exe b/roacont.exe index e630b36..d9903a7 100644 Binary files a/roacont.exe and b/roacont.exe differ