# 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:175` si `:181` si `:190` — `amessagebox("Nu exista cursul BNR pentru data specificata!",48,"Atentie")` - `onom_curs.vc2:193` — `amessagebox("Doriti sa accesati pagina BNR?",4+32,"Confirmare")` (ramura `Otherwise`, `nTip` necunoscut) 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: 1. `COMUN\programe\oproceduri_facturare.prg:114-116` — ``` Procedure facturare_lista_de_preturi Do politica.mpr Endproc ``` Apelat din butonul `Page2.Cw1.do_actiune` (confirmat deja in `docs\plan_11_integrare_politici_preturi.md:16-18`). 2. `Meniuri\politica.mn2:12,37,43,46` — meniul `politica.mpr` cheama `factureaza(1)` (lei), `factureaza(5)`/`(6)` (invoice), `factureaza(10)` (factura fiscala in valuta), `factureaza(9)` (retur valuta) etc. 3. `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 trimite `poDate.zi_curs`: `ofacturare.prg:266-308` (dupa cercetarea deja existenta `docs\cercetare\s4d_zi_curs_reactiv.md:144-146`): ``` Case Inlist(tnTip, 1,22,5,29,7,10,23) -> cursor_preturi(?poDate.zi_curs, ...) ``` 4. Oracle, `pack_facturare.cursor_preturi` cheama **necondiționat** `pack_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 arunca `RAISE_APPLICATION_ERROR(-20005, 'Nu este setat cursul din data de ... pentru !')` (`:16268-16272`) daca vreo valuta din listele de preturi ale utilizatorului nu are curs valabil la data ceruta (exclude doar moneda nationala). 5. 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 ``` 6. `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 deschide `frm_curs_nou`. 7. `COMUN\clase\onom_curs.vc2:1305-1326` — `frm_curs_nou::Init` leaga 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) ``` 8. `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) ``` 9. `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)`. 10. `COMUN\clase\onom_curs.vc2:152-202` — `actualizare_curs_bnr::initializeaza(tdZiua)` — interogheaza Oracle (`pack_curs.citeste_cotatii_curs`, `:158-159`); daca **`Reccount(This.cCursor)=0`** (nimic salvat local pentru data ceruta), intra pe `Do Case` descris 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`, unde `ldData = 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`, proprietatea `cLink`). - **Niciun timeout explicit setat** pe `loHTTP` (nu apare `SetTimeouts`/`.Timeout` in 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 pe `Else -> repune_backup_cursoare(...)` (`:131`), fara mesaj propriu si fara sa arunce exceptie — pur si simplu `This.cCursor` ramane 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 la `Reccount=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 in `oproceduri_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`) apeleaza `pack_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_bnr` cu status 200), apelata din `initializeaza` la 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 automat `citeste_curs_bnr`/`scrie_curs_bnr` fara 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 clasei `actualizare_curs_bnr` in tot codul (`onom_curs.vc2:1325`, `:1699`) sunt ambele in formulare UI, niciuna intr-un `.prg` de pornire/batch/scheduler. Nu am gasit un al doilea produs/proces (in acest repo) care scrie in aceeasi tabela `CURS` — 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.