sync SVN r18174

This commit is contained in:
2026-09-17 19:54:37 +03:00
parent 4e9bd96c1a
commit 04a78f99db
2 changed files with 234 additions and 10 deletions

View File

@@ -21,14 +21,8 @@ skill-ului.
- final de sesiune sau de lucrare / "salveaza ce ai invatat" / actualizezi documentatia sau fluxurile -> skill `roa-actualizare-doc`
<!-- roa-skills:end -->
## Stil de interactiune
## Stil de interactiune si lucru
Cand ai o decizie, o ambiguitate sau mai multe abordari valide, prezinta optiuni cu trade-off-uri
si o recomandare clara, apoi intreaba — nu actiona unilateral. Pune recomandarea prima, marcat-o.
Intreaba o singura data, asteapta raspunsul; nu repeta intrebari la care nu s-a raspuns si nu intra
in loop de decizie. Actioneaza direct doar cand scopul e clar si schimbarea e mica; pentru orice
netrivial, propune un plan si intreaba inainte de a edita. Daca eziti intre doua variante
rezonabile, alege una si mergi — poti corecta. Separa "a raspunde" de "a executa": o intrebare sau
o parere primeste un raspuns, nu o implementare. Fii concis: fara preambul, fara sa povestesti ce
urmeaza sa faci. Extinderea completa (interactiune + cautare, distilata din promptul Claude Code):
`COMUN\docs\stil_interactiune_cautare.md`.
OBLIGATORIU, inainte de orice raspuns sau editare, si in sesiunea principala si in orice
subagent: citeste `COMUN\docs\stil_interactiune_cautare.md` si respecta-l (interactiune,
livrarea muncii, corectii, cautare). Nu il rezuma din memorie.

View File

@@ -0,0 +1,230 @@
# 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 <valuta>!')`
(`: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.