sync SVN r18174
This commit is contained in:
14
AGENTS.md
14
AGENTS.md
@@ -21,14 +21,8 @@ skill-ului.
|
|||||||
- final de sesiune sau de lucrare / "salveaza ce ai invatat" / actualizezi documentatia sau fluxurile -> skill `roa-actualizare-doc`
|
- final de sesiune sau de lucrare / "salveaza ce ai invatat" / actualizezi documentatia sau fluxurile -> skill `roa-actualizare-doc`
|
||||||
<!-- roa-skills:end -->
|
<!-- 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
|
OBLIGATORIU, inainte de orice raspuns sau editare, si in sesiunea principala si in orice
|
||||||
si o recomandare clara, apoi intreaba — nu actiona unilateral. Pune recomandarea prima, marcat-o.
|
subagent: citeste `COMUN\docs\stil_interactiune_cautare.md` si respecta-l (interactiune,
|
||||||
Intreaba o singura data, asteapta raspunsul; nu repeta intrebari la care nu s-a raspuns si nu intra
|
livrarea muncii, corectii, cautare). Nu il rezuma din memorie.
|
||||||
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`.
|
|
||||||
|
|||||||
230
docs/raport_curs_bnr_politici_preturi.md
Normal file
230
docs/raport_curs_bnr_politici_preturi.md
Normal 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.
|
||||||
Reference in New Issue
Block a user