From 5cac7069a67dbda9780ac1d43f8a70483c351f88 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Mon, 13 Jul 2026 10:49:46 +0300 Subject: [PATCH] feat(autopass): baseline transmitere operatii la RAR AUTOPASS (PRD ROAAUTO-1.0) Baseline al muncii autopass acumulate (necomise pana acum) pe branch dedicat, ca sa existe git diff pentru commit-uri incrementale ulterioare. - oproceduri_autopass.prg: client HTTP + proceduri situatie/transmite/valideaza; fix parsare JSON array (detectie ALEN, iterare directa); GetProprietate fara EVL; fix 'held' numeric -> normalizare la logic inainte de IIF (AUTOPASS_SITUATIE). - frm_autopass.scx: formular US-002/US-005; caption succes "statusuri actualizate". - roaauto.prg: NFJSON in SET PATH + SET PROCEDURE proceduri autopass/nfjson. - ovariabile_globale.prg: scoate SET STEP ON ramas commis din greseala. - ofundal_dev.vcx: buton de deschidere a formularului autopass. - test_autopass_driver.prg: driver de test. - docs: reorg README (insight-uri transversale mutate in COMUN/docs/). Co-Authored-By: Claude Opus 4.8 (1M context) --- Clase/ofundal_dev.VCT | Bin 27480 -> 28130 bytes Clase/ofundal_dev.vcx | Bin 7245 -> 7246 bytes Ferestre/frm_autopass.SCT | Bin 0 -> 55875 bytes Ferestre/frm_autopass.scx | Bin 0 -> 7573 bytes Programe/oproceduri_autopass.prg | 509 +++++++++++++----- Programe/ovariabile_globale.prg | 2 +- Programe/roaauto.prg | 7 + Programe/test_autopass_driver.prg | 196 +++++++ docs/README.md | 16 +- docs/cautare_vcx_vct.md | 127 ----- .../prd-roaauto-1.0-transmitere-autopass.md | 17 +- roaauto.pjt | Bin 2078175 -> 2078373 bytes roaauto.pjx | Bin 84783 -> 85173 bytes 13 files changed, 601 insertions(+), 273 deletions(-) create mode 100644 Ferestre/frm_autopass.SCT create mode 100644 Ferestre/frm_autopass.scx create mode 100644 Programe/test_autopass_driver.prg delete mode 100644 docs/cautare_vcx_vct.md diff --git a/Clase/ofundal_dev.VCT b/Clase/ofundal_dev.VCT index d6e1f032b776096243f27a238aff266077bd5220..24864486415663d7c4f4b7ca04fb76ec0cedac76 100644 GIT binary patch delta 3067 zcmb_ee@t7~6@J%XjGZ`O2>1uaJZwlp3&jRIHX$Ts@KQ_yHpX^H!s-%YsHFgwZJKCp zLX<*CRQbCydL=XgWMR$u_64f}PDuzm7P?yFhBU91o z*eo1UlSUmMn~Y8*#-^e$D9q@MS&eGQIPp}Pew@FuA)N{fCBGyohJO#Vm ziFYP__;|pA^MKRK0-nb%$ zI&3tMd3@PFbiY?tE^T07nfGaOfN%Uk-!3_&om1Z5HxJK)CSc#gdC6e8VjHtW{khjB zH+FL4pZ7e6V2uf9ES30PPeJv#ILX(&L=NHO9gW!9-6{98lT*Ijvw)ZO1o8r4YGeX% zLerHe1^a<+Zg{=(hj6$o_(i8D&-m|R#`t!*3tx0t<-2;g@duu>3UZpJry8E($~oy| zd?t|+M|Yo`I^MbVG82y`#!|7EI59S!nx&6XijOCs=p5Z0OvI*Qsc0f9Za|7ni(|8? znfO>T`A}kJYGN#zqIYYPR@3Vfd%A}ut%m&fWkb*L^W0eFex$+POw9Db zAMo{PHF(TjlRH*6_qtNocNQqs!IQn{dYvj2E-3C&@mq?Uh&G&RyMTtaLvs1p!Lp&% zzDO%qg2`SbC$J-B!?l)YIpNKg0s`x6He7!28A>=Tw`T3khOVY;ZEr?NlUq(=-N=T| zwk;4hKVYwKYs{;_Os`?*XC!Yu(`4AX6Oo33WbLGGtqRhBcSbEJi|Em`w;bj=GoG?l z;i9=3Kd95-nzjs=Y(}hD{g<0-X@*RguF+t^N}CqdBHQlRAzY{efe zar~~X3jfkF-$uMguNCWdMCw|Z?+MG%VnH^FSfjS$)tY!gP`@=ZS5D#`n3LYC zT#|#l?{e_-ih1J6s93QrPp!3y!CAM<56WHe@yJ0%`LkqzyLh8)gC<93H>Igiq}3a8 zp9k$7>t6>Bj~zoz)|}kyl4RsIY{|&-Gi?ja?AlguZr-&~eu)cD8jr|hc^JXsX3P|l*FqzcQ*YFt+uvAx8IMdc`-AFM@v@f}zcdfYs4 z3V)qQ;loH6OUHf;_0g~B^S|+%@7}~uy4YF+-a4{?_Cq)5+9_N-n8f0)^9Y1a^Y^8p zB=o-bsh=-!+q;5SHidDZe>*&VXVX*CCIZQ{fY-irA^p+bNC#yPP^D>R#E+OSB_mo$JRZ%e9QJ}TeG`Ef^ z81@fg)k-m?Q2n)D_?8*4Zc@ZdsDEan`kyAnf_ar@qf}4}2L^rqpf@xqDTR@NB55#$ zyYA*am4Y@UhV#^@al8NOvgc^LGyqR|vs?cfcEz0#a@b zSBt7E9IAH(9U%%GspnU>fGho`vLw}n%hE(e=~CcQg%Pd?!noU|hk3IOzjV&SUB7@I zInL4bCo#}^p5K?7&!=4vC^D51-tm3B*7#}KU9TbOq1vq^Et$5Ev{_$9xbeQKbGWMe a48Pv;^-o`+)%1Dk-*C0$MtV;1BKa?+INuKd delta 2292 zcmZvdYe<_{9L96fs7W-{#Khc;m((_KCey?ure3PnXjAQsvEHiH)Vr>>lT~LKoiOXA z#f^15g~~pxY=d1s^!kpi8?uKM&?@@3$xPlJ2(cX_P|D-t!XRZAs1YNm?u zfn5h{rg?|Be+tdV*TkX0EQ4oFK8(h)0fnxLnjv&uQAmr5sI;jc!Iy zV7f_9D1)vjFM5Ik-xRWa(iL{Z);9NWtoIQHUaUgcJ}21U%hd0WWw;z9O1g161gm7zI1 z=4}fRdm7sCewP*nCz8?KzJQLbW-OcLgklU{+>fn#LXJ@9nfTo(gUeKgnasBYEdu~| zr$tTZ*cmA*>3D1`g57vgP%;EW#^PhcE`D*F%O#!r>Toil92s$2UxjS_tWbp|WOB5d z#{3zEbgfv`o)tt4A>0>12_Xj~(KqGq#!0P;oQXmb5O!>ZgEzJb4h+}5frG&bcr^{U zsphZx%mhizl!G<(Ebst2wHt4zQ~IL{#u0s~W-%W)h2@=DSXbF1b}X1t9gXQ(KRj!{ zD(QqvHp1FE;cg@<=^YbY-#q23?dP!AI89%eR?MXS@4JW@b>7v5mv~q8XxvHf1Iaen zXB}F@hYCNk6=wwj1231|kVwdDG;J+fQ-*LWIaY{ew6Ol~$oE^5ZJ3wO3dI-GAQ1I;=T_&occ_=)Ur+L#cia2soW2=2 zZ>EilX%RJ82+^|nbaMS@+DwRCk1LSn^*QWhG$FODe~QS@$?g6G-^f1CjWe#1{S@{mG_s${esLrFY3!GT+0WkDGdWn*R6Mv17AyGQDH{u@mF!B0fGoF)k$~EiN%3ngV?= z8sqDV&8j;#EM3UUS<>JoozdWHvspr@Sp_GrMJ1|n3>Oe-n9UMH&0Mz$F$UAI2rID- zGWy;w#H1{>w#{aKEc5;7iXAw=FtwYJS>v*b{-Hz>FZpW!TO5mk4q zSjRDIlx-Vzp4luV)a+{to<}S0H(k&h!FvyntzkVR`&OH!hMINehSn1!QHUv63>nQ@ z2{CB2TH9vx%F;s3914!bd_0erPzf0+t%c||My+kL*=%sASrrAVaR!%g9np2;a1+O_ z9@{qRJfm5BT&USu3SK~)yM%~Ee~g5T8jh_UwYJS>Gi`!&Y(o>I!;gtr2pLVp_B+qO~Xna#8b@;e2iVuTog!N@@w zA`P>dHbD+ixCXWO3sIeg=mi-caI6lfwQV+=X%nPV7a_VM0|89LLda;r2e6uawYJS> zGi`#DQgAlbV;gp%1~T%x3URtXt!=Z}Oq(FDQE)F#;(J^}r*5{T&GXD=+60+F;rZBv z9oUQGkTE%y{{~}&OY3Ad(Gi`!gyq`~G5Ae?&i5P{ch%}65iP{9&PvIl@0e_%*Pu?a##m4?~fK$&m3A$IM67+BTcbv)X4(XqOToq1irqMXlaNvL zAnzrM)!H_j&9n*f3I!{19B1$w+6}TTZJuW~(XnhtDCSgg;FA zo=|JsY&O#-$aV@=;R~F@W!#miO8eZrNiDZ+qt5&H_xk_*AxQhpe%+l*oj5w{<_d>9 GJLYeBKD@O6 literal 7245 zcmeI1eT)?49mgLIw0C&L7YZVHbg_4Wh&Z#mw=dO$?(Oc~?(NRbzTGZhD+tnS1GEUH zRI8Qb8m%GH)Y!(9#!GV*PKiN@#)h^jR|2*@gK2DQF!+x^sn)2~q{R}YpV{Z`W@dL8 zX7BI*lF##dZhp^vzdLWwJiDjNYq=~eglL|zh1MQT(}l>UBgGy)uThCkqiMDe5&fBD zu0NiP8TA&LnN{^-Qi~Ug5iKi3W_F!=G#x1v5~#tTAI>(`6%Xfe(S&^qhXwRkb1 z$0|pt8RxnB`1pKLJtjY2SEX(rp{FY~Dng6Y+dm!m8bzXVl=i)a$j1xud?_B|vujBP zU966XW?bZp)qT`+4s}2Eeup~eMqBMr4^aP_Lp@0SE{A%E`o=o-eh#0_MrhxpsXo5X zS4#hHJW*ZKcoIC(&3A3z?y*Nhr>E)CqdnzhCZ?AQp0#SCTS;Y7-9^epF$tuI;RexK|di+YM)885Pgu<#Xpdj*P<^H%lY76t06=@DWeP zR^tgF+R3-xE&Fa;N3#yZro%@Ygg6Uh5CbFry=neGh9}@r*aGj|DYb{l2aHLpfp9c?8nHpx1_O|SV_%i(33Bp|2Amjb zF{`~BS0dI1tuPJFtPx^87%`u`%$Tkl2uHIU3^Dj%l@O=l4-f?-J}C>)0*!>-EFBEy zc)liX{R<@_-hx-*F#Hl0f)Rh@C3Qr;@8t#;t=+6LU3tQxfIk=vsi6aiJq9~r6X@`t z@fD2=?V2HWvy9pk(M_{c2#>;xFa(2eGfXw?W^y8)JdKMGzQD`#eRvy=K{pt2j~`c&&eZOfbb_N zA^rhx!65ca_?Xa*x*OhB*fyKU`enMgFBGOyXQi2V?D zz-Gw8t36VB8+jn^)<%xAo5_cIa#>J7Ooev15fMdj_6_$KW1#Z-vzU8F^2a zYoE%%dS+@kpoRiUIP?f&8(|ehVKKaRdt(OH(H65*S;>|3qOoWkUtJwVco-gsM_?12 zyiM-@FnQ;ftx}in*O-+`<|1`7VtvpBOQ02A3SV0EVk1m5UoN5aCHmJ`UY`3AeiXh7 zTObF=LsDu7`Ch(>H4-*6Rn4U$VRiDFtwU@DsBpa@3?qR?3@ppI~UWN@Y)u=MF&-$wo z4#JJF04{ii_zoD+MxM>L|3<>mtOc>N3QKSpg9ESxj5vNP+m_3|+t$%+6tN-L4TCTM zZy3MR5D${?Uncu*TSv34h-IJ)yf7Db-7KY5^75r_ZR9vdvv$Oq;9oa!lfa)K07jfx z!f9MA`)*sinVw#q?=f%vQN)H|Hw?l6ynUmT9wz^$!>x@RXEz&P5v!`=>r{fl5yT#b z``~U!z=!RP8CXZ#&2&v)oi&f_B*KT_Id~EtfOeQ_*v+a|#Pbn8_a$zBI0=X0i5sNV zZDe&(W45N5S4k_$RItC_!|W(eV;f@UJuJWB1pF2@!c?QytYn_XI}i@T5@>^I@cV_L z5^Kq+HraRETFlg-l1+5SLZ(>+u`j|bm=0&J7vk9kQd%RgobT2~jx)_BCgP|$5#NH? z74X@0Li`h6gYUsq!)~VMlfG5vk==u^4lBS1*TUdDY1KiVdu?O3cC)Ixu@$j*=W=tv zaX1JI!HA=Cg!uIrWZ!LD$IQdZsx#>I1_Cc2_9OT{Y=<(8&2}qYJI-#V7h|2h=4l*7 zcnEgGAPm5%Yoy39`Oww1f=*j%&HBt6WG`YrfE}9FMlS@6Wo)A&=w?u7v;LI_UJbQW}~R+-rsJZ~cWE7%7=hn?`>7Abd_e5%<=y0+$M zb^@`NVK3}~hv5CsNs(vBhc2%bblPGuD`%B*N)MFHTmN~)cEf|P4N7p$Wm0bb3|^{D cPSUlt$=~%S{t;yTW#2k{UW3BceJA?=1NEu`O#lD@ diff --git a/Ferestre/frm_autopass.SCT b/Ferestre/frm_autopass.SCT new file mode 100644 index 0000000000000000000000000000000000000000..04d4ed0ed3a8e3d9b934d4e042e456ab2cdaed22 GIT binary patch literal 55875 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�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 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 +LOCAL lcServer, lnStatus, lcResponseText, loJson, lcUrl, lcKey loReturn = Createobject('empty') AddProperty(loReturn, 'lSucces', .F.) @@ -112,7 +125,12 @@ AddProperty(loReturn, 'lAreCredsProd', .F.) AddProperty(loReturn, 'cEroare', '') loConfig = autopass_get_config() -IF !loConfig.lConfigOk + +*-- 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 @@ -122,12 +140,12 @@ lnStatus = 0 lcResponseText = '' TRY - lcServer = ALLTRIM(loConfig.cUrlGateway) + [/v1/ping] + 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', loConfig.cApiKey) + loHTTP.setRequestHeader('X-API-Key', lcKey) loHTTP.Send() lnStatus = loHTTP.Status @@ -139,15 +157,10 @@ CATCH TO loEx RETURN loReturn ENDTRY -DO CASE -CASE lnStatus = 401 - loReturn.cEroare = 'Cheie API invalida sau neautorizata (HTTP 401) - corecteaza cheia' + ; - ' in panoul Configurare.' +IF lnStatus <> 200 + loReturn.cEroare = autopass_mesaj_eroare_http(lnStatus) RETURN loReturn -CASE lnStatus <> 200 - loReturn.cEroare = 'Gateway AUTOPASS a raspuns cu eroare (HTTP ' + TRANSFORM(lnStatus) + ').' - RETURN loReturn -ENDCASE +ENDIF loJson = Nfjsonread(lcResponseText) IF VARTYPE(loJson) <> 'O' @@ -155,11 +168,16 @@ IF VARTYPE(loJson) <> 'O' 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.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.) +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 @@ -171,7 +189,7 @@ ENDFUNC && autopass_test_conexiune FUNCTION autopass_cursor_comenzi LPARAMETERS tdDataInceput, tdDataSfarsit LOCAL lcCursorHeader, lcD1, lcD2, lcSql, lcFiltruSters, lnSucces -LOCAL llOk, lcMotivRand, ldDataPrest +LOCAL llOk, lcMotivRand, ldDataPrest, lcCursorRaw lcCursorHeader = 'crs_autopass_comenzi' IF USED(lcCursorHeader) @@ -183,16 +201,21 @@ lcD1 = "TO_DATE('" + STR(YEAR(tdDataInceput),4) + "-" + PADL(MONTH(tdDataInceput 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') +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 " + gcS + ".auto_istoric_comenzi " + ; + "FROM 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) +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()) @@ -200,8 +223,20 @@ IF lnSucces < 0 RETURN '' ENDIF -ALTER TABLE (lcCursorHeader) ADD COLUMN lvalid L -ALTER TABLE (lcCursorHeader) ADD COLUMN motiv_invalid C(100) +*-- 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 @@ -215,8 +250,12 @@ SCAN 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 lvalid WITH llOk, motiv_invalid WITH lcMotivRand + REPLACE nrinmat WITH autopass_normalizeaza_nrinmat(nrinmat), ; + lvalid WITH llOk, motiv_invalid WITH lcMotivRand ENDSCAN RETURN lcCursorHeader @@ -227,7 +266,7 @@ ENDFUNC && autopass_cursor_comenzi FUNCTION autopass_cursor_operatii LPARAMETERS tcCursorHeader LOCAL lcCursorDetaliu, lcCursorCount, lcSql, lcChunkSql, lcIdList, lnSucces -LOCAL lnTotal, lnChunkSize, lnStart, lnEnd, i +LOCAL lnTotal, lnChunkSize, lnStart, lnEnd, i, lcCursorRaw LOCAL ARRAY laId[1] LOCAL lnCnt, lcMotivExist @@ -245,8 +284,8 @@ 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 " + ; + "FROM dev_oper o " + ; + "JOIN dev_nom_norme n ON o.id_norme = n.id_norme " + ; "WHERE 1 = 2" ELSE DIMENSION laId[lnTotal] @@ -269,8 +308,8 @@ ELSE 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 " + ; + "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 @@ -278,7 +317,12 @@ ELSE ENDDO ENDIF -lnSucces = goExecutor.oExecute(lcSql, lcCursorDetaliu) +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()) @@ -286,8 +330,11 @@ IF lnSucces < 0 RETURN '' ENDIF -ALTER TABLE (lcCursorDetaliu) ADD COLUMN lvalid_op L -ALTER TABLE (lcCursorDetaliu) ADD COLUMN motiv_op C(100) +*-- 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 @@ -351,9 +398,13 @@ 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 ; + GROUP BY id_ordl, codop, denop, lvalid_op, motiv_op ; INTO CURSOR (lcTemp) READWRITE lcPath = DBF(lcTemp) @@ -380,11 +431,17 @@ LOCAL lcVin, lcNrinmat, lcMotiv, ldData, llOk lcMotiv = '' lcVin = ALLTRIM(UPPER(IIF(VARTYPE(tcVin) = 'C', tcVin, ''))) -lcNrinmat = ALLTRIM(UPPER(IIF(VARTYPE(tcNrinmat) = 'C', tcNrinmat, ''))) +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 @@ -407,6 +464,9 @@ 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 @@ -437,10 +497,29 @@ ENDFUNC && autopass_alfanumeric ***************************************************************************************** -* Verifica existenta unei coloane intr-un tabel Oracle (owner/table/column) +* 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 tcSchema, tcTabel, tcColoana +LPARAMETERS tcTabel, tcColoana LOCAL lcCursor, llExista, lnSucces lcCursor = 'crs_ap_colcheck' @@ -448,8 +527,8 @@ 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) +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) @@ -524,7 +603,7 @@ DEFINE CLASS AutopassClient AS Custom Lparameters taComenzi, tcPath, tcAction Local lnCount, i, lcJson, loBody, loComanda, loPrezentare, loHttpRez - Local loReturn, loParsed, laRezultate, loColectie, loElem, loRezItem, lnIndexRel + Local loReturn, loParsed, lcNumeLista, lnNr, loColectie, loElem, loRezItem, lnIndexRel Local Array laPrezentari(1), laIndexMap(1) lnCount = This.GetNumarElemente(m.taComenzi) @@ -567,12 +646,16 @@ DEFINE CLASS AutopassClient AS Custom 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.) +* 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.) @@ -598,37 +681,47 @@ DEFINE CLASS AutopassClient AS Custom 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) +* 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 Vartype(m.laRezultate) # 'A' + 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 Alen(m.laRezultate, 1) + For i = 1 To m.lnNr - loElem = laRezultate(m.i) + 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)) - 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' +* 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, 'coduri_nemapate', This.GetProprietate(m.loElem, 'coduri_nemapate', .Null.)) + AddProperty(loRezItem, 'rar_env', This.GetProprietate(m.loElem, 'rar_env', '')) + AddProperty(loRezItem, 'erori', This.GetProprietate(m.loElem, 'erori', .Null.)) + AddProperty(loRezItem, 'nemapate', This.GetProprietate(m.loElem, 'nemapate', .Null.)) + 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) @@ -645,9 +738,17 @@ DEFINE CLASS AutopassClient AS Custom *------------------------------------------------------------------- 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 + Local loHttpRez, loReturn, loParsed, loRarError loReturn = Createobject('empty') AddProperty(loReturn, 'lSucces', .F.) @@ -657,7 +758,8 @@ DEFINE CLASS AutopassClient AS Custom AddProperty(loReturn, 'status', '') AddProperty(loReturn, 'motiv', '') AddProperty(loReturn, 'id_prezentare', '') - AddProperty(loReturn, 'vehicul', '') + AddProperty(loReturn, 'rar_env', '') + AddProperty(loReturn, 'held', .F.) loHttpRez = This.Get('/v1/prezentari/' + Alltrim(m.tcSubmissionId), 'StatusPrezentare') @@ -679,15 +781,49 @@ DEFINE CLASS AutopassClient AS Custom 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) + 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 *------------------------------------------------------------------- @@ -711,7 +847,9 @@ DEFINE CLASS AutopassClient AS Custom loHTTP = Createobject('winHTTP.winHTTPrequest.5.1') loHTTP.Open('POST', m.lcUrl, .F.) loHTTP.SetTimeouts(30000, 30000, 30000, 30000) - loHTTP.setRequestHeader('Content-Type', 'application/json') +* 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) @@ -803,27 +941,42 @@ DEFINE CLASS AutopassClient AS Custom *------------------------------------------------------------------- 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(4) - Local i + Local Array laNume(6) + Local i, lcNume - laNume(1) = 'rezultate' - laNume(2) = 'prezentari' - laNume(3) = 'items' - laNume(4) = 'data' + 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) - If Type('toParsed.' + laNume(m.i)) = 'A' - Return toParsed.&laNume(m.i) + 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 - If Type('toParsed.array') = 'A' - Return toParsed.array - Endif - - Return .Null. + Return '' Endproc @@ -879,7 +1032,10 @@ DEFINE CLASS AutopassClient AS Custom Lparameters toObiect, tcProprietate, tuDefault If Vartype(m.toObiect) = 'O' And Type('toObiect.' + m.tcProprietate) # 'U' - Return Evl(toObiect.&tcProprietate, m.tuDefault) + *-- 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 @@ -923,14 +1079,40 @@ 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). +* 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 -LOCAL lcCursor, oClient, lcD1, lcD2, lcPath, loHttpRez, loParsed, laLista, i, loElem +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) @@ -941,44 +1123,94 @@ 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) +*-- 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') -lcPath = '/v1/prezentari?data_de=' + lcD1 + '&data_pana=' + lcD2 +*-- 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 -laLista = oClient.ExtrageListaRezultate(loParsed) -IF VARTYPE(laLista) <> 'A' - oClient.LogEroare('Situatie', -1, loHttpRez.nStatusHttp, loHttpRez.cRaspuns) - RETURN .F. +*-- 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 ALEN(laLista, 1) - loElem = laLista(i) +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 ( ; - oClient.GetProprietate(loElem, 'submission_id', ''), ; - oClient.GetProprietate(loElem, 'vin', ''), ; - oClient.GetProprietate(loElem, 'nr_inmatriculare', ''), ; - oClient.GetProprietate(loElem, 'data_prestatie', ''), ; + 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', ''), ; - oClient.GetProprietate(loElem, 'motiv', ''), ; - oClient.GetProprietate(loElem, 'id_prezentare', ''), ; - oClient.GetProprietate(loElem, 'obs', '')) + lcMotiv, ; + TRANSFORM(oClient.GetProprietate(loElem, 'id_prezentare', '')), ; + IIF(VARTYPE(loPrezentare) = 'O', ALLTRIM(oClient.GetProprietate(loPrezentare, 'obs', '')), '')) ENDFOR RETURN .T. @@ -995,36 +1227,14 @@ ENDFUNC && autopass_situatie 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 +*-- 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" @@ -1214,11 +1424,28 @@ 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. +* 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 -MESSAGEBOX('Fereastra Trimitere AUTOPASS (US-004) nu este inca construita in VFP IDE.', 48, 'AUTOPASS') -RETURN .F. +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 diff --git a/Programe/ovariabile_globale.prg b/Programe/ovariabile_globale.prg index 51a4412..aaea9b1 100644 --- a/Programe/ovariabile_globale.prg +++ b/Programe/ovariabile_globale.prg @@ -64,7 +64,7 @@ ELSE USE IN gest_permis ENDIF ENDIF -SET STEP ON + * initializare variabile publice gnInchidereValidare1/2/3/4/5/6/7 =cctipuri_deviz() SELECT crstipuri_deviz diff --git a/Programe/roaauto.prg b/Programe/roaauto.prg index fdd79e6..fb49736 100644 --- a/Programe/roaauto.prg +++ b/Programe/roaauto.prg @@ -101,6 +101,7 @@ lcPath = gcAppPath + 'Date;' + ; gcAppPath + 'COMUN\UTILE\HPDF;' + ; gcAppPath + 'COMUN\UTILE\HPDF\REPORTOUTPUT;' + ; gcAppPath + 'COMUN\UTILE\WEB;' + ; + gcAppPath + 'COMUN\UTILE\NFJSON;' + ; Addbs(Substr(gcAppPath,1,Rat([\],gcAppPath,2)))+[COMUNROA\] SET PATH TO &lcPath ADDITIVE @@ -197,6 +198,12 @@ SET PROCEDURE TO xdate.prg Additive SET PROCEDURE TO validare.prg Additive SET PROCEDURE TO regex.prg Additive +*!* modificare AUTOPASS (PRD ROAAUTO-1.0) - transmitere operatii la RAR prin gateway +Set Procedure To oproceduri_autopass Additive +Set Procedure To nfjsoncreate.prg Additive +Set Procedure To nfjsonread.prg Additive +*!* modificare AUTOPASS ^ + SET PROCEDURE TO excelxml.prg ADDITIVE Declare Integer GetPrivateProfileString In Kernel32 ; diff --git a/Programe/test_autopass_driver.prg b/Programe/test_autopass_driver.prg new file mode 100644 index 0000000..47f4efb --- /dev/null +++ b/Programe/test_autopass_driver.prg @@ -0,0 +1,196 @@ +***************************************************************************************** +* test_autopass_driver.prg +* Driver .prg fara forma pentru US-003 (client AutopassClient) - PRD ROAAUTO-1.0, +* sectiunea "Acoperire de test": "driver .prg fara forma (1 comanda hand-built) - E2E: +* acopera encoding/headers/timeouts izolat". +* +* Ruleaza autonom din Command Window: +* DO Programe\test_autopass_driver.prg +* +* Ce face: +* (a) apeleaza autopass_test_conexiune() si afiseaza rezultatul (ping) +* (b) construieste MANUAL (fara Oracle) 2 obiecte comanda in formatul asteptat de +* AutopassClient.EnviaLot - una din ele cu operatii cu diacritice in denumire, +* ca sa acopere codepath-ul de encoding +* (c) apeleaza Valideaza (dry-run) pe cele 2 comenzi +* (d) doar cu confirmare MESSAGEBOX explicita SI doar daca mediul citit din config e +* "test", apeleaza si TrimiteLot - altfel se opreste dupa dry-run +* (e) afiseaza raspunsurile per element (index, id_ordl, status, motiv, submission_id) +* +* Nu se apeleaza din meniul aplicatiei - e un instrument de diagnostic, lansat manual. +***************************************************************************************** + +CLOSE ALL +CLEAR + +SET PROCEDURE TO Programe\oproceduri_autopass.prg ADDITIVE +SET PROCEDURE TO COMUN\utile\nfjson\nfjsoncreate.prg ADDITIVE +SET PROCEDURE TO COMUN\utile\nfjson\nfjsonread.prg ADDITIVE + +LOCAL loPing, oClient, loComenzi, loRezValidare, loRezTrimite, loConfig +LOCAL lnAlegere, i, loItem + +?'=============================================================================' +?'AUTOPASS - driver de test fara forma' +?'=============================================================================' + +*-- (a) test conexiune (GET /v1/ping) ------------------------------------------------- +? +?'--- (a) autopass_test_conexiune() ---' + +loPing = autopass_test_conexiune() + +?' lSucces : ' + TRANSFORM(loPing.lSucces) +?' cMediu : ' + loPing.cMediu +?' lAutentificatCuCheie : ' + TRANSFORM(loPing.lAutentificatCuCheie) +?' lAreCredsTest : ' + TRANSFORM(loPing.lAreCredsTest) +?' lAreCredsProd : ' + TRANSFORM(loPing.lAreCredsProd) +?' cEroare : ' + loPing.cEroare + +IF !loPing.lSucces + ? + ?'Ping esuat - verifica AUTOPASS_URL/AUTOPASS_KEY in tabelul optiuni inainte de a continua.' + ?'Driverul continua totusi (poate testa doar constructia payload-ului si Valideaza va esua controlat).' +ENDIF + +*-- (b) construim manual 2 "comenzi", fara Oracle -------------------------------------- +? +?'--- (b) construire manuala a comenzilor de test ---' + +loComenzi = Createobject('Collection') + +*-- comanda 1: fara diacritice, caz de baza +loItem = Createobject('empty') +AddProperty(loItem, 'id_ordl', 900001) +AddProperty(loItem, 'vin', 'WVWZZZ1KZAW000001') +AddProperty(loItem, 'nrinmat', 'B99TST01') +AddProperty(loItem, 'data_prestatie', '2026-07-01') +AddProperty(loItem, 'odometru_final', '123456') + +LOCAL ARRAY laPrestatii1(2) +laPrestatii1(1) = Createobject('empty') +AddProperty(laPrestatii1(1), 'cod_op_service', 'SCH001') +AddProperty(laPrestatii1(1), 'denumire', 'Schimb ulei motor') +laPrestatii1(2) = Createobject('empty') +AddProperty(laPrestatii1(2), 'cod_op_service', 'FRN010') +AddProperty(laPrestatii1(2), 'denumire', 'Verificare frane') +AddProperty(loItem, 'prestatii(2)') +Acopy(laPrestatii1, loItem.prestatii) + +AddProperty(loItem, 'obs', autopass_obs(90001, 190001)) + +loComenzi.Add(loItem) + +*-- comanda 2: cu diacritice in denumirea operatiei - acopera codepath-ul de encoding +loItem = Createobject('empty') +AddProperty(loItem, 'id_ordl', 900002) +AddProperty(loItem, 'vin', 'WVWZZZ1KZAW000002') +AddProperty(loItem, 'nrinmat', 'B99TST02') +AddProperty(loItem, 'data_prestatie', '2026-07-02') +AddProperty(loItem, 'odometru_final', '87654') + +LOCAL ARRAY laPrestatii2(2) +laPrestatii2(1) = Createobject('empty') +AddProperty(laPrestatii2(1), 'cod_op_service', 'DIAC01') +* diacritice romanesti in text sursa (comentariile raman fara diacritice, dar +* denumirea trimisa in payload TREBUIE sa contina diacriticele reale, ca sa testeze +* efectiv encoding-ul): +AddProperty(laPrestatii2(1), 'denumire', 'înlocuire ambreiaj - verificare etanâs la strângere') +laPrestatii2(2) = Createobject('empty') +AddProperty(laPrestatii2(2), 'cod_op_service', 'DIAC02') +AddProperty(laPrestatii2(2), 'denumire', 'Reglaj geometrie roîi faâa/spate') +AddProperty(loItem, 'prestatii(2)') +Acopy(laPrestatii2, loItem.prestatii) + +AddProperty(loItem, 'obs', autopass_obs(90002, 190002)) + +loComenzi.Add(loItem) + +?' Comenzi construite: ' + TRANSFORM(loComenzi.Count) +FOR i = 1 TO loComenzi.Count + loItem = loComenzi.Item(i) + ?' [' + TRANSFORM(i) + '] id_ordl=' + TRANSFORM(loItem.id_ordl) + ; + ' vin=' + loItem.vin + ' obs="' + loItem.obs + '"' +ENDFOR + +*-- (c) Valideaza (dry-run) ------------------------------------------------------------- +? +?'--- (c) AutopassClient.Valideaza() [dry-run, fara enqueue] ---' + +oClient = Createobject('AutopassClient') + +loRezValidare = .NULL. +TRY + loRezValidare = oClient.Valideaza(loComenzi) +CATCH TO loEx + ?' EROARE la Valideaza(): ' + loEx.Message +ENDTRY + +IF VARTYPE(loRezValidare) = 'O' + ?' lSucces : ' + TRANSFORM(loRezValidare.lSucces) + ?' nStatusHttp : ' + TRANSFORM(loRezValidare.nStatusHttp) + ?' cDetaliu : ' + loRezValidare.cDetaliu + + IF loRezValidare.lSucces AND VARTYPE(loRezValidare.oRezultate) = 'O' + ?' Rezultate dry-run:' + FOR EACH loItem IN loRezValidare.oRezultate + ?' index=' + TRANSFORM(loItem.index) + ; + ' id_ordl=' + TRANSFORM(loItem.id_ordl) + ; + ' valid=' + TRANSFORM(loItem.valid) + ; + ' status_estimat=' + TRANSFORM(EVL(loItem.status_estimat,'')) + ; + ' nemapate=' + IIF(TYPE('loItem.nemapate') = 'A', TRANSFORM(ALEN(loItem.nemapate,1)), '0') + ENDFOR + ENDIF +ENDIF + +*-- (d) TrimiteLot - DOAR cu confirmare explicita SI DOAR daca mediul e test ------------- +? +?'--- (d) AutopassClient.TrimiteLot() [enqueue real - necesita confirmare] ---' + +loConfig = autopass_get_config() + +IF UPPER(ALLTRIM(loConfig.cRarEnv)) <> 'TEST' + ?' Mediul curent din configurare (AUTOPASS_ENV) nu este "test" (este: "' + ; + loConfig.cRarEnv + '") - TrimiteLot NU se apeleaza din driver.' + ?' Seteaza AUTOPASS_ENV=test in tabelul optiuni daca vrei sa testezi enqueue-ul real.' +ELSE + lnAlegere = MESSAGEBOX('Mediul este TEST. Trimiti (enqueue real) cele ' + ; + TRANSFORM(loComenzi.Count) + ' comenzi de test la gateway-ul AUTOPASS?' + CHR(13) + ; + 'Aceasta e o transmitere reala pe mediul de test (nu Productie).', 4 + 32, 'AUTOPASS - confirmare TrimiteLot') + + IF lnAlegere = 6 && Yes + loRezTrimite = .NULL. + TRY + loRezTrimite = oClient.TrimiteLot(loComenzi) + CATCH TO loEx + ?' EROARE la TrimiteLot(): ' + loEx.Message + ENDTRY + + IF VARTYPE(loRezTrimite) = 'O' + ?' lSucces : ' + TRANSFORM(loRezTrimite.lSucces) + ?' nStatusHttp : ' + TRANSFORM(loRezTrimite.nStatusHttp) + ?' cDetaliu : ' + loRezTrimite.cDetaliu + + IF loRezTrimite.lSucces AND VARTYPE(loRezTrimite.oRezultate) = 'O' + ?' Rezultate TrimiteLot:' + FOR EACH loItem IN loRezTrimite.oRezultate + ?' index=' + TRANSFORM(loItem.index) + ; + ' id_ordl=' + TRANSFORM(loItem.id_ordl) + ; + ' status=' + TRANSFORM(loItem.status) + ; + ' motiv=' + TRANSFORM(loItem.motiv) + ; + ' submission_id=' + TRANSFORM(loItem.submission_id) + ; + ' id_prezentare=' + TRANSFORM(loItem.id_prezentare) + ENDFOR + ENDIF + ENDIF + ELSE + ?' Renuntat de utilizator - TrimiteLot nu s-a apelat.' + ENDIF +ENDIF + +? +?'=============================================================================' +?'Driver terminat.' +?'=============================================================================' + +RETURN diff --git a/docs/README.md b/docs/README.md index 6243a1e..638183c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,4 +13,18 @@ NotiÈ›ele sunt organizate pe fiÈ™iere separate, câte un subiect pe fiÈ™ier (ex. ## FiÈ™iere -- [cautare_vcx_vct.md](cautare_vcx_vct.md) — cum se caută cod în binarele `.vcx`/`.scx` (conversie text + grep pe cache) +Insight-uri valabile doar pentru ROAAUTO stau aici. Insight-urile valabile pe orice proiect +ROA* (convenÈ›ii, capcane de tooling) stau în `COMUN/docs/` (repo separat, partajat) — vezi mai +jos. + +### Specifice ROAAUTO + +(niciunul încă — primele insight-uri de proiect au fost, până acum, toate transversale È™i au +migrat în `COMUN/docs/`) + +### Partajate (COMUN/docs/, valabile pe toate proiectele ROA*) + +- [cautare_vcx_vct.md](../COMUN/docs/cautare_vcx_vct.md) — cum se caută cod în binarele `.vcx`/`.scx` (conversie text + grep pe cache) +- [conventie_encoding_cp1252.md](../COMUN/docs/conventie_encoding_cp1252.md) — capcană: editarea `.sc2`/`.vc2` cu encoding greÈ™it corupe ireversibil diacriticele +- [conventie_ux_formulare.md](../COMUN/docs/conventie_ux_formulare.md) — cum îi place lui Marius formatat un formular nou (grupare, spaÈ›iere, culoare = semnal) +- [conventie_goexecutor_alter_table.md](../COMUN/docs/conventie_goexecutor_alter_table.md) — capcană: `ALTER TABLE` pe cursorul întors de `goExecutor.oExecute()` pică; include coloanele noi direct în SELECT diff --git a/docs/cautare_vcx_vct.md b/docs/cautare_vcx_vct.md deleted file mode 100644 index 8deb6ea..0000000 --- a/docs/cautare_vcx_vct.md +++ /dev/null @@ -1,127 +0,0 @@ -# Căutare în codul din librăriile/formele VFP (`.vcx`/`.vct`, `.scx`/`.sct`, ...) - -Cum se caută cod-sursă (proceduri, metode, parametri) care **nu** stă în fiÈ™iere `.prg`, ci -în interiorul fiÈ™ierelor binare VFP: librării de clase (`.vcx`+`.vct`), forme (`.scx`+`.sct`), -rapoarte (`.frx`+`.frt`), meniuri (`.mnx`+`.mnt`), baze de date (`.dbc`+`.dct`). - -## De ce e nevoie de asta - -Un `.vcx` este de fapt o **tabelă DBF**: codul metodelor stă în câmpuri memo în `.vct`. Un -`grep` direct pe `.vct` *găseÈ™te* textul, dar îl vede ca fiÈ™ier binar (octeÈ›i `\0` între linii), -fără numere de linie utile È™i greu de citit. SoluÈ›ia: convertim binarul în reprezentarea lui -**TEXT** (`.vc2`, `.sc2`, …) — un PRG-style lizibil, cu metode sortate alfabetic — È™i căutăm -în text cu `grep`/`Grep`. - -> **Regula de aur:** textul generat e **doar pentru citire/căutare**. Modificările de cod se fac -> tot în **IDE-ul VFP** (vezi `CLAUDE.md`). Nu edita `.vcx/.vct` pe text È™i nu pune textul înapoi -> în binar pentru aceste scopuri. - -## Unelte disponibile (verificate) - -| Unealtă | Cale | Rol | Recomandare | -|---|---|---|---| -| **FoxBin2Prg** | `D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE` | Binar VFP ⇄ text bidirecÈ›ional (`.vc2/.sc2/.fr2/.mn2/.dc2/.lb2`) | **De folosit.** Complet, fidel, include corpul metodelor, parametri, comentarii | -| vcx2prg | `D:\ROA\UTIL\vcx2prg\x2prg.prg` | Doar `.vcx` → `.prg` *compilabil* | Alternativă; mai vechi, doar clase, necesită IDE VFP deschis pentru a rula forma | -| prgtovcx | `D:\ROA\UTIL\prgtovcx\prgtovcx.PRG` | Invers: `.prg` → `.vcx` | **Nu** pentru căutare (e direcÈ›ia opusă) | - -FoxBin2Prg e prima alegere: rulează din linia de comandă fără IDE deschis (EXE compilat), acoperă -toate tipurile de container, È™i produce text mult mai fidel decât vcx2prg. - -> Dacă `FoxBin2Prg.EXE` lipseÈ™te, se compilează o singură dată: deschide -> `D:\ROA\UTIL\foxbin2prg\foxbin2prg.pj2` în VFP 9 È™i *Build > Build Executable*. Lângă EXE trebuie -> să existe `filename_caps.exe` È™i fiÈ™ierele `props*.txt` (sunt deja în folder). - -## Mod recomandat: scriptul `vcx2txt.ps1` - -`D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1` automatizează tot: copiază binarul + memo-ul într-un -**cache** care oglindeÈ™te structura proiectului (ca să nu murdărească arborele SVN), rulează -FoxBin2Prg, păstrează doar fiÈ™ierul text. E **incremental** (sare peste ce e deja la zi). - -Pentru ROAAUTO, scriptul trebuie parametrizat cu proiectul curent È™i un cache separat (nu-l -amesteca cu cel al altor proiecte ROA, ex. ROAACNPRO): - -```powershell -# DOAR fiÈ™ierele proiectului ROAAUTO (recomandat, rapid) -> cache text propriu -powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 ` - -Project D:\ROA\ROAAUTO\roaauto.pjx -ProjectRoot D:\ROA\ROAAUTO ` - -CacheRoot D:\ROA\UTIL\foxbin2prg\_textcache_roaauto - -# include È™i rapoartele (.frx) È™i meniurile (.mnx) -powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 ` - -Project D:\ROA\ROAAUTO\roaauto.pjx -ProjectRoot D:\ROA\ROAAUTO ` - -CacheRoot D:\ROA\UTIL\foxbin2prg\_textcache_roaauto -Types vcx,scx,frx,mnx - -# conversie punctuală a unui fiÈ™ier sau folder (chiar dacă NU e în proiect) -powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 -Source D:\ROA\ROAAUTO\COMUN\clase\caut.vcx - -# rebuild curat (È™terge cache-ul întâi) / forÈ›ează reconversia -powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 ` - -Project D:\ROA\ROAAUTO\roaauto.pjx -ProjectRoot D:\ROA\ROAAUTO ` - -CacheRoot D:\ROA\UTIL\foxbin2prg\_textcache_roaauto -Clean -powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 ` - -Project D:\ROA\ROAAUTO\roaauto.pjx -ProjectRoot D:\ROA\ROAAUTO ` - -CacheRoot D:\ROA\UTIL\foxbin2prg\_textcache_roaauto -Force -``` - -Rezultatul ajunge în `D:\ROA\UTIL\foxbin2prg\_textcache_roaauto\` cu aceeaÈ™i structură de -directoare ca proiectul (ex.: `_textcache_roaauto\Clase\odevize.vc2`, -`_textcache_roaauto\comun\clase\_frm_base.vc2`). Apoi: - -```bash -# cu unealta Grep (preferat) sau grep: -grep -rn "PROCEDURE do_calcul_tarife" "D:\ROA\UTIL\foxbin2prg\_textcache_roaauto" -grep -rln "factura_salvare" "D:\ROA\UTIL\foxbin2prg\_textcache_roaauto" # ce clase ating subiectul -``` - -Flux tipic pentru o problemă: rulează scriptul cu parametrii de mai sus o dată (populează -cache-ul), apoi caută cu `Grep` în `_textcache_roaauto`. La sesiuni următoare rescrie doar ce -s-a schimbat (incremental). Dacă ai nevoie de o librărie din `COMUN\` care nu e în proiect, -converteÈ™te-o punctual cu `-Source`. - -## Alternativă manuală (fără script) - -FoxBin2Prg scrie textul **lângă sursă**. Ca să nu murdăreÈ™ti `Clase/`, copiază perechea -binar+memo într-un folder temporar È™i converteÈ™te acolo: - -```powershell -# .vcx -> .vc2 (cType gol = extensia decide direcÈ›ia BIN2PRG) -Copy-Item D:\ROA\ROAAUTO\Clase\odevize.vcx, D:\ROA\ROAAUTO\Clase\odevize.vct D:\tmp\ -& 'D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE' 'D:\tmp\odevize.vcx' '' '' '' '1' '0' '1' -# => D:\tmp\odevize.vc2 -``` - -PoziÈ›iile parametrilor folosite: `fiÈ™ier, cType, cTextName, lGenText, cDontShowErrors=1, -cDebug=0, cDontShowProgress=1`. **Lasă `cType` gol** — extensia (`.vcx`) determină direcÈ›ia. -(Nu da explicit `"BIN2PRG"` pentru un singur fiÈ™ier: în acel mod EXE-ul aÈ™teaptă un *director*.) - -Detalii complete de parametri: `D:\ROA\UTIL\foxbin2prg\docs\FoxBin2Prg_Run.md`. - -## SoluÈ›ie rapidă „murdară" (un singur fiÈ™ier, fără conversie) - -Pentru o verificare rapidă „există metoda X undeva în librărie?", `grep` merge direct pe `.vct` -(o vede ca binar, dar găseÈ™te potrivirile): - -```bash -grep -c "PROCEDURE" "D:\ROA\ROAAUTO\Clase\odevize.VCT" -grep -a -o "PROCEDURE [a-zA-Z0-9_]*" "D:\ROA\ROAAUTO\Clase\odevize.VCT" -``` - -Bun pentru „da/nu" È™i nume de metode; pentru citit corpul/contextul, foloseÈ™te conversia în text. - -## Corespondență extensii binar → text - -| Container | Binar (+memo) | Text | -|---|---|---| -| Librărie clase | `.vcx` + `.vct` | `.vc2` | -| Formă | `.scx` + `.sct` | `.sc2` | -| Raport | `.frx` + `.frt` | `.fr2` | -| Etichetă | `.lbx` + `.lbt` | `.lb2` | -| Meniu | `.mnx` + `.mnt` | `.mn2` | -| Bază de date | `.dbc` + `.dct` | `.dc2` | - -## Note - -- Codul aplicaÈ›iei stă mai ales în `Clase\*.vcx`; cea mai mare parte din `.vcx`-uri sunt în - `COMUN\` (framework partajat — vezi `CLAUDE.md`). -- `_textcache_roaauto\` e regenerabil È™i e în afara arborelui SVN — poate fi È™ters oricând. -- Conversia **nu** necesită IDE-ul VFP deschis (foloseÈ™te EXE-ul compilat). diff --git a/docs/prd/prd-roaauto-1.0-transmitere-autopass.md b/docs/prd/prd-roaauto-1.0-transmitere-autopass.md index 5ead6d8..cc9281f 100644 --- a/docs/prd/prd-roaauto-1.0-transmitere-autopass.md +++ b/docs/prd/prd-roaauto-1.0-transmitere-autopass.md @@ -127,8 +127,8 @@ operatiile lor, **pentru ca** sa vad exact ce urmeaza sa se transmita. **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) +- **Fisiere**: `Programe\oproceduri_autopass.prg` (clasa `AutopassClient`); biblioteca JSON + `nfjsoncreate.prg`/`nfjsonread.prg` din `COMUN\utile\nfjson\` - **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`. @@ -378,9 +378,17 @@ Decizii luate (fost intrebari 1-4): 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`). +9. **DECIS (revizie 2026-07-10, Marius)** — **normalizare NRINMAT aprobata**: eliminarea + spatiilor, cratimelor si punctelor din numarul de inmatriculare inainte de validare se + aplica consecvent pe cele 3 canale: gateway `rar-autopass` (validare + idempotency), + clientul VFP (`autopass_valideaza_rand` din `oproceduri_autopass.prg`) si spike-ul SQL de + calitate a datelor (`docs\spike_calitate_date_autopass_v2.sql`, versiune noua fata de + spike-ul v1 rulat initial). Motivatie: spike-ul v1 a aratat doar 13,8% NRINMAT valide din + cauza formatelor reale cu spatii/cratime (ex. "B 99 XYZ"), regula stricta anterioara + (doar strip+upper) fiind prea restrictiva fata de datele istorice reale. Nu mai exista intrebari deschise — PRD aprobat prin poarta /autoplan (2026-07-09), -revizuit conform deciziilor 7-8. +revizuit conform deciziilor 7-9 (decizia 9 e o completare post-aprobare, 2026-07-10). ## 7. Valuri de executie @@ -557,5 +565,8 @@ nu se arunca. - **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. +- **Revizie post-aprobare (2026-07-10, Marius)**: decizia 9 — normalizare NRINMAT (strip + spatii/cratime/puncte) aprobata pe cele 3 canale (gateway, client VFP, spike SQL v2); + motivata de rata scazuta de valid (13,8%) gasita in spike-ul v1. NO UNRESOLVED DECISIONS diff --git a/roaauto.pjt b/roaauto.pjt index 140f9edab2b95992222b3f063c23cfba65e2597e..09b3646c6c96eee3a87d000f25c3a4c2b97f2e9d 100644 GIT binary patch delta 239 zcmX}jI}gE70LAg!OO>kk`%zLG(GS75XdgqJ+FfLWp+GP|COa1UG1ZWzda51^6}uVGtn#5=0>eGQ>fF1SBB^ zX~;kpa*&4t6rluVs6Z8JPzMznSA#a!7e3S_eo|#+%pB921+HiLK3}R`LEGKi>4Nl| gdb-A6op7V&F?Yf&#~uW>AN()oh)w4<M&_*k(3yMhY;n0t;{mEXM*ILt?!KXU_fOUTHm+YKUua S2+WS)c$Xm0FxMb-%~Al#VJ$ZR delta 21 ccmdlwlXd+xR#pQkX8Gnu)~&3Jn%<0307wx9VgLXD