14 KiB
Raport — mesajul "Nu poate fi preluat cursul BNR" la facturare pe politici de preturi
Investigatie READ-ONLY. Sursa: D:\ROA\ROAFACTURARE + D:\ROA\ROAFACTURARE\COMUN. Nicio editare,
nicio rulare, niciun write-back.
1. Textul exact al mesajului si locatia lui
Gasit in COMUN\clase\onom_curs.vc2, clasa actualizare_curs_bnr, PROCEDURE initializeaza:
onom_curs.vc2:171— textul exact reclamat de utilizator: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- Variante inrudite, in aceeasi metoda:
onom_curs.vc2:175si:181si:190—amessagebox("Nu exista cursul BNR pentru data specificata!",48,"Atentie")onom_curs.vc2:193—amessagebox("Doriti sa accesati pagina BNR?",4+32,"Confirmare")(ramuraOtherwise,nTipnecunoscut)
Nu exista alt loc in .prg/.vc2/.sc2 (in ROAFACTURARE sau COMUN) cu text asemanator
("cursul BNR", "nbrfxrates", "curs BNR") legat de un mesaj de eroare — un singur punct de emitere,
in actualizare_curs_bnr::initializeaza.
2. Lantul de apel complet, de la facturare pana la mesaj
Punct de intrare din meniul de facturare pe lista/politica de preturi:
COMUN\programe\oproceduri_facturare.prg:114-116—Apelat din butonulProcedure facturare_lista_de_preturi Do politica.mpr EndprocPage2.Cw1.do_actiune(confirmat deja indocs\plan_11_integrare_politici_preturi.md:16-18).Meniuri\politica.mn2:12,37,43,46— meniulpolitica.mprcheamafactureaza(1)(lei),factureaza(5)/(6)(invoice),factureaza(10)(factura fiscala in valuta),factureaza(9)(retur valuta) etc.COMUN\programe\ofacturare.prg:103—Procedure factureaza(tnTip, toFactura, toSursa). Pentru tipurile de lista de preturi (1,5,7,10,22,23,29 — vezi comentariul din cod,ofacturare.prg:140-149) populeaza articolele prin SQL server-side care trimitepoDate.zi_curs:ofacturare.prg:266-308(dupa cercetarea deja existentadocs\cercetare\s4d_zi_curs_reactiv.md:144-146):Case Inlist(tnTip, 1,22,5,29,7,10,23) -> cursor_preturi(?poDate.zi_curs, ...)- Oracle,
pack_facturare.cursor_preturicheama necondiționatpack_facturare.verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)(D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2153), care aruncaRAISE_APPLICATION_ERROR(-20005, 'Nu este setat cursul din data de ... pentru <valuta>!')(:16268-16272) daca vreo valuta din listele de preturi ale utilizatorului nu are curs valabil la data ceruta (exclude doar moneda nationala). - VFP prinde eroarea Oracle:
COMUN\programe\ofacturare.prg:313-317(identic la:828-832):If lnSucces < 0 AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare") If goExecutor.nEroare = 20005 vizualizeaza_curs(poDate.zi_curs) ENDIF COMUN\programe\oproceduri_curs.prg:8-42—Procedure vizualizeaza_curs(tdDataCurs)— deschide ecranul de administrare a cursurilor:loFrmCurs = Createobject("frm_curs",tdDataCurs)(:34),loFrmCurs.Show(1)(:36, modal).frm_curs(clasa de cautare/lista) NU declanseaza singura preluarea BNR — utilizatorul apasa "Nou" (But_nou1) ca sa adauge cursul lipsa, ceea ce deschidefrm_curs_nou.COMUN\clase\onom_curs.vc2:1305-1326—frm_curs_nou::Initleaga schimbarea datei/valutei inregistrarii noi la fetch automat:onom_curs.vc2:1323-1325 Bindevent(poRec,"data",Thisform,"actualizeaza_curs_bnr",1) Bindevent(poRec,"iso_valuta",Thisform,"actualizeaza_curs_bnr",1) Thisform.oactualizare = Createobject("actualizare_curs_bnr",gnTipActualizareCurs)COMUN\clase\onom_curs.vc2:1209-1226—frm_curs_nou::actualizeaza_curs_bnr(declansat la fiecare schimbare a datei/valutei din formular):onom_curs.vc2:1211 This.oactualizare.citeste_curs(poRec.Data,poRec.iso_valuta)COMUN\clase\onom_curs.vc2:46-62—actualizare_curs_bnr::citeste_curs(tdZiua,tcIsoValuta)—:50-52:If !Used(This.cCursor) Or tdZiua <> This.dData Then This.initializeaza(tdZiua).COMUN\clase\onom_curs.vc2:152-202—actualizare_curs_bnr::initializeaza(tdZiua)— interogheaza Oracle (pack_curs.citeste_cotatii_curs,:158-159); dacaReccount(This.cCursor)=0(nimic salvat local pentru data ceruta), intra peDo Casedescris la punctul 3 mai jos, unde se afla mesajul de la punctul 1.
A doua cale de intrare, directa, manuala, separata de fluxul de facturare de mai sus, spre acelasi
mesaj: Clase\ofundal_facturare.vc2:929-931 (fereastra principala de facturare, tab Page1, buton Cw7):
PROCEDURE Page1.Cw7.do_actiune
DO vizualizeaza_curs IN oproceduri_curs.prg WITH Ttod(get_ora())
ENDPROC
Trimite explicit data curenta (Ttod(get_ora())), deci un utilizator care apasa acest buton la
ora 19 si cursul zilei nu exista inca in Oracle intra pe acelasi drum (pasii 6-10 de mai sus).
3. Cum se decide data pentru care se cere cursul
Doua surse posibile pentru tdZiua/This.dData, ambele data curenta a sistemului, nu data
documentului si nu "data - 1":
- Ruta automata (eroare -20005):
poDate.zi_curs, care e populat necondiționat cu data curenta la initializarea antetului de factura —COMUN\programe\ofacturare_comun.prg:247(Init) si:496(Reset):.zi_curs = ldData, undeldData = Ttod(get_ora())ajustat doar la luna/anul de facturare deschisa (docs\cercetare\zi_curs_validare.md:198-204, citat ca sursa deja verificata). Nu e data documentului (dataact) decat daca operatorul a sincronizat manual campul (acelasi punct, §4b). - Ruta manuala (buton Cw7):
Ttod(get_ora())— literal ora curenta a sistemului, transformata in data (ofundal_facturare.vc2:930).
Decizia efectiva "azi sau nu" si "inainte sau dupa ora 13" se ia in
actualizare_curs_bnr::initializeaza, onom_curs.vc2:164-176:
onom_curs.vc2:164-176
ltDataOra = get_Ora()
Do Case
Case This.nTip = 1
Do Case
Case (This.dData = Ttod(ltDataOra) And Hour(ltDataOra) <13) Or (This.dData = Ttod(ltDataOra) + 1 And Hour(ltDataOra) >= 13)
This.citeste_curs_bnr()
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")
Endif
Otherwise
amessagebox("Nu exista cursul BNR pentru data specificata!",48,"Atentie")
Endcase
ltDataOra = ora curenta a sistemului (get_Ora()). Pentru scenariul raportat (data ceruta = azi,
ora curenta = 19:00): This.dData = Ttod(ltDataOra) si Hour(ltDataOra) >= 13 -> intra direct pe
a doua ramura (linia 170), care nu incearca deloc This.citeste_curs_bnr() — afiseaza direct
mesajul de la linia 171. Prima ramura (fetch live, linia 168) se executa doar cand se cere ziua
curenta inainte de ora 13, sau ziua urmatoare dupa ora 13 — niciodata pentru "azi, dupa ora 13".
4. Fallback la curs anterior — nu exista
In toata actualizare_curs_bnr::initializeaza (onom_curs.vc2:152-202) nu exista nicio ramura care sa
citeasca "ultimul curs disponibil" sau "ziua lucratoare anterioara" cand data ceruta nu are curs local
si e prea tarziu sa se mai incerce live-ul. Cele trei ramuri posibile pentru nTip=1 (liniile 166-176)
sunt exhaustiv: (a) incearca live fetch, (b) cere confirmare pentru deschidere manuala a bnr.ro, (c)
"Nu exista cursul BNR pentru data specificata!". Niciuna nu cade inapoi pe un rand data2 >= data anterioara din tabela locala. Confirmat si de cercetarea independenta anterioara
(docs\cercetare\s4d_zi_curs_reactiv.md:160-171): reteta Oracle de sub verifica_cursuri_valute
verifica strict daca exista curs care sa acopere data ceruta — daca nu, arunca -20005, fara fallback pe
partea de baza de date.
Exista o a treia "sursa alternativa" definita in cod, actualizare_curs_bnr::citeste_curs_altesurse
(onom_curs.vc2:64-98, nTip=2), care foloseste clasa curs (INFOVALUTAR, nu BNR direct) — dar se
activeaza doar cand This.nTip = 2 sau = 3 (onom_curs.vc2:177-191). Valoarea lui nTip vine
din parametrul gnTipActualizareCurs transmis la creare (onom_curs.vc2:1325, :1699). Nu am gasit
nicio declarare/atribuire a variabilei gnTipActualizareCurs in tot codul text-cache din
ROAFACTURARE sau COMUN (cautare exhaustiva, case-insensitive) — deci nu se poate confirma din cod
ce valoare are efectiv la rulare; actualizare_curs_bnr::Init (onom_curs.vc2:144-146) o trateaza ca
Iif(Empty(tnTip),1,tnTip), deci daca variabila e goala/nedefinita comportamentul cade pe nTip=1
(ramura fara alte surse, cea din mesajul raportat).
5. Sursa BNR: cum se apeleaza, timeout, tratarea erorii
actualizare_curs_bnr::citeste_curs_bnr — onom_curs.vc2:100-136:
onom_curs.vc2:100-118
PROCEDURE citeste_curs_bnr
Local loHTTP As 'winHTTP.winHTTPrequest.5.1'
...
lcServer = This.cLink && "https://curs.bnr.ro/nbrfxrates.xml" (onom_curs.vc2:28)
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
...
Xmltocursor(lcFisier,This.cCursor)
This.dData = Ttod(Ctot(lcData+[T000000]))+1
sterge_backup_cursoare(This.cCursor)
Else
repune_backup_cursoare(This.cCursor)
Endif
- Componenta:
WinHTTP.WinHTTPRequest.5.1(COM), cerere sincrona (Open(..., .F.)= async=False). - URL fix:
https://curs.bnr.ro/nbrfxrates.xml(onom_curs.vc2:28, proprietateacLink). - Niciun timeout explicit setat pe
loHTTP(nu apareSetTimeouts/.Timeoutin metoda) — foloseste timeout-ul implicit WinHTTP. - Tratare eroare: un singur test,
If loHTTP.Status = 200— orice alt rezultat (server jos, DNS, timeout, 404, XML gol, 500) cade peElse -> repune_backup_cursoare(...)(:131), fara mesaj propriu si fara sa arunce exceptie — pur si simpluThis.cCursorramane fara date noi (Reccount=0). - Important: aceasta metoda (
citeste_curs_bnr) e cea care ar face fetch-ul live, dar in scenariul raportat (azi, ora 19) nu e apelata deloc — vezi punctul 3, ramura (b) sare peste ea. Deci mesajul "Nu poate fi preluat automat cursul BNR" nu vine dintr-un fetch HTTP esuat in acest scenariu — vine dintr-o decizie de orar (Hour(ltDataOra) >= 13) care presupune ca preluarea trebuia sa se fi intamplat deja (manual sau altfel) inainte de ora 13, si nu mai incearca live dupa aceea. - Consecinta directa la intrebarea 5: mesajul de eroare NU distinge "serviciu indisponibil" de "data
lipsa din raspuns", pentru simplul motiv ca in cazul raportat nici macar nu se ajunge sa se apeleze
serviciul — testul e pur temporal (
Hour>=13), nu bazat pe rezultatul unei incercari HTTP. Pe ramura care CHIAR apeleaza HTTP (citeste_curs_bnr, cazul "azi, inainte de ora 13" sau "maine, dupa ora 13"), un eventual esec HTTP (status <>200) e complet tacut fata de operator (fara amessagebox) — duce doar laReccount=0, ceea ce las document fara curs, fara mesaj dedicat de "server indisponibil".
6. Stocare si import
- View Oracle citit de UI:
gcS.vcurs(schema curenta), coloane confirmate inoproceduri_curs.prg:20-21:id_curs, id_valuta, data, data2, nume_val, curs, multiplicator, id_valuta_iso, iso_valuta, curs_bnr. - Scriere:
actualizare_curs_bnr::scrie_curs_bnr(onom_curs.vc2:204-225) apeleazapack_curs.scrie_cotatii_curs(?pdData,?pcSirCursuri,?gnIdUtil)— daca ziua e vineri (Dow(pdData,2)=6), scrie si pentru sambata/duminica urmatoare (:213-216, curs valabil pe weekend). Aceasta scriere se intampla doar dupa un fetch reusit (citeste_curs_bnrcu status 200), apelata dininitializeazala linia 198 (If Reccount(This.cCursor)>0 Then This.scrie_curs_bnr()). - Citire cotatii curente pentru o data:
pack_curs.citeste_cotatii_curs(?pdData)(onom_curs.vc2:158). - Nu exista, in codul text-cache din
ROAFACTURARE/COMUN, niciun job/scheduler/task separat care sa apeleze automatciteste_curs_bnr/scrie_curs_bnrfara interventia unui utilizator care deschide un formular de curs (frm_curs_nou/frm_curs_nou_multiplu) sau declanseaza fluxul de eroare -20005. Singurele doua puncte de instantiere a claseiactualizare_curs_bnrin tot codul (onom_curs.vc2:1325,:1699) sunt ambele in formulare UI, niciuna intr-un.prgde pornire/batch/scheduler. Nu am gasit un al doilea produs/proces (in acest repo) care scrie in aceeasi tabelaCURS— nu pot confirma sau infirma existenta unui import extern din alt sistem (ex. ROAPRETURI sau alt exe din suita), care nu are cache text generat si nu a fost cautat aici.
Rezumat lant, pe scurt
facturare_lista_de_preturi (oproceduri_facturare.prg:114) -> politica.mpr -> factureaza(tnTip)
(ofacturare.prg:103) -> SQL cursor_preturi(?poDate.zi_curs) -> Oracle
pack_facturare.verifica_cursuri_valute -> ORA-20005 daca lipseste cursul zilei curente -> VFP
ofacturare.prg:313-317 -> vizualizeaza_curs(poDate.zi_curs) (oproceduri_curs.prg:8) ->
frm_curs/frm_curs_nou (onom_curs.vc2) -> actualizare_curs_bnr.citeste_curs (:46) ->
initializeaza (:152) -> la ora >=13 pentru data curenta, ramura care afiseaza direct
"Nu poate fi preluat automat cursul BNR. Doriti sa accesati pagina BNR?" (:171), fara sa mai
incerce fetch live si fara fallback pe ultimul curs disponibil.