sync SVN r18175
This commit is contained in:
158
docs/handoff_activ.md
Normal file
158
docs/handoff_activ.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# HANDOFF ACTIV - QA factura/aviz + prototip context
|
||||||
|
|
||||||
|
Scris 17.09.2026 de sesiunea orchestratoare, la ~200k context. Acesta e fisierul pe care
|
||||||
|
`handoff_inject.ps1` il reinjecteaza automat la `compact` / `clear` / `resume`.
|
||||||
|
**Cine il citeste: aceasta e STARE, nu instructiune noua. Continua de aici.**
|
||||||
|
|
||||||
|
## GAURA DE MECANISM, neacoperita - de rezolvat la reluare
|
||||||
|
|
||||||
|
Documentele din `docs/` (inclusiv ACEST fisier) sunt **netracked in git si nu se comit pe main**.
|
||||||
|
Deci nu au niciun transport automat: pe VM 304 au ajuns prin copiere manuala, si orice modificare
|
||||||
|
ulterioara pe o masina NU ajunge pe cealalta. Cele doua copii diverg tacut.
|
||||||
|
|
||||||
|
Mecanismul de reinjectare functioneaza perfect **in cadrul unei masini**, si deloc intre masini.
|
||||||
|
|
||||||
|
Solutia propusa, NEAPLICATA (cere decizia lui Marius): commit-ul documentelor de lucru pe un
|
||||||
|
**branch dedicat** in ROAFACTURARE (nu pe main - regula ramane), ca transportul sa fie `git`, nu
|
||||||
|
copierea manuala. Alternativ, un singur fisier de stare comis pe branch, iar restul raman locale.
|
||||||
|
|
||||||
|
Stare la 17.09.2026, dupa curatenie: pe AMBELE masini exista exact `plan_qa_factura_aviz.md`,
|
||||||
|
`progres_qa_factura.md`, `handoff_activ.md`, `qa_factura_harta.md`, `qa_factura_unelte.md`
|
||||||
|
(+ `PORNIRE.md` doar pe VM). Sincronizate manual. Orice divergenta de aici incolo e tacuta.
|
||||||
|
|
||||||
|
## Ce se lucreaza
|
||||||
|
|
||||||
|
Plan: `docs/plan_qa_factura_aviz.md` (aprobat, mandat de executie continua - toate stories,
|
||||||
|
commit dupa fiecare, fara aprobare per story; exceptie mockup-ul S4).
|
||||||
|
Stare pe stories: `docs/progres_qa_factura.md`.
|
||||||
|
|
||||||
|
Doua fire in paralel:
|
||||||
|
- **A. QA factura/aviz** - S0 gata, S1-S5 de facut, S1 incolo pe VM 304.
|
||||||
|
- **B. Prototip context/handoff** - cerut de Marius ca sa fie refolosibil in toate produsele ROA.
|
||||||
|
|
||||||
|
## B. Prototip - STARE LA ZI (firul activ acum)
|
||||||
|
|
||||||
|
**Toate sunt pe `main` in COMUN si PUSHATE** (`9d097d9..c196642`, gitea romfast/comun.git),
|
||||||
|
cu aprobarea explicita a lui Marius din 17.09.2026. Rebazate peste 9 commit-uri `roa_sync`,
|
||||||
|
fara conflicte. Hash-urile de mai jos sunt cele DE DUPA rebase.
|
||||||
|
Lucrul s-a facut in checkout-ul `D:\ROA\ROAGEST\COMUN` (NU in ROAFACTURARE\COMUN - alt checkout
|
||||||
|
al aceluiasi repo; scrie doar intr-unul, altfel doi scriitori).
|
||||||
|
|
||||||
|
| Commit | Ce |
|
||||||
|
|---|---|
|
||||||
|
| `65d681a` | `context_watch.ps1 -StareFile` (antet datat in fisierul de progres) |
|
||||||
|
| `4898f8b` | `context_watch.ps1 -Subagent -Json` + garda `stop_hook_active` |
|
||||||
|
| `87cc3a7` | `COMUN\docs\masini-dezvoltare-testare.md` (VM 304) + `monitorizare-context.md` + README |
|
||||||
|
| `c196642` | `handoff_inject.ps1` - reinjectare handoff pe SessionStart |
|
||||||
|
|
||||||
|
`settings.json` (nu e versionat; backup `settings.json.bak_20260917`):
|
||||||
|
- `SubagentStop` -> `context_watch.ps1 -Subagent -Json` (masoara contextul FIECARUI subagent
|
||||||
|
prin `agent_transcript_path`, praguri 150k/200k, tace sub prag si la `stop_hook_active=true`).
|
||||||
|
- `SessionStart` -> `handoff_inject.ps1` adaugat langa `docs_revizie_check.ps1` cel existent.
|
||||||
|
Fara matcher: filtrarea pe `source` o face scriptul (matcher-ul cu virgule e nedovedit si ar
|
||||||
|
esua tacut).
|
||||||
|
- `env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = "30"`.
|
||||||
|
|
||||||
|
### Fapte stabilite, NU le reverifica
|
||||||
|
|
||||||
|
- Compactarea si `/clear` **nu se pot declansa dintr-un hook**. Confirmat prin citat direct.
|
||||||
|
- `PostCompact` **nu poate injecta context** (nu are control de decizie). Reinjectarea se face
|
||||||
|
prin `SessionStart` cu `source=compact`. Matcher-ele SessionStart: `startup`, `resume`,
|
||||||
|
`clear`, `compact`, `fork`.
|
||||||
|
- `additionalContext` are prag de 10.000 caractere; peste el Claude Code il scrie in fisier si
|
||||||
|
paseaza calea. `handoff_inject.ps1` taie la 9000 si spune explicit ca a taiat + da calea.
|
||||||
|
- **Niciun hook nu primeste tokenii de-a gata** - nici `command`, nici SDK. Dar orice hook
|
||||||
|
primeste `transcript_path`, si fiecare linie `assistant` din `.jsonl` are `message.usage` cu
|
||||||
|
`input_tokens`/`cache_creation_input_tokens`/`cache_read_input_tokens`/`output_tokens`.
|
||||||
|
Pe asta e construit `context_watch.ps1`. Singurul canal cu procent gata calculat e
|
||||||
|
`statusLine`, care nu e hook.
|
||||||
|
- Claude Code are 5 tipuri de hook: `command`/`http`/`mcp_tool`/`prompt`/`agent`. Nu exista tip
|
||||||
|
"function" in produsul shipat.
|
||||||
|
- **Aritmetica ferestrei**: fereastra e 1M. 250k = 25%, nu 70%. De aceea
|
||||||
|
`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` e 30 (=300k), imediat peste pragul max de 275k - nu 70,
|
||||||
|
care ar fi insemnat 700k.
|
||||||
|
|
||||||
|
### Ce a ramas de facut la B
|
||||||
|
|
||||||
|
1. **Function hooks - INCHIS, nu se poate.** Verificat pe binarul 2.1.274 prin citire directa
|
||||||
|
(raportul de cercetare a fost sters dupa condensare; concluzia e mai jos). Blogul claudefa.st supraliciteaza: binarul are 8
|
||||||
|
hook-uri (`session.start`, `ui.render`, `command.run`, `ui.close`, `ui.focus`, `ui.scroll`,
|
||||||
|
`tool.call`, `prompt.submit`), **fara `turn.complete`/`turn.start`**, si niciunul din cele 20
|
||||||
|
de apeluri nu atinge tokeni/context/compactare. `prompt.submit` e HOOK, nu apel invocabil -
|
||||||
|
deci `$.prompt.submit({text})` din blog nu e confirmat. Flag real: `tengu_plugin_hooks_modules`,
|
||||||
|
implicit oprit. **Nu relua investigatia** fara o versiune noua de Claude Code.
|
||||||
|
2. **Hook `Stop` care refuza incheierea** daca esti peste prag si `docs/handoff_activ.md` e mai
|
||||||
|
vechi decat ultima modificare reala. Proiectat, NEimplementat. Atentie la `stop_hook_active`
|
||||||
|
(plafon 8 blocari consecutive).
|
||||||
|
3. **Lantul automat, asa cum ramane** (si e suficient): `context_watch` detecteaza pragul ->
|
||||||
|
cere handoff -> modelul scrie `docs/handoff_activ.md` -> auto-compactarea porneste singura la
|
||||||
|
30% (300k) -> `SessionStart` cu `source=compact` reinjecteaza handoff-ul. Zero interventie.
|
||||||
|
`/clear` ramane doar varianta manuala, cand se vrea context chiar gol.
|
||||||
|
4. Proba end-to-end a reinjectarii: acest fisier ESTE proba. Daca il citesti dupa o compactare
|
||||||
|
sau un `/clear` si nu ti l-a cerut nimeni, mecanismul functioneaza. Noteaza asta in
|
||||||
|
`docs/progres_qa_factura.md`.
|
||||||
|
|
||||||
|
## A. QA factura/aviz - unde s-a ajuns
|
||||||
|
|
||||||
|
S0 terminat. S1 (baseline QA, capturi, zero cod) urmeaza, **pe VM 304**.
|
||||||
|
|
||||||
|
Constatari platite deja, cu `fisier:linie` in `docs/qa_factura_harta.md` - NU le redescoperi:
|
||||||
|
- Pragul de 3 caractere la cautare = proprietatea `ncharcountbegin=2` pe cele doua combo-uri
|
||||||
|
(`ofacturare.vc2:17524`, `17560`) + garda in `combosql_cautare.refreshdata` (`463-496`).
|
||||||
|
- Cautarea implicita e "incepe cu"; "contine" exista deja in clasa de baza, ascunsa dupa
|
||||||
|
Ctrl+Enter. Zero feedback, zero debounce.
|
||||||
|
- **Duplicatele de articol vin din Oracle**, din `pack_facturare.cursor_preturi`, nu din VFP.
|
||||||
|
S3 e blocat pana se citeste corpul din `ALL_SOURCE` pe MARIUSM_AUTO. Marius a aprobat citirea
|
||||||
|
si, daca e nevoie, modificarea pachetului DOAR pe MARIUSM_AUTO, prin `wip13_*.sql.txt`.
|
||||||
|
- Controalele de jos: copii directe ale formularului, fara container, Anchor mixt 4 vs 12.
|
||||||
|
|
||||||
|
Interdictii ferme: **nu se modifica `combosql` din `COMUN\clase\_cb_base.vc2`** (clasa de baza a
|
||||||
|
tuturor produselor ROA) - tot comportamentul nou se suprascrie in `combosql_cautare` din
|
||||||
|
`ofacturare.vc2`. `.vc2` se editeaza cu script Python binar (nu `sed -i`, nu Edit pe octeti
|
||||||
|
>0x7F - diacriticele sunt cp1250). Write-back doar cu `-ProjectRoot` explicit.
|
||||||
|
|
||||||
|
### VM 304 - CAPCANA DE MEDIU, cea mai importanta constatare
|
||||||
|
Tot ce se lanseaza prin `qm guest exec` ruleaza ca **SYSTEM in sesiunea 0 (`services`)**;
|
||||||
|
desktopul lui Marius e sesiunea 1 (`console`, user `romfast`). Verificat cu `query session`.
|
||||||
|
Consecinte platite:
|
||||||
|
- **GUI invizibil**: procesele ruleaza real, dar nu apar pe ecranul lui Marius. Deci **probele UI
|
||||||
|
vizibile din S1 NU se pot rula prin guest exec** - trebuie lansate din sesiunea interactiva de
|
||||||
|
pe VM (adica din sesiunea Claude Code pe care o deschide Marius acolo).
|
||||||
|
- **Fara credentiale**: `svn` lansat asa a stat agatat 14 minute asteptand autentificare. Procesele
|
||||||
|
au fost omorate (PID 9136/1048/1820). `roa_sync` il ruleaza Marius, din sesiunea lui, DUPA
|
||||||
|
`svn cleanup` (svn a fost intrerupt la mijloc, working copy-ul poate fi blocat).
|
||||||
|
- `roa_sync.ps1` s-a mutat in `COMUN\scripts\`; cel din `UTIL\foxbin2prg\` e doar stub.
|
||||||
|
- `exited: 0` din `qm guest exec-status` inseamna **inca ruleaza**, e boolean, nu cod de iesire.
|
||||||
|
Ce merge prin guest exec: copiat fisiere (EncodedCommand in bucati de ~1500 octeti), citit,
|
||||||
|
verificat, inventar de mediu.
|
||||||
|
|
||||||
|
Mediu confirmat pe VM: `D:\roa\ROAFACTURARE`, `D:\roa\ROAGEST`, vfp9.exe, `vfp_ui_harness.ps1`,
|
||||||
|
`test_init_env_auto_roafacturare.prg`, `vfp_symbols.ps1` - toate exista.
|
||||||
|
|
||||||
|
Proxmox VM ID 304 "Win11-Marius", nod `pvemini`. Acces probat:
|
||||||
|
`ssh root@10.0.20.201 'qm agent 304 ping'` si `qm guest exec 304 -- <cmd>`. Fara share UNC.
|
||||||
|
Documentat in `COMUN\docs\masini-dezvoltare-testare.md`.
|
||||||
|
**`vm304-deploy` rula cand s-a scris handoff-ul** - copia pachetul de pornire si urma sa ruleze
|
||||||
|
`roa_sync` ACOLO - anulat, vezi capcana de mediu de mai sus. Fara livrabil separat.
|
||||||
|
Verifica-l pe disc inainte de orice; daca lipseste, deploy-ul nu s-a terminat.
|
||||||
|
Pachetul sursa: `<scratchpad>\pachet_vm304\` (PORNIRE.md + 8 documente + context_watch.ps1).
|
||||||
|
|
||||||
|
## Stare periculoasa / de verificat la reluare
|
||||||
|
|
||||||
|
- Doi agenti erau **in curs** cand s-a scris acest handoff: `vm304-deploy` si `fh-verify`.
|
||||||
|
Livrabilele lor au fost condensate aici si sterse; nu le cauta pe disc,
|
||||||
|
nu relansa orbeste - relansarea produce doi scriitori.
|
||||||
|
- Cele 4 commit-uri sunt pe `main` si **pushate** pe gitea. SVN neatins - merge-ul spre SVN
|
||||||
|
ramane la Marius. Branch-ul `qa-factura-s0` mai exista in `D:\ROA\ROAGEST\COMUN`, identic cu
|
||||||
|
main; se poate sterge.
|
||||||
|
- Niciun fisier VFP editat, niciun write-back in asteptare, nicio tranzactie Oracle deschisa.
|
||||||
|
- Documentele din `docs/` sunt netracked si nu se comit pe main.
|
||||||
|
|
||||||
|
## ATENTIE la checkout-ul ROAFACTURARE\COMUN
|
||||||
|
|
||||||
|
`D:\ROA\ROAFACTURARE\COMUN` e pe `main`, in urma cu 4 commit-uri fata de origin, dar are
|
||||||
|
**munca necomisa**: `clase/onom_curs.vc2` modificat, plus `utile/Teste/test_curs_bnr_headless.prg`
|
||||||
|
si `utile/Teste/_test_curs_bnr_out.txt` netracked. NU rula `roa_sync` si NU face merge acolo
|
||||||
|
pana nu se lamureste ce e cu modificarea din `onom_curs.vc2` - pe un tree murdar se pierde.
|
||||||
|
Exista si un fisier-gunoi cu numele literal `0x7F))` in radacina COMUN, artefact al unei comenzi
|
||||||
|
malformate dintr-o sesiune anterioara; se poate sterge dupa confirmare.
|
||||||
167
docs/patch_curs_bnr_arhiva.diff
Normal file
167
docs/patch_curs_bnr_arhiva.diff
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
diff --git a/COMUN/clase/onom_curs.vc2 b/COMUN/clase/onom_curs.vc2
|
||||||
|
index 4042942..7fb9fd6 100644
|
||||||
|
--- a/COMUN/clase/onom_curs.vc2
|
||||||
|
+++ b/COMUN/clase/onom_curs.vc2
|
||||||
|
@@ -15,6 +15,8 @@ DEFINE CLASS actualizare_curs_bnr AS _custom OF "_baza.vcx"
|
||||||
|
*m: scrie_curs_bnr
|
||||||
|
*p: ccursor
|
||||||
|
*p: clink
|
||||||
|
+ *p: clinkan
|
||||||
|
+ *p: clinkarhiva
|
||||||
|
*p: ctempfile
|
||||||
|
*p: ddata
|
||||||
|
*p: ncurs
|
||||||
|
@@ -26,6 +28,8 @@ DEFINE CLASS actualizare_curs_bnr AS _custom OF "_baza.vcx"
|
||||||
|
*<PropValue>
|
||||||
|
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
|
||||||
|
ddata = {}
|
||||||
|
Height = 17
|
||||||
|
@@ -37,6 +41,8 @@ DEFINE CLASS actualizare_curs_bnr AS _custom OF "_baza.vcx"
|
||||||
|
_memberdata = <VFPData>
|
||||||
|
<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"/>
|
||||||
|
<memberdata name="ddata" display="dData"/>
|
||||||
|
<memberdata name="ntip" display="nTip"/>
|
||||||
|
@@ -98,8 +104,13 @@ DEFINE CLASS actualizare_curs_bnr AS _custom OF "_baza.vcx"
|
||||||
|
ENDPROC
|
||||||
|
|
||||||
|
PROCEDURE citeste_curs_bnr
|
||||||
|
+ Lparameters tnSursa
|
||||||
|
Local loHTTP As 'winHTTP.winHTTPrequest.5.1'
|
||||||
|
- Local lcData, lcFisier, lcServer
|
||||||
|
+ Local lcData, lcFisier, lcServer, lcCubeCautat, ldCandidat, lnIncercari, lnAnCautat, llRetrasAnAnterior, lnStatus
|
||||||
|
+
|
||||||
|
+ If Vartype(tnSursa) <> "N"
|
||||||
|
+ tnSursa = 0
|
||||||
|
+ Endif
|
||||||
|
|
||||||
|
If Empty(This.cCursor)
|
||||||
|
This.cCursor = [ccrsbnr]
|
||||||
|
@@ -109,30 +120,90 @@ DEFINE CLASS actualizare_curs_bnr AS _custom OF "_baza.vcx"
|
||||||
|
Use In (This.cCursor)
|
||||||
|
Endif
|
||||||
|
|
||||||
|
- lcServer = This.cLink
|
||||||
|
+ 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)
|
||||||
|
- loHTTP.Send()
|
||||||
|
-
|
||||||
|
- If loHTTP.Status = 200
|
||||||
|
+ Try
|
||||||
|
+ loHTTP.Send()
|
||||||
|
+ lnStatus = loHTTP.Status
|
||||||
|
+ Catch
|
||||||
|
+ lnStatus = 0
|
||||||
|
+ Endtry
|
||||||
|
+
|
||||||
|
+ If lnStatus = 200
|
||||||
|
lcFisier = loHTTP.Responsebody
|
||||||
|
|
||||||
|
lcFisier = Strextract(lcFisier,[</OrigCurrency>],[</Body>])
|
||||||
|
- lcData = Strextract(lcFisier,[date="],["])
|
||||||
|
- lcFisier= Strtran(lcFisier,[date="]+lcData+["],[])
|
||||||
|
+ 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)
|
||||||
|
- This.dData = Ttod(Ctot(lcData+[T000000]))+1
|
||||||
|
+ 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
|
||||||
|
|
||||||
|
PROCEDURE Destroy
|
||||||
|
@@ -168,9 +239,20 @@ DEFINE CLASS actualizare_curs_bnr AS _custom OF "_baza.vcx"
|
||||||
|
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") && din wwutils.prg
|
||||||
|
+ 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
|
||||||
|
Otherwise
|
||||||
|
amessagebox("Nu exista cursul BNR pentru data specificata!",48,"Atentie")
|
||||||
|
Endcase
|
||||||
368
docs/patch_curs_bnr_arhiva.md
Normal file
368
docs/patch_curs_bnr_arhiva.md
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
# 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, pastreaza
|
||||||
|
`This.dData = publishingDate+1`.
|
||||||
|
- `tnSursa=1` — identic v2: cauta cel mai recent `Cube` cu 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 anului `Year(This.dData-1)`;
|
||||||
|
daca data candidata trece in anul precedent fara sa fi gasit inca un `Cube`, face un singur request
|
||||||
|
suplimentar pe fisierul anului precedent si continua cautarea acolo. Daca acel request esueaza
|
||||||
|
(`Status<>200`), intoarce direct `1` (indisponibilitate), nu `2`.
|
||||||
|
- `This.dData` NU se rescrie pentru `tnSursa=1` sau `2` — ramane ziua ceruta, exact ce asteapta
|
||||||
|
`scrie_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:
|
||||||
|
1. incearca arhiva 10 zile (`citeste_curs_bnr(1)`);
|
||||||
|
2. daca intoarce `2` (data lipsa din raspuns), incearca fisierul anual (`citeste_curs_bnr(2)`);
|
||||||
|
3. daca si acela intoarce `2` -> `"Nu exista cursul BNR pentru data specificata!"`;
|
||||||
|
4. daca oricare din cele doua intoarce `1` (eroare HTTP/timeout) -> mesajul de indisponibilitate,
|
||||||
|
cu oferta de a deschide **`https://www.cursbnr.ro/`** (nu mai `http://www.bnr.ro`, doar in aceasta
|
||||||
|
ramura — celelalte doua aparitii ale `goUrl("http://www.bnr.ro")` din fisier, ramura de dinainte
|
||||||
|
de ora 13 si `Otherwise`-ul cu `nTip` necunoscut, 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
|
||||||
|
|
||||||
|
1. **Al doilea request HTTP inline in bucla, doar pentru `tnSursa=2` la 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.
|
||||||
|
2. **Confirmat prin fetch real**: structura `nbrfxrates2026.xml` e identica per-Cube cu celelalte
|
||||||
|
doua fisiere BNR, doar cu mai multe elemente `<Cube date="...">`, in ordine crescatoare — extractia
|
||||||
|
printr-un singur `Cube` inainte de `Xmltocursor` ramane corecta si aici.
|
||||||
|
3. **`repune_backup_cursoare` pe cursor deja inchis — risc preexistent, nu introdus acum** (aceeasi
|
||||||
|
observatie ca in v1/v2): `citeste_curs_bnr` inchide `This.cCursor` inainte de orice apel HTTP;
|
||||||
|
`repune_backup_cursoare` (`oproceduri_comune.prg:6358-6375`) face `Select` pe un alias deja inchis
|
||||||
|
in acel moment — comportament mostenit, nu nou, si in afara scope-ului acestui patch.
|
||||||
|
4. **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.
|
||||||
|
5. **Fisierul anual creste an de an** (~245 KB la mijlocul lui 2026) — cautarea foloseste tot
|
||||||
|
`Strextract` pe 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:
|
||||||
|
|
||||||
|
1. Aplica diff-ul (`docs\patch_curs_bnr_arhiva.diff`) pe `COMUN\clase\onom_curs.vc2` prin fluxul
|
||||||
|
text->binar existent (skill `roa-vfp-text-edit`, `txt2vcx.ps1` cu `-ProjectRoot D:\ROA\ROAFACTURARE`).
|
||||||
|
2. Caz "azi dupa ora 13": instantiaza `actualizare_curs_bnr` cu `nTip=1`, seteaza data sistemului
|
||||||
|
sau `ltDataOra` simulat dupa ora 13, cere `citeste_curs(Ttod(get_ora()))` pentru o zi pentru care
|
||||||
|
Oracle nu are inca rand — verifica in `poLog` ca al doilea link apelat e
|
||||||
|
`https://curs.bnr.ro/nbrfxrates10days.xml`.
|
||||||
|
3. Caz "mai vechi de 10 zile, in acelasi an": alege o data acoperita doar de fisierul anual —
|
||||||
|
verifica in `poLog` ca dupa arhiva urmeaza apelul catre `https://curs.bnr.ro/files/xml/years/nbrfxrates<an>.xml`.
|
||||||
|
4. 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.dData` ramane 2 ianuarie (nu 31 decembrie).
|
||||||
|
5. Verifica in `This.cCursor` dupa apel: `Reccount()>0`, campurile `Currency`/`Curs`/`multiplier`
|
||||||
|
populate identic cu un fetch normal.
|
||||||
|
6. Verifica in Oracle, dupa `scrie_curs_bnr`, ca randul scris in `CURS` are `data` = ziua ceruta.
|
||||||
|
7. Caz negativ, mai vechi decat orice arhiva: seteaza `This.dData` cu cativa ani in urma — asteapta
|
||||||
|
mesajul "Nu exista cursul BNR pentru data specificata!".
|
||||||
|
8. Caz eroare retea: schimba temporar `cLinkArhiva` (sau `cLinkAn`) la un URL invalid — asteapta
|
||||||
|
mesajul de indisponibilitate si confirma linkul nou `https://www.cursbnr.ro/` in loc de
|
||||||
|
`http://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.
|
||||||
133
docs/plan_qa_factura_aviz.md
Normal file
133
docs/plan_qa_factura_aviz.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Plan QA + corectii: adaugare/editare FACTURA si AVIZ
|
||||||
|
|
||||||
|
Stare: **APROBAT 17.09.2026**. Mandat de executie continua: toate stories, commit dupa fiecare,
|
||||||
|
fara aprobare per story; singura exceptie e mockup-ul de la S4, care se arata inainte de
|
||||||
|
implementare. Handoff la limita de context. Ultima actualizare: 17.09.2026.
|
||||||
|
Fisier de stare viu asociat: `docs/progres_qa_factura.md` (se actualizeaza dupa FIECARE story).
|
||||||
|
Harti de pornire (deja scrise, read-only): `docs/qa_factura_harta.md`,
|
||||||
|
`docs/qa_factura_unelte.md`.
|
||||||
|
|
||||||
|
## 0. Unde se executa
|
||||||
|
|
||||||
|
Executia trece pe **VM 304** (are D:\ROA, VFP 9, acces Oracle ROA_CENTRAL/MARIUSM_AUTO).
|
||||||
|
Motiv: UI vizibil fara sa fure focusul de pe masina lui Marius. O sesiune noua acolo porneste
|
||||||
|
din acest fisier + `docs/progres_qa_factura.md`.
|
||||||
|
|
||||||
|
Momentul mutarii (decizie Marius): **dupa S0**, cu prototipul de context/handoff deja functional.
|
||||||
|
S0 se face pe masina curenta (nu cere UI); de la S1 incolo totul e pe VM 304.
|
||||||
|
|
||||||
|
Conexiune probe: `DO test_init_env_auto_roafacturare WITH 'CENTRAL','MARIUSM_AUTO','ROMFASTSOFT'`
|
||||||
|
(`COMUN\utile\Teste\test_init_env_auto_roafacturare.prg:11,16-18`). `gnAn=2026`, `gnLuna=8`,
|
||||||
|
`gnIdFirma=110`, `gnIdUtil=8` inainte de serii.
|
||||||
|
|
||||||
|
## 1. Constatari confirmate pe cod (baza planului)
|
||||||
|
|
||||||
|
| # | Constatare | Dovada |
|
||||||
|
|---|---|---|
|
||||||
|
| C1 | Ecranul e clasa `frm_facturare_articole2`, nu `.scx`; instantiata doar cand `llFacturareNoua` | `COMUN\clase\ofacturare.vc2:15936-22614`; `COMUN\programe\ofacturare.prg:246-248` |
|
||||||
|
| C2 | Pragul de 3 caractere = `ncharcountbegin=2` pe cele doua combo-uri + garda `Len(...) <= nCharCountBegin` | `ofacturare.vc2:17524-17537`, `17560-17574`, `463-496` |
|
||||||
|
| C3 | Cautarea implicita e "incepe cu" (`RefreshData(1)`); "contine" exista dar e ascunsa dupa Ctrl+Enter | `COMUN\clase\_cb_base.vc2:636-649,720`; `ofacturare.vc2:476-489` |
|
||||||
|
| C4 | Zero feedback in timpul cautarii: `KeyPress` cheama direct `RefreshData`, fara indicator, fara numar de rezultate, fara mesaj "nimic gasit" | `_cb_base.vc2:610-776` |
|
||||||
|
| C5 | Nu exista debounce: se cauta la fiecare tasta peste prag | idem C4 |
|
||||||
|
| C6 | Sursa duplicatelor pe articol e **in Oracle**, in `pack_facturare.cursor_preturi` (join cu politicile de pret) - nu in VFP | `ofacturare.vc2:446-461`; tabela `crm_politici_pret_art` folosita la `ofacturare.vc2:21755` |
|
||||||
|
| C7 | Nu exista niciun selector de lista de preturi in formular; se trimite `poDate.id_gestiune_init`, sau `poDate.listaid` doar cand `tip=45` | `ofacturare.vc2:453-457` |
|
||||||
|
| C8 | Controalele de jos sunt copii directe ale formularului, fara container, cu Top 742/745/767/789/792 si Anchor mixt 4 vs 12; nimic nu le repozitioneaza la runtime | `ofacturare.vc2:18082-18195`; `Resize` la `21847-21854` nu le atinge |
|
||||||
|
| C9 | Factura vs aviz = `poDate.nIdTipDoc` 5 vs 6; helper `EsteAviz()` | `COMUN\programe\ofacturare_antet.prg:16-18` |
|
||||||
|
|
||||||
|
**Necunoscuta blocanta**: corpul `pack_facturare.cursor_preturi` nu e in working copy. Se citeste
|
||||||
|
din `ALL_SOURCE` pe MARIUSM_AUTO inainte de orice decizie despre listele de preturi (Story S3).
|
||||||
|
|
||||||
|
## 2. Constrangeri de perimetru
|
||||||
|
|
||||||
|
- `combosql` (`COMUN\clase\_cb_base.vc2:519`) e clasa de baza folosita de TOATE produsele ROA.
|
||||||
|
**Nu se modifica.** Tot comportamentul nou de cautare se suprascrie in `combosql_cautare`
|
||||||
|
(`ofacturare.vc2:407-531`), care e specifica facturarii.
|
||||||
|
- `ofacturare.vc2` traieste in `COMUN\` - dublu-versionat SVN+git, commit tintit din `COMUN\`.
|
||||||
|
- `.vc2` se editeaza cu script Python binar (nu `sed -i`, nu Edit pe linii cu octeti >0x7F);
|
||||||
|
diacriticele sunt cp1250. Write-back doar `txt2vcx.ps1 -ProjectRoot D:\ROA\ROAFACTURARE`.
|
||||||
|
- Dupa fiecare write-back: `MODIFY CLASS` + captura, apoi designerul se INCHIDE inainte de
|
||||||
|
urmatorul write-back.
|
||||||
|
- Un singur agent scrie pe `ofacturare.vc2` la un moment dat. Probele pe aceeasi clasa nu ruleaza
|
||||||
|
in paralel cu write-back-ul.
|
||||||
|
|
||||||
|
## 3. Stories
|
||||||
|
|
||||||
|
### S0 - Prototip context/handoff (se face PRIMUL, se foloseste pe restul planului)
|
||||||
|
|
||||||
|
Reutilizeaza ce exista: `context_watch.ps1` (in `D:\ROA\ROAGEST\COMUN\utile\`) deja citeste
|
||||||
|
`usage` din transcriptul JSONL si alerteaza la 250k/275k. Nu se rescrie. Se repara si se extinde:
|
||||||
|
|
||||||
|
1. **Bug activ**: hook-ul `SubagentStop` din `~/.claude/settings.json` re-injecteaza acelasi
|
||||||
|
mesaj runda dupa runda (observat azi, si notat deja in memorie). Cauza documentata: hook-ul
|
||||||
|
nu citeste `stop_hook_active` din JSON-ul de pe stdin si nu iese cu 0 cand e adevarat
|
||||||
|
(plafon 8 blocari consecutive). Fix: garda `stop_hook_active` la inceputul hook-ului.
|
||||||
|
2. **Fisier de stare scris de hook, nu de model**: `context_watch.ps1` primeste `-StareFile` si,
|
||||||
|
la pragul de avertisment, scrie/actualizeaza un antet in `docs/progres_<subiect>.md` (procent
|
||||||
|
context, ora, ultimul bloc). Modelul completeaza continutul; hook-ul garanteaza ca fisierul
|
||||||
|
exista si e datat chiar daca sesiunea moare.
|
||||||
|
3. **Compactare mai devreme si previzibila**: `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` in `env` din
|
||||||
|
settings (ex. 70), ca auto-compact-ul sa cada DUPA pragul de avertisment al lui
|
||||||
|
`context_watch`, nu inaintea lui. Compactarea NU poate fi declansata programatic - documentat,
|
||||||
|
deci mecanismul ramane: hook avertizeaza -> modelul scrie handoff -> compactarea vine singura.
|
||||||
|
4. Se muta in `COMUN` (cerinta Marius) ca sa fie refolosibil de toate produsele ROA.
|
||||||
|
|
||||||
|
Proba: o sesiune de proba cu un subagent care se termina => hook-ul ruleaza o singura data;
|
||||||
|
fisierul de stare exista si are marca de timp.
|
||||||
|
|
||||||
|
### S1 - Baseline QA: dovada vizuala si scenarii
|
||||||
|
|
||||||
|
Pe VM 304, formular vizibil prin `vfp_ui_harness.ps1`:
|
||||||
|
- capturi la 1366x768 si la rezolutia de lucru, pentru FACTURA si pentru AVIZ;
|
||||||
|
- captura zonei de jos (totaluri+discount) cu formularul la inaltime minima si maximizat -
|
||||||
|
dovada pentru C8 si pentru comportamentul Anchor 4 vs 12;
|
||||||
|
- scenariul de cautare: tastare progresiva pe un cod cunoscut din MARIUSM_AUTO, captura dupa
|
||||||
|
fiecare caracter - dovada pentru C2/C4;
|
||||||
|
- articol prezent in mai multe politici: captura cu randurile duplicate - dovada pentru C6.
|
||||||
|
|
||||||
|
Livrabil: `docs/qa_factura_baseline.md` + capturi. Nicio modificare de cod in S1.
|
||||||
|
|
||||||
|
### S2 - Cautare articole (toate cele patru cerinte)
|
||||||
|
|
||||||
|
In `combosql_cautare`, fara sa atinga `combosql`:
|
||||||
|
- prag de la primul caracter (`ncharcountbegin=0` pe cele doua instante + garda actualizata);
|
||||||
|
- cautare "contine" implicita, si pe cod si pe denumire;
|
||||||
|
- debounce ~250ms + limita de randuri, ca sa nu plece o interogare per tasta;
|
||||||
|
- feedback: indicator "se cauta...", numarul de rezultate, mesaj explicit la zero potriviri.
|
||||||
|
|
||||||
|
Proba: headless pe filtrul construit (asertii pe sirul trimis catre `cursor_preturi`) + proba UI
|
||||||
|
vizibila pe VM pentru feedback si latenta.
|
||||||
|
|
||||||
|
### S3 - Selector de lista de preturi (implicit lista clientului)
|
||||||
|
|
||||||
|
**Depinde de citirea `pack_facturare.cursor_preturi` din `ALL_SOURCE` pe MARIUSM_AUTO.**
|
||||||
|
Decizie Marius 17.09.2026: citirea `ALL_SOURCE` e permisa, iar modificarea pachetului e permisa
|
||||||
|
**doar pe MARIUSM_AUTO**, prin script `wip13_*.sql.txt` idempotent (`CREATE OR REPLACE`),
|
||||||
|
inregistrat in registrul de scripturi. Nimic nu pleaca spre clienti.
|
||||||
|
Tinta functionala: un combo in formular, prefixat cu lista din politica clientului, plus optiunea
|
||||||
|
"Toate listele"; in modul implicit fiecare articol apare o singura data.
|
||||||
|
|
||||||
|
### S4 - Zona totaluri + discount, regandita ca bloc
|
||||||
|
|
||||||
|
Mockup HTML inainte de implementare (mockup-urile stau online, nu in `docs/`). Dupa aprobarea
|
||||||
|
mockup-ului: container dedicat, Anchor unitar, aliniere pe grila, etichete. Atentie: `Anchor=4`
|
||||||
|
face ca Top-ul design-time sa conteze la rulare - orice schimbare de inaltime se probeaza la
|
||||||
|
1366 maximizat.
|
||||||
|
|
||||||
|
### S5 - Regresie
|
||||||
|
|
||||||
|
Suita existenta + probele noi, pe FACTURA si pe AVIZ, adaugare si editare. Raport prin
|
||||||
|
`raport_teste.ps1`, nu loguri brute.
|
||||||
|
|
||||||
|
## 4. Ordine si commit
|
||||||
|
|
||||||
|
S0 -> S1 -> S2 -> S3 -> S4 -> S5. Commit pe branch de lucru dupa fiecare story (cod `COMUN` din
|
||||||
|
`COMUN\`, restul din radacina), fara push; SVN si merge raman la Marius.
|
||||||
|
`docs/progres_qa_factura.md` se actualizeaza dupa fiecare story, nu la final.
|
||||||
|
Documentele de lucru din `docs/` nu se comit pe main.
|
||||||
|
|
||||||
|
## 5. Datorii deschise cunoscute la scrierea planului
|
||||||
|
|
||||||
|
- Corpul `pack_facturare.cursor_preturi` necitit - blocheaza S3.
|
||||||
|
- Cifra plafonului de blocari repetate ale unui stop hook: 8 (confirmat in ghid) vs 100
|
||||||
|
(`CLAUDE_CODE_STOP_HOOK_BLOCK_CAP`), nereconciliat.
|
||||||
|
- Daca `PreCompact` poate bloca compactarea prin exit 2: nedocumentat.
|
||||||
98
docs/progres_qa_factura.md
Normal file
98
docs/progres_qa_factura.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Progres QA factura/aviz
|
||||||
|
|
||||||
|
Plan: `docs/plan_qa_factura_aviz.md` (aprobat 17.09.2026, mandat de executie continua).
|
||||||
|
Acest fisier spune **unde s-a ajuns**, nu ce e de facut. Se actualizeaza dupa FIECARE story.
|
||||||
|
|
||||||
|
Harti de pornire (read-only, nu se refac):
|
||||||
|
`docs/qa_factura_harta.md` - harta de cod a formularului
|
||||||
|
`docs/qa_factura_unelte.md` - harness UI, headless, Oracle, hook-uri existente
|
||||||
|
|
||||||
|
## Stare pe story
|
||||||
|
|
||||||
|
| Story | Stare | Unde |
|
||||||
|
|---|---|---|
|
||||||
|
| S0 prototip context/handoff | **TERMINAT** 17.09.2026 | `docs/handoff_activ.md` |
|
||||||
|
| S1 baseline QA cu capturi | de facut, **pe VM 304** | - |
|
||||||
|
| S2 cautare articole | de facut | - |
|
||||||
|
| S3 selector lista de preturi | blocat pana se citeste `pack_facturare.cursor_preturi` | - |
|
||||||
|
| S4 zona totaluri+discount | de facut, mockup inainte | - |
|
||||||
|
| S5 regresie | de facut | - |
|
||||||
|
|
||||||
|
## S0 - terminat (doua runde)
|
||||||
|
|
||||||
|
**Runda 2 (dupa ce prima s-a dovedit nedovedita).** Prima varianta a lui S0 a fost probata doar
|
||||||
|
cu stdin sintetic; in ciclul real bucla a continuat. Am instalat logare temporara pe hook si am
|
||||||
|
capturat inputul real al lui `SubagentStop`. Ce a iesit:
|
||||||
|
|
||||||
|
- `stop_hook_active` EXISTA in input si trece pe `true` la a doua declansare - garda e corecta.
|
||||||
|
Bucla observata venea de la agenti porniti inainte de fix.
|
||||||
|
- Mecanismul buclei: mesajul injectat trezeste agentul idle, el raspunde, redevine idle, hook-ul
|
||||||
|
se declanseaza iar.
|
||||||
|
- **Inputul contine `agent_transcript_path`** - transcriptul separat al subagentului oprit.
|
||||||
|
Deci un hook POATE masura contextul fiecarui subagent, nu doar al sesiunii.
|
||||||
|
|
||||||
|
Consecinta: `context_watch.ps1` are acum `-Subagent` (citeste `agent_transcript_path`, praguri
|
||||||
|
150k/200k, mai joase decat la sesiunea principala) si `-Json` (ambaleaza in
|
||||||
|
`hookSpecificOutput`). Garda `stop_hook_active` a intrat in script, deci comanda hook-ului e o
|
||||||
|
singura linie. Logarea temporara e scoasa.
|
||||||
|
|
||||||
|
Probe pe input real capturat: subagent peste prag -> mesaj cu numele agentului si 40k; sub prag
|
||||||
|
-> tacere; `stop_hook_active=true` -> tacere. Comis: `9823f1e` pe `qa-factura-s0`.
|
||||||
|
|
||||||
|
Compromis acceptat: mesajul vechi "verifica daca subagentul si-a scris starea pe disc" nu mai
|
||||||
|
apare la fiecare oprire, ci doar in avertismentul de context.
|
||||||
|
|
||||||
|
**Corectie de fond**, dupa ce Marius a contestat afirmatia: e fals ca "niciun hook nu poate afla
|
||||||
|
contextul". Niciun hook nu primeste tokenii de-a gata (nici `command`, nici function hook din
|
||||||
|
SDK), dar orice hook primeste `transcript_path` si poate citi singur `usage` din JSONL. Doar
|
||||||
|
`statusLine` primeste `context_window.used_percentage` gata calculat, si nu e hook.
|
||||||
|
Detalii condensate in `docs/handoff_activ.md`.
|
||||||
|
|
||||||
|
## S0 - runda 1
|
||||||
|
|
||||||
|
Trei modificari, toate probate (rezultatele probelor sunt in `docs/handoff_activ.md`):
|
||||||
|
|
||||||
|
1. `C:\Users\mmari\.claude\settings.json`, hook `SubagentStop`: garda `stop_hook_active`. Era un
|
||||||
|
`echo` static care reinjecta acelasi `additionalContext` la fiecare oprire de subagent - de
|
||||||
|
aici bucla observata azi (patru agenti invartindu-se in gol). Backup:
|
||||||
|
`settings.json.bak_20260917`.
|
||||||
|
2. `D:\ROA\ROAGEST\COMUN\utile\context_watch.ps1`: parametru nou `-StareFile`. La atingerea
|
||||||
|
pragului scrie/inlocuieste un singur antet pe primul rand al fisierului de stare
|
||||||
|
(`<!-- context_watch: data ora | tokeni: N | nivel: ... -->`), fara sa atinga restul
|
||||||
|
continutului. Fara `-StareFile` comportamentul e neschimbat.
|
||||||
|
3. `settings.json`, `env`: `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`. Pus initial pe 70 - **gresit**:
|
||||||
|
fereastra e 1M, deci 70% = 700k, la trei ori distanta de pragul de handoff de 250k. Marius a
|
||||||
|
prins eroarea. Valoarea corecta, aplicata: **30** (=300k), imediat peste pragul max de 275k.
|
||||||
|
|
||||||
|
Ce s-a stabilit si nu se mai rediscuta:
|
||||||
|
- Compactarea **nu** poate fi declansata programatic dintr-un hook. Mecanismul ramas: hook
|
||||||
|
avertizeaza -> modelul scrie handoff-ul -> compactarea vine singura la pragul coborat.
|
||||||
|
- Niciun hook nu primeste numarul de tokeni. Singura sursa e transcriptul JSONL
|
||||||
|
(`transcript_path`), pe care `context_watch.ps1` il citea deja.
|
||||||
|
- `D:\ROA\ROAGEST\COMUN` si `D:\ROA\ROAFACTURARE\COMUN` sunt checkout-uri separate ale aceluiasi
|
||||||
|
repo `comun.git`. Scriptul e deci deja in biblioteca partajata; nu trebuie mutat.
|
||||||
|
|
||||||
|
Ramas deschis din S0:
|
||||||
|
- Proba pe un ciclu real cu subagent viu (garda a fost probata in izolare, cu stdin sintetic).
|
||||||
|
Se confirma implicit la prima rulare de subagent din sesiunea urmatoare.
|
||||||
|
- `settings.json` trimite la `context_watch.ps1` prin calea din checkout-ul ROAGEST. Pe VM 304
|
||||||
|
trebuie verificat ca `D:\ROA\ROAGEST\COMUN\utile\context_watch.ps1` exista acolo, altfel
|
||||||
|
hook-ul tace fara sa dea eroare.
|
||||||
|
|
||||||
|
## VM 304 - ce s-a aflat
|
||||||
|
|
||||||
|
Proxmox VM ID 304 "Win11-Marius", nod `pvemini`, clona lui VM 303.
|
||||||
|
Acces probat si functional: `ssh root@10.0.20.201 'qm agent 304 ping'` -> raspunde;
|
||||||
|
comenzi in VM prin `qm guest exec 304 -- <cmd>`. **Nu exista share UNC** catre discul ei.
|
||||||
|
Documentatia NU mentioneaza VFP sau client Oracle instalat - de verificat pe teren, planul le
|
||||||
|
presupune. Credentialele sunt documentate in `E:\proiecte\ROMFASTSQL\docs\` (nu se copiaza).
|
||||||
|
|
||||||
|
Marius a cerut explicit copierea directa si `roa_sync` rulat pe VM. Pachetul de pornire e
|
||||||
|
pregatit in scratchpad (`pachet_vm304\` cu `PORNIRE.md` + cele 8 documente + `context_watch.ps1`);
|
||||||
|
roa_sync pe VM a fost anulat (svn fara credentiale in sesiunea 0); il ruleaza Marius.
|
||||||
|
|
||||||
|
## Urmatorul pas
|
||||||
|
|
||||||
|
S1, **pe VM 304**: sesiune noua care porneste din `docs/plan_qa_factura_aviz.md` + acest fisier.
|
||||||
|
Baseline vizual pentru FACTURA si AVIZ, capturi la 1366x768 si maximizat, dovada pentru
|
||||||
|
constatarile C2, C4, C6 si C8 din plan. Nicio modificare de cod in S1.
|
||||||
161
docs/qa_factura_harta.md
Normal file
161
docs/qa_factura_harta.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Harta cod: adaugare/editare FACTURA si AVIZ (read-only)
|
||||||
|
|
||||||
|
Sursa: text FoxBin2Prg deja in-tree (`.vc2`/`.prg`), citit cu `Grep`/`Read` si indexat cu
|
||||||
|
`vfp_symbols.ps1` (`-CacheRoot/-ProjectRoot D:\ROA\ROAFACTURARE`,
|
||||||
|
`-IndexFile D:\ROA\_vfp_textcache\roafacturare\_symbols.tsv`). Nicio modificare de fisier.
|
||||||
|
|
||||||
|
## 1. Formulare si clase implicate
|
||||||
|
|
||||||
|
Nu exista formular `.scx` pentru factura/aviz in acest proiect (`.scx`-urile din arbore sunt
|
||||||
|
doar utilitare: `frm_borderou_facturi.scx`, `frm_import_efactura.scx` etc.). Ecranul de
|
||||||
|
adaugare/editare e o **clasa `.vcx` instantiata prin `Createobject()`**, nu un `DO FORM`:
|
||||||
|
|
||||||
|
- **`frm_facturare_articole2`** — `COMUN\clase\ofacturare.vcx` (text `COMUN\clase\ofacturare.vc2:15936-22614`).
|
||||||
|
Formularul unificat, curent, folosit in productie pentru factura SI aviz. Instantiat in
|
||||||
|
`COMUN\programe\ofacturare.prg:248` (`ofrmdetaliifactura = Createobject('frm_facturare_articole2')`)
|
||||||
|
si in `COMUN\clase\ofacturare_comun.vc2:4028`, doar cand `llFacturareNoua` e adevarat
|
||||||
|
(`COMUN\programe\ofacturare.prg:246-248`).
|
||||||
|
- **`frm_facturare_articole`** — acelasi fisier, `ofacturare.vc2:11123-15936`. Varianta mai veche;
|
||||||
|
in cod de productie nu mai e instantiata (doar in probele din
|
||||||
|
`COMUN\utile\Teste\facturare_unificat\*.prg` si `COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg:571`,
|
||||||
|
`emite_document_stoc_s93.prg:264`) — ramane in clasa dar calea curenta e `frm_facturare_articole2`.
|
||||||
|
- **Flux vechi, ne-unificat** (`llFacturareNoua = .F.`, `ofacturare.prg:255-266`) — alege un
|
||||||
|
formular de antet separat pe tip de document, fara grila unificata de articole:
|
||||||
|
- `frm_date_aviz_lucrare` — `ofacturare.vc2:7775` — pentru `tnTip` 27 sau 30.
|
||||||
|
- `frm_date_factura` — `ofacturare.vc2:8637` — pentru `tnTip < 21` sau in `(45,48,49,51,52)`.
|
||||||
|
- `frm_date_aviz` — `ofacturare.vc2:6721` — altfel (aviz).
|
||||||
|
- **`oAntetFacturare`** (`Define Class ... As Custom`) — `COMUN\programe\ofacturare_antet.prg:14`.
|
||||||
|
Logica de antet a lui `frm_facturare_articole2`: cautari (`do_cauta_*`), validare
|
||||||
|
(`valideaza_antet`), schimbarea tipului de document (`alege_tipdoc`, `schimba_tipdoc`).
|
||||||
|
Header-ul fisierului (`ofacturare_antet.prg:1-7`) spune explicit ca a fost **portata din
|
||||||
|
`frm_date_factura`/`frm_date_aviz`** cand s-a facut unificarea. Instantiata in
|
||||||
|
`frm_facturare_articole2.Init`: `This.oAntet = Createobject('oAntetFacturare')` (`ofacturare.vc2:21365`).
|
||||||
|
- **`oDateFactura`** (`poDate`) — `COMUN\programe\ofacturare_comun.prg:131`. Obiectul de stare al
|
||||||
|
documentului curent (creat in `ofacturare.prg:196`); tine `nIdTipDoc`, `nIdTipDocFactura`(=5),
|
||||||
|
`nIdTipDocAvizExpeditie`(=6), `id_gestiune_init`, `listaid`, `id_pol`, `zi_curs` etc.
|
||||||
|
- **`ofacturare_editare.prg`** (`COMUN\programe\ofacturare_editare.prg`, 1444 linii) — flux **separat**,
|
||||||
|
pentru editarea liniilor unei facturi/aviz deja emise (nu formularul de creare):
|
||||||
|
`IncarcaAntetFacturaEditare` (63), `IncarcaLiniiFacturaEditare` (209),
|
||||||
|
`PregatesteArticoleFacturaEditare` (605), `ScrieArticoleFacturaEditate` (741),
|
||||||
|
clasa `ArticoleNotaEditor As Custom` (1226). Inregistrat via
|
||||||
|
`Set Procedure To ofacturare_editare.prg Additive` (`Programe\roafacturare.prg:219`).
|
||||||
|
- **`combosql_cautare`** — clasa de baza pentru comboboxurile de cautare din grid (vezi pct. 2),
|
||||||
|
`ofacturare.vc2:407-531`, mostenind `combosql As combobox` din `COMUN\clase\_cb_base.vc2:519`.
|
||||||
|
|
||||||
|
## 2. Zona de introducere articole (cautare cod material / denumire)
|
||||||
|
|
||||||
|
In grila `grd_factura` a lui `frm_facturare_articole2`, coloanele au cate un combobox de tip
|
||||||
|
`combosql_cautare`:
|
||||||
|
|
||||||
|
- `grd_factura.cCodMat.cboCodmat` — `ADD OBJECT` la `ofacturare.vc2:17524-17537`,
|
||||||
|
`ncharcountbegin = 2`, `pcursorname = crsCodmat`, `pfieldactiv = codmat`.
|
||||||
|
- `grd_factura.cDenumire.cCboDenumire` — `ADD OBJECT` la `ofacturare.vc2:17560-17574`,
|
||||||
|
`ncharcountbegin = 2`, `pcursorname = crsDenumire`, `pfieldactiv = denumire`.
|
||||||
|
|
||||||
|
**Pragul de caractere**: logica e in `combosql_cautare.refreshdata` (`ofacturare.vc2:463-496`):
|
||||||
|
|
||||||
|
```
|
||||||
|
lnTip = Iif(Type('tnTip') = 'N', m.tnTip, 0)
|
||||||
|
If m.lnTip <> 0 And Len(Alltrim(This.cSearchString)) <= This.nCharCountBegin
|
||||||
|
Return .T. && nu cauta, iese
|
||||||
|
Endif
|
||||||
|
```
|
||||||
|
|
||||||
|
Cu `ncharcountbegin = 2` pe ambele combo-uri, filtrarea porneste abia cand
|
||||||
|
`Len(cSearchString) > 2`, adica de la **al treilea caracter tastat**.
|
||||||
|
|
||||||
|
**Interactivechange/Keypress**: nu sunt suprascrise in `combosql_cautare` — vin din parintele
|
||||||
|
`combosql` (`COMUN\clase\_cb_base.vc2:519`):
|
||||||
|
- `InteractiveChange` (linia 606-608): doar reseteaza `cSearchString`.
|
||||||
|
- `KeyPress` (linia 610-776): construieste `csearchstring` caracter cu caracter (sageti,
|
||||||
|
Del/Backspace tratate separat), apoi cheama `This.RefreshData(1)` = cautare "incepe cu"
|
||||||
|
(linia 720) sau, la Ctrl+Enter, `RefreshData(2)` = cautare "contine" (liniile 636-649).
|
||||||
|
|
||||||
|
**Filtrul/SQL aplicat**: `refreshdata` seteaza `cfiltrucod`/`cfiltruden` in functie de tip
|
||||||
|
(`ofacturare.vc2:476-489`):
|
||||||
|
```
|
||||||
|
Case lnTip = 1 (incepe cu): cfiltrucod/cfiltruden = cSearchString + '%'
|
||||||
|
Case lnTip = 2 (contine): cfiltrucod/cfiltruden = '%' + cSearchString + '%'
|
||||||
|
```
|
||||||
|
apoi `cursor_preturi_call()` (`ofacturare.vc2:446-461`) construieste apelul catre pachetul
|
||||||
|
Oracle `pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,
|
||||||
|
?poDate.id_gestiune_init sau ?poDate.listaid,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala,
|
||||||
|
?pcFiltruCod,?pcFiltruDen)` (sau `pack_facturare.cursor_gestiune(...)` cand `poDate.tip = 41`),
|
||||||
|
executat prin `goExecutor.oExecuta` in `selectdata` (linia 514-529). Corpul PL/SQL al
|
||||||
|
pachetului nu e in acest repo VFP (schema Oracle) — nu l-am putut citi read-only de aici.
|
||||||
|
Proprietatile `csourcesql`/`csourcewhere` de pe `ADD OBJECT` (ex. `select codmat, denumire, ...
|
||||||
|
from vnom_articole ... where inactiv = 0`) sunt doar sablonul design-time al cursorului gol
|
||||||
|
(`creeaza_cursor_gol`, linia 426), nu interogarea reala rulata la tastare.
|
||||||
|
|
||||||
|
**Sincronizare cod<->denumire** dupa alegere: `grd_factura.cCodMat.cboCodmat.LostFocus`
|
||||||
|
(`ofacturare.vc2:22088-22109`) si perechea ei `cDenumire.cCboDenumire.LostFocus`
|
||||||
|
(22115-22136) cheama `thisform.do_adauga_articol_cautat(loArticol, loArticol.cantitate)`
|
||||||
|
si fortez re-creerea cursorului celeilalte combo (seteaza `cSearchString` cu valoarea gasita
|
||||||
|
si reseteaza `RowSource`), ca sa afiseze articolul ales in ambele coloane.
|
||||||
|
|
||||||
|
## 3. Lista de preturi / politici de pret
|
||||||
|
|
||||||
|
- `poDate.id_gestiune_init` e parametrul implicit trimis la `pack_facturare.cursor_preturi`;
|
||||||
|
cand `poDate.tip = 45` se trimite `poDate.listaid` in loc (`ofacturare.vc2:453-457`).
|
||||||
|
- De ce poate aparea un articol de mai multe ori: interogarea serverului (Oracle,
|
||||||
|
`pack_facturare.cursor_preturi`) uneste nomenclatorul de articole cu politicile de pret —
|
||||||
|
confirmarea client-side ca politicile sunt cheia vine din `modifica_lista_preturi`
|
||||||
|
(`ofacturare.vc2:21735-21837`), care interogheaza direct tabelele de politici:
|
||||||
|
`Select id_pol_art, id_venchelt, proc_tvav from crm_politici_pret_art where id_pol = ...
|
||||||
|
and id_articol = ...` (linia 21755) si scrie prin `pack_preturi.adauga_politica_pret_art`/
|
||||||
|
`pack_preturi.modifica_pret_pol_pret_art` (21779, 21792). Corpul exact al join-ului din
|
||||||
|
`cursor_preturi` e in PL/SQL, nevizibil din acest working copy VFP — afirmatia despre
|
||||||
|
"un rand per politica aplicabila" e o inferenta din tabelele folosite, nu o citire directa
|
||||||
|
a interogarii.
|
||||||
|
|
||||||
|
## 4. Zona de jos: controale de discount/totaluri
|
||||||
|
|
||||||
|
Toate sunt copii directe ale formularului `frm_facturare_articole2` (fara container
|
||||||
|
intermediar), clasa `_textbox` din `_baza.vcx`, asezate sub grila (Top ~742-792):
|
||||||
|
|
||||||
|
| Obiect | Top | Left | Width | Anchor | Note |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `tx_total_baza_nat` (18123) | 745 | 17 | (implicit) | 4 | ReadOnly, `ControlSource=thisform.nbazaron` |
|
||||||
|
| `tx_total_baza_val` (18137) | 789 | 17 | (implicit) | 4 | ReadOnly, `thisform.nbazaval` |
|
||||||
|
| `tx_disc_factura_nat` (18082) | 745 | 351 | (implicit) | 4 | ReadOnly, `thisform.ndiscfactron` |
|
||||||
|
| `tx_disc_factura_procent` (18096) | 767 | 542 | 54 | 4 | editabil, `Value=0` |
|
||||||
|
| `tx_disc_factura_val` (18109) | 789 | 351 | (implicit) | 4 | ReadOnly, `thisform.ndiscfactval` |
|
||||||
|
| `tx_total_tva_nat` (18181) | 742 | 689 | (implicit) | 12 | ReadOnly, `thisform.ntvaron` |
|
||||||
|
| `tx_total_tva_val` (18195) | 792 | 689 | (implicit) | 12 | ReadOnly, `thisform.ntvaval` |
|
||||||
|
| `tx_total_factura_nat` (18151) | 742 | 833 | (implicit) | 12 | ReadOnly, FontBold, `thisform.ntotalron` |
|
||||||
|
| `tx_total_factura_val` (18166) | 792 | 835 | (implicit) | 12 | ReadOnly, FontBold, `thisform.ntotalval` |
|
||||||
|
|
||||||
|
(linii = `COMUN\clase\ofacturare.vc2`). `Anchor=4` = ancorat de Bottom (coboara odata cu
|
||||||
|
formularul); `Anchor=12` (4+8) = Bottom+Right, pe coloana totalurilor din dreapta. Niciun
|
||||||
|
`Height`/`Width` explicit pe majoritatea — mostenesc default-ul clasei `_textbox`; doar
|
||||||
|
`tx_disc_factura_procent` are `Width=54` explicit. Nu am gasit cod care repozitioneaza
|
||||||
|
aceste controale la runtime in `frm_facturare_articole2` — pozitionarea e strict design-time
|
||||||
|
+ `Anchor` (fara logica de `Resize`/`Move` pe ele; `Resize` al formularului, linia
|
||||||
|
21847-21854, nu le atinge explicit).
|
||||||
|
|
||||||
|
## 5. Diferente factura vs aviz pe `frm_facturare_articole2`
|
||||||
|
|
||||||
|
- **Discriminator unic**: `poDate.nIdTipDoc`, comparat cu `poDate.nIdTipDocFactura` (=5) /
|
||||||
|
`poDate.nIdTipDocAvizExpeditie` (=6). Helper: `oAntetFacturare.EsteAviz()`
|
||||||
|
(`ofacturare_antet.prg:16-18`): `Return poDate.nIdTipDoc = poDate.nIdTipDocAvizExpeditie`.
|
||||||
|
- **Setare initiala**, dupa `tnTip` primit la deschidere (`COMUN\programe\ofacturare.prg:196-206`):
|
||||||
|
```
|
||||||
|
Case tnTip = 27 or 30 -> nIdTipDoc = 6 (AVIZ)
|
||||||
|
Case tnTip < 21 sau tnTip in (45,48,49,51,52) -> nIdTipDoc = 5 (FACTURA)
|
||||||
|
Otherwise -> nIdTipDoc = 6 (AVIZ)
|
||||||
|
```
|
||||||
|
- **Schimbare interactiva**: combo `clb_fdoc.cboFdoc` -> `do_schimba_tipdoc`
|
||||||
|
(`ofacturare.vc2:20046-20071`) -> `oAntet.alege_tipdoc(valoare_combo)` mapeaza textul
|
||||||
|
("FACTURA"/"AVIZ"/"PROFORMA"/"BON FISCAL") pe id-ul de tip, apoi `oAntet.schimba_tipdoc(id)`
|
||||||
|
(`ofacturare_antet.prg:889-923`) actualizeaza `poDate.nIdTipDoc` si seriile. Exista o garda:
|
||||||
|
daca documentul e proforma si au fost deja adaugate articole, schimbarea tipului e blocata
|
||||||
|
cu `amessagebox` (`ofacturare.vc2:20050-20055`).
|
||||||
|
- **Ramuri `EsteAviz()` in `oAntetFacturare`** (aceleasi metode de cautare/validare servesc
|
||||||
|
ambele tipuri, dar cu cai diferite): `do_cauta_altele` (20-45), `do_cauta_client` (97-100),
|
||||||
|
`do_cauta_comanda` (240-243), `do_cauta_contract` (296-299), `do_cauta_gestiune_init`
|
||||||
|
(518-521), `do_cauta_lucrare` (553-556), `do_cauta_sectie` (633-636), `do_cauta_venchelt`
|
||||||
|
(708-711), `valideaza_antet` (762-766) — toate in `COMUN\programe\ofacturare_antet.prg`.
|
||||||
|
- **Fluxul vechi (ne-unificat)** folosea deja formulare separate per tip pentru antet
|
||||||
|
(`frm_date_factura` / `frm_date_aviz` / `frm_date_aviz_lucrare`, vezi pct. 1) — unificarea
|
||||||
|
descrisa in header-ul `ofacturare_antet.prg` a mutat logica lor comuna intr-o singura clasa,
|
||||||
|
ramura aleasa la runtime pe `poDate.nIdTipDoc`.
|
||||||
191
docs/qa_factura_unelte.md
Normal file
191
docs/qa_factura_unelte.md
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# Inventar unelte QA/testare UI si infrastructura de progres — ROAFACTURARE
|
||||||
|
|
||||||
|
Inventar read-only, 17.09.2026. Fara propuneri, doar stare (citate `fisier:linie`).
|
||||||
|
|
||||||
|
## 1. `vfp_ui_harness.ps1` — orchestrator UI
|
||||||
|
|
||||||
|
Locatie: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\vfp_ui_harness.ps1`.
|
||||||
|
Complement in VFP: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\ui_harness.prg` (proceduri de handshake
|
||||||
|
incluse in testul .prg via `SET PROCEDURE TO ui_harness ADDITIVE`).
|
||||||
|
|
||||||
|
Parametri (`vfp_ui_harness.ps1:20-28`):
|
||||||
|
- `-TestPrg` (obligatoriu) — calea `.prg` a testului.
|
||||||
|
- `-Steps` (obligatoriu) — array de etichete, una per pas asteptat (`ready_<n>.txt`/`cont_<n>.txt`).
|
||||||
|
- `-StepTimeoutSec` (implicit 130), `-ReadyTimeoutSec` (implicit 180, pt. `ready_0` = afisarea formularului).
|
||||||
|
- `-ShotsDir` (implicit `<folder test>\screenshots`), `-SyncDir` (implicit `<folder test>\uisync`).
|
||||||
|
- `-Vfp` (implicit `C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe`).
|
||||||
|
|
||||||
|
Ce face exact:
|
||||||
|
- Precompileaza izolat (`_precompile.ps1`, proces copil) inainte de lansare (`vfp_ui_harness.ps1:145-154`).
|
||||||
|
- Lanseaza `.FXP`-ul cu `vfp9.exe -A <test.fxp>` (nu `.prg`), cu pana la 8 incercari daca testul nu
|
||||||
|
scrie START in 30s (`vfp_ui_harness.ps1:160-190`).
|
||||||
|
- Fereastra principala e mutata **off-screen** (`x=-4000`, `HWND_BOTTOM`, `SWP_NOACTIVATE`) imediat
|
||||||
|
ce apare `MainWindowHandle`, si re-impinsa la fiecare pas — **fara furt de focus**, cerinta Marius
|
||||||
|
17/07/2026 (`vfp_ui_harness.ps1:13-19,73-77`).
|
||||||
|
- Screenshot per pas via `PrintWindow` pe handle (flag `PW_RENDERFULLCONTENT=2`, fallback flag 0
|
||||||
|
daca iese gol) — nu `CopyFromScreen` (`vfp_ui_harness.ps1:106-131`).
|
||||||
|
- Bucla pasi: asteapta `ready_<n>.txt` (scris de test din `ui_harness.prg`), face screenshot, scrie
|
||||||
|
`cont_<n>.txt` ca sa continue testul (`vfp_ui_harness.ps1:200-221`).
|
||||||
|
- La final asteapta `done.txt`, omoara doar instantele `vfp9.exe` proprii (linie de comanda contine
|
||||||
|
folderul testului) (`vfp_ui_harness.ps1:133-140,223-231`).
|
||||||
|
|
||||||
|
`ui_harness.prg` — API apelat din testul VFP:
|
||||||
|
- `HarnessLog(mesaj)` (`ui_harness.prg:19-28`), `HarnessReady(n, nota)` (`ui_harness.prg:30-35`),
|
||||||
|
`HarnessWaitContinue(n, autoSec=30)` (`ui_harness.prg:37-51`), `HarnessStep(n, nota, autoSec)`
|
||||||
|
= Ready+WaitContinue (`ui_harness.prg:53-57`), `HarnessDone(status)` (`ui_harness.prg:59-64`).
|
||||||
|
- `HarnessWaitContinue` are auto-continue dupa `autoSec` (implicit 30s) daca orchestratorul nu
|
||||||
|
raspunde — testul merge si fara `vfp_ui_harness.ps1`, doar fara capturi (`ui_harness.prg:8-9,43-49`).
|
||||||
|
- `HarnessInit`: `SET SAFETY OFF` + `SET TALK OFF` defensiv (`ui_harness.prg:11-17`).
|
||||||
|
|
||||||
|
Limitari cunoscute:
|
||||||
|
- **Nu trimite input real** (fara `SendInput`/`keybd_event`/click injectat) — harness-ul doar
|
||||||
|
citeste semafoare si face screenshot; actiunile UI (click, taste) sunt executate **din interiorul
|
||||||
|
testului VFP** (apeluri directe de metode/evenimente), nu de PowerShell din afara.
|
||||||
|
- Watchdog-ul de dialoguri (`watchdog_vfp.ps1`) e un instrument separat, folosit doar pt. dialoguri
|
||||||
|
native neasteptate; dismiss-ul se face STRICT prin mesaje Windows tintite pe handle (`BM_CLICK`,
|
||||||
|
`WM_COMMAND IDCANCEL`, `WM_KEYDOWN/UP` ESCAPE, `WM_CLOSE`) — interzis explicit input real de
|
||||||
|
tastatura/mouse, masina fiind partajata cu utilizatorul (`watchdog_vfp.ps1:16-26`).
|
||||||
|
- Dialogurile VFP owner-drawn (ex. "View Parameter") pot sa nu raspunda la niciun mesaj — dismiss-ul
|
||||||
|
esueaza cinstit, ramane deschis pana la timeout (`watchdog_vfp.ps1:23-26`).
|
||||||
|
- Coloanele de grid nu se materializeaza sub `-A -T`/headless (`ColumnCount=0`, `RecordSource` sunt
|
||||||
|
artefacte) — cunoscut, documentat separat (memorie `grid-coloane-nu-se-materializeaza-headless`);
|
||||||
|
simptomul apare si in suitele UI (ex. `test_page3_articole` 14/2 in `docs\progres.md:67`, cele 2
|
||||||
|
FAIL = artefactul de baseline).
|
||||||
|
- `watchdog_vfp.ps1` clasifica orice fereastra noua diferita de `MainWindowHandle` ca dialog blocant
|
||||||
|
(nu dupa numele clasei — VFP refoloseste acelasi prefix de clasa si pt. shell, si pt. dialoguri
|
||||||
|
proprii) (`watchdog_vfp.ps1:6-9`).
|
||||||
|
|
||||||
|
## 2. Harness headless (skill `roa-vfp-headless-test`)
|
||||||
|
|
||||||
|
Skill: `D:\ROA\ROAFACTURARE\COMUN\skills\roa-vfp-headless-test\SKILL.md`.
|
||||||
|
|
||||||
|
Lansare probe (`SKILL.md:102-103`): `vfp9.exe -A -T "<script.prg>" <param>`, din PowerShell, cu
|
||||||
|
timeout si `$p.Kill()` daca nu iese (`$p.WaitForExit(120000)`). `-A` si `-T` obligatorii amandoua
|
||||||
|
(`SKILL.md:45-46`). Precompilare izolata obligatorie inainte (`_precompile.ps1`, proces copil),
|
||||||
|
altfel `vfp9 -A` poate deschide editorul in loc sa ruleze (`SKILL.md:28-29,99-100`).
|
||||||
|
|
||||||
|
Loguri: fiecare test scrie propriul `<test>_log.txt` langa `.prg` (convenit prin `gcUILog`/logica
|
||||||
|
proprie a testului); sinteza vine din `raport_teste.ps1` (`D:\ROA\ROAFACTURARE\COMUN\utile\Teste\raport_teste.ps1`),
|
||||||
|
care citeste **doar** fisierele `*_log.txt` dintr-un folder (implicit `achizitie_import`, parametrizabil
|
||||||
|
cu `-Dir`), numara linii `^PASS` si `^(FAIL|BUG)` si scoate PASS/FAIL per fisier + varsta (minute de
|
||||||
|
la ultima scriere) (`raport_teste.ps1:9-33`). `-Baseline <fisier>` marcheaza NOU vs. preexistent
|
||||||
|
(`raport_teste.ps1:7-8,16-19`). Regula: orchestratorul citeste doar acest raport, nu logurile brute
|
||||||
|
(`raport_teste.ps1:2-3`, trimite la `COMUN\docs\orchestrare-subagenti.md`).
|
||||||
|
|
||||||
|
Mock-uri disponibile in `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\`:
|
||||||
|
- `mock_amessagebox.prg` — inlocuieste `FUNCTION amessagebox` din `oproceduri_comune.prg`; returneaza
|
||||||
|
6 (Da/OK) implicit, configurabil per apel prin `PUBLIC gnMockRaspuns` (raspuns generic),
|
||||||
|
`gnMockRaspunsTotal` (raspuns separat pt. mesajul "Actualizati totalul facturii"),
|
||||||
|
`gcMockUltimMesaj`/`gnMockUltimTip` (captura textul/tipul ultimului dialog, pt. asertii)
|
||||||
|
(`mock_amessagebox.prg:1-44`). **Trebuie incarcat PRIMUL** in `SET PROCEDURE` (VFP foloseste, la
|
||||||
|
nume duplicat, fisierul cautat primul, nu cel deschis cel mai recent) — vezi
|
||||||
|
`test_init_env_auto_roafacturare.prg:119-121` unde e adaugat inaintea listei aplicatiei.
|
||||||
|
LIMITA 1: nu acopera apeluri intra-fisier (ex. `verifica_partener_show_info` -> `amessagebox` in
|
||||||
|
acelasi `oproceduri_comune.prg`) — necesita mock dedicat per caz (`mock_amessagebox.prg:13-15`).
|
||||||
|
LIMITA 2: **nu acopera dialogurile de eroare ale lui `goExecutor`** — o interogare gresita agata
|
||||||
|
headless fara nicio linie in log (`mock_amessagebox.prg:16-17`). Nu exista un mock generic pentru
|
||||||
|
`goExecutor` la radacina `Teste\`; exista doar mock-uri punctuale per test in subfoldere (ex.
|
||||||
|
`achizitie_import\mock_cauta_alfa_tva11.prg`, `achizitie_import\mock_oscrie_in_fisiere.prg`).
|
||||||
|
- `watchdog_vfp.ps1` — pt. dialoguri native neprinse de ON ERROR/mock-uri (`SKILL.md:97-98`).
|
||||||
|
|
||||||
|
Init de mediu specific ROAFACTURARE: `test_init_env_auto_roafacturare.prg` — vezi punctul 3.
|
||||||
|
|
||||||
|
## 3. Conectare Oracle in probe (schema MARIUSM_AUTO)
|
||||||
|
|
||||||
|
Script: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto_roafacturare.prg`.
|
||||||
|
|
||||||
|
Apel: `DO test_init_env_auto_roafacturare WITH 'CENTRAL', 'MARIUSM_AUTO', 'parola'`
|
||||||
|
(`test_init_env_auto_roafacturare.prg:11`).
|
||||||
|
|
||||||
|
Valori implicite daca parametrii sunt goi (`test_init_env_auto_roafacturare.prg:16-18`):
|
||||||
|
- `tcHost` = `CENTRAL`
|
||||||
|
- `tcSchema` = `MARIUSM_AUTO`
|
||||||
|
- `tcPassword` = `ROMFASTSOFT`
|
||||||
|
|
||||||
|
Conexiunea propriu-zisa: `goConn = createobject("oConn")` +
|
||||||
|
`goConn.Connect(tcHost, tcSchema, tcPassword)` (`test_init_env_auto_roafacturare.prg:209-214`),
|
||||||
|
verificata prin `gnHandle > 0`. Executorul de SQL e `goExecutor = createobject("oExecutor")`
|
||||||
|
(`test_init_env_auto_roafacturare.prg:209`), folosit apoi pt. `oExecuta(...)`.
|
||||||
|
|
||||||
|
An/luna de lucru fixe (independente de `Date()`): `gnAn=2026`, `gnLuna=8` daca nu sunt pasate ca
|
||||||
|
parametri 4/5 (`test_init_env_auto_roafacturare.prg:36-37`). Firma: `gnIdFirma=110` implicit
|
||||||
|
(parametru 6 optional) (`test_init_env_auto_roafacturare.prg:39,221-224`).
|
||||||
|
|
||||||
|
Cale aplicatie fixa: `gcAppPath = 'D:\ROA\ROAFACTURARE\'` (`test_init_env_auto_roafacturare.prg:57`) —
|
||||||
|
seteaza `SET PATH`/`SET CLASSLIB`/`SET PROCEDURE` identic cu `roafacturare.prg`, plus mock-ul
|
||||||
|
`amessagebox` incarcat primul (`test_init_env_auto_roafacturare.prg:82-176`).
|
||||||
|
|
||||||
|
Nota din antet: variantele `test_init_env_auto_<produs>.prg` (ROACONT/ROAGEST/ACNPRO/ROADEF) au
|
||||||
|
cale + SET-uri specifice produsului, dar **partea de conexiune Oracle e identica** in toate
|
||||||
|
(`test_init_env_auto_roafacturare.prg:6-9`). Alegerea harness-ului gresit incarca alt working copy
|
||||||
|
silentios (`SKILL.md:21-23`).
|
||||||
|
|
||||||
|
## 4. Fisiere de progres/status existente
|
||||||
|
|
||||||
|
In `D:\ROA\ROAFACTURARE\docs\`:
|
||||||
|
- `progres.md` — **fisierul curent de stare**, actualizat de fiecare sesiune inainte sa se incheie;
|
||||||
|
planurile (`plan_0*.md`) spun *ce*, `progres.md` spune *unde s-a ajuns* (`progres.md:1-7`). Format:
|
||||||
|
titlu cu punctele acoperite, "Ultima actualizare: DD.MM.YYYY", apoi blocuri
|
||||||
|
`> **RUNDA DD.MM.YYYY — titlu.**` cu subsectiuni in proza (ce s-a schimbat, decizii numerotate,
|
||||||
|
teste rulate cu PASS/FAIL, capcane platite, ramas deschis) — nu tabel, istoricul sesiunilor nu se
|
||||||
|
pastreaza, doar starea la zi (`progres.md:1-9`).
|
||||||
|
- `handoff_*.md` — 6 fisiere curent (`handoff_update_romfast.md`, `handoff_cont_discount_667_709.md`,
|
||||||
|
`handoff_id_set_skilluri.md`, `handoff_idempotenta_id_set.md`, plus altele). Format observat in
|
||||||
|
`handoff_idempotenta_id_set.md:1-13`: titlu cu subiect+data, sectiuni numerotate H2 — "Livrabile
|
||||||
|
terminate" (tabel Ce/Unde/Stare), "Ce face X", "APROBAT dar NEEXECUTAT", "NEFINALIZAT/necomis",
|
||||||
|
"Comenzi de reluat" (bloc powershell), incheiat de regula cu inventar fisier:linie si stare
|
||||||
|
write-back per fisier atins.
|
||||||
|
- `diff_*.md` / `diff_*.patch` — diff-uri de revizuit inainte de commit (ex.
|
||||||
|
`diff_cont_discount_667_709.md`).
|
||||||
|
- `plan_index.md` + `plan_1*.md` — planuri pe story-uri (ex. `plan_10_integrare_contracte.md`).
|
||||||
|
- `docs\cercetare\*.md` — ~60 de fisiere de cercetare/investigatie punctuala (un subiect per fisier).
|
||||||
|
- `erori_deschise.md`, `livrare_13.md`, `raport_doc.md`, `raport_src.md`.
|
||||||
|
|
||||||
|
In `D:\ROA\ROAFACTURARE\COMUN\docs\` (relevante pt. orchestrare/testare, nu fisiere de progres in
|
||||||
|
sine ci proceduri):
|
||||||
|
- `orchestrare-subagenti.md` — procedura Regula zero (predare context), citata de `raport_teste.ps1:2-3`.
|
||||||
|
- `reguli_lucru.md` — index de reguli de lucru/testare, ruteaza pe zona la skill-ul potrivit.
|
||||||
|
- `depanare_testare_vfp.md`, `testare-ui-vfp.md` — capcanele detaliate din `SKILL.md` (sec. 6-7 / integral).
|
||||||
|
|
||||||
|
Nu exista fisiere de progres/status in `D:\ROA\COMUNROA\` — directorul `COMUNROA` (shared suite-wide,
|
||||||
|
distinct de `COMUN\` din interiorul ROAFACTURARE) nu are un `docs\progres.md` propriu vizibil din
|
||||||
|
acest working copy.
|
||||||
|
|
||||||
|
## 5. Hook-uri configurate
|
||||||
|
|
||||||
|
### `C:\Users\mmari\.claude\settings.json` (nivel user, global)
|
||||||
|
|
||||||
|
- `UserPromptSubmit` -> `context_watch.ps1 -Stdout` (mesaj injectat, exit 0).
|
||||||
|
- `SessionStart` -> `docs_revizie_check.ps1 -Stdout`.
|
||||||
|
- `PostToolUse` -> `context_watch.ps1 -OSinguraData` (async, timeout 20s).
|
||||||
|
- `PreCompact` -> mesaj fix de sistem (Regula zero — predare la compactare).
|
||||||
|
- `PostCompact` -> mesaj fix + `additionalContext` (Regula zero — predare obligatorie dupa compactare).
|
||||||
|
- `SubagentStop` -> mesaj fix (`additionalContext`) care aminteste orchestratorului sa verifice
|
||||||
|
daca subagentul si-a scris starea pe disc inainte sa-i dea sarcina urmatoare.
|
||||||
|
|
||||||
|
**Da, exista deja un hook `context_watch`**, script:
|
||||||
|
`D:\ROA\ROAGEST\COMUN\utile\context_watch.ps1` (in COMUNROA/ROAGEST, partajat intre proiecte, invocat
|
||||||
|
din `settings.json` cu cale absoluta acolo).
|
||||||
|
|
||||||
|
Ce face (`context_watch.ps1:1-9,55-84`):
|
||||||
|
- Citeste ultimul `"usage":{...}` din coada transcriptului JSONL al sesiunii curente (ultimii 400KB
|
||||||
|
din fisier, nu tot fisierul), insumeaza `input_tokens + cache_creation_input_tokens +
|
||||||
|
cache_read_input_tokens`.
|
||||||
|
- Praguri: `-Prag 250000` (implicit, nivel "avertisment") si `-PragMax 275000` (implicit, nivel
|
||||||
|
"max" — peste limita, predare OBLIGATORIE acum).
|
||||||
|
- La `avertisment`: mesaj care cere incheierea blocului curent, actualizarea handoff-ului pe disc,
|
||||||
|
delegarea catre subagenti a oricarei citiri/testari ramase.
|
||||||
|
- La `max`: mesaj cu procedura completa Regula zero (opreste lucrul, scrie handoff pe disc, confirma
|
||||||
|
in doua randuri, preda unei sesiuni noi).
|
||||||
|
- `-Stdout`: scrie mesajul pe stdout si iese cu 0 (folosit pe `UserPromptSubmit`, injecteaza context
|
||||||
|
fara sa esueze hook-ul). Fara `-Stdout`: scrie pe stderr si iese cu exit 2 (semnaleaza agentului).
|
||||||
|
- `-OSinguraData` (folosit pe `PostToolUse`): emite alerta o singura data per sesiune+prag, marcaj
|
||||||
|
fisier in `%TEMP%\claude_ctxwatch_<sesiune>_<nivel>.flag` — altfel mesajul s-ar repeta la fiecare
|
||||||
|
tool call (`context_watch.ps1:9-10,74-80`).
|
||||||
|
- Fara `-TranscriptPath` explicit, il citeste din stdin (JSON-ul hook-ului) sau, ca fallback, cauta
|
||||||
|
cel mai recent `.jsonl` din `~/.claude/projects/<cwd-encodat>\` (`context_watch.ps1:18-34`).
|
||||||
|
|
||||||
|
### `D:\ROA\ROAFACTURARE\.claude\` (nivel proiect)
|
||||||
|
|
||||||
|
Singurul fisier: `settings.local.json`, continut integral `{"outputStyle": "Concise"}`. **Niciun
|
||||||
|
hook definit la nivel de proiect** — toate hook-urile active vin din `settings.json` global de user.
|
||||||
170
docs/test_curs_bnr_headless.md
Normal file
170
docs/test_curs_bnr_headless.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Test headless — `citeste_curs_bnr` (arhiva 10 zile + fisier anual BNR)
|
||||||
|
|
||||||
|
Tinta: `COMUN\clase\onom_curs.vcx`, clasa `actualizare_curs_bnr`, metoda `citeste_curs_bnr`
|
||||||
|
(patch v3, deja scris in binar — confirmat inainte de test, vezi sectiunea 0). Metoda a fost
|
||||||
|
apelata **DIRECT**, niciodata prin `initializeaza` — niciun apel la `initializeaza()` sau
|
||||||
|
`scrie_curs_bnr()` in tot scriptul, deci **nicio scriere in Oracle**. Conexiunea Oracle deschisa de
|
||||||
|
mediul de test (`test_init_env_auto_roafacturare.prg`) face doar `SELECT`-uri (firma, calendar,
|
||||||
|
`pack_sesiune.set*`) pentru initializarea sesiunii — nimic legat de cursuri BNR.
|
||||||
|
|
||||||
|
## 0. Confirmare inainte de test
|
||||||
|
|
||||||
|
`COMUN\clase\onom_curs.vc2` are deja proprietatile `clinkan`/`clinkarhiva` si noua
|
||||||
|
`citeste_curs_bnr` cu parametrul `tnSursa` (linia 106+), identice cu blocul `NOU` din
|
||||||
|
`docs\patch_curs_bnr_arhiva.md` — verificat cu grep inainte de a scrie testul. `.vcx`/`.VCT` au
|
||||||
|
acelasi mtime cu `.vc2` (22:11, 17.09.2026) — write-back-ul din `docs\writeback_curs_bnr.md`
|
||||||
|
confirmat aplicat, nu s-a atins din nou.
|
||||||
|
|
||||||
|
## 1. Artefact de harness gasit si ocolit (NU e defect de cod)
|
||||||
|
|
||||||
|
Apeland `citeste_curs_bnr` direct (fara `initializeaza`), primul apel a agatat testul (90s
|
||||||
|
timeout, 0 dialoguri detectate de `watchdog_vfp.ps1`). Cauza, confirmata prin proba minimala
|
||||||
|
(`test_curs_bnr_probe2.prg`, sters dupa confirmare):
|
||||||
|
|
||||||
|
- `citeste_curs_bnr` incepe cu `creeaza_backup_cursoare(This.cCursor)` (`onom_curs.vc2:118`), care
|
||||||
|
cheama `copiaza_structura_cursor()` din `COMUN\programe\oproceduri_comune.prg:3024`.
|
||||||
|
- Acolo, `If Used(tcSursa)` (`oproceduri_comune.prg:3027`) — daca `ccrsbnr` nu e deja deschis (cazul
|
||||||
|
cand chemi metoda direct, fara sa treci prin `initializeaza`, care il creeaza via
|
||||||
|
`goExecutor.oExecute`), cade pe `Else` si cheama
|
||||||
|
`amessagebox("Eroare interna 2 - copiaza structura cursor",...)` (`oproceduri_comune.prg:3036`).
|
||||||
|
- `amessagebox` (functia REALA, `oproceduri_comune.prg:1205`) e apelata **din acelasi fisier** ca
|
||||||
|
`copiaza_structura_cursor` — `mock_amessagebox.prg` documenteaza explicit aceasta limita
|
||||||
|
("LIMITA 1: nu acopera sigur apelurile INTRA-FISIER") — deci mock-ul nu intercepteaza, se
|
||||||
|
deschide `messagebox_form` real (`MessageBox.vcx`), modal, headless agata.
|
||||||
|
- In productie nu se intampla: `initializeaza()` creeaza `This.cCursor` cu
|
||||||
|
`goExecutor.oExecute(lcSql, This.cCursor)` (`onom_curs.vc2:159`) INAINTE de a chema
|
||||||
|
`citeste_curs_bnr`.
|
||||||
|
|
||||||
|
**Reparatia e in test, nu in cod**: inainte de FIECARE apel, testul precreaza `ccrsbnr` gol
|
||||||
|
(`CREATE CURSOR ccrsbnr (Currency C(10), multiplier N(10,4), Curs N(14,6))` — campurile confirmate
|
||||||
|
din `scrie_curs_bnr`, `onom_curs.vc2:282-283`, care citeste exact `Currency`/`multiplier`/`Curs`
|
||||||
|
din cursor). Metoda oricum inchide cursorul (`Use In`) si il reconstruieste din raspunsul HTTP
|
||||||
|
(`Xmltocursor`), deci structura precreata conteaza doar pentru backup, nu pentru rezultat.
|
||||||
|
Confirmat cu proba minimala: dupa precreare, Return=0, Reccount=37, EUR curs=5.2637 — fara
|
||||||
|
agatare. Niciun fisier de productie nu a fost atins pentru asta.
|
||||||
|
|
||||||
|
## 2. Rezultate — 7 cazuri cerute + 1 caz suplimentar (cascada)
|
||||||
|
|
||||||
|
| # | Caz | Parametri | Return asteptat | Return obtinut | Durata | Rezultat |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| 1 | sursa 1, azi (zi lucratoare) | `dData=17.09.2026` (joi) | 0 | 0 | 0.238s | **PASS** — Reccount=37, EUR curs=5.2637 |
|
||||||
|
| 2 | sursa 1, luni recenta | `dData=14.09.2026` (luni) | 0 | 0 | 0.048s | **PASS** — cade pe publicarea de vineri, Reccount=37, EUR curs=5.2557 |
|
||||||
|
| 3 | sursa 2, martie an curent | `dData=15.03.2026` | 0 | 0 | 0.084s | **PASS** — Reccount=37, EUR curs=5.0947 |
|
||||||
|
| 4 | sursa 2, 2 ianuarie an curent | `dData=02.01.2026` | 0 | 0 | 0.202s | **PASS** — trecere in 2025, Reccount=38, EUR curs=5.0985 |
|
||||||
|
| 5 | sursa 1, data mult mai veche decat arhiva | `dData=01.01.2026` | 2 | 2 | 0.047s | **PASS** — "data lipsa" distinct de "BNR picat" |
|
||||||
|
| 6 | sursa 1, host invalid (indisponibilitate) | `cLinkArhiva`=host `.invalid` inexistent, `dData`=azi | 1 | **2** | 0.181s | **FAIL** — vezi sectiunea 3, defect real in cod |
|
||||||
|
| 7 | fara parametru (compatibilitate inapoi) | `dData=18.08.2026` | 0 (comportament sursa 0) | 0 | 0.041s | **PASS** — `This.dData` s-a suprascris la 18.09.2026 (publicare+1), ca inainte |
|
||||||
|
| 8 (suplimentar, cerut de coordonator) | cascada reala: acelasi obiect/`dData` din caz 5 (`Return 2` pe sursa 1), apoi sursa 2 | `dData=01.01.2026` | 0 | 0 | 0.120s | **PASS** — Reccount=38, EUR curs=5.0985, FARA "Eroare interna 2" la al doilea apel |
|
||||||
|
|
||||||
|
**Sumar: 7 PASS, 1 FAIL** (cazul 6).
|
||||||
|
|
||||||
|
## 3. Cazul 5 — eroare tranzitorie auto-vindecata, nu afecteaza Return
|
||||||
|
|
||||||
|
La finalul cazului 5 (Return 2, data prea veche), logul a prins:
|
||||||
|
```
|
||||||
|
[ON ERROR] 13 Alias 'CCRSBNR' is not found. in REPUNE_BACKUP_CURSOARE linia 6363
|
||||||
|
```
|
||||||
|
Cauza: `citeste_curs_bnr` inchide `This.cCursor` (`Use In`) inainte de apelul HTTP; pe ramura
|
||||||
|
`Return 2` cheama `repune_backup_cursoare(This.cCursor)` (`onom_curs.vc2:175`), a carei prima
|
||||||
|
linie utila e `Select (tcOriginal)` (`oproceduri_comune.prg:6363`) pe un alias deja inchis —
|
||||||
|
**risc preexistent, deja documentat** in `docs\patch_curs_bnr_arhiva.md` sectiunea 5, punctul 3
|
||||||
|
("`repune_backup_cursoare` pe cursor deja inchis"), nu introdus de acest patch. Confirmat acum ca
|
||||||
|
se reproduce efectiv. Efect: eroarea e prinsa de `ON ERROR`, executia continua la linia
|
||||||
|
urmatoare (`lnRecno = Recno()`), iar liniile de dupa (`Select * From bckpccrsbnr Into Cursor
|
||||||
|
ccrsbnr`) reconstruiesc singure alias-ul `ccrsbnr` — **auto-vindecare in aceeasi rulare**, `Return
|
||||||
|
2` final e corect si cazul 8 (cascada imediat urmatoare, sursa 2 pe acelasi obiect) confirma ca
|
||||||
|
`ccrsbnr` ramane intr-o stare corecta (Used, fara sa mai loveasca "Eroare interna 2" la
|
||||||
|
`creeaza_backup_cursoare` din apelul urmator). Nu s-a reparat codul — doar raportat, cum s-a cerut.
|
||||||
|
|
||||||
|
## 4. Cazul 6 — defect REAL, nu artefact de test
|
||||||
|
|
||||||
|
**Return obtinut 2, asteptat 1.** Nu e problema harnessului — e un gol real in
|
||||||
|
`citeste_curs_bnr` pentru cazul specific "host complet nerezolvabil" (nu doar HTTP non-200 sau
|
||||||
|
timeout pe conexiune).
|
||||||
|
|
||||||
|
Secventa exacta din log (prima eroare e cea reala, restul e cascada, exact ca in nota de procedura
|
||||||
|
despre `ON ERROR`):
|
||||||
|
|
||||||
|
```
|
||||||
|
[ON ERROR] 1429 OLE IDispatch exception ... WinHttp.WinHttpRequest: The server name or address
|
||||||
|
could not be resolved. in ACTUALIZARE_CURS_BNR.CITESTE_CURS_BNR (linia loHTTP.Send(), onom_curs.vc2:137)
|
||||||
|
[ON ERROR] 1429 ... The data necessary to complete this operation is not yet available. (citirea
|
||||||
|
ulterioara a lui loHTTP.Status, onom_curs.vc2:139)
|
||||||
|
... (cascada: 8x eroare 11 "Function argument value, type, or count is invalid" in bucla
|
||||||
|
Do While de la onom_curs.vc2:151-173, cu lcFisier ramas needefinit)
|
||||||
|
[ON ERROR] 13 Alias 'CCRSBNR' is not found. in REPUNE_BACKUP_CURSOARE linia 6363
|
||||||
|
```
|
||||||
|
|
||||||
|
Explicatie: `loHTTP.Send()` (`onom_curs.vc2:137`) NU e in `TRY/CATCH` si NU verifica intai daca
|
||||||
|
request-ul a esuat la nivel de retea/DNS — arunca direct o exceptie OLE cand hostul nu se rezolva.
|
||||||
|
Fara `TRY/CATCH` in metoda, `ON ERROR` global preia eroarea si, conform semanticii VFP (executia
|
||||||
|
continua la linia URMATOARE, nu la ramura `Else` corecta), programul "sare" peste blocul
|
||||||
|
`If loHTTP.Status = 200 ... Else ... Return 1 ... Endif` fara sa execute nici ramura de succes, nici
|
||||||
|
`Return 1` — continua direct in bucla de cautare `Cube` cu `lcFisier` gol/nedefinit, epuizeaza cele
|
||||||
|
10 incercari (cascada de erori 11), si iese pe ramura "nimic gasit" -> `Return 2`. Rezultatul final
|
||||||
|
e gresit: un host complet indisponibil (DNS esuat) e raportat ca "data lipsa" (2), nu ca
|
||||||
|
"indisponibilitate" (1) — exact confuzia pe care patch-ul trebuia sa o elimine, dar doar pentru
|
||||||
|
raspunsuri HTTP non-200 de la un server care RASPUNDE, nu pentru esecul de rezolvare DNS.
|
||||||
|
|
||||||
|
**Durata: 0.181s** — nu agata deloc (departe de cele 5s/5s/5s/15s din `SetTimeouts`); esecul de
|
||||||
|
rezolvare DNS e instant, nu trece prin timeout-uri.
|
||||||
|
|
||||||
|
Conform instructiunii explicite: **nu am reparat codul**. Raportez cu dovada de mai sus.
|
||||||
|
|
||||||
|
## 5. Cum s-a rulat
|
||||||
|
|
||||||
|
Script: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_curs_bnr_headless.prg` (helper-e proprii:
|
||||||
|
`crea_cursor_gol`, `log_txt`, `log_eroare`, `verifica_caz` — toate in acelasi fisier).
|
||||||
|
|
||||||
|
Precompilare izolata:
|
||||||
|
```
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\_precompile.ps1 -Prg D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_curs_bnr_headless.prg
|
||||||
|
```
|
||||||
|
|
||||||
|
Rulare (proces separat, timeout 240s, in PowerShell):
|
||||||
|
```powershell
|
||||||
|
$p = Start-Process -FilePath 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe' `
|
||||||
|
-ArgumentList '-A','-T','"D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_curs_bnr_headless.prg"' -PassThru
|
||||||
|
if (-not $p.WaitForExit(240000)) { $p.Kill() }
|
||||||
|
```
|
||||||
|
|
||||||
|
Log complet (marca unica de rulare in prima linie, `SYS(2015)`):
|
||||||
|
`D:\ROA\ROAFACTURARE\COMUN\utile\Teste\_test_curs_bnr_out.txt` — ultima rulare confirmata: mtime
|
||||||
|
17.09.2026 22:26, marca `_7KA1C340N`.
|
||||||
|
|
||||||
|
Retea: toate cele 8 sub-cazuri au ajuns efectiv la `curs.bnr.ro` (sau la hostul invalid pentru
|
||||||
|
cazul 6) — reteaua a functionat, niciun rezultat nu e raportat fals din lipsa de conectivitate.
|
||||||
|
|
||||||
|
## 6. Confirmare: nicio scriere in Oracle
|
||||||
|
|
||||||
|
Scriptul apeleaza EXCLUSIV `loCurs.citeste_curs_bnr(...)` pe obiecte `actualizare_curs_bnr`
|
||||||
|
instantiate direct — niciun apel la `initializeaza()` (care ar citi din Oracle si, la
|
||||||
|
`Reccount=0`, ar declansa cascada de UI) si niciun apel la `scrie_curs_bnr()` (singura metoda a
|
||||||
|
clasei care scrie in `pack_curs.scrie_cotatii_curs`). Singurele interactiuni cu Oracle din intreaga
|
||||||
|
rulare sunt cele din `test_init_env_auto_roafacturare.prg` (conectare + `SELECT`-uri de sesiune),
|
||||||
|
neschimbate fata de orice alt test headless din acest proiect.
|
||||||
|
|
||||||
|
## 7. Fisiere temporare de test, sterse dupa confirmare
|
||||||
|
|
||||||
|
`test_curs_bnr_probe.prg` / `.fxp` si `test_curs_bnr_probe2.prg` / `.fxp` (folosite doar pentru
|
||||||
|
diagnostic, sterse din `COMUN\utile\Teste\` dupa ce artefactul de harness a fost confirmat si
|
||||||
|
reparat in scriptul final). Raman pe disc doar `test_curs_bnr_headless.prg` / `.fxp` si logul
|
||||||
|
`_test_curs_bnr_out.txt`.
|
||||||
|
|
||||||
|
## 8. Rerulare dupa reparatia `Try/Catch` (v4)
|
||||||
|
|
||||||
|
Data/ora: 17.09.2026 22:36. Reparatia din `docs\patch_curs_bnr_arhiva.md` sectiunea 7 (`Try/Catch`
|
||||||
|
in jurul ambelor `loHTTP.Send()` din `citeste_curs_bnr`) e scrisa in binar (write-back confirmat,
|
||||||
|
fidelity-check pe round-trip identic). `.FXP` vechi al testului sters, precompilat din nou, o
|
||||||
|
singura rulare vfp9 (`-A -T`), log nou confirmat pe mtime si marca (`_7KA1CGZRZ`, 22:36:54).
|
||||||
|
|
||||||
|
| # | Caz | Return inainte (sectiunea 2) | Return dupa | Rezultat |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1-4, 7, 8 | neschimbate | 0 | 0 | PASS (neschimbat) |
|
||||||
|
| 5 | data mult mai veche decat arhiva | 2 | 2 | PASS (neschimbat) |
|
||||||
|
| 6 | host invalid (indisponibilitate) | **2 (FAIL, asteptat 1)** | **1** | **PASS** |
|
||||||
|
|
||||||
|
**Sumar nou: 8 PASS, 0 FAIL** (fata de 7 PASS, 1 FAIL inainte de reparatie). Nicio regresie pe
|
||||||
|
cazurile 1-5, 7, 8. Cazul 6 intoarce acum `1` ("BNR picat") in loc de `2` ("data lipsa"), deci
|
||||||
|
`initializeaza` ofera acum linkul `https://www.cursbnr.ro/` in loc de mesajul gresit "Nu exista
|
||||||
|
cursul BNR pentru data specificata!".
|
||||||
95
docs/writeback_curs_bnr.md
Normal file
95
docs/writeback_curs_bnr.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Write-back patch curs BNR arhiva (v3) — onom_curs.vc2
|
||||||
|
|
||||||
|
Tinta: `COMUN\clase\onom_curs.vc2`, clasa `actualizare_curs_bnr`. Sursa patch-ului:
|
||||||
|
`docs\patch_curs_bnr_arhiva.md` (v3) si `docs\patch_curs_bnr_arhiva.diff`.
|
||||||
|
|
||||||
|
## 0. Stare de pornire
|
||||||
|
|
||||||
|
- `git status --short clase/onom_curs.vc2` (COMUN) — gol, fara modificari necomise.
|
||||||
|
- `svn status clase/onom_curs.vc2` — `I` (ignorat de SVN, fisier text generat).
|
||||||
|
- Backup inainte de orice modificare, in scratchpad: `onom_curs.vc2.bak`, `onom_curs.vcx.bak`,
|
||||||
|
`onom_curs.VCT.bak`.
|
||||||
|
|
||||||
|
## 1. Metoda de editare
|
||||||
|
|
||||||
|
Editare byte-safe cu script Python (mod binar `rb`/`wb`), fara `Edit`/`sed -i`. Continutul nou
|
||||||
|
(ASCII pur) a fost extras direct din `docs\patch_curs_bnr_arhiva.md` (blocurile `NOU` pentru
|
||||||
|
`citeste_curs_bnr`, indentare identica cu fisierul tinta — offset 0) si reconstruit manual pentru
|
||||||
|
blocul `initializeaza` (indentarea reala din fisier e cu 1 tab mai adanca decat in markdown: linia
|
||||||
|
`Case This.dData < ...` are 7 tab-uri in fisier, nu 6 ca in document — s-a aplicat +1 tab pe toate
|
||||||
|
liniile blocului nou, pastrand structura interna neschimbata).
|
||||||
|
|
||||||
|
## 2. Linii schimbate efectiv (dupa write-back, confirmate cu `git diff`)
|
||||||
|
|
||||||
|
Trei zone, toate in `actualizare_curs_bnr`:
|
||||||
|
|
||||||
|
1. **Proprietati noi** `clinkan` / `clinkarhiva` — inserate in ordine alfabetica in
|
||||||
|
`*<DefinedPropArrayMethod>` (dupa `*p: clink`), in `*<PropValue>` (dupa `clink = ...`) si in
|
||||||
|
`_memberdata` (dupa `<memberdata name="clink".../>`), exact ca in patch.
|
||||||
|
2. **`PROCEDURE citeste_curs_bnr`** (era 37 linii, acum 92) — parametru `tnSursa`, `Do Case` pentru
|
||||||
|
alegerea link-ului (`cLink`/`cLinkArhiva`/`cLinkAn`), cautare inapoi zi cu zi (max 10 incercari),
|
||||||
|
reincercare pe anul precedent pentru `tnSursa=2`, coduri de retur `0`/`1`/`2`.
|
||||||
|
3. **`PROCEDURE initializeaza`**, ramura `nTip=1` de dupa ora 13 (era 4 linii, acum 15) — cascada
|
||||||
|
`citeste_curs_bnr(1)` -> `citeste_curs_bnr(2)` -> mesaje, link nou `https://www.cursbnr.ro/` in
|
||||||
|
loc de `http://www.bnr.ro` doar pe aceasta ramura.
|
||||||
|
|
||||||
|
`git diff --stat` (COMUN): `1 file changed, 79 insertions(+), 7 deletions(-)`. `git diff` integral
|
||||||
|
verificat — apar EXCLUSIV liniile din patch, nimic in alta zona a fisierului (fara diacritice
|
||||||
|
schimbate, fara CRLF/reindentari colaterale).
|
||||||
|
|
||||||
|
## 3. Cens octeti >= 0x80 (diacritice)
|
||||||
|
|
||||||
|
| | inainte | dupa |
|
||||||
|
|---|---|---|
|
||||||
|
| octeti >= 0x80 (total) | 0 | 0 |
|
||||||
|
| CRLF | 1718 | 1790 |
|
||||||
|
| LF izolate (lf - crlf) | 0 | 0 |
|
||||||
|
|
||||||
|
Secventa de octeti inalti e identica (goala in ambele versiuni — fisierul nu contine diacritice in
|
||||||
|
zonele atinse sau in restul fisierului). Zero LF izolate inainte si dupa — toate liniile noi au
|
||||||
|
fost inserate cu `\r\n`, consistent cu restul fisierului. Cresterea de 1790-1718=72 linii CRLF
|
||||||
|
corespunde exact insertiilor nete (2+2+2 proprietati + 11 linii nete in `initializeaza` + 55 linii
|
||||||
|
nete in `citeste_curs_bnr`).
|
||||||
|
|
||||||
|
## 4. Write-back text -> binar
|
||||||
|
|
||||||
|
Comanda:
|
||||||
|
```
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1 -TextFile D:\ROA\ROAFACTURARE\COMUN\clase\onom_curs.vc2 -ProjectRoot D:\ROA\ROAFACTURARE -AllowComun
|
||||||
|
```
|
||||||
|
Iesire:
|
||||||
|
```
|
||||||
|
O,P1,E0,S1,X0,c:\users\mmari\appdata\local\temp\txt2bin_roafacturare\16352-20260917221154630\comun\clase\onom_curs\verify\onom_curs.vc2
|
||||||
|
|
||||||
|
--- Sumar txt2vcx ---
|
||||||
|
OK D:\ROA\ROAFACTURARE\COMUN\clase\onom_curs.vc2
|
||||||
|
|
||||||
|
Toate cele 1 fisier(e) au fost scrise cu succes.
|
||||||
|
```
|
||||||
|
|
||||||
|
`onom_curs.vcx` si `onom_curs.VCT` au mtime nou (22:11:55, imediat inaintea `.vc2` la 22:11:56 —
|
||||||
|
scriere in aceeasi rulare).
|
||||||
|
|
||||||
|
## 5. Fidelity check pe binar (independent de mtime)
|
||||||
|
|
||||||
|
Reconvertit binarul proaspat scris (`onom_curs.vcx`) intr-un cache TEMPORAR separat, cu
|
||||||
|
`vcx2txt.ps1 -Source ... -CacheRoot <scratchpad>\fidelity_check -Force`, si comparat textul
|
||||||
|
regenerat cu textul sursa:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmp fidelity_check\COMUN\clase\onom_curs.vc2 COMUN\clase\onom_curs.vc2
|
||||||
|
-> FIDELITY OK: byte-identical
|
||||||
|
```
|
||||||
|
|
||||||
|
Rezultat: **identic byte cu byte**. Round-trip confirmat.
|
||||||
|
|
||||||
|
## 6. Stare finala
|
||||||
|
|
||||||
|
`git status --short` in COMUN:
|
||||||
|
```
|
||||||
|
M clase/onom_curs.vc2
|
||||||
|
```
|
||||||
|
Doar `.vc2` e urmarit de git (binarele `.vcx`/`.VCT` nu sunt versionate); niciun alt fisier atins.
|
||||||
|
**Niciun commit, niciun `svn commit`, niciun rebuild de EXE.** Modificarea ramane necomisa, in
|
||||||
|
asteptarea aprobarii/probei manuale a lui Marius (pasii de proba sunt in
|
||||||
|
`docs\patch_curs_bnr_arhiva.md`, sectiunea 6).
|
||||||
Reference in New Issue
Block a user