sync SVN r18175

This commit is contained in:
2026-09-17 22:44:24 +03:00
parent 04a78f99db
commit 30fee191f3
9 changed files with 1541 additions and 0 deletions

158
docs/handoff_activ.md Normal file
View 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.

View 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

View 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.

View 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.

View 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
View 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
View 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.

View 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!".

View 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).