16 KiB
Patch propus (v3) — arhiva 10 zile + fisier anual BNR, link alternativ cursbnr.ro
Tinta: COMUN\clase\onom_curs.vc2, clasa actualizare_curs_bnr. Investigatie sursa:
docs\raport_curs_bnr_politici_preturi.md. Diff unificat aplicabil: docs\patch_curs_bnr_arhiva.diff.
Inlocuieste integral v1 si v2 (nu e delta) — aplicat pe fisierul original, neschimbat intre timp.
Scop restrans de Marius, valabil din v2: se lucreaza DOAR pe ramura nTip = 1, cazul de dupa
ora 13. nTip = 2 si nTip = 3 raman neatinse.
Doua verificari live, facute de Marius, folosite ca atare (nereverificate aici):
https://curs.bnr.ro/nbrfxrates10days.xml-> HTTP 200, un<OrigCurrency>, mai multe<Cube date="YYYY-MM-DD">descrescator, pana la 10 zile lucratoare (v2).https://curs.bnr.ro/files/xml/years/nbrfxrates2026.xml-> HTTP 200, ~245 KB, structura IDENTICA (un<OrigCurrency>, apoi<Cube date="...">de la 2026-01-05 pana azi, ordine crescatoare). Acelasi cod de extragere a unui<Cube>merge neschimbat.
1. Proprietati noi: cLinkArhiva si cLinkAn
Bloc afectat: definitia clasei (lista *p:, PropValue, _memberdata), onom_curs.vc2:16-42.
VECHI (onom_curs.vc2:16-18, in *<DefinedPropArrayMethod>):
*p: ccursor
*p: clink
*p: ctempfile
NOU:
*p: ccursor
*p: clink
*p: clinkan
*p: clinkarhiva
*p: ctempfile
VECHI (onom_curs.vc2:26-29, in *<PropValue>):
ccursor = ccrsbnr
clink = https://curs.bnr.ro/nbrfxrates.xml
ctempfile = C:\temp\temp.xml
NOU:
ccursor = ccrsbnr
clink = https://curs.bnr.ro/nbrfxrates.xml
clinkan = https://curs.bnr.ro/files/xml/years/nbrfxrates%.xml
clinkarhiva = https://curs.bnr.ro/nbrfxrates10days.xml
ctempfile = C:\temp\temp.xml
cLinkAn tine sablonul cu % in locul anului; se compune la rulare cu
Strtran(This.cLinkAn,"%",Alltrim(Str(lnAn))).
VECHI (onom_curs.vc2:37-40, in _memberdata):
<memberdata name="ctempfile" display="cTempFile"/>
<memberdata name="clink" display="cLink"/>
<memberdata name="ccursor" display="cCursor"/>
NOU:
<memberdata name="ctempfile" display="cTempFile"/>
<memberdata name="clink" display="cLink"/>
<memberdata name="clinkan" display="cLinkAn"/>
<memberdata name="clinkarhiva" display="cLinkArhiva"/>
<memberdata name="ccursor" display="cCursor"/>
2. citeste_curs_bnr (onom_curs.vc2:100-136) — parametru tnSursa (0/1/2) + cautare inapoi + reincercare an anterior
Parametrul tlArhiva (logic, din v2) devine tnSursa (numeric): 0 = fisierul zilei (cLink,
comportamentul actual, apelul existent This.citeste_curs_bnr() fara argumente ramane identic —
parametrul netransmis e .F. logic, convertit explicit la 0 cu Vartype), 1 = arhiva 10 zile
(cLinkArhiva), 2 = fisierul anual (cLinkAn, cu anul Year(This.dData - 1)). Cod de retur
neschimbat fata de v2: 0 = succes, 1 = eroare HTTP/serviciu indisponibil, 2 = nicio zi din
fereastra cautata nu exista in raspuns.
Cautarea inapoi zi cu zi (max 10 incercari) din v2 e neschimbata pentru tnSursa=1 si e refolosita
identic pentru tnSursa=2. Cazul special pentru tnSursa=2: daca in timpul cautarii data candidata
trece in anul precedent fata de anul fisierului deja descarcat (caz real: o factura din primele zile
ale lui ianuarie are nevoie de publicarea din 31 decembrie anul trecut), se face o singura
reincercare — un al doilea request HTTP pe fisierul anual anterior — si cautarea continua in noul
raspuns. Nu se reincearca a doua oara peste alt an (llRetrasAnAnterior blocheaza a doua trecere).
VECHI (onom_curs.vc2:100-136, integral):
PROCEDURE citeste_curs_bnr
Local loHTTP As 'winHTTP.winHTTPrequest.5.1'
Local lcData, lcFisier, lcServer
If Empty(This.cCursor)
This.cCursor = [ccrsbnr]
Endif
creeaza_backup_cursoare(This.cCursor) && modificare 03.06.2013
If Used(This.cCursor)
Use In (This.cCursor)
Endif
lcServer = This.cLink
loHTTP = Createobject('winHTTP.winHTTPrequest.5.1')
loHTTP.Open('GET', lcServer, .F.)
loHTTP.setRequestHeader("Content-Type", "application/xml;")
poLog.Log(m.lcServer)
loHTTP.Send()
If loHTTP.Status = 200
lcFisier = loHTTP.Responsebody
lcFisier = Strextract(lcFisier,[</OrigCurrency>],[</Body>])
lcData = Strextract(lcFisier,[date="],["])
lcFisier= Strtran(lcFisier,[date="]+lcData+["],[])
lcFisier= Strtran(Strtran(lcFisier,[">],[" curs="]),[</Rate>],[" />])
Xmltocursor(lcFisier,This.cCursor)
This.dData = Ttod(Ctot(lcData+[T000000]))+1
*!* modificare 03.06.2013
sterge_backup_cursoare(This.cCursor)
Else
repune_backup_cursoare(This.cCursor)
Endif
*!* modificare 03.06.2013 ^
Release loUpdate,loIp,loUrl,lcData,lnSize,lcFisier,lcData
ENDPROC
NOU (v4, cu reparatia Try/Catch de la sectiunea 7):
PROCEDURE citeste_curs_bnr
Lparameters tnSursa
Local loHTTP As 'winHTTP.winHTTPrequest.5.1'
Local lcData, lcFisier, lcServer, lcCubeCautat, ldCandidat, lnIncercari, lnAnCautat, llRetrasAnAnterior, lnStatus
If Vartype(tnSursa) <> "N"
tnSursa = 0
Endif
If Empty(This.cCursor)
This.cCursor = [ccrsbnr]
Endif
creeaza_backup_cursoare(This.cCursor) && modificare 03.06.2013
If Used(This.cCursor)
Use In (This.cCursor)
Endif
Do Case
Case tnSursa = 1
lcServer = This.cLinkArhiva
Case tnSursa = 2
lnAnCautat = Year(This.dData - 1)
lcServer = Strtran(This.cLinkAn,"%",Alltrim(Str(lnAnCautat)))
Otherwise
lcServer = This.cLink
Endcase
loHTTP = Createobject('winHTTP.winHTTPrequest.5.1')
loHTTP.Open('GET', lcServer, .F.)
loHTTP.SetTimeouts(5000,5000,5000,15000)
loHTTP.setRequestHeader("Content-Type", "application/xml;")
poLog.Log(m.lcServer)
Try
loHTTP.Send()
lnStatus = loHTTP.Status
Catch
lnStatus = 0
Endtry
If lnStatus = 200
lcFisier = loHTTP.Responsebody
lcFisier = Strextract(lcFisier,[</OrigCurrency>],[</Body>])
If tnSursa = 0
lcData = Strextract(lcFisier,[date="],["])
lcFisier= Strtran(lcFisier,[date="]+lcData+["],[])
Else
lcCubeCautat = []
ldCandidat = This.dData - 1
lnIncercari = 0
llRetrasAnAnterior = .F.
Do While Empty(lcCubeCautat) And lnIncercari < 10
If tnSursa = 2 And Year(ldCandidat) < lnAnCautat And !llRetrasAnAnterior
llRetrasAnAnterior = .T.
lnAnCautat = lnAnCautat - 1
lcServer = Strtran(This.cLinkAn,"%",Alltrim(Str(lnAnCautat)))
loHTTP = Createobject('winHTTP.winHTTPrequest.5.1')
loHTTP.Open('GET', lcServer, .F.)
loHTTP.SetTimeouts(5000,5000,5000,15000)
loHTTP.setRequestHeader("Content-Type", "application/xml;")
poLog.Log(m.lcServer)
Try
loHTTP.Send()
lnStatus = loHTTP.Status
Catch
lnStatus = 0
Endtry
If lnStatus <> 200
repune_backup_cursoare(This.cCursor)
Release loUpdate,loIp,loUrl,lcData,lnSize,lcFisier,lcData
Return 1
Endif
lcFisier = Strextract(loHTTP.Responsebody,[</OrigCurrency>],[</Body>])
Endif
lcData = Str(Year(ldCandidat),4) + "-" + Padl(Alltrim(Str(Month(ldCandidat))),2,"0") + "-" + Padl(Alltrim(Str(Day(ldCandidat))),2,"0")
lcCubeCautat = Strextract(lcFisier,[<Cube date="]+lcData+[">],[</Cube>])
ldCandidat = ldCandidat - 1
lnIncercari = lnIncercari + 1
Enddo
If Empty(lcCubeCautat)
repune_backup_cursoare(This.cCursor)
Release loUpdate,loIp,loUrl,lcData,lnSize,lcFisier,lcData
Return 2
Endif
lcFisier = [<Cube>] + lcCubeCautat + [</Cube>]
Endif
lcFisier= Strtran(Strtran(lcFisier,[">],[" curs="]),[</Rate>],[" />])
Xmltocursor(lcFisier,This.cCursor)
If tnSursa = 0
This.dData = Ttod(Ctot(lcData+[T000000]))+1
Endif
*!* modificare 03.06.2013
sterge_backup_cursoare(This.cCursor)
Else
repune_backup_cursoare(This.cCursor)
Release loUpdate,loIp,loUrl,lcData,lnSize,lcFisier,lcData
Return 1
Endif
*!* modificare 03.06.2013 ^
Release loUpdate,loIp,loUrl,lcData,lnSize,lcFisier,lcData
Return 0
ENDPROC
Explicatie tehnica pe scurt (nu intra in cod, doar aici):
tnSursa=0(apel normal, nemodificat functional) merge exact pe ramura veche, pastreazaThis.dData = publishingDate+1.tnSursa=1— identic v2: cauta cel mai recentCubecu data<= This.dData - 1, mergand inapoi zi cu zi, max 10 incercari.tnSursa=2— acelasi mers inapoi zi cu zi, dar pe fisierul anual al anuluiYear(This.dData-1); daca data candidata trece in anul precedent fara sa fi gasit inca unCube, face un singur request suplimentar pe fisierul anului precedent si continua cautarea acolo. Daca acel request esueaza (Status<>200), intoarce direct1(indisponibilitate), nu2.This.dDataNU se rescrie pentrutnSursa=1sau2— ramane ziua ceruta, exact ce asteaptascrie_curs_bnr.
3. initializeaza (onom_curs.vc2:152-202) — ramura nTip=1, cazul "prea tarziu azi": cascada + link alternativ
Se atinge STRICT ramura de la onom_curs.vc2:170-173. nTip=2 si nTip=3 raman neatinse.
VECHI (onom_curs.vc2:170-173):
Case This.dData < Ttod(ltDataOra) Or This.dData = Ttod(ltDataOra) And Hour(ltDataOra) >= 13
If amessagebox("Nu poate fi preluat automat cursul BNR. Doriti sa accesati pagina BNR?",4+32,"Confirmare") = 6
goUrl("http://www.bnr.ro") && din wwutils.prg
Endif
NOU:
Case This.dData < Ttod(ltDataOra) Or This.dData = Ttod(ltDataOra) And Hour(ltDataOra) >= 13
lnStareBnr = This.citeste_curs_bnr(1)
If lnStareBnr = 2
lnStareBnr = This.citeste_curs_bnr(2)
Endif
Do Case
Case lnStareBnr = 0
&& curs preluat din arhiva sau fisierul anual BNR
Case lnStareBnr = 2
amessagebox("Nu exista cursul BNR pentru data specificata!",48,"Atentie")
Otherwise
If amessagebox("Nu poate fi preluat automat cursul BNR. Doriti sa accesati pagina cursbnr.ro?",4+32,"Confirmare") = 6
goUrl("https://www.cursbnr.ro/")
Endif
Endcase
Cascada, exact cum a cerut Marius:
- incearca arhiva 10 zile (
citeste_curs_bnr(1)); - daca intoarce
2(data lipsa din raspuns), incearca fisierul anual (citeste_curs_bnr(2)); - daca si acela intoarce
2->"Nu exista cursul BNR pentru data specificata!"; - daca oricare din cele doua intoarce
1(eroare HTTP/timeout) -> mesajul de indisponibilitate, cu oferta de a deschidehttps://www.cursbnr.ro/(nu maihttp://www.bnr.ro, doar in aceasta ramura — celelalte doua aparitii alegoUrl("http://www.bnr.ro")din fisier, ramura de dinainte de ora 13 siOtherwise-ul cunTipnecunoscut, raman neschimbate).
lnStareBnr nu e declarata explicit — la fel ca lcSql, lnSucces, ltDataOra deja folosite in
aceeasi metoda fara declarare explicita (scop implicit PRIVATE al VFP); pastrat pentru consistenta
stilistica.
Raspuns despre nTip
Neschimbat fata de v2: se acopera doar nTip=1; nTip=2 si nTip=3 raman neatinse, prin decizie
explicita a lui Marius.
4. Ce acopera
| Scenariu | Rezultat |
|---|---|
| Azi, dupa ora 13, Oracle nu are cursul zilei | arhiva 10 zile gaseste ziua anterioara (lnStareBnr=0), fara mesaj |
| Weekend (luni, dupa ora 13, vinerea nu era salvata local) | arhiva gaseste Cube-ul de vineri prin mersul inapoi zi cu zi |
| Sarbatoare legala (o zi lucratoare inconjurata de nelucratoare) | arhiva merge inapoi pana gaseste ultima zi publicata, fara lista de sarbatori in cod |
| Data mai veche de 10 zile lucratoare, dar in anul curent sau anul trecut | arhiva 10 zile esueaza cu 2, se incearca fisierul anual (cLinkAn), care acopera restul anului |
| Factura pe inceput de ianuarie (ex. 2 ianuarie), cursul cerut e din 31 decembrie anul trecut | fisierul anual porneste pe anul curerii (Year(This.dData-1)), cautarea trece granita anului, se face o singura reincercare pe fisierul anului precedent |
| Data mai veche decat acopera si arhiva, si fisierul anual (inclusiv anul precedent, dupa reincercare) | "Nu exista cursul BNR pentru data specificata!" |
| BNR picat / timeout la oricare din cele doua incercari (arhiva sau an) | mesajul de indisponibilitate, cu oferta de a deschide https://www.cursbnr.ro/ |
5. Riscuri deschise
- Al doilea request HTTP inline in bucla, doar pentru
tnSursa=2la trecerea de an — cost suplimentar minor (o cerere HTTP in plus, o singura data, doar cand data ceruta cade in primele zile ale unui an); restul cautarii ramane pe sirul deja descarcat in memorie. - Confirmat prin fetch real: structura
nbrfxrates2026.xmle identica per-Cube cu celelalte doua fisiere BNR, doar cu mai multe elemente<Cube date="...">, in ordine crescatoare — extractia printr-un singurCubeinainte deXmltocursorramane corecta si aici. repune_backup_cursoarepe cursor deja inchis — risc preexistent, nu introdus acum (aceeasi observatie ca in v1/v2):citeste_curs_bnrinchideThis.cCursorinainte de orice apel HTTP;repune_backup_cursoare(oproceduri_comune.prg:6358-6375) faceSelectpe un alias deja inchis in acel moment — comportament mostenit, nu nou, si in afara scope-ului acestui patch.- URL-uri BNR pe host
curs.bnr.ro, confirmate live acum (nbrfxrates10days.xml,nbrfxrates2026.xml); se pot schimba fara preaviz — de reverificat manual daca reapare mesajul de indisponibilitate des dupa aplicare. - Fisierul anual creste an de an (~245 KB la mijlocul lui 2026) — cautarea foloseste tot
Strextractpe sirul deja incarcat in memorie, fara reincarcare per incercare; cost acceptabil pentru o operatie declansata manual din UI, nu intr-o bucla de facturare in masa.
6. Cum se probeaza (fara sa ruleze acum)
Nu am rulat nimic din ce urmeaza — se lasa pentru proba manuala dupa aprobare si aplicare:
- Aplica diff-ul (
docs\patch_curs_bnr_arhiva.diff) peCOMUN\clase\onom_curs.vc2prin fluxul text->binar existent (skillroa-vfp-text-edit,txt2vcx.ps1cu-ProjectRoot D:\ROA\ROAFACTURARE). - Caz "azi dupa ora 13": instantiaza
actualizare_curs_bnrcunTip=1, seteaza data sistemului saultDataOrasimulat dupa ora 13, cereciteste_curs(Ttod(get_ora()))pentru o zi pentru care Oracle nu are inca rand — verifica inpoLogca al doilea link apelat ehttps://curs.bnr.ro/nbrfxrates10days.xml. - Caz "mai vechi de 10 zile, in acelasi an": alege o data acoperita doar de fisierul anual —
verifica in
poLogca dupa arhiva urmeaza apelul catrehttps://curs.bnr.ro/files/xml/years/nbrfxrates<an>.xml. - Caz trecere de an: cere cursul pentru 2 ianuarie (anul curent), cand 31 decembrie anul trecut nu
e salvat local — verifica cele doua request-uri succesive catre fisierul anual curent si cel
anterior, si ca
This.dDataramane 2 ianuarie (nu 31 decembrie). - Verifica in
This.cCursordupa apel:Reccount()>0, campurileCurrency/Curs/multiplierpopulate identic cu un fetch normal. - Verifica in Oracle, dupa
scrie_curs_bnr, ca randul scris inCURSaredata= ziua ceruta. - Caz negativ, mai vechi decat orice arhiva: seteaza
This.dDatacu cativa ani in urma — asteapta mesajul "Nu exista cursul BNR pentru data specificata!". - Caz eroare retea: schimba temporar
cLinkArhiva(saucLinkAn) la un URL invalid — asteapta mesajul de indisponibilitate si confirma linkul nouhttps://www.cursbnr.ro/in loc dehttp://www.bnr.ro, doar pe aceasta ramura.
7. Reparatie (v4) — loHTTP.Send() neprotejat (host nerezolvabil)
Defect gasit de testul headless (Cazul 6, docs\test_curs_bnr_headless.md): loHTTP.Send() nu era
in Try/Catch. La host complet nerezolvabil (DNS esuat, eroare OLE 1429), executia sarea peste
blocul If loHTTP.Status = 200 ... Else ... Return 1 ... Endif, intra in bucla de cautare Cube
cu lcFisier nedefinit si iesea gresit pe Return 2 ("data lipsa" in loc de "BNR picat").
Reparatie, pe ambele aparitii ale loHTTP.Send() din metoda (fisierul zilei/arhiva/an, si
reincercarea pe anul precedent):
Try
loHTTP.Send()
lnStatus = loHTTP.Status
Catch
lnStatus = 0
Endtry
Status se citeste o singura data, in interiorul lui Try — citit dupa, a doua exceptie OLE
("The data necessary...") ar fi picat imediat. lnStatus inlocuieste toate referintele la
loHTTP.Status din metoda si e adaugat la lista Local existenta. Catch gol, fara logare noua;
pe al doilea Send() comportamentul la esec ramane neschimbat (repune_backup_cursoare +
Return 1). Cod complet, integrat in blocul "NOU" de la sectiunea 2.