diff --git a/Programe/oproceduri_autopass.prg b/Programe/oproceduri_autopass.prg new file mode 100644 index 0000000..0bcab46 --- /dev/null +++ b/Programe/oproceduri_autopass.prg @@ -0,0 +1,1224 @@ +***************************************************************************************** +* oproceduri_autopass.prg +* Transmitere operatii de service (comenzi inchise + facturate) la RAR prin gateway-ul +* autopass.romfast.ro. Vezi PRD: docs\prd\prd-roaauto-1.0-transmitere-autopass.md +* +* Continut: +* US-001 autopass_get_config / autopass_salveaza_config / autopass_test_conexiune +* US-002 autopass_cursor_comenzi / autopass_cursor_operatii / autopass_dedup_operatii / +* autopass_valideaza_rand / autopass_alfanumeric / autopass_coloana_exista +* US-003 DEFINE CLASS AutopassClient / autopass_obs +* US-005 autopass_situatie / autopass_coreleaza_situatie +* US-006 autopass_export_csv / export_operatii_csv / autopass_cere_perioada +* +* Configurare (US-001): fara tabele noi - AUTOPASS_URL/AUTOPASS_KEY/AUTOPASS_ENV se tin +* in tabelul standard optiuni, citite/scrise prin citeste_optiune()/scrie_optiune() din +* COMUN\programe\oinit_optiuni.prg. Situatia transmiterilor (US-005) nu se persista local - +* se citeste din gateway la fiecare refresh, intr-un cursor temporar. +* +* Fisierul trebuie adaugat ca membru in roaauto.pjx din VFP IDE (Project > Add File) - +* nu se poate face prin editare de text pe binarul .pjx. +***************************************************************************************** + +***************************************************************************************** +* US-001: Configurare conexiune AUTOPASS (URL gateway, cheie API, mediu RAR) +***************************************************************************************** +FUNCTION autopass_get_config +LOCAL loConfig, lcUrl, lcKey, lcEnv + +loConfig = Createobject('empty') +AddProperty(loConfig, 'cUrlGateway', '') +AddProperty(loConfig, 'cApiKey', '') +AddProperty(loConfig, 'cRarEnv', '') +AddProperty(loConfig, 'lConfigOk', .F.) + +lcUrl = '' +lcKey = '' +lcEnv = '' + +*-- citire prin mecanismul standard optiuni (crsOptiuni, incarcat la pornirea aplicatiei +*-- de actualizeaza_optiuni in COMUN\programe\oinit_optiuni.prg) +IF USED('crsOptiuni') + lcUrl = ALLTRIM(citeste_optiune('AUTOPASS_URL')) + lcKey = ALLTRIM(citeste_optiune('AUTOPASS_KEY')) + lcEnv = ALLTRIM(citeste_optiune('AUTOPASS_ENV')) +ENDIF + +*-- fallback pe variabilele globale gc* (setate de optiuni_firma la pornire), pentru +*-- cazul in care crsOptiuni nu mai e deschis in momentul apelului +IF EMPTY(lcUrl) AND TYPE('gcAUTOPASS_URL') = 'C' + lcUrl = ALLTRIM(gcAUTOPASS_URL) +ENDIF +IF EMPTY(lcKey) AND TYPE('gcAUTOPASS_KEY') = 'C' + lcKey = ALLTRIM(gcAUTOPASS_KEY) +ENDIF +IF EMPTY(lcEnv) AND TYPE('gcAUTOPASS_ENV') = 'C' + lcEnv = ALLTRIM(gcAUTOPASS_ENV) +ENDIF + +*-- la prima rulare, daca lipseste URL-ul gateway-ului, scriem valoarea implicita +IF EMPTY(lcUrl) + lcUrl = 'https://autopass.romfast.ro' + scrie_optiune('AUTOPASS_URL', lcUrl, 'URL gateway RAR AUTOPASS') +ENDIF + +loConfig.cUrlGateway = lcUrl +loConfig.cApiKey = lcKey +loConfig.cRarEnv = lcEnv + +* fara URL sau cheie API configurate (panoul Configurare din fereastra de transmitere), +* transmiterea nu poate functiona +loConfig.lConfigOk = !EMPTY(loConfig.cUrlGateway) AND !EMPTY(loConfig.cApiKey) + +RETURN loConfig +ENDFUNC && autopass_get_config + + +***************************************************************************************** +* Salveaza configurarea AUTOPASS din panoul Configurare al ferestrei de transmitere +* (US-004) - scrie cele 3 optiuni prin scrie_optiune (randurile se creeaza la prima +* scriere, nu e nevoie de script de instalare) +***************************************************************************************** +FUNCTION autopass_salveaza_config +LPARAMETERS tcUrl, tcKey, tcEnv +LOCAL lnSucces + +lnSucces = scrie_optiune('AUTOPASS_URL', ALLTRIM(NVL(tcUrl,'')), 'URL gateway RAR AUTOPASS') +IF lnSucces >= 0 + lnSucces = scrie_optiune('AUTOPASS_KEY', ALLTRIM(NVL(tcKey,'')), 'Cheie API gateway AUTOPASS') +ENDIF +IF lnSucces >= 0 + lnSucces = scrie_optiune('AUTOPASS_ENV', LOWER(ALLTRIM(NVL(tcEnv,''))), 'Mediu RAR AUTOPASS (test/prod/gol=implicit cont)') +ENDIF + +RETURN lnSucces >= 0 +ENDFUNC && autopass_salveaza_config + + +***************************************************************************************** +* Testeaza conexiunea la gateway-ul AUTOPASS (GET /v1/ping) +* Intoarce obiect: lSucces, cMediu, lAutentificatCuCheie, lAreCredsTest, lAreCredsProd, cEroare +***************************************************************************************** +FUNCTION autopass_test_conexiune +LOCAL loConfig, loHTTP As "winHTTP.winHTTPrequest.5.1", loReturn As "empty", loEx As Exception +LOCAL lcServer, lnStatus, lcResponseText, loJson + +loReturn = Createobject('empty') +AddProperty(loReturn, 'lSucces', .F.) +AddProperty(loReturn, 'cMediu', '') +AddProperty(loReturn, 'lAutentificatCuCheie', .F.) +AddProperty(loReturn, 'lAreCredsTest', .F.) +AddProperty(loReturn, 'lAreCredsProd', .F.) +AddProperty(loReturn, 'cEroare', '') + +loConfig = autopass_get_config() +IF !loConfig.lConfigOk + loReturn.cEroare = 'Configurare AUTOPASS incompleta: completeaza cheia API' + ; + ' (si URL-ul, daca e nevoie) in panoul Configurare.' + RETURN loReturn +ENDIF + +lnStatus = 0 +lcResponseText = '' + +TRY + lcServer = ALLTRIM(loConfig.cUrlGateway) + [/v1/ping] + + loHTTP = Createobject('winHTTP.winHTTPrequest.5.1') + loHTTP.Open('GET', lcServer, .F.) + loHTTP.SetTimeouts(30000, 30000, 30000, 30000) + loHTTP.setRequestHeader('X-API-Key', loConfig.cApiKey) + loHTTP.Send() + + lnStatus = loHTTP.Status + lcResponseText = loHTTP.ResponseText +CATCH TO loEx +* de regula lipsa retea / timeout / gateway inaccesibil - nu se reincearca automat aici, +* reincercarea (daca e cazul) ramane manuala, initiata de utilizator + loReturn.cEroare = 'Nu s-a putut contacta gateway-ul AUTOPASS: ' + loEx.Message + RETURN loReturn +ENDTRY + +DO CASE +CASE lnStatus = 401 + loReturn.cEroare = 'Cheie API invalida sau neautorizata (HTTP 401) - corecteaza cheia' + ; + ' in panoul Configurare.' + RETURN loReturn +CASE lnStatus <> 200 + loReturn.cEroare = 'Gateway AUTOPASS a raspuns cu eroare (HTTP ' + TRANSFORM(lnStatus) + ').' + RETURN loReturn +ENDCASE + +loJson = Nfjsonread(lcResponseText) +IF VARTYPE(loJson) <> 'O' + loReturn.cEroare = 'Raspuns invalid (JSON malformat) de la gateway-ul AUTOPASS.' + RETURN loReturn +ENDIF + +loReturn.lSucces = .T. +loReturn.cMediu = IIF(TYPE('loJson.mediu') = 'C', loJson.mediu, '') +loReturn.lAutentificatCuCheie = IIF(TYPE('loJson.autentificatCuCheie') = 'L', loJson.autentificatCuCheie, .F.) +loReturn.lAreCredsTest = IIF(TYPE('loJson.areCredsTest') = 'L', loJson.areCredsTest, .F.) +loReturn.lAreCredsProd = IIF(TYPE('loJson.areCredsProd') = 'L', loJson.areCredsProd, .F.) + +RETURN loReturn +ENDFUNC && autopass_test_conexiune + + +***************************************************************************************** +* US-002: extragere comenzi + operatii pe perioada +***************************************************************************************** +FUNCTION autopass_cursor_comenzi +LPARAMETERS tdDataInceput, tdDataSfarsit +LOCAL lcCursorHeader, lcD1, lcD2, lcSql, lcFiltruSters, lnSucces +LOCAL llOk, lcMotivRand, ldDataPrest + +lcCursorHeader = 'crs_autopass_comenzi' +IF USED(lcCursorHeader) + USE IN (lcCursorHeader) +ENDIF + +*-- literalii de data construiti explicit (independent de SET DATE) +lcD1 = "TO_DATE('" + STR(YEAR(tdDataInceput),4) + "-" + PADL(MONTH(tdDataInceput),2,'0') + "-" + PADL(DAY(tdDataInceput),2,'0') + "','YYYY-MM-DD')" +lcD2 = "TO_DATE('" + STR(YEAR(tdDataSfarsit),4) + "-" + PADL(MONTH(tdDataSfarsit),2,'0') + "-" + PADL(DAY(tdDataSfarsit),2,'0') + "','YYYY-MM-DD')" + +lcFiltruSters = '' +IF autopass_coloana_exista(gcS, 'AUTO_ISTORIC_COMENZI', 'STERS') + lcFiltruSters = ' AND sters = 0' +ENDIF + +lcSql = "SELECT id_ordl, nrord, dataoravalid, datafact, nrfact, nrinmat, series, kmint, nume, tip_comanda " + ; + "FROM " + gcS + ".auto_istoric_comenzi " + ; + "WHERE validat = 1 AND facturat = 1 AND id_tip IN (1,2) " + ; + "AND datafact BETWEEN " + lcD1 + " AND " + lcD2 + lcFiltruSters + +lnSucces = goExecutor.oExecute(lcSql, lcCursorHeader) +IF lnSucces < 0 + IF TYPE('goLog') = 'O' + goLog.Log('autopass_cursor_comenzi: ' + goExecutor.cEroare, PROGRAM()) + ENDIF + RETURN '' +ENDIF + +ALTER TABLE (lcCursorHeader) ADD COLUMN lvalid L +ALTER TABLE (lcCursorHeader) ADD COLUMN motiv_invalid C(100) + +SELECT (lcCursorHeader) +SCAN + lcMotivRand = '' + DO CASE + CASE VARTYPE(dataoravalid) = 'T' + ldDataPrest = TTOD(dataoravalid) + CASE VARTYPE(dataoravalid) = 'D' + ldDataPrest = dataoravalid + OTHERWISE + ldDataPrest = {} + ENDCASE + + llOk = autopass_valideaza_rand(series, nrinmat, ldDataPrest, kmint, @lcMotivRand) + REPLACE lvalid WITH llOk, motiv_invalid WITH lcMotivRand +ENDSCAN + +RETURN lcCursorHeader +ENDFUNC && autopass_cursor_comenzi + + +***************************************************************************************** +FUNCTION autopass_cursor_operatii +LPARAMETERS tcCursorHeader +LOCAL lcCursorDetaliu, lcCursorCount, lcSql, lcChunkSql, lcIdList, lnSucces +LOCAL lnTotal, lnChunkSize, lnStart, lnEnd, i +LOCAL ARRAY laId[1] +LOCAL lnCnt, lcMotivExist + +IF EMPTY(tcCursorHeader) OR !USED(tcCursorHeader) + RETURN '' +ENDIF + +lcCursorDetaliu = 'crs_autopass_operatii' +IF USED(lcCursorDetaliu) + USE IN (lcCursorDetaliu) +ENDIF + +SELECT (tcCursorHeader) +lnTotal = RECCOUNT() + +IF lnTotal = 0 + lcSql = "SELECT o.id_ordl, n.codop, n.denop " + ; + "FROM " + gcS + ".dev_oper o " + ; + "JOIN " + gcS + ".dev_nom_norme n ON o.id_norme = n.id_norme " + ; + "WHERE 1 = 2" +ELSE + DIMENSION laId[lnTotal] + i = 0 + SELECT (tcCursorHeader) + GO TOP + SCAN + i = i + 1 + laId[i] = id_ordl + ENDSCAN + + lnChunkSize = 500 + lcSql = '' + lnStart = 1 + DO WHILE lnStart <= lnTotal + lnEnd = MIN(lnStart + lnChunkSize - 1, lnTotal) + lcIdList = '' + FOR i = lnStart TO lnEnd + lcIdList = lcIdList + IIF(EMPTY(lcIdList),'',',') + ALLTRIM(STR(laId[i])) + ENDFOR + + lcChunkSql = "SELECT o.id_ordl, n.codop, n.denop " + ; + "FROM " + gcS + ".dev_oper o " + ; + "JOIN " + gcS + ".dev_nom_norme n ON o.id_norme = n.id_norme " + ; + "WHERE o.sters = 0 AND o.id_ordl IN (" + lcIdList + ")" + + lcSql = lcSql + IIF(EMPTY(lcSql),'',' UNION ALL ') + lcChunkSql + lnStart = lnEnd + 1 + ENDDO +ENDIF + +lnSucces = goExecutor.oExecute(lcSql, lcCursorDetaliu) +IF lnSucces < 0 + IF TYPE('goLog') = 'O' + goLog.Log('autopass_cursor_operatii: ' + goExecutor.cEroare, PROGRAM()) + ENDIF + RETURN '' +ENDIF + +ALTER TABLE (lcCursorDetaliu) ADD COLUMN lvalid_op L +ALTER TABLE (lcCursorDetaliu) ADD COLUMN motiv_op C(100) + +SELECT (lcCursorDetaliu) +SCAN + IF EMPTY(ALLTRIM(IIF(VARTYPE(codop) = 'C', codop, ''))) + REPLACE lvalid_op WITH .F., motiv_op WITH 'operatie fara cod' + ELSE + REPLACE lvalid_op WITH .T., motiv_op WITH '' + ENDIF +ENDSCAN + +*-- dedup operatii identice (acelasi id_ordl+codop) inainte de transmitere +lcCursorDetaliu = autopass_dedup_operatii(lcCursorDetaliu) + +*-- numaram operatiile valide (cu codop) ramase per comanda +lcCursorCount = 'crs_autopass_opcount' +IF USED(lcCursorCount) + USE IN (lcCursorCount) +ENDIF + +SELECT id_ordl, COUNT(*) AS ncnt ; + FROM (lcCursorDetaliu) ; + WHERE lvalid_op ; + GROUP BY id_ordl ; + INTO CURSOR (lcCursorCount) READWRITE + +INDEX ON id_ordl TAG id_ordl +SET ORDER TO id_ordl IN (lcCursorCount) + +*-- marcam in header comenzile ramase fara nicio operatie de manopera valida +SELECT (tcCursorHeader) +SCAN + lnCnt = 0 + IF SEEK(id_ordl, lcCursorCount) + lnCnt = &lcCursorCount..ncnt + ENDIF + + IF lnCnt = 0 + lcMotivExist = ALLTRIM(motiv_invalid) + IF !('fara operatii de manopera' $ lcMotivExist) + REPLACE motiv_invalid WITH lcMotivExist + IIF(EMPTY(lcMotivExist),'','; ') + 'fara operatii de manopera', ; + lvalid WITH .F. + ENDIF + ENDIF +ENDSCAN + +USE IN (lcCursorCount) + +RETURN lcCursorDetaliu +ENDFUNC && autopass_cursor_operatii + + +***************************************************************************************** +FUNCTION autopass_dedup_operatii +LPARAMETERS tcCursorDetaliu +LOCAL lcTemp, lcPath + +IF EMPTY(tcCursorDetaliu) OR !USED(tcCursorDetaliu) + RETURN tcCursorDetaliu +ENDIF + +lcTemp = 'crs_ap_dedup_' + SYS(2015) + +*-- pastram o singura instanta per id_ordl+codop +SELECT id_ordl, codop, denop, lvalid_op, motiv_op ; + FROM (tcCursorDetaliu) ; + GROUP BY id_ordl, codop ; + INTO CURSOR (lcTemp) READWRITE + +lcPath = DBF(lcTemp) + +USE IN (tcCursorDetaliu) +USE (lcPath) AGAIN ALIAS (tcCursorDetaliu) IN 0 + +SELECT (tcCursorDetaliu) +INDEX ON id_ordl TAG id_ordl + +USE IN (lcTemp) + +RETURN tcCursorDetaliu +ENDFUNC && autopass_dedup_operatii + + +***************************************************************************************** +* Validare LOCALA per rand comanda (reutilizabila si de US-004 grid, US-006 CSV) +* Returneaza .T./.F.; motivul (daca invalid) e scris prin referinta in tcMotiv (@) +***************************************************************************************** +FUNCTION autopass_valideaza_rand +LPARAMETERS tcVin, tcNrinmat, tdDataPrestatie, tnKm, tcMotiv +LOCAL lcVin, lcNrinmat, lcMotiv, ldData, llOk + +lcMotiv = '' +lcVin = ALLTRIM(UPPER(IIF(VARTYPE(tcVin) = 'C', tcVin, ''))) +lcNrinmat = ALLTRIM(UPPER(IIF(VARTYPE(tcNrinmat) = 'C', tcNrinmat, ''))) + +IF LEN(lcVin) # 17 + lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'VIN invalid (trebuie 17 caractere)' +ELSE + IF 'O' $ lcVin OR 'I' $ lcVin OR 'Q' $ lcVin + lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'VIN invalid (contine litera interzisa O/I/Q)' + ENDIF +ENDIF + +IF EMPTY(lcNrinmat) OR LEN(lcNrinmat) > 10 OR !autopass_alfanumeric(lcNrinmat) + lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'Numar de inmatriculare invalid' +ENDIF + +DO CASE +CASE VARTYPE(tdDataPrestatie) = 'T' + ldData = TTOD(tdDataPrestatie) +CASE VARTYPE(tdDataPrestatie) = 'D' + ldData = tdDataPrestatie +OTHERWISE + ldData = {} +ENDCASE + +IF EMPTY(ldData) OR ldData < {^2024-12-01} OR ldData > DATE() + lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'Data prestatiei in afara intervalului permis' +ENDIF + +IF VARTYPE(tnKm) # 'N' OR tnKm <= 0 + lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'Kilometraj invalid' +ENDIF + +llOk = EMPTY(lcMotiv) +tcMotiv = lcMotiv + +RETURN llOk +ENDFUNC && autopass_valideaza_rand + + +***************************************************************************************** +FUNCTION autopass_alfanumeric +LPARAMETERS tcStr +LOCAL i, lcCh, llOk + +llOk = !EMPTY(tcStr) +FOR i = 1 TO LEN(tcStr) + lcCh = UPPER(SUBSTR(tcStr, i, 1)) + IF !(lcCh $ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') + llOk = .F. + EXIT + ENDIF +ENDFOR + +RETURN llOk +ENDFUNC && autopass_alfanumeric + + +***************************************************************************************** +* Verifica existenta unei coloane intr-un tabel Oracle (owner/table/column) +***************************************************************************************** +FUNCTION autopass_coloana_exista +LPARAMETERS tcSchema, tcTabel, tcColoana +LOCAL lcCursor, llExista, lnSucces + +lcCursor = 'crs_ap_colcheck' +IF USED(lcCursor) + USE IN (lcCursor) +ENDIF + +lnSucces = goExecutor.oExecute("SELECT COUNT(*) AS ncnt FROM ALL_TAB_COLUMNS WHERE OWNER = UPPER('" + ; + tcSchema + "') AND TABLE_NAME = UPPER('" + tcTabel + "') AND COLUMN_NAME = UPPER('" + tcColoana + "')", lcCursor) + +llExista = .F. +IF lnSucces >= 0 AND USED(lcCursor) + llExista = (&lcCursor..ncnt > 0) + USE IN (lcCursor) +ENDIF + +RETURN llExista +ENDFUNC && autopass_coloana_exista + + +***************************************************************************************** +* US-003: Client HTTP + serializare JSON (POST /v1/prezentari) +***************************************************************************************** +DEFINE CLASS AutopassClient AS Custom + + cUrlGateway = '' + cApiKey = '' + cRarEnv = '' + + cLogFile = 'LOG.txt' + nMaxLot = 50 + + *------------------------------------------------------------------- + Procedure Init + *------------------------------------------------------------------- + Local loConfig + + loConfig = autopass_get_config() + + This.cUrlGateway = Alltrim(loConfig.cUrlGateway) + This.cApiKey = Alltrim(loConfig.cApiKey) + This.cRarEnv = Alltrim(loConfig.cRarEnv) + Endproc + + + *------------------------------------------------------------------- + Procedure Ping + *------------------------------------------------------------------- + * Testarea conexiunii este implementata in autopass_test_conexiune() (US-001); + * nu o duplicam aici. Metoda exista doar ca AutopassClient sa poata delega la ea + * din contextul clasei, daca apelantul are deja o instanta creata. + Return autopass_test_conexiune() + Endproc + + + *------------------------------------------------------------------- + Procedure TrimiteLot + *------------------------------------------------------------------- + Lparameters taComenzi + + Return This.EnviaLot(m.taComenzi, '/v1/prezentari', 'TrimiteLot') + Endproc + + + *------------------------------------------------------------------- + Procedure Valideaza + *------------------------------------------------------------------- + Lparameters taComenzi + + Return This.EnviaLot(m.taComenzi, '/v1/prezentari/valideaza', 'Valideaza') + Endproc + + + *------------------------------------------------------------------- + Procedure EnviaLot + *------------------------------------------------------------------- + * Motor comun pentru TrimiteLot (enqueue) si Valideaza (dry-run): construieste + * payload-ul, tine maparea index_in_lot -> id_ordl, apeleaza gateway-ul si + * normalizeaza raspunsul. FARA retry automat - idempotenta e responsabilitatea + * serverului, retrimiterea o initiaza manual utilizatorul (US-004). + Lparameters taComenzi, tcPath, tcAction + + Local lnCount, i, lcJson, loBody, loComanda, loPrezentare, loHttpRez + Local loReturn, loParsed, laRezultate, loColectie, loElem, loRezItem, lnIndexRel + Local Array laPrezentari(1), laIndexMap(1) + + lnCount = This.GetNumarElemente(m.taComenzi) + + If m.lnCount = 0 + Error 'AutopassClient.' + m.tcAction + ': lista de comenzi este goala' + Endif + + If m.lnCount > This.nMaxLot + Error 'AutopassClient.' + m.tcAction + ': maxim ' + Transform(This.nMaxLot) + ; + ' comenzi per apel (primit ' + Transform(m.lnCount) + ; + '); impartirea pe loturi de ' + Transform(This.nMaxLot) + ; + ' este responsabilitatea apelantului (US-004), nu a clientului' + Endif + + Dimension laPrezentari(m.lnCount) + Dimension laIndexMap(m.lnCount) + + For i = 1 To m.lnCount + + loComanda = This.GetElement(m.taComenzi, m.i) + laIndexMap(m.i) = loComanda.id_ordl + + loPrezentare = Createobject('empty') + AddProperty(loPrezentare, 'vin', Alltrim(loComanda.vin)) + AddProperty(loPrezentare, 'nr_inmatriculare', Alltrim(loComanda.nrinmat)) + AddProperty(loPrezentare, 'data_prestatie', Alltrim(loComanda.data_prestatie)) + AddProperty(loPrezentare, 'odometru_final', Alltrim(Transform(loComanda.odometru_final))) + This.AsignaListaProprietate(loPrezentare, 'prestatii', loComanda.prestatii) + AddProperty(loPrezentare, 'obs', Alltrim(loComanda.obs)) + + laPrezentari(m.i) = loPrezentare + + Endfor + + loBody = Createobject('empty') + This.AsignaListaProprietate(loBody, 'prezentari', laPrezentari) + + If !Empty(This.cRarEnv) + AddProperty(loBody, 'rar_env', This.cRarEnv) + Endif + +* TODO verificare empirica: testeaza cu diacritice pe mediul RAR test inainte de prima +* trimitere reala; presupunem ca nfjsoncreate.prg emite \uXXXX pentru caractere ne-ASCII. +* Daca emite in schimb bytes cp1250, aici trebuie STRCONV(...,9) sau trimitere ca +* byte-array cu charset=utf-8 - NU aplica STRCONV suplimentar peste rezultatul lui +* nfjsoncreate fara sa verifici mai intai, ar risca dubla-conversie. + lcJson = Nfjsoncreate(loBody, .F.) + + loReturn = Createobject('empty') + AddProperty(loReturn, 'lSucces', .F.) + AddProperty(loReturn, 'nStatusHttp', 0) + AddProperty(loReturn, 'cDetaliu', '') + AddProperty(loReturn, 'oRezultate', .Null.) + + loHttpRez = This.Post(m.tcPath, m.lcJson, m.tcAction) + + loReturn.nStatusHttp = loHttpRez.nStatusHttp + + If !loHttpRez.lSucces + loReturn.cDetaliu = loHttpRez.cDetaliu + This.LogEroare(m.tcAction, -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + Return loReturn + Endif + + loParsed = Nfjsonread(loHttpRez.cRaspuns) + + If Vartype(m.loParsed) # 'O' + loReturn.cDetaliu = 'raspuns JSON invalid de la gateway' + This.LogEroare(m.tcAction, -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + Return loReturn + Endif + +* TODO verificare empirica: numele exact al proprietatii-container din raspunsul real +* al gateway-ului (rezultate/prezentari/altul) nu e confirmat in specificatie folosita +* aici; ExtrageListaRezultate() incearca variantele uzuale, ajusteaza cand se cunoaste +* contractul exact. + laRezultate = This.ExtrageListaRezultate(m.loParsed) + + If Vartype(m.laRezultate) # 'A' + loReturn.cDetaliu = 'formatul raspunsului nu contine lista de rezultate asteptata' + This.LogEroare(m.tcAction, -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + Return loReturn + Endif + + loColectie = Createobject('Collection') + + For i = 1 To Alen(m.laRezultate, 1) + + loElem = laRezultate(m.i) + lnIndexRel = This.GetProprietate(m.loElem, 'index', m.i - 1) + + loRezItem = Createobject('empty') + AddProperty(loRezItem, 'index', m.lnIndexRel) + AddProperty(loRezItem, 'id_ordl', ; + Iif(Between(m.lnIndexRel + 1, 1, m.lnCount), laIndexMap(m.lnIndexRel + 1), 0)) + AddProperty(loRezItem, 'submission_id', This.GetProprietate(m.loElem, 'submission_id', '')) + AddProperty(loRezItem, 'status', This.GetProprietate(m.loElem, 'status', '')) + AddProperty(loRezItem, 'motiv', This.GetProprietate(m.loElem, 'motiv', '')) + AddProperty(loRezItem, 'id_prezentare', This.GetProprietate(m.loElem, 'id_prezentare', '')) + + If m.tcAction == 'Valideaza' + AddProperty(loRezItem, 'status_estimat', This.GetProprietate(m.loElem, 'status_estimat', '')) + AddProperty(loRezItem, 'coduri_nemapate', This.GetProprietate(m.loElem, 'coduri_nemapate', .Null.)) + Endif + + loColectie.Add(loRezItem) + + Endfor + + loReturn.lSucces = .T. + loReturn.oRezultate = loColectie + + Return loReturn + Endproc + + + *------------------------------------------------------------------- + Procedure StatusPrezentare + *------------------------------------------------------------------- + Lparameters tcSubmissionId + + Local loHttpRez, loReturn, loParsed + + loReturn = Createobject('empty') + AddProperty(loReturn, 'lSucces', .F.) + AddProperty(loReturn, 'nStatusHttp', 0) + AddProperty(loReturn, 'cDetaliu', '') + AddProperty(loReturn, 'submission_id', m.tcSubmissionId) + AddProperty(loReturn, 'status', '') + AddProperty(loReturn, 'motiv', '') + AddProperty(loReturn, 'id_prezentare', '') + AddProperty(loReturn, 'vehicul', '') + + loHttpRez = This.Get('/v1/prezentari/' + Alltrim(m.tcSubmissionId), 'StatusPrezentare') + + loReturn.nStatusHttp = loHttpRez.nStatusHttp + + If !loHttpRez.lSucces + loReturn.cDetaliu = loHttpRez.cDetaliu + This.LogEroare('StatusPrezentare', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + Return loReturn + Endif + + loParsed = Nfjsonread(loHttpRez.cRaspuns) + + If Vartype(m.loParsed) # 'O' + loReturn.cDetaliu = 'raspuns JSON invalid de la gateway' + This.LogEroare('StatusPrezentare', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + Return loReturn + Endif + + loReturn.lSucces = .T. + loReturn.status = This.GetProprietate(m.loParsed, 'status', '') + loReturn.motiv = This.GetProprietate(m.loParsed, 'motiv', '') + loReturn.id_prezentare = This.GetProprietate(m.loParsed, 'id_prezentare', '') + loReturn.vehicul = This.GetProprietate(m.loParsed, 'vehicul', '') + loReturn.submission_id = This.GetProprietate(m.loParsed, 'submission_id', m.tcSubmissionId) + + Return loReturn + Endproc + + + *------------------------------------------------------------------- + Procedure Post + *------------------------------------------------------------------- + * Transport HTTP POST pur - FARA retry automat. In caz de eroare (HTTP != 200 + * sau exceptie de retea) nu arunca, intoarce obiect cu lSucces=.F. + Lparameters tcPath, tcJsonBody, tcAction + + Local loHTTP As 'winHTTP.winHTTPrequest.5.1' + Local loReturn As "empty", loEx As Exception + Local lcUrl + + loReturn = Createobject('empty') + AddProperty(loReturn, 'lSucces', .F.) + AddProperty(loReturn, 'nStatusHttp', 0) + AddProperty(loReturn, 'cRaspuns', '') + AddProperty(loReturn, 'cDetaliu', '') + + lcUrl = Alltrim(This.cUrlGateway) + m.tcPath + + Try + loHTTP = Createobject('winHTTP.winHTTPrequest.5.1') + loHTTP.Open('POST', m.lcUrl, .F.) + loHTTP.SetTimeouts(30000, 30000, 30000, 30000) + loHTTP.setRequestHeader('Content-Type', 'application/json') + loHTTP.setRequestHeader('X-API-Key', This.cApiKey) + loHTTP.Send(m.tcJsonBody) + + loReturn.nStatusHttp = loHTTP.Status + loReturn.cRaspuns = loHTTP.ResponseText + loReturn.lSucces = (loHTTP.Status = 200) + + If !loReturn.lSucces + loReturn.cDetaliu = This.ExtrageDetaliuEroare(loReturn.cRaspuns) + Endif + + Catch To loEx + loReturn.lSucces = .F. + loReturn.nStatusHttp = 0 + loReturn.cDetaliu = 'Eroare comunicare HTTP (' + m.tcAction + '): ' + loEx.Message + Endtry + + Return loReturn + Endproc + + + *------------------------------------------------------------------- + Procedure Get + *------------------------------------------------------------------- + * Transport HTTP GET pur - vezi Post() pentru contractul de raspuns. + Lparameters tcPath, tcAction + + Local loHTTP As 'winHTTP.winHTTPrequest.5.1' + Local loReturn As "empty", loEx As Exception + Local lcUrl + + loReturn = Createobject('empty') + AddProperty(loReturn, 'lSucces', .F.) + AddProperty(loReturn, 'nStatusHttp', 0) + AddProperty(loReturn, 'cRaspuns', '') + AddProperty(loReturn, 'cDetaliu', '') + + lcUrl = Alltrim(This.cUrlGateway) + m.tcPath + + Try + loHTTP = Createobject('winHTTP.winHTTPrequest.5.1') + loHTTP.Open('GET', m.lcUrl, .F.) + loHTTP.SetTimeouts(30000, 30000, 30000, 30000) + loHTTP.setRequestHeader('Content-Type', 'application/json') + loHTTP.setRequestHeader('X-API-Key', This.cApiKey) + loHTTP.Send() + + loReturn.nStatusHttp = loHTTP.Status + loReturn.cRaspuns = loHTTP.ResponseText + loReturn.lSucces = (loHTTP.Status = 200) + + If !loReturn.lSucces + loReturn.cDetaliu = This.ExtrageDetaliuEroare(loReturn.cRaspuns) + Endif + + Catch To loEx + loReturn.lSucces = .F. + loReturn.nStatusHttp = 0 + loReturn.cDetaliu = 'Eroare comunicare HTTP (' + m.tcAction + '): ' + loEx.Message + Endtry + + Return loReturn + Endproc + + + *------------------------------------------------------------------- + Procedure ExtrageDetaliuEroare + *------------------------------------------------------------------- + Lparameters tcRaspuns + + Local loParsed + + loParsed = .Null. + + Try + loParsed = Nfjsonread(m.tcRaspuns) + Catch + loParsed = .Null. + Endtry + + If Vartype(m.loParsed) = 'O' And Type('loParsed.detail') # 'U' + Return Transform(loParsed.detail) + Endif + + Return Left(Alltrim(m.tcRaspuns), 500) + Endproc + + + *------------------------------------------------------------------- + Procedure ExtrageListaRezultate + *------------------------------------------------------------------- + Lparameters toParsed + + Local Array laNume(4) + Local i + + laNume(1) = 'rezultate' + laNume(2) = 'prezentari' + laNume(3) = 'items' + laNume(4) = 'data' + + For i = 1 To Alen(laNume, 1) + If Type('toParsed.' + laNume(m.i)) = 'A' + Return toParsed.&laNume(m.i) + Endif + Endfor + + If Type('toParsed.array') = 'A' + Return toParsed.array + Endif + + Return .Null. + Endproc + + + *------------------------------------------------------------------- + Procedure AsignaListaProprietate + *------------------------------------------------------------------- + * Adauga o proprietate array/collection unui obiect "empty", respectand + * conventia nfjsoncreate.prg pentru proprietati de tip array (AddProperty + * cu dimensiune + Acopy). + Lparameters toObj, tcPropName, txValue + + Local lnCount + + If Vartype(m.txValue) = 'A' + lnCount = Max(Alen(m.txValue, 1), 1) + AddProperty(m.toObj, m.tcPropName + '(' + Transform(m.lnCount) + ')') + Acopy(m.txValue, toObj.&tcPropName) + Else + AddProperty(m.toObj, m.tcPropName, m.txValue) + Endif + Endproc + + + *------------------------------------------------------------------- + Procedure GetNumarElemente + *------------------------------------------------------------------- + Lparameters taElemente + + If Vartype(m.taElemente) = 'A' + Return Alen(m.taElemente, 1) + Else + Return m.taElemente.Count + Endif + Endproc + + + *------------------------------------------------------------------- + Procedure GetElement + *------------------------------------------------------------------- + Lparameters taElemente, tnIndex + + If Vartype(m.taElemente) = 'A' + Return m.taElemente(m.tnIndex) + Else + Return m.taElemente.Item(m.tnIndex) + Endif + Endproc + + + *------------------------------------------------------------------- + Procedure GetProprietate + *------------------------------------------------------------------- + Lparameters toObiect, tcProprietate, tuDefault + + If Vartype(m.toObiect) = 'O' And Type('toObiect.' + m.tcProprietate) # 'U' + Return Evl(toObiect.&tcProprietate, m.tuDefault) + Endif + + Return m.tuDefault + Endproc + + + *------------------------------------------------------------------- + Procedure LogEroare + *------------------------------------------------------------------- + * Scrie context de diagnostic in LOG.txt. NU logheaza NICIODATA cheia API + * (This.cApiKey) sau headerele complete de request. + Lparameters tcAction, tnIndexLot, tnStatusHttp, tcRaspuns + + Local lcMesaj + + lcMesaj = Ttoc(Datetime()) + ' AUTOPASS [' + m.tcAction + ']' + ; + ' index_lot=' + Transform(m.tnIndexLot) + ; + ' http_status=' + Transform(m.tnStatusHttp) + ; + ' raspuns=' + Left(Alltrim(m.tcRaspuns), 500) + Chr(13) + Chr(10) + + Strtofile(m.lcMesaj, This.cLogFile, 1) + Endproc + +ENDDEFINE && AutopassClient + + +***************************************************************************************** +* Sirul "obs" folosit ca cheie de corelare comanda <-> prezentare in gateway. Sursa unica - +* aceeasi functie e folosita la construirea payload-ului POST, la corelarea situatiei +* (US-005) si la exportul CSV (US-006), ca sa nu se construiasca ad-hoc in mai multe locuri. +***************************************************************************************** +FUNCTION autopass_obs +LPARAMETERS tnNrord, tcNrfact +LOCAL lcNrord, lcNrfact + +lcNrord = ALLTRIM(TRANSFORM(tnNrord)) +lcNrfact = ALLTRIM(TRANSFORM(tcNrfact)) + +RETURN 'Comanda ' + lcNrord + ' / Factura ' + lcNrfact +ENDFUNC && autopass_obs + + +***************************************************************************************** +* US-005: Situatia prezentarilor - citita direct din gateway (GET /v1/prezentari), +* FARA nicio evidenta locala. Cursorul temporar crs_autopass_situatie se reconstruieste +* la fiecare apel; la eroare HTTP/parse ramane gol si functia intoarce .F. (eroarea se +* logheaza prin AutopassClient.LogEroare, fara sa logheze cheia API). +***************************************************************************************** +FUNCTION autopass_situatie +LPARAMETERS tdD1, tdD2 +LOCAL lcCursor, oClient, lcD1, lcD2, lcPath, loHttpRez, loParsed, laLista, i, loElem + +lcCursor = 'crs_autopass_situatie' +IF USED(lcCursor) + USE IN (lcCursor) +ENDIF + +CREATE CURSOR (lcCursor) ; + (submission_id C(60), vin C(20), nr_inmatriculare C(15), data_prestatie C(10), ; + status C(30), motiv C(254), id_prezentare C(60), obs C(120)) + +*-- date construite explicit YYYY-MM-DD, niciodata DTOC/DTOS (depind de SET DATE) +lcD1 = STR(YEAR(tdD1),4) + '-' + PADL(MONTH(tdD1),2,'0') + '-' + PADL(DAY(tdD1),2,'0') +lcD2 = STR(YEAR(tdD2),4) + '-' + PADL(MONTH(tdD2),2,'0') + '-' + PADL(DAY(tdD2),2,'0') + +lcPath = '/v1/prezentari?data_de=' + lcD1 + '&data_pana=' + lcD2 + +oClient = CREATEOBJECT('AutopassClient') + +loHttpRez = oClient.Get(lcPath, 'Situatie') +IF !loHttpRez.lSucces + oClient.LogEroare('Situatie', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + RETURN .F. +ENDIF + +loParsed = Nfjsonread(loHttpRez.cRaspuns) +IF VARTYPE(loParsed) <> 'O' + oClient.LogEroare('Situatie', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + RETURN .F. +ENDIF + +laLista = oClient.ExtrageListaRezultate(loParsed) +IF VARTYPE(laLista) <> 'A' + oClient.LogEroare('Situatie', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) + RETURN .F. +ENDIF + +SELECT (lcCursor) +FOR i = 1 TO ALEN(laLista, 1) + loElem = laLista(i) + INSERT INTO (lcCursor) (submission_id, vin, nr_inmatriculare, data_prestatie, status, motiv, id_prezentare, obs) VALUES ( ; + oClient.GetProprietate(loElem, 'submission_id', ''), ; + oClient.GetProprietate(loElem, 'vin', ''), ; + oClient.GetProprietate(loElem, 'nr_inmatriculare', ''), ; + oClient.GetProprietate(loElem, 'data_prestatie', ''), ; + oClient.GetProprietate(loElem, 'status', ''), ; + oClient.GetProprietate(loElem, 'motiv', ''), ; + oClient.GetProprietate(loElem, 'id_prezentare', ''), ; + oClient.GetProprietate(loElem, 'obs', '')) +ENDFOR + +RETURN .T. +ENDFUNC && autopass_situatie + + +***************************************************************************************** +* Coreleaza fiecare rand din cursorul de comenzi (tcCursorGrid - format autopass_cursor_ +* comenzi, cu nrord/nrfact/series/nrinmat/dataoravalid) cu crs_autopass_situatie +* (populat de autopass_situatie): primar pe obs, fallback pe vin+data_prestatie. Adauga +* pe cursorul grid coloanele status_autopass/motiv_autopass/id_prezentare_autopass; +* comanda negasita = status gol ("netransmis"). +***************************************************************************************** +FUNCTION autopass_coreleaza_situatie +LPARAMETERS tcCursorGrid +LOCAL lcObsCurent, lcDataPrest, lcVin, llGasit +LOCAL laFields[1], lnFieldCount, i, llAreStatus, llAreMotiv, llAreIdPrez + +IF EMPTY(tcCursorGrid) OR !USED(tcCursorGrid) + RETURN .F. +ENDIF + +lnFieldCount = AFIELDS(laFields, tcCursorGrid) +llAreStatus = .F. +llAreMotiv = .F. +llAreIdPrez = .F. +FOR i = 1 TO lnFieldCount + DO CASE + CASE UPPER(laFields[i,1]) == 'STATUS_AUTOPASS' + llAreStatus = .T. + CASE UPPER(laFields[i,1]) == 'MOTIV_AUTOPASS' + llAreMotiv = .T. + CASE UPPER(laFields[i,1]) == 'ID_PREZENTARE_AUTOPASS' + llAreIdPrez = .T. + ENDCASE +ENDFOR + +IF !llAreStatus + ALTER TABLE (tcCursorGrid) ADD COLUMN status_autopass C(30) +ENDIF +IF !llAreMotiv + ALTER TABLE (tcCursorGrid) ADD COLUMN motiv_autopass C(254) +ENDIF +IF !llAreIdPrez + ALTER TABLE (tcCursorGrid) ADD COLUMN id_prezentare_autopass C(60) +ENDIF + +IF !USED('crs_autopass_situatie') +* situatia gateway-ului nu e incarcata - toate randurile raman "netransmis" + SELECT (tcCursorGrid) + REPLACE ALL status_autopass WITH '', motiv_autopass WITH '', id_prezentare_autopass WITH '' + RETURN .F. +ENDIF + +SELECT (tcCursorGrid) +SCAN + lcObsCurent = autopass_obs(nrord, nrfact) + + DO CASE + CASE VARTYPE(dataoravalid) = 'T' + lcDataPrest = STR(YEAR(TTOD(dataoravalid)),4) + '-' + PADL(MONTH(TTOD(dataoravalid)),2,'0') + '-' + PADL(DAY(TTOD(dataoravalid)),2,'0') + CASE VARTYPE(dataoravalid) = 'D' + lcDataPrest = STR(YEAR(dataoravalid),4) + '-' + PADL(MONTH(dataoravalid),2,'0') + '-' + PADL(DAY(dataoravalid),2,'0') + OTHERWISE + lcDataPrest = '' + ENDCASE + lcVin = ALLTRIM(UPPER(series)) + + llGasit = .F. + SELECT crs_autopass_situatie + LOCATE FOR ALLTRIM(obs) == ALLTRIM(m.lcObsCurent) + IF FOUND() + llGasit = .T. + ELSE + IF !EMPTY(m.lcVin) AND !EMPTY(m.lcDataPrest) + LOCATE FOR ALLTRIM(UPPER(vin)) == m.lcVin AND ALLTRIM(data_prestatie) == m.lcDataPrest + llGasit = FOUND() + ENDIF + ENDIF + + SELECT (tcCursorGrid) + IF llGasit + REPLACE status_autopass WITH crs_autopass_situatie.status, ; + motiv_autopass WITH crs_autopass_situatie.motiv, ; + id_prezentare_autopass WITH crs_autopass_situatie.id_prezentare + ELSE + REPLACE status_autopass WITH '', motiv_autopass WITH '', id_prezentare_autopass WITH '' + ENDIF +ENDSCAN + +RETURN .T. +ENDFUNC && autopass_coreleaza_situatie + + +***************************************************************************************** +* US-006: Export CSV operatii facturate (fallback oficial, functioneaza fara config API) +***************************************************************************************** +FUNCTION autopass_export_csv +LPARAMETERS tcCursorHeader, tcCursorDetaliu, tcFisierDestinatie +LOCAL lcContinut, lcDataPrestatie, lcOperatie, lcObservatii +LOCAL lnIdOrdl, lcVin, lcNrInmat, lnKmInt + +IF EMPTY(tcCursorHeader) OR !USED(tcCursorHeader) OR EMPTY(tcCursorDetaliu) OR !USED(tcCursorDetaliu) + RETURN .F. +ENDIF + +lcContinut = "VIN;Numar inmatriculare;Data prestatie;Odometru initial;Odometru final;Operatie;Observatii" + CHR(13) + CHR(10) + +SELECT (tcCursorHeader) +SCAN + lnIdOrdl = id_ordl + lcVin = ALLTRIM(series) + lcNrInmat = ALLTRIM(nrinmat) + lnKmInt = kmint + + IF !EMPTY(dataoravalid) + lcDataPrestatie = STR(YEAR(dataoravalid),4) + '-' + PADL(MONTH(dataoravalid),2,'0') + '-' + PADL(DAY(dataoravalid),2,'0') + ELSE + lcDataPrestatie = '' + ENDIF + + lcObservatii = autopass_obs(nrord, nrfact) + + SELECT (tcCursorDetaliu) + SCAN FOR id_ordl = lnIdOrdl + lcOperatie = ALLTRIM(codop) + IF EMPTY(lcOperatie) + lcOperatie = ALLTRIM(denop) + ENDIF + + lcContinut = lcContinut + ; + lcVin + ';' + ; + lcNrInmat + ';' + ; + lcDataPrestatie + ';' + ; + ';' + ; + TRANSFORM(lnKmInt) + ';' + ; + lcOperatie + ';' + ; + lcObservatii + CHR(13) + CHR(10) + ENDSCAN + SELECT (tcCursorHeader) +ENDSCAN + +RETURN STRTOFILE(lcContinut, tcFisierDestinatie) +ENDFUNC && autopass_export_csv + + +***************************************************************************************** +* Dialog minimal de perioada (data facturii), implicit luna curenta. US-004 va inlocui +* acest apel cu propriile campuri din forma daca export_operatii_csv se declanseaza +* din interiorul ferestrei "Trimitere AUTOPASS"; aici e varianta standalone din xmenu. +***************************************************************************************** +FUNCTION autopass_cere_perioada +LPARAMETERS tdDataInceput, tdDataSfarsit +LOCAL ldInceput, ldSfarsit, lcInceput, lcSfarsit + +ldInceput = IIF(VARTYPE(tdDataInceput) = 'D' AND !EMPTY(tdDataInceput), tdDataInceput, GOMONTH(DATE(), -1*(DAY(DATE())-1)) ) +ldSfarsit = IIF(VARTYPE(tdDataSfarsit) = 'D' AND !EMPTY(tdDataSfarsit), tdDataSfarsit, DATE()) + +lcInceput = INPUTBOX('Data inceput perioada (facturii), format ZZ.LL.AAAA:', 'Perioada export AUTOPASS', DTOC(ldInceput)) +IF EMPTY(lcInceput) + RETURN .NULL. +ENDIF + +lcSfarsit = INPUTBOX('Data sfarsit perioada (facturii), format ZZ.LL.AAAA:', 'Perioada export AUTOPASS', DTOC(ldSfarsit)) +IF EMPTY(lcSfarsit) + RETURN .NULL. +ENDIF + +ldInceput = CTOD(lcInceput) +ldSfarsit = CTOD(lcSfarsit) + +IF EMPTY(ldInceput) OR EMPTY(ldSfarsit) OR ldInceput > ldSfarsit + MESSAGEBOX('Perioada introdusa este invalida.', 48, 'AUTOPASS') + RETURN .NULL. +ENDIF + +RETURN {||ldInceput, ldSfarsit} +ENDFUNC && autopass_cere_perioada (returneaza .NULL. la Renunta/eroare; altfel array-code-block cu 2 date - inlocuit efectiv de US-004) + + +***************************************************************************************** +FUNCTION export_operatii_csv +LOCAL ldDataInceput, ldDataSfarsit, lcCursorHeader, lcCursorDetaliu, lcFisier + +ldDataInceput = GOMONTH(DATE(), -1*(DAY(DATE())-1)) +ldDataSfarsit = DATE() + +lcInceput = INPUTBOX('Data inceput perioada (facturii), format ZZ.LL.AAAA:', 'Export CSV AUTOPASS', DTOC(ldDataInceput)) +IF EMPTY(lcInceput) + RETURN .F. +ENDIF +lcSfarsit = INPUTBOX('Data sfarsit perioada (facturii), format ZZ.LL.AAAA:', 'Export CSV AUTOPASS', DTOC(ldDataSfarsit)) +IF EMPTY(lcSfarsit) + RETURN .F. +ENDIF + +ldDataInceput = CTOD(lcInceput) +ldDataSfarsit = CTOD(lcSfarsit) + +IF EMPTY(ldDataInceput) OR EMPTY(ldDataSfarsit) OR ldDataInceput > ldDataSfarsit + MESSAGEBOX('Perioada introdusa este invalida.', 48, 'AUTOPASS') + RETURN .F. +ENDIF + +lcCursorHeader = autopass_cursor_comenzi(ldDataInceput, ldDataSfarsit) +IF EMPTY(lcCursorHeader) + MESSAGEBOX('Nu s-au putut extrage comenzile pentru perioada aleasa.', 48, 'AUTOPASS') + RETURN .F. +ENDIF + +lcCursorDetaliu = autopass_cursor_operatii(lcCursorHeader) + +lcFisier = PUTFILE('Export CSV AUTOPASS', 'export_autopass_' + DTOS(DATE()) + '.csv', 'csv') +IF EMPTY(lcFisier) + RETURN .F. +ENDIF + +IF autopass_export_csv(lcCursorHeader, lcCursorDetaliu, lcFisier) + MESSAGEBOX('Export finalizat: ' + lcFisier, 64, 'AUTOPASS') +ELSE + MESSAGEBOX('Exportul a esuat - vezi LOG.txt.', 48, 'AUTOPASS') +ENDIF + +IF USED(lcCursorHeader) + USE IN (lcCursorHeader) +ENDIF +IF !EMPTY(lcCursorDetaliu) AND USED(lcCursorDetaliu) + USE IN (lcCursorDetaliu) +ENDIF + +RETURN .T. +ENDFUNC && export_operatii_csv + + +***************************************************************************************** +* Punct de intrare US-004 (forma frm_autopass.scx - lansata din xmenu ofundal_dev.vcx). +* Placeholder pana la construirea formei in VFP IDE (val 3 din PRD) - momentan doar +* deschide un mesaj, ca meniul sa poata fi cablat si testat inaintea formei complete. +***************************************************************************************** +FUNCTION trimitere_autopass +MESSAGEBOX('Fereastra Trimitere AUTOPASS (US-004) nu este inca construita in VFP IDE.', 48, 'AUTOPASS') +RETURN .F. +ENDFUNC && trimitere_autopass diff --git a/docs/prd/prd-roaauto-1.0-transmitere-autopass.md b/docs/prd/prd-roaauto-1.0-transmitere-autopass.md new file mode 100644 index 0000000..5ead6d8 --- /dev/null +++ b/docs/prd/prd-roaauto-1.0-transmitere-autopass.md @@ -0,0 +1,561 @@ + +# PRD ROAAUTO-1.0 — Transmitere operatii service la RAR AUTOPASS + +**Stare**: aprobat (review /autoplan complet, 2026-07-09); **revizuit 2026-07-09** — +eliminat orice tabel nou Oracle (decizia lui Marius): configurarea sta in tabelul standard +`optiuni`, iar situatia transmiterilor se citeste din gateway intr-un **cursor temporar** +(gateway-ul e singura sursa de adevar; ROAAUTO nu persista nimic local). + +> Feature ROAAUTO (VFP9/Oracle): buton/fereastra pentru transmiterea operatiilor de service +> din comenzile **inchise (validate) si facturate intr-o perioada** catre gateway-ul +> **autopass.romfast.ro** (`POST /v1/prezentari`, auth `X-API-Key`). +> Contract API (sursa de adevar): repo `rar-autopass` — `app/models.py` (`PrezentareIn`), +> `app/api/v1/router.py`, `docs/api-rar-contract.md`. Maparea cod operatie ROAAUTO → cod +> prestatie RAR se face **server-side** in gateway (`operations_mapping`), NU in ROAAUTO. + +## 1. Obiectiv + +Legea 142/2023 + OM 210/2024 obliga service-urile sa declare la RAR, la finalul fiecarei +lucrari, datele obligatorii (VIN, nr. inmatriculare, kilometraj, prestatii). ROAAUTO detine +deja toate datele; livrabila adauga o fereastra cu **filtru pe perioada (data facturii)** +care listeaza comenzile inchise+facturate si le transmite in lot la gateway-ul AUTOPASS. +Gateway-ul preia validarea de continut, maparea codurilor, coada, retry, declararea +efectiva la RAR **si evidenta a ce s-a transmis** — ROAAUTO ramane un **client subtire, +fara stocare locala**. + +Fereastra pe perioada are doua roluri: (a) **stingerea backlog-ului** — obligatia e activa +din 01.12.2024, deci prima utilizare recupereaza lucrarile nedeclarate de la start; si +(b) transmiterea periodica curenta, pana cand ROAAUTO-1.1 adauga transmiterea automata +la inchiderea comenzii (fereastra ramane atunci UI de review/exceptii). + +## 2. Non-Goals (anti scope-creep) + +- **NU se creeaza NICIUN tabel/secventa nou(a) in schema Oracle** — configurarea sta in + tabelul standard `optiuni` (mecanism existent), evidenta transmiterilor sta in gateway + si se consulta prin API intr-un cursor temporar. Zero scripturi de instalare. +- NU se apeleaza API-ul RAR direct — doar gateway-ul autopass.romfast.ro. +- NU se face maparea cod operatie → cod prestatie RAR in ROAAUTO (exista in gateway; + operatiile nemapate se rezolva in dashboard-ul web AUTOPASS, tab Mapari). +- NU se implementeaza management de cont/credentiale RAR in ROAAUTO (se face pe web). +- NU se transmit piese/materiale — doar operatiile de manopera (`DEV_OPER`). +- NU se construieste transmitere automata pe timer in aceasta livrabila (poate deveni + ROAAUTO-1.1 dupa ce fluxul manual e verificat in productie). +- NU se modifica fluxul de inchidere/facturare a comenzilor. + +## 3. Date sursa (ROAAUTO / Oracle) + +Filtru: `validat = 1 AND facturat = 1 AND id_tip IN (1,2) AND datafact BETWEEN :d1 AND :d2` +pe view-ul **`auto_istoric_comenzi`** (coloanele complete: +`Programe\oproceduri_vizualizare.prg:447`). **Decis**: TOATE comenzile facturate — +POST GARANTIE (`ID_TIP=1`) si GARANTIE (`ID_TIP=2`), conform nomenclatorului +`DEV_TIP_DEVIZ` (`Scripturi_instalare\initializari.sql:199-208`). REGIE (3), +PREGATIRE (4), REGIE 2 (5), PRODUCTIE si constatarile NU se transmit +(`id_tip >= 3` exclus; oricum majoritatea nu au factura → cad la `facturat=1`). + +| Camp payload AUTOPASS | Sursa ROAAUTO | Observatii | +|---|---|---| +| `vin` | `auto_istoric_comenzi.SERIES` | VARCHAR2(17), serie sasiu | +| `nr_inmatriculare` | `auto_istoric_comenzi.NRINMAT` | VARCHAR2(10) | +| `data_prestatie` | `DATAORAVALID` (data inchiderii/validarii) | format `YYYY-MM-DD`; **decis** — perioada de filtrare ramane pe `DATAFACT` | +| `odometru_final` | `DEV_ORDL.KMINT` (km la primire, snapshot pe comanda) | string numeric; **decis** — singura citire disponibila | +| `prestatii[]` | `DEV_OPER` (sters=0) JOIN `DEV_NOM_NORME` | per operatie: `{cod_op_service: CODOP, denumire: DENOP}` | +| `obs` | `NRORD` + `NRFACT` (ex. `Comanda 1234 / Factura 5678`) | **cheia de corelare** comanda ↔ prezentare in gateway (vezi US-005) | + +Comenzile fara operatii (doar piese) nu se transmit (gateway respinge `PRESTATII_GOALE`). + +## 4. Stories atomice + +### US-001: Configurare conexiune AUTOPASS (tabelul `optiuni`) +**Ca** administrator **vreau** sa configurez URL-ul gateway-ului, cheia API si mediul RAR +(`test`/`prod`) **pentru ca** transmiterea sa functioneze per societate, fara valori hardcodate +si **fara tabele noi**. + +- **Depinde de**: — +- **Fisiere**: `Programe\oproceduri_autopass.prg` (citire config); NIMIC in `Scripturi_instalare\` +- **Mecanism (existent, refolosit)**: tabelul `optiuni` (varname/vartype/varvalue/vardesc/programe) + + procedurile din `COMUN\programe\oinit_optiuni.prg`: + - la pornirea aplicatiei, `optiuni_firma` creeaza variabilele globale din `optiuni` + (CHARACTER → prefix `gc`, ex. `gcAUTOPASS_URL`); + - la runtime, `citeste_optiune('')` citeste din cursorul `crsOptiuni` + (incarcat la pornire de `actualizeaza_optiuni`), `scrie_optiune()` scrie prin + `PACK_SESIUNE.SetOptiune` (merge: randul se creeaza la prima scriere — deci + NU e nevoie de niciun script de seed); + - **UI de configurare (decis)**: valorile se editeaza DIRECT din fereastra de + transmitere (US-004) — panou "Configurare" in aceeasi forma, un singur formular, + fara fereastra separata. (Fiind tot tabelul `optiuni`, raman vizibile si in + fereastra standard Optiuni, dar acela nu e fluxul principal.) +- **Optiuni noi** (toate CHARACTER): + | varname | global | continut | + |---|---|---| + | `AUTOPASS_URL` | `gcAUTOPASS_URL` | URL gateway, implicit `https://autopass.romfast.ro` | + | `AUTOPASS_KEY` | `gcAUTOPASS_KEY` | cheia API (`X-API-Key`) | + | `AUTOPASS_ENV` | `gcAUTOPASS_ENV` | `test` / `prod` / gol = default cont | +- **Acceptance criteria**: + - [ ] `autopass_get_config()` citeste cele 3 valori prin `citeste_optiune()` (fallback pe + variabilele globale `gcAUTOPASS_*` daca exista); la prima rulare, daca `AUTOPASS_URL` + lipseste, se scrie valoarea implicita cu `scrie_optiune('AUTOPASS_URL', + 'https://autopass.romfast.ro', 'URL gateway RAR AUTOPASS')`. + - [ ] `autopass_salveaza_config(tcUrl, tcKey, tcEnv)` scrie cele 3 valori prin + `scrie_optiune()` — apelata de panoul Configurare din fereastra (US-004). + - [ ] Panoul **Configurare** din fereastra de transmitere: URL gateway, cheie API + (textbox cu `PasswordChar` + buton arata/ascunde), mediu RAR (combo: + `Testare`/`Productie`/`implicit cont`), butoanele **Test conexiune** si + **Salveaza**. Test conexiune apeleaza `GET /v1/ping` si afiseaza `mediu`, + `autentificat_cu_cheie`, `are_creds_test/prod`. + - [ ] `AUTOPASS_KEY` goala → fereastra se deschide direct cu panoul Configurare extins + + mesaj clar; gridul si Transmite raman dezactivate pana exista cheie salvata. + - [ ] Dupa **Salveaza**: config-ul se reciteste, ping-ul se reia automat, badge-ul de + mediu si starea butoanelor se actualizeaza fara a inchide fereastra. +- **Verificare E2E**: completare cheie in panou → Salveaza → `GET /v1/ping` intoarce + `autentificat_cu_cheie: true`; valorile apar in tabelul `optiuni`. + +### US-002: Extragere comenzi + operatii pe perioada +**Ca** utilizator **vreau** lista comenzilor inchise si facturate in perioada aleasa, cu +operatiile lor, **pentru ca** sa vad exact ce urmeaza sa se transmita. + +- **Depinde de**: — +- **Fisiere**: `Programe\oproceduri_autopass.prg` — proceduri cursor header + detaliu +- **Acceptance criteria**: + - [ ] Cursor header din `auto_istoric_comenzi`: `id_ordl, nrord, dataoravalid, datafact, nrfact, nrinmat, series, kmint, nume, tip_comanda`, filtrat `validat=1 AND facturat=1 AND id_tip IN (1,2) AND datafact BETWEEN d1 AND d2` (+ `sters=0` unde e cazul). + - [ ] Cursor detaliu: `SELECT o.id_ordl, n.codop, n.denop FROM dev_oper o JOIN dev_nom_norme n ON o.id_norme=n.id_norme WHERE o.sters=0 AND o.id_ordl IN (…)`. + - [ ] Pre-validare locala pe rand (aceleasi reguli ca `app/validation.py`): VIN 17 caractere fara O/I/Q, NRINMAT ≤10 alfanumeric, `data_prestatie ≥ 2024-12-01` si ≤ azi, km numeric — randurile invalide se marcheaza vizual cu motivul si nu se pot bifa. + - [ ] `CODOP` gol/NULL in `DEV_NOM_NORME` → operatia se marcheaza pe rand (motiv "operatie fara cod"); operatiile identice duplicate in aceeasi comanda se deduplica inainte de trimitere. +- **Verificare E2E**: perioada cu comenzi cunoscute → numarul de randuri corespunde cu istoricul comenzilor. + +### US-003: Client HTTP + serializare JSON (`POST /v1/prezentari`) +**Ca** sistem **vreau** o clasa VFP care construieste JSON-ul si face POST cu `X-API-Key` +**pentru ca** transmiterea sa fie robusta si raspunsul parsabil. + +- **Depinde de**: US-001 +- **Fisiere**: `Programe\oproceduri_autopass.prg` (clasa `AutopassClient`); vendored + `nfjsoncreate.prg`/`nfjsonread.prg` in `comun_plugins\utils\` (sursa: `rar-autopass/legacy-vfp/` sau ROAPRELUARECONPRESS) +- **Acceptance criteria**: + - [ ] POST via `winHTTP.winHTTPrequest.5.1` (pattern `D:\ROA\ems_trimite_facturi_spv.prg` → `SendeFactura`), headere `Content-Type: application/json` + `X-API-Key`, body UTF-8. + - [ ] Body: `{"prezentari": [...], "rar_env": }` — prestatii cu `cod_op_service` + `denumire` (maparea ramane in gateway); `raspuns` implicit `minim`. + - [ ] Transmitere in loturi de max 50 prezentari per request; corelarea raspunsului + se face pe `index` **per lot** (index e relativ la cererea curenta, nu global) — + clientul tine maparea `index lot → id_ordl` explicit. + - [ ] Encoding **verificat empiric, nu presupus**: intai se testeaza ce emite + `nfjsoncreate` pentru ă/â/î/ș/ț (escape `\uXXXX` ASCII vs bytes locale). Daca + escapeaza in ASCII → NU se aplica STRCONV (l-ar corupe); daca emite bytes + cp1250 → conversie UTF-8 (`STRCONV(...,9)`) SAU Send pe byte-array cu + `charset=utf-8`. Criteriu: round-trip pe mediul test cu diacritice, verificat + in dashboard. + - [ ] Datele Oracle (DATAORAVALID) → `YYYY-MM-DD` construit explicit + (`STR(YEAR())+'-'+PADL(MONTH(),2,'0')+'-'+PADL(DAY(),2,'0')`), NICIODATA + DTOC/DTOS (depind de SET DATE). Valabil si pentru CSV (US-006). + - [ ] Parametrii de perioada in SQL pass-through ca literali + `TO_DATE('YYYY-MM-DD','YYYY-MM-DD')` — elimina ambiguitatea DMY/MDY. + - [ ] Timeout-uri explicite pe WinHTTP — semnatura reala e + `SetTimeouts(resolve, connect, send, receive)` in ms (ex. 30000 fiecare); + FARA retry automat in client — retrimiterea e manuala si sigura (idempotenta + server-side). Statusul/eroarea WinHTTP bruta intra in contextul logat + (diagnoza proxy-uri client). + - [ ] `DOEVENTS` + verificare Renunta/ESC intre loturi (Send e sincron si blocheaza + UI-ul); peste ~200 comenzi bifate, dialog suplimentar de confirmare. + - [ ] **Cheia API nu se logheaza niciodata** (nici header-ele complete); raspunsurile + logate in LOG.txt contin VIN/nr. inmatriculare → se trateaza ca date personale + (trunchiate la strictul necesar diagnosticului). + - [ ] Clientul ramane **transport pur**: primeste obiecte/cursoare si intoarce + `{index, submission_id, status, motiv, id_prezentare}`; corelarea cu id_ordl si + actualizarea gridului raman in proceduri/forma → testabil dintr-un driver .prg + fara forma. + - [ ] Parsare raspuns minim per element: `index, submission_id, vehicul, status, motiv, id_prezentare`; HTTP ≠ 200 (401/422/5xx) → mesaj cu detaliul din body, fara crash. + - [ ] Erorile HTTP/parse se scriu si in `LOG.txt` (logul runtime existent), cu context: + actiune, lot, cod HTTP, primele 500 caractere din raspuns. + - [ ] Retrimiterea aceluiasi continut e sigura (idempotenta server-side → `deduped`); statusul intors se afiseaza onest (inclusiv `needs_mapping`/`needs_data`/`held`). +- **Verificare E2E**: POST pe mediul RAR **test** cu o comanda reala → `status: queued` si submission vizibil in dashboardul web. + +### US-004: Fereastra "Transmitere AUTOPASS" + buton +**Ca** utilizator **vreau** o fereastra cu perioada, grid cu bife si butonul **Transmite la +AUTOPASS** **pentru ca** sa controlez ce se declara si sa vad rezultatul per comanda. + +- **Depinde de**: US-002, US-003, US-005 +- **Fisiere**: `Ferestre\frm_autopass.scx` (forma noua, in VFP IDE), lansata din procedura + `trimitere_autopass` in `oproceduri_autopass.prg`; **punct de intrare (decis)**: butonul + existent "Export" — `Clase\ofundal_dev.vcx`, clasa `pg_meniu_princ`, + `Page2.Cw8.do_actiune()` (azi: `DO export_comanda_xml IN oproceduri_devize.prg`) devine + meniu contextual, dupa pattern-ul `Page5.Cw3.do_actiune`: + ```foxpro + lnOptiune = xmenu("Export \>] | + +- Configurare (panou pliabil, extins automat cand lipseste cheia) ---------+ + | URL gateway: [https://autopass.romfast.ro ] | + | Cheie API: [************************] [arata] | + | Mediu RAR: [Testare v] [Test conexiune] [Salveaza] | + +--------------------------------------------------------------------------+ + | [x] | NrCmd | DataInch | Factura | NrInmat | VIN | Km | Client | Op | Status | Motiv | + | x | 1234 | 02.07 | 5678 | B99XYZ | ... | .. | ... | 3 | sent | | <- 2: ce se trimite + | | 1235 | 03.07 | 5679 | (gri — fara operatii / VIN invalid) | | + +--------------------------------------------------------------------------+ + | [Bifeaza tot] [Debifeaza] [Valideaza (dry-run)] [TRANSMITE] [Statusuri] | <- 3: actiuni + | [Deschide dashboard AUTOPASS] n bifate / m total | + +--------------------------------------------------------------------------+ + ``` + **Un singur formular**: configurarea (US-001) traieste in acelasi `frm_autopass.scx`, + ca panou pliabil sub bara de perioada — implicit strans cand config-ul e valid, extins + automat (cu focus pe cheie) cand `AUTOPASS_KEY` lipseste sau ping-ul intoarce 401. + Grid dens, limbaj utilitar (conventiile aplicatiei, pattern `frm_istoric_comenzi`); + butonul TRANSMITE e cel mai proeminent, dar la dreapta actiunilor de verificare. +- **Acceptance criteria**: + - [ ] Filtru perioada (implicit luna curenta) pe **data facturii** + buton Actualizeaza. + - [ ] Grid: bifa, nr. comanda, data inchidere, nr./data factura, nr. inmatriculare, VIN, km, client, nr. operatii, **status transmitere** (din situatia gateway-ului, US-005) + motiv. + - [ ] Bifeaza tot / debifeaza tot; comenzile regasite in situatia gateway-ului cu status ne-esuat (`queued`/`sent`/`deduped`/`held`/`needs_*`) vin implicit nebifate. + - [ ] Badge vizibil de mediu: `PRODUCTIE` (rosu) / `Testare` (gri) — regula "rosu = real"; pe `prod`, confirmare explicita inainte de trimitere cu numarul de comenzi. + - [ ] Dupa trimitere, statusul si motivul se actualizeaza pe fiecare rand fara a inchide fereastra (din raspunsul POST, direct in cursorul gridului — nu se persista nimic). + - [ ] Butonul Transmite se dezactiveaza pe durata transmiterii (guard dubla-apasare); + progres vizibil (ex. "lot 2/5..."). + - [ ] Stare goala: perioada fara comenzi → mesaj clar in grid + buton Transmite dezactivat. + - [ ] **Indicator de acoperire** vizibil permanent: "Declarabile in perioada: N | + Transmise (sent): M | Blocate/nedeclarabile: K" — singura metrica ce raspunde + la "suntem conformi?"; K > 0 e evidentiat. (M/K calculate din situatia US-005 + + pre-validarea US-002, la fiecare refresh.) + - [ ] Comenzile fara operatii apar in grid (gri, nebifabile) cu motivul "fara operatii de manopera" — vizibile, nu ascunse tacit. + - [ ] La deschiderea ferestrei se apeleaza `GET /v1/ping`: mediul si starea creds RAR + apar in bara de titlu/status; cheie lipsa/invalida → gridul si actiunile raman + dezactivate, panoul Configurare se extinde automat cu mesaj (US-001) — utilizatorul + corecteaza si salveaza fara sa inchida fereastra. + - [ ] Schimbarea mediului RAR din panoul Configurare + Salveaza → badge-ul si situatia + (US-005) se reincarca imediat (statusurile difera intre test si prod). + - [ ] Buton **Valideaza (dry-run)**: `POST /v1/prezentari/valideaza` pe selectie — + arata verdictul per comanda (status_estimat, erori, coduri nemapate) FARA enqueue; + recomandat inainte de prima trimitere pe Productie. + - [ ] Buton/link **Deschide dashboard AUTOPASS** (browser, URL-ul din config). + - [ ] **Vocabular statusuri** (gateway → eticheta romaneasca + culoare + pasul urmator; + NU se afiseaza tokenii englezesti bruti): + | Gateway | Eticheta | Culoare | Pas urmator afisat | + |---|---|---|---| + | `queued` | In asteptare la RAR | galben | se actualizeaza cu butonul Statusuri | + | `sent` | Transmis | verde | — (afiseaza nr. prezentare) | + | `needs_mapping` | Cod fara mapare RAR | portocaliu | rezolva in dashboardul AUTOPASS, tab Mapari | + | `needs_data` | Date incomplete | portocaliu | corecteaza comanda si retrimite | + | `held` | Retinut (Auto OFF) | rosu | elibereaza din dashboard sau activeaza Auto | + | `error` | Eroare | rosu | vezi motivul; corecteaza si retrimite | + | `deduped` | Deja transmis | gri | nimic de facut | + | *(negasit in situatie)* | Netransmis | alb | bifeaza si transmite | + - [ ] **Fail-closed la deschidere**: pana raspunde ping-ul, badge = "Se verifica mediul...", + Transmite si Valideaza dezactivate; wait cursor pe apelurile sincrone. + - [ ] Badge-ul de mediu = banda plina in partea de sus a formei (nu doar title bar) + + cuvantul PRODUCTIE/TESTARE si langa butonul Transmite; niciodata doar culoare. + Dialogul de confirmare repeta cuvantul mediului + numarul de comenzi bifate valide. + - [ ] Verdictul dry-run se afiseaza pe **canal separat** (coloana "Verdict validare" + + banner sumar "Validare: 12 OK, 2 coduri nemapate") — NU suprascrie coloana de + status transmitere. + - [ ] **Sumar la final de transmitere**: dialog/linie "18 transmise, 3 in asteptare, + 1 cod fara mapare — vezi randurile portocalii." + - [ ] Textbox read-only sub grid cu status+motiv complet al randului curent + (motivele lungi nu incap in celula de grid). + - [ ] "Bifeaza tot" selecteaza DOAR randurile eligibile (valide si netransmise). + - [ ] Buton **Renunta** onorat intre loturi (WinHTTP e sincron — singurul punct de anulare). + - [ ] Nota implementare VFP: randuri invalide = `DynamicBackColor`/`DynamicForeColor` + + `When() = .F.` pe coloana de bifa (gridul VFP nu are readonly per celula). +- **Verificare E2E**: flux complet pe mediul test — filtrare, bifare, transmitere, statusuri corecte in grid. + +### US-005: Situatia prezentarilor din gateway (cursor temporar, FARA stocare locala) +**Ca** utilizator **vreau** sa vad, per comanda din perioada, ce s-a transmis deja si cu ce +status **pentru ca** sa nu retransmit inutil si sa pot proba conformarea — **fara** ca ROAAUTO +sa tina vreo evidenta locala. + +- **Depinde de**: US-003 +- **Fisiere**: `Programe\oproceduri_autopass.prg` — procedura `autopass_situatie(d1, d2)` + → cursor temporar `crs_autopass_situatie`. NICIUN tabel, NICIO secventa, NICIUN script. +- **Principiu**: gateway-ul este **singura evidenta** a transmiterilor (are deja submissions, + statusuri, istoricul complet si dashboardul web). ROAAUTO doar interogheaza si coreleaza + in memorie, la fiecare refresh; la inchiderea ferestrei totul dispare. +- **Acceptance criteria**: + - [ ] `autopass_situatie(d1, d2)` apeleaza endpoint-ul de listare al gateway-ului + (`GET /v1/prezentari?data_de=YYYY-MM-DD&data_pana=YYYY-MM-DD`, filtrat pe + `data_prestatie` si pe cheia API a societatii) si construieste cursorul temporar + `crs_autopass_situatie`: `submission_id, vin, nr_inmatriculare, data_prestatie, + status, motiv, id_prezentare, obs`. + **Dependenta gateway**: daca endpoint-ul de listare pe perioada nu exista inca in + `rar-autopass`, se adauga INTAI acolo (produs propriu) — blocheaza US-005, nu se + ocoleste cu stocare locala. + - [ ] **Corelare** prezentare ↔ comanda: primar pe `obs` (`Comanda NRORD / Factura NRFACT` + — string determinist generat de ROAAUTO la trimitere); fallback pe + `vin + data_prestatie`. Rezultatul populeaza coloanele status/motiv din gridul US-004. + - [ ] Comanda negasita in situatie = "Netransmis" (eligibila la bifare); comanda gasita + cu orice status ne-esuat vine implicit nebifata (protectie la retransmitere, pe + langa idempotenta server-side). + - [ ] Buton **Actualizeaza statusuri** = re-apeleaza `autopass_situatie()` pe perioada + afisata si reimprospateaza gridul (aduce `queued` → `sent` + `id_prezentare` dupa + ce workerul gateway proceseaza). + - [ ] Acelasi refresh ruleaza **automat la deschiderea ferestrei** (decizia 6, poarta + /autoplan) si dupa fiecare transmitere, cu wait cursor. + - [ ] Situatia indisponibila (endpoint picat / eroare) → gridul afiseaza "Status + necunoscut — situatia gateway indisponibila" pe toate randurile, transmiterea + ramane posibila DOAR cu confirmare explicita (idempotenta face retrimiterea sigura); + eroarea se logheaza in LOG.txt. +- **Verificare E2E**: dupa ce workerul gateway proceseaza, Actualizeaza statusuri aduce + `sent` + `id_prezentare` in grid; cazul 2 comenzi / acelasi VIN / aceeasi zi se coreleaza + corect pe `obs` (chei de idempotenta diferite prin NRORD). + +### US-006: Export operatii facturate CSV (canal alternativ + mod degradat oficial) +**Ca** utilizator **vreau** sa export operatiile facturate din perioada intr-un CSV compatibil +cu importul web AUTOPASS **pentru ca** sa pot incarca manual lucrarile in dashboard cand +prefer canalul de import — si ca **fallback desemnat** cand API-ul e indisponibil sau +neconfigurat (exportul functioneaza si cu config-ul API gol; obligatia legala nu depinde +de un singur canal). + +- **Depinde de**: US-002 +- **Fisiere**: `Programe\oproceduri_autopass.prg` — procedura `export_operatii_csv` +- **Acceptance criteria**: + - [ ] Cere perioada (data facturii), refoloseste cursoarele din US-002 (acelasi filtru `validat=1, facturat=1, id_tip IN (1,2)`). + - [ ] Format acceptat de `POST /v1/import` / upload web (vezi `rar-autopass/exemple/prezentari_test.csv`): separator `;`, antet `VIN;Numar inmatriculare;Data prestatie;Odometru initial;Odometru final;Operatie;Observatii`, **un rand per operatie** (aceeasi comanda se repeta pe mai multe randuri — importul le grupeaza pe vehicul+data). + - [ ] `Data prestatie` = `DATAORAVALID` in format `YYYY-MM-DD`; `Odometru final` = `KMINT`; `Operatie` = `CODOP` (sau `DENOP` — coloana `Operatie` accepta text liber, maparea o face gateway-ul); `Observatii` = `Comanda NRORD / Factura NRFACT`. + - [ ] Fisier salvat cu PUTFILE/GETDIR, encoding compatibil (diacriticele din denumiri nu sparg importul). +- **Verificare E2E**: CSV-ul exportat se incarca in dashboardul web AUTOPASS si coloanele se auto-mapeaza corect. + +## 5. Riscuri + +- **Calitatea datelor istorice** (VIN gol/invalid, NRINMAT cu spatii/cratime, km 0): + mitigat prin pre-validarea din US-002 (randuri marcate, netransmisibile) + statusul onest + `needs_data` intors de gateway. +- **Dubla declarare la RAR**: mitigat dublu — idempotenta server-side (dedup pe continut) + + situatia din gateway (US-005: comenzile regasite vin nebifate). +- **Corelarea pe `obs`** depinde de formatul fix `Comanda NRORD / Factura NRFACT`: + formatul se genereaza dintr-un singur loc (functie dedicata, folosita si la POST, si la + corelare, si la CSV) — nu se construieste ad-hoc in doua locuri. +- **Trimitere accidentala pe Productie** (ireversibila conform Legii 142): badge rosu + + dialog de confirmare cu mediul si numarul de comenzi (US-004). +- **Operatii nemapate in gateway**: primele loturi vor produce `needs_mapping`; se rezolva + o singura data per cod in dashboardul web (sau bulk cu `tools/import_dbf.py` din + `legacy-vfp/mapare_prestatii.DBF`, care contine deja mapari ROAAUTO). +- **VFP + TLS**: `winHTTP` pe Windows vechi poate esua pe TLS 1.2; pattern-ul e deja folosit + in productie pentru ANAF SPV (`ems_trimite_facturi_spv.prg`), deci riscul e cunoscut/mic. +- **Endpoint de listare in gateway**: US-005 presupune `GET /v1/prezentari` cu filtru pe + perioada; daca lipseste, se implementeaza intai in `rar-autopass` (blocant asumat — + NU se compenseaza cu tabel local). + +## 6. Intrebari deschise + +> Se rezolva cu utilizatorul INAINTE de executie. + +Decizii luate (fost intrebari 1-4): + +1. **DECIS** — `data_prestatie` = `DATAORAVALID` (data validarii/inchiderii comenzii); + perioada de filtrare ramane pe `DATAFACT`. +2. **DECIS (confirmat la poarta /autoplan, dupa contestarea ambelor voci independente)** — + `odometru_final` = `KMINT`. Justificare asumata: e singura citire de bord existenta in + date, e o citire reala a vehiculului in perioada lucrarii, iar contractul RAR cere + `odometruFinal` obligatoriu (a trimite doar `initial` ar fi respins). Semantic, KMINT + e km la primire — decizie documentata, de revizuit doar daca RAR o conteste. +3. **DECIS** (revizuit la review-ul CEO) — TOATE comenzile facturate: post-garantie + (`ID_TIP=1`) si garantie (`ID_TIP=2`). Regie/pregatire/productie/constatare + (`id_tip >= 3`) NU se transmit. +4. **DECIS** — punct de intrare: butonul "Export" din `Clase\ofundal_dev.vcx` + (`pg_meniu_princ.Page2.Cw8.do_actiune`) devine `xmenu` cu 3 optiuni: + Export xml (existent) / Export operatii facturate csv (US-006) / + Trimitere operatii facturate in autopass.romfast.ro (US-004). + +5. **DECIS** — o singura cheie API per baza de date (societate); optiunile `AUTOPASS_*` + din tabelul `optiuni` tin o singura configurare. Context (poarta /autoplan): clientii + actuali au un singur service auto per firma; daca apar clienti cu mai multe + service-uri/conturi RAR, config-ul se extinde atunci. +6. **DECIS (poarta /autoplan)** — la deschiderea ferestrei, pe langa ping, se + actualizeaza automat situatia prezentarilor (US-005) pentru perioada afisata; + butonul "Actualizeaza statusuri" ramane pentru refresh manual. +7. **DECIS (revizie 2026-07-09, Marius)** — **ZERO tabele noi**: configurarea in tabelul + `optiuni` (mecanismul existent `oinit_optiuni.prg`), evidenta transmiterilor NUMAI in + gateway, consultata prin API intr-un cursor temporar. Fostele `AUTO_AUTOPASS_CONFIG`, + `AUTO_AUTOPASS_TRIMITERI`, `SEQ_AUTOPASS_TRIMITERI` sunt eliminate din plan (si + revertate din `Scripturi_instalare\`). +8. **DECIS (revizie 2026-07-10, Marius)** — **un singur formular**: URL-ul, cheia API si + mediul RAR se editeaza direct din fereastra de transmitere (panou Configurare pliabil + in `frm_autopass.scx`), nu dintr-o fereastra separata; salvarea trece prin + `scrie_optiune()` (`autopass_salveaza_config`). + +Nu mai exista intrebari deschise — PRD aprobat prin poarta /autoplan (2026-07-09), +revizuit conform deciziilor 7-8. + +## 7. Valuri de executie + +``` +Val 0: [SPIKE calitate date] ← un singur SQL, INAINTE de orice implementare: + din comenzile facturate (id_tip 1,2) de la 01.12.2024, + ce procent au VIN valid 17 car., NRINMAT si KMINT > 0? + Rezultatul calibreaza asteptarile si decide daca e + nevoie si de un proiect de remediere a datelor. + [VERIF gateway] ← exista GET /v1/prezentari (listare pe perioada)? + daca nu → se adauga intai in rar-autopass. +Val 1: [US-001] [US-002] ← independente, zone distincte → paralel +Val 2: [US-003] [US-005] [US-006]← US-003 dupa config; US-005 dupa client HTTP; US-006 dupa cursoare +Val 3: [US-004] ← forma finala + xmenu in ofundal_dev.vcx (VFP IDE) +``` + +--- + +## Anexa review /autoplan (CEO + Design + Eng, 2026-07-09) + +### NOT in scope (considerat si amanat explicit) +- **Transmitere automata la validarea comenzii** (hook in `validare_comenzi`) — ROAAUTO-1.1, + dupa ce fluxul manual e verificat in productie. +- **Vizualizare/rezolvare mapari pending in ROAAUTO** (`GET /v1/mapari/pending`) — se face + in dashboardul web AUTOPASS; duplicarea editorului in VFP nu aduce valoare. +- **Listare/tiparire jurnal transmiteri** (raport FRX) — gridul + dashboardul web acopera + nevoia; raport doar la cerere ulterioara. +- **Actualizare automata a statusurilor pe timer** — refresh-ul la deschidere + butonul + manual "Actualizeaza statusuri" sunt suficiente in v1.0. +- **Criptarea cheii API** — cheia sta in clar in `optiuni.varvalue` (AUTOPASS_KEY), ca + restul configurarilor din schema; acces limitat la schema aplicatiei. Se poate cripta ulterior. + +### What already exists (refolosit, nu reconstruit) +- `auto_istoric_comenzi` — view-ul cu tot header-ul necesar (comanda+factura+vehicul+km). +- `DEV_OPER` + `DEV_NOM_NORME` — operatiile cu CODOP/DENOP. +- **Tabelul `optiuni` + `oinit_optiuni.prg`** (`optiuni_firma`, `citeste_optiune`, + `scrie_optiune`, fereastra standard Optiuni) — configurarea AUTOPASS fara tabele noi. +- `D:\ROA\ems_trimite_facturi_spv.prg` (`SendeFactura`) — pattern-ul WinHTTP + header auth, + deja folosit in productie pentru ANAF SPV (inclusiv TLS pe Windows-urile clientilor). +- `nfjsoncreate/nfjsonread` — biblioteca JSON VFP (vendored din rar-autopass/legacy-vfp). +- Gateway-ul insusi: validare continut, mapare coduri, idempotenta, coada, retry, dashboard + **si evidenta completa a transmiterilor** (motivul pentru care ROAAUTO nu mai tine jurnal). +- Pattern-ul `xmenu` + `do_actiune` din `ofundal_dev.vcx` (Page5.Cw3) pentru punctul de intrare. + +### Error & Rescue Registry (US-003 `AutopassClient` + fereastra) +| Codepath | Ce poate esua | Tratare | Utilizatorul vede | +|---|---|---|---| +| WinHTTP Send | timeout / DNS / TLS / gateway down | catch `loEx`, log LOG.txt, opreste lotul curent | "Gateway indisponibil: . Reincearca mai tarziu." | +| HTTP 401 | cheie API invalida/lipsa | opreste tot, nu continua loturile; panoul Configurare se extinde | "Cheie API respinsa — corecteaza cheia in panoul Configurare." | +| HTTP 422 | mediu RAR indisponibil / limita plan / JSON invalid | afiseaza `detail`-ul din body per lot | mesajul gateway-ului, netradus | +| HTTP 5xx | eroare interna gateway | log + mesaj, lotul se poate retrimite (idempotent) | "Eroare gateway (500). Retrimiterea e sigura." | +| Parse JSON raspuns | raspuns malformat/gol | catch nfjsonread, log body brut in LOG.txt | "Raspuns neasteptat de la gateway — vezi LOG.txt" | +| SQL pass-through Oracle | conexiune cazuta / view lipsa | pattern-ul standard al aplicatiei (verificare cursor) | eroarea standard a aplicatiei | +| GET situatie (US-005) | endpoint picat / raspuns invalid | statusuri "necunoscut" in grid, transmitere doar cu confirmare explicita; log | "Situatia gateway indisponibila — statusurile nu au putut fi citite" | +| Rand `needs_data`/`needs_mapping` | date incomplete / cod nemapat | status onest in grid, motiv afisat | motivul exact (ex. "Coduri fara mapare RAR: ...") | + +**Reguli**: fara catch-all mut — orice Catch scrie context in LOG.txt; niciun esec nu e silentios +(fiecare rand bifat primeste un status vizibil sau un motiv de esec). + +### Failure Modes Registry +| Codepath | Failure mode | Tratat? | Vizibil? | +|---|---|---|---| +| Transmitere lot | crash intre loturi (lot 2/5 trimis, 3 nu) | gateway-ul are submissions-urile trimise; refresh-ul situatiei (US-005) le arata; retrimiterea e idempotenta | DA — statusuri per rand la urmatorul refresh | +| Dubla transmitere | acelasi continut retrimis | dedup server-side → `deduped` | DA — status "deja transmis" | +| KMINT=0 / VIN gol | date istorice proaste | pre-validare US-002 → rand marcat, nebifabil | DA — motiv pe rand | +| Diacritice corupte | encoding gresit in JSON | encoding verificat empiric (US-003) | test E2E cu denumiri cu diacritice | +| IN (...) > 1000 elemente | perioada mare, Oracle ORA-01795 | detaliul se citeste pe chunk-uri de max 500 id-uri | transparent | +| Situatie indisponibila | GET listare esueaza | statusuri necunoscute + confirmare explicita la transmitere | DA — mesaj pe grid | +| Trimitere pe prod din greseala | mediu gresit | badge rosu + dialog de confirmare cu mediu si numar | DA — confirmare explicita | + +### Decizii auto-luate (audit trail — principiile /autoplan) +| # | Faza | Decizie | Clasificare | Principiu | +|---|------|---------|-------------|-----------| +| 1 | CEO 0C-bis | Client subtire → gateway (vs CSV-only vs API RAR direct in VFP) | mecanic | P1 completeness, P4 DRY | +| 2 | CEO 0D | Accept: buton "Deschide dashboard AUTOPASS" | auto-accept | P2 blast radius, S | +| 3 | CEO 0D | Accept: ping + badge mediu la deschiderea ferestrei | auto-accept | P2, era partial in US-004 | +| 4 | CEO 0D | Accept: buton Valideaza (dry-run) via `/v1/prezentari/valideaza` | auto-accept | P1 — verificare fara efecte | +| 5 | CEO 0D | Defer: auto-send la validare, mapari pending, listare jurnal, timer statusuri | auto-defer | P3 — in afara blast radius | +| 6 | CEO S2 | Timeout-uri explicite + fara retry client (idempotenta server) | mecanic | P5 explicit | +| 7 | CEO S4 | Encoding UTF-8 verificat + test diacritice | mecanic | P1 | +| 8 | CEO S7 | Chunk-uri de max 500 id-uri la detaliul operatiilor | mecanic | corectitudine Oracle | +| 9 | Premise gate | Scope: toate comenzile facturate (id_tip 1,2), fara regie/productie | decizia utilizatorului | — | +| 10 | Design P2 | Tabel stari interactiune adaugat in US-004 (loading/empty/error/success) | mecanic | P1 | +| 11 | Eng S1 | Corelare index→id_ordl per lot, explicit in US-003 | mecanic | P5 | +| 12 | Design voice #1 | Vocabular statusuri RO (fara tokeni englezesti in grid) | mecanic | P1 — utilizatorul e consilier service, nu dev | +| 13 | Design voice #2/#3 | Banda de mediu + fail-closed pana la ping | mecanic | P1 — actiune ireversibila | +| 14 | Design voice #4 | Verdict dry-run pe canal separat de statusul real | mecanic | P5 — evita retrimiterile accidentale | +| 15 | Design voice #5-#9 | Sumar final, textbox motiv, Bifeaza-tot doar eligibile, Renunta intre loturi, nota DynamicBackColor | mecanic | P1/P5 | +| 16 | CEO voice #2 | Reframe: v1.0 = stingere backlog (obligatie din 01.12.2024) + transmitere curenta | mecanic | P6 | +| 17 | CEO voice #4 | Val 0: SPIKE SQL calitate date (procent comenzi declarabile) inainte de implementare | auto-accept | P1 — un singur SQL | +| 18 | CEO voice #5 | Indicator de acoperire (declarabile/transmise/blocate) in fereastra | auto-accept | P1 — metrica de conformare | +| 19 | CEO voice #7 | CSV US-006 desemnat mod degradat oficial (functioneaza fara config API) | mecanic | P3 | +| 20 | Eng voice #2 | Encoding verificat empiric (nfjsoncreate) inainte de STRCONV | mecanic | P5 — nu pe incredere | +| 21 | Eng voice #3/#12 | Date YYYY-MM-DD construite explicit + TO_DATE in SQL | mecanic | P5 | +| 22 | Eng voice #4 | DOEVENTS + Renunta intre loturi + prag confirmare 200 | mecanic | P1 | +| 23 | ~~Eng voice #5~~ | ~~Jurnal append-only cu PK secventa~~ **ANULAT de decizia 7 (Marius): fara jurnal local — evidenta e in gateway, consultata prin API** | decizia utilizatorului | — | +| 24 | Eng voice #6-#9 | Dedup VIN+zi verificat in E2E, cheia API nelogata, CODOP gol marcat, client transport pur | mecanic | P1/P5 | +| 25 | Revizie Marius | Config in `optiuni` (mecanism existent), zero tabele noi, situatie in cursor temporar | decizia utilizatorului | — | +| 26 | Revizie Marius | Un singur formular: panou Configurare (URL/cheie/mediu) direct in fereastra de transmitere | decizia utilizatorului | — | + +Contestari NEauto-decise (poarta finala): C1 enqueue-on-close vs manual (CEO voice #1); +C2 KMINT ca odometru_final (CEO #3 + Eng #1 — ambele voci independente); C3 multi-CUI (CEO #6); +T1 auto-refresh statusuri la deschiderea ferestrei (taste). + +### Arhitectura (Eng S1, revizuita — zero tabele noi) +``` +ofundal_dev.vcx (Page2.Cw8 xmenu) + |1 Export xml -> export_comanda_xml (existent, oproceduri_devize.prg) + |2 Export CSV -> export_operatii_csv \ + |3 Trimitere AUTOPASS -> trimitere_autopass } oproceduri_autopass.prg (NOU) + | + frm_autopass.scx (NOU) + | | + cursoare Oracle AutopassClient (WinHTTP, JSON UTF-8) + (auto_istoric_comenzi, | + dev_oper+dev_nom_norme) +--> https://autopass.romfast.ro + | /v1/ping /v1/prezentari + config: tabel `optiuni` /v1/prezentari/valideaza + (AUTOPASS_URL/KEY/ENV via /v1/prezentari?perioada (situatie) + citeste_optiune/gc*) | + crs_autopass_situatie (cursor temporar, + corelat pe `obs` cu gridul — NU se + persista nimic local) +``` +Cuplaj: fereastra depinde doar de procedurile din `oproceduri_autopass.prg`; clientul HTTP +nu stie de Oracle (primeste/intoarce cursoare/obiecte) → reutilizabil pentru auto-send 1.1. + +### Acoperire de test (Eng S3 — verificare manuala E2E, VFP nu are framework de teste) +``` +CODEPATHS VERIFICARE +AUTOPASS_KEY goala -> panou Configurare extins E2E: goleste cheia, redeschide fereastra +salvare config din panou -> ping + badge E2E: schimba mediul test<->prod, Salveaza +GET /v1/ping ok/401 E2E: cheie buna / cheie gresita +cursor header (perioada cu/fara comenzi) E2E: 2 perioade cunoscute +pre-validare (VIN invalid, km 0, fara operatii)E2E: comenzi istorice reale +dry-run /valideaza E2E: verdictele = trimiterea reala +POST lot ok / 422 / 5xx E2E pe mediul RAR TEST +dedup (retrimitere aceleasi comenzi) E2E: a doua trimitere -> deduped +diacritice in DENOP E2E: comanda cu operatii cu s,t,a,i +situatie: corelare obs + refresh statusuri E2E: queued -> sent + id_prezentare in grid +situatie indisponibila E2E: URL gresit -> statusuri necunoscute + confirmare +CSV export -> import web E2E: upload in dashboard, automap coloane +xmenu (3 optiuni, Esc) E2E: fiecare ramura + anulare +driver .prg fara forma (1 comanda hand-built) E2E: acopera encoding/headers/timeouts izolat +2 comenzi, acelasi VIN, aceeasi zi E2E: corelare corecta pe obs (NRORD diferit) +``` +Toate pe mediul RAR **test** inainte de orice trimitere pe prod. + +### Dream state delta +12 luni: declararea RAR e zero-touch — la validarea comenzii, transmiterea pleaca automat, +statusul apare pe comanda, iar utilizatorul intervine doar la `needs_mapping`/`needs_data`. +Planul v1.0 construieste exact fundatia reutilizabila (config in optiuni, client HTTP, +situatie din gateway, fereastra); 1.1 adauga hook-ul de auto-send. Nicio piesa din v1.0 +nu se arunca. + +## Raport VERIFY + +> Se completeaza la faza VERIFY: PASS/FAIL per criteriu, cu dovezi (ping, POST pe RAR test, +> capturi grid + dashboard web). Lipseste pana la VERIFY. + +## GSTACK REVIEW REPORT + +| Review | Trigger | Why | Runs | Status | Findings | +|--------|---------|-----|------|--------|----------| +| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | CLEAN (PLAN via /autoplan) | 6 propuneri, 3 acceptate, 4 amanate; contestarile C1-C3 rezolvate la poarta | +| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — (Codex neinstalat, voci = subagent-only) | — | +| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | CLEAN (PLAN via /autoplan) | 12 constatari, 0 critical gaps, toate incorporate | +| Design Review | `/plan-design-review` | UI/UX gaps | 1 | CLEAN (FULL via /autoplan) | scor 5/10 → 9/10, 9 decizii adaugate | +| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | SKIPPED | fara scope developer-facing | + +- **VERDICT:** CEO + ENG + DESIGN CLEARED — gata de implementare. Decizii poarta finala: + C1=A (manual v1.0, auto-send in 1.1), C2=A (KMINT ca odometru_final, documentat), + C3=A (o cheie/BD; multi-service se revizuieste la nevoie), T1=A (auto-refresh la deschidere). +- **Revizie post-aprobare (2026-07-09, Marius)**: decizia 7 — zero tabele noi; US-001 + rescris pe tabelul `optiuni`, US-005 rescris pe situatia din gateway (cursor temporar); + scripturile SQL revertate. + +NO UNRESOLVED DECISIONS