1485 lines
56 KiB
Plaintext
1485 lines
56 KiB
Plaintext
*****************************************************************************************
|
|
* 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(NVL(citeste_optiune('AUTOPASS_URL'),''))
|
|
lcKey = ALLTRIM(NVL(citeste_optiune('AUTOPASS_KEY'),''))
|
|
lcEnv = ALLTRIM(NVL(citeste_optiune('AUTOPASS_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
|
|
IF EMPTY(lcKey)
|
|
scrie_optiune('AUTOPASS_KEY', lcUrl, 'Cheie API gateway 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
|
|
|
|
|
|
*****************************************************************************************
|
|
* Mesaj de eroare uman pt un status HTTP de la gateway-ul AUTOPASS - folosit oriunde in UI
|
|
* se afiseaza rezultatul unui apel esuat (test conexiune, situatie), ca sa nu mai trimitem
|
|
* utilizatorul la "vezi LOG.txt" cand avem deja un motiv clar de aratat direct pe forma.
|
|
*****************************************************************************************
|
|
FUNCTION autopass_mesaj_eroare_http
|
|
LPARAMETERS tnStatus
|
|
|
|
DO CASE
|
|
CASE tnStatus = 401
|
|
RETURN 'Cheie API invalida sau neautorizata (HTTP 401) - corecteaza cheia in panoul Configurare.'
|
|
CASE tnStatus = 0
|
|
RETURN 'Gateway AUTOPASS indisponibil (fara raspuns) - verifica URL-ul si conexiunea la retea.'
|
|
OTHERWISE
|
|
RETURN 'Gateway AUTOPASS a raspuns cu eroare (HTTP ' + TRANSFORM(tnStatus) + ').'
|
|
ENDCASE
|
|
ENDFUNC && autopass_mesaj_eroare_http
|
|
|
|
|
|
*****************************************************************************************
|
|
* Testeaza conexiunea la gateway-ul AUTOPASS (GET /v1/ping)
|
|
* Intoarce obiect: lSucces, cMediu, lAutentificatCuCheie, lAreCredsTest, lAreCredsProd, cEroare
|
|
*****************************************************************************************
|
|
FUNCTION autopass_test_conexiune
|
|
LPARAMETERS tcUrl, tcKey
|
|
LOCAL loConfig, loHTTP As "winHTTP.winHTTPrequest.5.1", loReturn As "empty", loEx As Exception
|
|
LOCAL lcServer, lnStatus, lcResponseText, loJson, lcUrl, lcKey
|
|
|
|
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()
|
|
|
|
*-- URL/cheie efective: daca apelantul le trimite explicit (panoul Configurare, ca sa poata
|
|
*-- testa o cheie inainte de a o salva), au prioritate; altfel se cade pe configul salvat.
|
|
lcUrl = IIF(VARTYPE(tcUrl) = 'C' AND !EMPTY(ALLTRIM(tcUrl)), ALLTRIM(tcUrl), ALLTRIM(loConfig.cUrlGateway))
|
|
lcKey = IIF(VARTYPE(tcKey) = 'C' AND !EMPTY(ALLTRIM(tcKey)), ALLTRIM(tcKey), ALLTRIM(loConfig.cApiKey))
|
|
IF EMPTY(lcUrl) OR EMPTY(lcKey)
|
|
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 = lcUrl + [/v1/ping]
|
|
|
|
loHTTP = Createobject('winHTTP.winHTTPrequest.5.1')
|
|
loHTTP.Open('GET', lcServer, .F.)
|
|
loHTTP.SetTimeouts(30000, 30000, 30000, 30000)
|
|
loHTTP.setRequestHeader('X-API-Key', lcKey)
|
|
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
|
|
|
|
IF lnStatus <> 200
|
|
loReturn.cEroare = autopass_mesaj_eroare_http(lnStatus)
|
|
RETURN loReturn
|
|
ENDIF
|
|
|
|
loJson = Nfjsonread(lcResponseText)
|
|
IF VARTYPE(loJson) <> 'O'
|
|
loReturn.cEroare = 'Raspuns invalid (JSON malformat) de la gateway-ul AUTOPASS.'
|
|
RETURN loReturn
|
|
ENDIF
|
|
|
|
*-- nume de proprietati confirmate din contract (rar-autopass app/api/v1/integrare_router.py:74-82,
|
|
*-- GET /v1/ping): raspunsul e snake_case (mediu, autentificat_cu_cheie, are_creds_test,
|
|
*-- are_creds_prod, are_creds_rar) - NU camelCase. nfjsonread.prg (procedura propertyName)
|
|
*-- pastreaza numele de proprietate JSON asa cum vine (litere/cifre/underscore), fara nicio
|
|
*-- conversie de convenite, deci underscore-urile din JSON raman underscore-uri in obiectul VFP.
|
|
loReturn.lSucces = .T.
|
|
loReturn.cMediu = IIF(TYPE('loJson.mediu') = 'C', loJson.mediu, '')
|
|
loReturn.lAutentificatCuCheie = IIF(TYPE('loJson.autentificat_cu_cheie') = 'L', loJson.autentificat_cu_cheie, .F.)
|
|
loReturn.lAreCredsTest = IIF(TYPE('loJson.are_creds_test') = 'L', loJson.are_creds_test, .F.)
|
|
loReturn.lAreCredsProd = IIF(TYPE('loJson.are_creds_prod') = 'L', loJson.are_creds_prod, .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, lcCursorRaw
|
|
|
|
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('AUTO_ISTORIC_COMENZI', 'STERS')
|
|
lcFiltruSters = ' AND sters = 0'
|
|
ENDIF
|
|
|
|
lcSql = "SELECT id_ordl, nrord, dataoravalid, datafact, nrfact, nrinmat, series, kmint, nume, tip_comanda " + ;
|
|
"FROM auto_istoric_comenzi " + ;
|
|
"WHERE validat = 1 AND facturat = 1 AND id_tip IN (1,2) " + ;
|
|
"AND datafact BETWEEN " + lcD1 + " AND " + lcD2 + lcFiltruSters
|
|
|
|
lcCursorRaw = lcCursorHeader + '_raw'
|
|
IF USED(lcCursorRaw)
|
|
USE IN (lcCursorRaw)
|
|
ENDIF
|
|
|
|
lnSucces = goExecutor.oExecute(lcSql, lcCursorRaw)
|
|
IF lnSucces < 0
|
|
IF TYPE('goLog') = 'O'
|
|
goLog.Log('autopass_cursor_comenzi: ' + goExecutor.cEroare, PROGRAM())
|
|
ENDIF
|
|
RETURN ''
|
|
ENDIF
|
|
|
|
*-- rematerializam ca cursor local simplu, sub numele final, CU coloanele suplimentare deja
|
|
*-- incluse in SELECT (nu ALTER TABLE dupa) - cursorul intors de goExecutor.oExecute() poate
|
|
*-- ramane legat de conexiune ("remote table"/cursor cu operatii de structura restrictionate),
|
|
*-- caz in care ALTER TABLE separat pica ("Function is not supported on remote tables",
|
|
*-- apoi "Invalid operation for the cursor" chiar si dupa o rematerializare simpla). Taina de
|
|
*-- "remote" persista pe acest cursor oricat de multe ori il rematerializam - de-aia coloanele
|
|
*-- de UI/corelare (lbifat/ncntop/cstatusro/cverdict, status_autopass/motiv_autopass/
|
|
*-- id_prezentare_autopass), adaugate altfel prin ALTER TABLE mai jos in Load()/incarca_date()/
|
|
*-- autopass_coreleaza_situatie(), trebuie incluse direct AICI, la creare.
|
|
SELECT *, .F. AS lvalid, SPACE(100) AS motiv_invalid, ;
|
|
.F. AS lbifat, 0 AS ncntop, SPACE(40) AS cstatusro, SPACE(200) AS cverdict, ;
|
|
SPACE(30) AS status_autopass, SPACE(254) AS motiv_autopass, SPACE(60) AS id_prezentare_autopass ;
|
|
FROM (lcCursorRaw) INTO CURSOR (lcCursorHeader) READWRITE
|
|
USE IN (lcCursorRaw)
|
|
|
|
SELECT (lcCursorHeader)
|
|
SCAN
|
|
lcMotivRand = ''
|
|
DO CASE
|
|
CASE VARTYPE(dataoravalid) = 'T'
|
|
ldDataPrest = TTOD(dataoravalid)
|
|
CASE VARTYPE(dataoravalid) = 'D'
|
|
ldDataPrest = dataoravalid
|
|
OTHERWISE
|
|
ldDataPrest = {}
|
|
ENDCASE
|
|
|
|
*-- nr. inmatriculare normalizat aici (sters spatii/cratime/puncte, cf. gateway), ca
|
|
*-- valoarea propagata mai departe in cursor (CSV, payload catre AutopassClient) sa fie
|
|
*-- deja normalizata, nu doar cea validata local
|
|
llOk = autopass_valideaza_rand(series, nrinmat, ldDataPrest, kmint, @lcMotivRand)
|
|
REPLACE nrinmat WITH autopass_normalizeaza_nrinmat(nrinmat), ;
|
|
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, lcCursorRaw
|
|
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 dev_oper o " + ;
|
|
"JOIN 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 dev_oper o " + ;
|
|
"JOIN 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
|
|
|
|
lcCursorRaw = lcCursorDetaliu + '_raw'
|
|
IF USED(lcCursorRaw)
|
|
USE IN (lcCursorRaw)
|
|
ENDIF
|
|
|
|
lnSucces = goExecutor.oExecute(lcSql, lcCursorRaw)
|
|
IF lnSucces < 0
|
|
IF TYPE('goLog') = 'O'
|
|
goLog.Log('autopass_cursor_operatii: ' + goExecutor.cEroare, PROGRAM())
|
|
ENDIF
|
|
RETURN ''
|
|
ENDIF
|
|
|
|
*-- vezi autopass_cursor_comenzi() - rematerializam ca cursor local simplu, sub numele final,
|
|
*-- CU coloanele suplimentare deja incluse in SELECT (nu ALTER TABLE dupa)
|
|
SELECT *, .F. AS lvalid_op, SPACE(100) AS motiv_op ;
|
|
FROM (lcCursorRaw) INTO CURSOR (lcCursorDetaliu) READWRITE
|
|
USE IN (lcCursorRaw)
|
|
|
|
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
|
|
*-- VFP e strict ANSI-92 pe GROUP BY: orice camp din SELECT trebuie sa fie fie in GROUP BY,
|
|
*-- fie agregat. denop/lvalid_op/motiv_op sunt deja deterministe per codop (calculate mai sus,
|
|
*-- la liniile 322-326), deci adaugarea lor in GROUP BY nu schimba rezultatul, doar satisface
|
|
*-- sintaxa (fara aceasta, eroare runtime "GROUP BY clause is missing or invalid").
|
|
SELECT id_ordl, codop, denop, lvalid_op, motiv_op ;
|
|
FROM (tcCursorDetaliu) ;
|
|
GROUP BY id_ordl, codop, denop, lvalid_op, motiv_op ;
|
|
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 = autopass_normalizeaza_nrinmat(tcNrinmat)
|
|
|
|
*-- aliniat la regula reala din gateway (rar-autopass app/validation.py, VIN_RE =
|
|
*-- ^[A-HJ-NPR-Z0-9]{17}$): 17 caractere, DOAR A-Z/0-9 (fara spatii/cratime/caractere
|
|
*-- speciale) si fara literele interzise O/I/Q
|
|
IF LEN(lcVin) # 17
|
|
lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'VIN invalid (trebuie 17 caractere)'
|
|
ELSE
|
|
IF !autopass_alfanumeric(lcVin)
|
|
lcMotiv = lcMotiv + IIF(EMPTY(lcMotiv),'','; ') + 'VIN invalid (caractere nepermise)'
|
|
ENDIF
|
|
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
|
|
|
|
*-- intentionat mai strict decat gateway-ul (care accepta si 0): PRD (Failure Modes
|
|
*-- Registry) trateaza KMINT=0 explicit ca date istorice proaste, nebifabile - NU se
|
|
*-- aliniaza la contract pe acest punct
|
|
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
|
|
|
|
|
|
*****************************************************************************************
|
|
* Normalizare nr. inmatriculare - aliniata la normalizarea aplicata acum si in gateway
|
|
* (rar-autopass, inainte de validare): elimina spatii, cratime si puncte, pe langa
|
|
* strip+upper standard. Trebuie aplicata local ACEEASI normalizare inainte de orice
|
|
* validare/trimitere, ca "B 99 XYZ" / "B-99-XYZ" sa devina "B99XYZ" si clientul sa nu
|
|
* respinga local randuri pe care gateway-ul le-ar accepta.
|
|
*****************************************************************************************
|
|
FUNCTION autopass_normalizeaza_nrinmat
|
|
LPARAMETERS tcNrinmat
|
|
LOCAL lcNrinmat
|
|
|
|
lcNrinmat = ALLTRIM(UPPER(IIF(VARTYPE(tcNrinmat) = 'C', tcNrinmat, '')))
|
|
lcNrinmat = STRTRAN(STRTRAN(STRTRAN(lcNrinmat, ' ', ''), '-', ''), '.', '')
|
|
|
|
RETURN lcNrinmat
|
|
ENDFUNC && autopass_normalizeaza_nrinmat
|
|
|
|
|
|
*****************************************************************************************
|
|
* Verifica existenta unei coloane intr-un tabel Oracle (schema curenta - aplicatia e
|
|
* conectata permanent pe schema curenta, USER_TAB_COLUMNS nu mai are nevoie de OWNER)
|
|
*****************************************************************************************
|
|
FUNCTION autopass_coloana_exista
|
|
LPARAMETERS 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 USER_TAB_COLUMNS WHERE " + ;
|
|
"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, lcNumeLista, lnNr, 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
|
|
|
|
* Verificat static (D:\ROA\PROGRAME\nfjsoncreate.prg, functiile concatval/mustEncode/
|
|
* escapeandencode): mustEncode() escapeaza in \uXXXX DOAR caracterele de control
|
|
* (chr(0)-chr(31)) si \ / " - diacriticele (a,a,i,s,t cu diacritice romanesti) NU sunt in
|
|
* specialChars, deci nfjsoncreate le scrie ca octeti bruti in codepage-ul nativ VFP
|
|
* (NU \uXXXX, NU UTF-8). JSON cere UTF-8 (RFC 8259) -> conversia explicita e obligatorie,
|
|
* o singura data, DUPA Nfjsoncreate (nu inainte - ar corupe delimitatorii JSON, care sunt
|
|
* toti ASCII si trec neschimbati prin STRCONV ANSI->UTF-8).
|
|
* Ramane de facut: round-trip pe mediul RAR test cu operatii cu diacritice, verificat in
|
|
* dashboard (nu se poate rula VFP din acest mediu de analiza statica).
|
|
lcJson = Strconv(Nfjsoncreate(loBody, .F.), 9)
|
|
|
|
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
|
|
|
|
* Confirmat din contract (rar-autopass app/models.py PrezentariResponse/ValidareResponse
|
|
* + app/api/v1/router.py:179,429): proprietatea-container e "results" pe AMBELE
|
|
* POST /v1/prezentari si POST /v1/prezentari/valideaza. ExtrageListaRezultate() cauta
|
|
* "results" intai; celelalte variante raman doar fallback defensiv.
|
|
lcNumeLista = This.ExtrageListaRezultate(m.loParsed)
|
|
|
|
If Empty(m.lcNumeLista)
|
|
loReturn.cDetaliu = 'formatul raspunsului nu contine lista de rezultate asteptata'
|
|
This.LogEroare(m.tcAction, -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns)
|
|
Return loReturn
|
|
Endif
|
|
|
|
lnNr = Alen(m.loParsed.&lcNumeLista, 1)
|
|
loColectie = Createobject('Collection')
|
|
|
|
For i = 1 To m.lnNr
|
|
|
|
loElem = m.loParsed.&lcNumeLista(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))
|
|
|
|
If m.tcAction == 'Valideaza'
|
|
* POST /v1/prezentari/valideaza intoarce ValidareResult (app/models.py) - forma DIFERITA
|
|
* de SubmissionResultMinim, fara submission_id/status/motiv/id_prezentare:
|
|
* index, valid, status_estimat, rar_env, erori[], nemapate[], prestatii_rezolvate[]
|
|
AddProperty(loRezItem, 'valid', This.GetProprietate(m.loElem, 'valid', .F.))
|
|
AddProperty(loRezItem, 'status_estimat', This.GetProprietate(m.loElem, 'status_estimat', ''))
|
|
AddProperty(loRezItem, 'rar_env', This.GetProprietate(m.loElem, 'rar_env', ''))
|
|
This.CopiazaListaProprietate(loRezItem, 'erori', m.loElem, 'erori')
|
|
This.CopiazaListaProprietate(loRezItem, 'nemapate', m.loElem, 'nemapate')
|
|
Else
|
|
* POST /v1/prezentari (raspuns implicit 'minim', SubmissionResultMinim din app/models.py):
|
|
* index, submission_id, vehicul, status, motiv, id_prezentare
|
|
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', ''))
|
|
Endif
|
|
|
|
loColectie.Add(loRezItem)
|
|
|
|
Endfor
|
|
|
|
loReturn.lSucces = .T.
|
|
loReturn.oRezultate = loColectie
|
|
|
|
Return loReturn
|
|
Endproc
|
|
|
|
|
|
*-------------------------------------------------------------------
|
|
Procedure StatusPrezentare
|
|
*-------------------------------------------------------------------
|
|
* Contract confirmat (rar-autopass app/api/v1/router.py:562 get_prezentare,
|
|
* allowlist _PREZENTARE_FIELDS linia 547): campurile expuse de GET /v1/prezentari/{id}
|
|
* sunt id, status, id_prezentare, rar_status_code, retry_count, next_attempt_at,
|
|
* created_at, updated_at, account_id, batch_id, row_index, purge_after, rar_error,
|
|
* rar_env, held. NU exista "submission_id"/"motiv"/"vehicul" ca atare in raspuns:
|
|
* id-ul e cel din URL (ecou = tcSubmissionId), iar "motiv" se deriva din rar_error
|
|
* (forma variabila - vezi ExtrageMotivRarError si docs/api-rar-contract.md "Envelope
|
|
* de eroare imbogatit").
|
|
Lparameters tcSubmissionId
|
|
|
|
Local loHttpRez, loReturn, loParsed, loRarError
|
|
|
|
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, 'rar_env', '')
|
|
AddProperty(loReturn, 'held', .F.)
|
|
|
|
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.id_prezentare = This.GetProprietate(m.loParsed, 'id_prezentare', '')
|
|
loReturn.rar_env = This.GetProprietate(m.loParsed, 'rar_env', '')
|
|
loReturn.held = This.GetProprietate(m.loParsed, 'held', .F.)
|
|
loRarError = This.GetProprietate(m.loParsed, 'rar_error', .Null.)
|
|
loReturn.motiv = This.ExtrageMotivRarError(m.loRarError)
|
|
|
|
Return loReturn
|
|
Endproc
|
|
|
|
|
|
*-------------------------------------------------------------------
|
|
Procedure ExtrageMotivRarError
|
|
*-------------------------------------------------------------------
|
|
* rar_error NU are forma fixa (docs/api-rar-contract.md "Envelope de eroare
|
|
* imbogatit"): poate fi NULL (fara eroare), array de obiecte {field,message,cod,
|
|
* problema,cauza,fix} (needs_data / eroare RAR 400 - un obiect per camp invalid),
|
|
* sau un obiect unic cu "unmapped"/"cauza" (needs_mapping / RAR_CREDS_INVALIDE /
|
|
* RAR_EROARE_SERVER). Extrage un rezumat uman pe o linie din campul cel mai util
|
|
* disponibil (cauza intai, apoi problema/message ca fallback).
|
|
Lparameters toRarError
|
|
|
|
Local lcTxt
|
|
|
|
If Vartype(m.toRarError) = 'A' And Alen(m.toRarError, 1) > 0
|
|
lcTxt = This.GetProprietate(m.toRarError(1), 'cauza', '')
|
|
If Empty(m.lcTxt)
|
|
lcTxt = This.GetProprietate(m.toRarError(1), 'message', '')
|
|
Endif
|
|
Return m.lcTxt
|
|
Endif
|
|
|
|
If Vartype(m.toRarError) = 'O'
|
|
lcTxt = This.GetProprietate(m.toRarError, 'cauza', '')
|
|
If Empty(m.lcTxt)
|
|
lcTxt = This.GetProprietate(m.toRarError, 'problema', '')
|
|
Endif
|
|
Return m.lcTxt
|
|
Endif
|
|
|
|
Return ''
|
|
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)
|
|
* charset explicit - corpul e convertit la UTF-8 in EnviaLot (Strconv(..., 9)) inainte
|
|
* sa ajunga aici; declaratia trebuie sa reflecte octetii trimisi efectiv.
|
|
loHTTP.setRequestHeader('Content-Type', 'application/json; charset=utf-8')
|
|
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
|
|
*-------------------------------------------------------------------
|
|
* Container confirmat din contract (rar-autopass app/models.py PrezentariResponse
|
|
* si ValidareResponse): "results" pe AMBELE POST /v1/prezentari si
|
|
* POST /v1/prezentari/valideaza. Celelalte nume raman doar fallback defensiv
|
|
* (nefolosit in contractul actual, pastrat pentru robustete la schimbari viitoare).
|
|
*
|
|
* Intoarce NUMELE proprietatii-container (string), NU array-ul insusi: o functie VFP nu
|
|
* poate intoarce un array (ar intoarce doar primul element). Apelantul itereaza direct
|
|
* cu toParsed.&nume(i). '' daca nu exista lista sau e goala.
|
|
Lparameters toParsed
|
|
|
|
Local Array laNume(6)
|
|
Local i, lcNume
|
|
|
|
laNume(1) = 'results'
|
|
laNume(2) = 'rezultate'
|
|
laNume(3) = 'prezentari'
|
|
laNume(4) = 'items'
|
|
laNume(5) = 'data'
|
|
laNume(6) = 'array'
|
|
|
|
For i = 1 To Alen(laNume, 1)
|
|
lcNume = laNume(m.i)
|
|
*-- TYPE('toParsed.'+nume) NU e de incredere - pe un array de OBIECTE, VFP intoarce
|
|
*-- tipul primului element ('O'), nu 'A'. Detectam prin accesul la elementul (1).
|
|
If Type('toParsed.' + m.lcNume + '(1)') = 'U'
|
|
Loop
|
|
Endif
|
|
*-- array gol marcat de nfjson cu flag pe elementul (1) -> se sare (cade pe '')
|
|
If Type('toParsed.' + m.lcNume + '(1)') = 'C' ;
|
|
And toParsed.&lcNume(1) == '_EMPTY_ARRAY_FLAG_'
|
|
Loop
|
|
Endif
|
|
Return m.lcNume
|
|
Endfor
|
|
|
|
Return ''
|
|
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 CopiazaListaProprietate
|
|
*-------------------------------------------------------------------
|
|
* Copiaza pe un obiect-empty destinatie o proprietate a unui obiect-sursa parsat
|
|
* de nfjson care poate fi ARRAY (erori[]/nemapate[] din raspunsul Valideaza),
|
|
* PASTRAND tot array-ul. GetProprietate NU merge aici: pe un array intoarce doar
|
|
* element(1) (o functie VFP nu poate intoarce un array). Array gol (conventie nfjson
|
|
* '_EMPTY_ARRAY_FLAG_') sau proprietate lipsa/scalara -> proprietate .Null. pe
|
|
* destinatie (consumatorul verifica TYPE(...) = 'A' inainte de a itera). Inversul
|
|
* practic al AsignaListaProprietate.
|
|
Lparameters toDest, tcPropDest, toSursa, tcPropSursa
|
|
|
|
Local lnCount
|
|
|
|
If Vartype(m.toSursa) # 'O' Or Type('toSursa.' + m.tcPropSursa + '(1)') = 'U'
|
|
*-- proprietate lipsa sau scalara (nu se poate indexa cu (1)) -> nimic de copiat
|
|
AddProperty(m.toDest, m.tcPropDest, .Null.)
|
|
Return
|
|
Endif
|
|
|
|
If Type('toSursa.' + m.tcPropSursa + '(1)') = 'C' ;
|
|
And toSursa.&tcPropSursa(1) == '_EMPTY_ARRAY_FLAG_'
|
|
*-- array gol semnalat de nfjson
|
|
AddProperty(m.toDest, m.tcPropDest, .Null.)
|
|
Return
|
|
Endif
|
|
|
|
lnCount = Alen(m.toSursa.&tcPropSursa, 1)
|
|
AddProperty(m.toDest, m.tcPropDest + '(' + Transform(m.lnCount) + ')')
|
|
Acopy(m.toSursa.&tcPropSursa, toDest.&tcPropDest)
|
|
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'
|
|
*-- proprietatea exista -> intoarce valoarea ei reala. NU folosim EVL: cere ambele
|
|
*-- argumente de ACELASI tip (ex. held=0 numeric vs default .F. logic -> "argument
|
|
*-- invalid") si ar inlocui gresit valori valide considerate "empty" (index=0) cu default.
|
|
Return toObiect.&tcProprietate
|
|
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, 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).
|
|
*
|
|
* *** GAP DE CONTRACT - BLOCANT PRD US-005 (risc documentat in PRD sect. 5 si Val 0) ***
|
|
* La data scrierii, GET /v1/prezentari (rar-autopass app/api/v1/router.py:506-540, functia
|
|
* list_prezentari) NU avea filtru de perioada: accepta doar `status` (un singur status,
|
|
* optional) si `limit` (implicit 100), rezultatele fiind sortate descrescator dupa id (nu
|
|
* dupa data_prestatie). Gap-ul se rezolva ACUM in gateway pe branch-ul
|
|
* feature/list-prezentari-filtru-perioada (parametri noi `data_de`/`data_pana` pe
|
|
* data_prestatie, format YYYY-MM-DD, restul structurii raspunsului neschimbat).
|
|
* Clientul de mai jos trimite deja `data_de`/`data_pana` (belt-and-suspenders, compatibil
|
|
* cu AMBELE versiuni de gateway):
|
|
* - gateway VECHI (fara filtru): parametrii necunoscuti sunt ignorati de FastAPI -
|
|
* filtrarea client-side de mai jos ramane singura activa;
|
|
* limita lnLimita ramane posibil trunchianta - vezi avertismentul din log mai jos.
|
|
* - gateway NOU (cu filtru): serverul filtreaza deja pe perioada - limita lnLimita nu
|
|
* mai trunchiaza perioada ceruta, filtrarea client-side devine redundanta dar inofensiva
|
|
* (re-verifica acelasi interval, deja garantat de server).
|
|
* US-004 trebuie sa trateze situatia ca posibil incompleta pana se confirma ca gateway-ul
|
|
* din productie ruleaza versiunea cu filtru.
|
|
*
|
|
* Envelope confirmat din contract (app/api/v1/router.py:540): {"submissions": [...]},
|
|
* fiecare element cu "id" (NU "submission_id"), "status", "id_prezentare", "rar_env",
|
|
* "held" si un sub-obiect "prezentare" (payload_view.prezentare_din_payload, linia 149)
|
|
* cu vehicul_nr/vin/data_prestatie/obs. NU exista camp "motiv" in listare (doar in
|
|
* GET pe un singur id, via rar_error - vezi StatusPrezentare) - aici se aproximeaza
|
|
* doar din "held".
|
|
*****************************************************************************************
|
|
FUNCTION autopass_situatie
|
|
LPARAMETERS tdD1, tdD2, tcEroare
|
|
LOCAL lcCursor, oClient, lcD1, lcD2, lcPath, loHttpRez, loParsed, lnNr, i, loElem
|
|
LOCAL loPrezentare, lcDataPrest, lnLimita, lcMotiv, llHeld, luHeld
|
|
|
|
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);
|
|
*-- format direct comparabil ca string cu data_prestatie pentru filtrul client-side
|
|
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')
|
|
|
|
*-- belt-and-suspenders (vezi comentariul-avertisment de mai sus): trimitem data_de/data_pana
|
|
*-- pentru gateway-ul nou cu filtru server-side, dar cerem si cel mai mare lot rezonabil,
|
|
*-- fara parametru status (toate starile), si filtram local - ramane corect si pe un
|
|
*-- gateway vechi care ignora data_de/data_pana
|
|
lnLimita = 1000
|
|
lcPath = '/v1/prezentari?limit=' + TRANSFORM(lnLimita) + '&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)
|
|
tcEroare = autopass_mesaj_eroare_http(loHttpRez.nStatusHttp)
|
|
RETURN .F.
|
|
ENDIF
|
|
|
|
loParsed = Nfjsonread(loHttpRez.cRaspuns)
|
|
IF VARTYPE(loParsed) <> 'O'
|
|
oClient.LogEroare('Situatie', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns)
|
|
tcEroare = 'Raspuns invalid (JSON malformat) de la gateway-ul AUTOPASS.'
|
|
RETURN .F.
|
|
ENDIF
|
|
|
|
*-- Numarul de submisii. ATENTIE: TYPE('loParsed.submissions') NU e de incredere - pe un
|
|
*-- array de OBIECTE, VFP intoarce tipul primului element ('O'), nu 'A'; verificarea veche
|
|
*-- `<> 'A'` raporta fals "fara lista de submisii" pe raspunsuri valide (confirmat in
|
|
*-- debugger). Numaram direct cu ALEN. Array gol (flag nfjson '_EMPTY_ARRAY_FLAG_') sau
|
|
*-- proprietate lipsa = zero submisii: rezultat VALID (cursor gol), NU eroare.
|
|
lnNr = 0
|
|
IF TYPE('loParsed.submissions(1)') <> 'U'
|
|
IF TYPE('loParsed.submissions(1)') = 'C' AND loParsed.submissions(1) == '_EMPTY_ARRAY_FLAG_'
|
|
lnNr = 0
|
|
ELSE
|
|
lnNr = ALEN(loParsed.submissions, 1)
|
|
ENDIF
|
|
ENDIF
|
|
|
|
IF lnNr >= lnLimita AND TYPE('goLog') = 'O'
|
|
*-- posibila trunchiere - vezi avertismentul de gap din capul functiei
|
|
goLog.Log('autopass_situatie: raspunsul a atins limita de ' + TRANSFORM(lnLimita) + ;
|
|
' submisii - situatia pe perioada poate fi incompleta (gateway fara filtru de perioada)', PROGRAM())
|
|
ENDIF
|
|
|
|
SELECT (lcCursor)
|
|
FOR i = 1 TO lnNr
|
|
loElem = loParsed.submissions(m.i)
|
|
loPrezentare = oClient.GetProprietate(loElem, 'prezentare', .NULL.)
|
|
|
|
lcDataPrest = ''
|
|
IF VARTYPE(loPrezentare) = 'O'
|
|
lcDataPrest = ALLTRIM(oClient.GetProprietate(loPrezentare, 'data_prestatie', ''))
|
|
ENDIF
|
|
|
|
*-- filtru client-side pe perioada - compensatie temporara, vezi avertismentul de gap
|
|
IF EMPTY(lcDataPrest) OR lcDataPrest < lcD1 OR lcDataPrest > lcD2
|
|
LOOP
|
|
ENDIF
|
|
|
|
*-- 'held' poate veni ca boolean (.T./.F.), numeric (0/1) sau string din gateway.
|
|
*-- IIF cere primul argument LOGIC - un numeric (held=0) arunca "Function argument
|
|
*-- value, type, or count is invalid". Normalizam la logic inainte de IIF.
|
|
luHeld = oClient.GetProprietate(loElem, 'held', .F.)
|
|
DO CASE
|
|
CASE VARTYPE(luHeld) = 'L'
|
|
llHeld = luHeld
|
|
CASE VARTYPE(luHeld) = 'N'
|
|
llHeld = (luHeld <> 0)
|
|
CASE VARTYPE(luHeld) = 'C'
|
|
llHeld = INLIST(UPPER(ALLTRIM(luHeld)), 'TRUE', '1', 'T', 'Y')
|
|
OTHERWISE
|
|
llHeld = .F.
|
|
ENDCASE
|
|
lcMotiv = IIF(llHeld, 'Retinut (Auto OFF)', '')
|
|
|
|
INSERT INTO (lcCursor) (submission_id, vin, nr_inmatriculare, data_prestatie, status, motiv, id_prezentare, obs) VALUES ( ;
|
|
TRANSFORM(oClient.GetProprietate(loElem, 'id', '')), ;
|
|
IIF(VARTYPE(loPrezentare) = 'O', ALLTRIM(oClient.GetProprietate(loPrezentare, 'vin', '')), ''), ;
|
|
IIF(VARTYPE(loPrezentare) = 'O', ALLTRIM(oClient.GetProprietate(loPrezentare, 'vehicul_nr', '')), ''), ;
|
|
lcDataPrest, ;
|
|
oClient.GetProprietate(loElem, 'status', ''), ;
|
|
lcMotiv, ;
|
|
TRANSFORM(oClient.GetProprietate(loElem, 'id_prezentare', '')), ;
|
|
IIF(VARTYPE(loPrezentare) = 'O', ALLTRIM(oClient.GetProprietate(loPrezentare, '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
|
|
|
|
IF EMPTY(tcCursorGrid) OR !USED(tcCursorGrid)
|
|
RETURN .F.
|
|
ENDIF
|
|
|
|
*-- status_autopass/motiv_autopass/id_prezentare_autopass sunt incluse direct la crearea
|
|
*-- cursorului in autopass_cursor_comenzi() (vezi comentariul de-acolo) - nu mai adaugam
|
|
*-- prin ALTER TABLE aici (cursorul ramane "remote"-tainted, ALTER TABLE pica oricum).
|
|
|
|
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: lanseaza fereastra "Trimitere AUTOPASS" (frm_autopass.scx),
|
|
* apelata din xmenu-ul din Clase\ofundal_dev.vcx (Page2.Cw8.do_actiune). Fereastra se
|
|
* lanseaza cu numele scurt (fara cale), la fel ca restul aplicatiei - vezi
|
|
* "Do Form frm_filtru_text" in oproceduri_devize.prg/oproceduri_vizualizare.prg -
|
|
* rezolvarea cade pe SET PATH ADDITIVE spre gcAppPath+'FERESTRE' facut la pornire in
|
|
* roaauto.prg (linia ~87), nu pe o cale explicita.
|
|
* Guard pe FILE(): shell-ul frm_autopass.scx se creeaza manual in VFP IDE de Marius si
|
|
* necesita write-back-ul foxbin2prg (textul .sc2 e deja livrat de lane 3 in cache) - pana
|
|
* atunci, xmenu-ul din ofundal_dev.vcx nu trebuie sa crape daca optiunea e aleasa inaintea
|
|
* acestor pasi manuali.
|
|
*****************************************************************************************
|
|
FUNCTION trimitere_autopass
|
|
LOCAL lcCaleForma
|
|
|
|
lcCaleForma = gcAppPath + 'FERESTRE\frm_autopass.scx'
|
|
|
|
IF !FILE(lcCaleForma)
|
|
MESSAGEBOX('Forma frm_autopass nu exista inca - creeaza shell-ul in VFP IDE si ruleaza write-back-ul foxbin2prg.', 48, 'AUTOPASS')
|
|
RETURN .F.
|
|
ENDIF
|
|
|
|
DO FORM frm_autopass
|
|
|
|
RETURN .T.
|
|
ENDFUNC && trimitere_autopass
|