From 30fee191f3a6a931ec7bdd4f8b595ec3db0f4c52 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Thu, 17 Sep 2026 22:44:24 +0300 Subject: [PATCH] sync SVN r18175 --- docs/handoff_activ.md | 158 ++++++++++++++ docs/patch_curs_bnr_arhiva.diff | 167 +++++++++++++++ docs/patch_curs_bnr_arhiva.md | 368 ++++++++++++++++++++++++++++++++ docs/plan_qa_factura_aviz.md | 133 ++++++++++++ docs/progres_qa_factura.md | 98 +++++++++ docs/qa_factura_harta.md | 161 ++++++++++++++ docs/qa_factura_unelte.md | 191 +++++++++++++++++ docs/test_curs_bnr_headless.md | 170 +++++++++++++++ docs/writeback_curs_bnr.md | 95 +++++++++ 9 files changed, 1541 insertions(+) create mode 100644 docs/handoff_activ.md create mode 100644 docs/patch_curs_bnr_arhiva.diff create mode 100644 docs/patch_curs_bnr_arhiva.md create mode 100644 docs/plan_qa_factura_aviz.md create mode 100644 docs/progres_qa_factura.md create mode 100644 docs/qa_factura_harta.md create mode 100644 docs/qa_factura_unelte.md create mode 100644 docs/test_curs_bnr_headless.md create mode 100644 docs/writeback_curs_bnr.md diff --git a/docs/handoff_activ.md b/docs/handoff_activ.md new file mode 100644 index 0000000..50c52db --- /dev/null +++ b/docs/handoff_activ.md @@ -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 -- `. 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: `\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. diff --git a/docs/patch_curs_bnr_arhiva.diff b/docs/patch_curs_bnr_arhiva.diff new file mode 100644 index 0000000..1d530cb --- /dev/null +++ b/docs/patch_curs_bnr_arhiva.diff @@ -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" + * + 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 = + + ++ ++ + + + +@@ -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,[],[]) +- 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,[],[]) ++ Endif ++ lcData = Str(Year(ldCandidat),4) + "-" + Padl(Alltrim(Str(Month(ldCandidat))),2,"0") + "-" + Padl(Alltrim(Str(Day(ldCandidat))),2,"0") ++ lcCubeCautat = Strextract(lcFisier,[],[]) ++ 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 = [] + lcCubeCautat + [] ++ Endif + lcFisier= Strtran(Strtran(lcFisier,[">],[" curs="]),[],[" />]) + 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 diff --git a/docs/patch_curs_bnr_arhiva.md b/docs/patch_curs_bnr_arhiva.md new file mode 100644 index 0000000..782d7c1 --- /dev/null +++ b/docs/patch_curs_bnr_arhiva.md @@ -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 ``, mai multe + `` descrescator, pana la 10 zile lucratoare (v2). +- `https://curs.bnr.ro/files/xml/years/nbrfxrates2026.xml` -> HTTP 200, ~245 KB, structura + IDENTICA (un ``, apoi `` de la 2026-01-05 pana azi, ordine + crescatoare). Acelasi cod de extragere a unui `` 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 `*`): +``` + *p: ccursor + *p: clink + *p: ctempfile +``` +**NOU:** +``` + *p: ccursor + *p: clink + *p: clinkan + *p: clinkarhiva + *p: ctempfile +``` + +**VECHI** (`onom_curs.vc2:26-29`, in `*`): +``` + 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`): +``` + + + +``` +**NOU:** +``` + + + + + +``` + +## 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,[],[]) + lcData = Strextract(lcFisier,[date="],["]) + lcFisier= Strtran(lcFisier,[date="]+lcData+["],[]) + lcFisier= Strtran(Strtran(lcFisier,[">],[" curs="]),[],[" />]) + 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,[],[]) + 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,[],[]) + Endif + lcData = Str(Year(ldCandidat),4) + "-" + Padl(Alltrim(Str(Month(ldCandidat))),2,"0") + "-" + Padl(Alltrim(Str(Day(ldCandidat))),2,"0") + lcCubeCautat = Strextract(lcFisier,[],[]) + 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 = [] + lcCubeCautat + [] + Endif + lcFisier= Strtran(Strtran(lcFisier,[">],[" curs="]),[],[" />]) + 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 ``, 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.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. diff --git a/docs/plan_qa_factura_aviz.md b/docs/plan_qa_factura_aviz.md new file mode 100644 index 0000000..cb7d667 --- /dev/null +++ b/docs/plan_qa_factura_aviz.md @@ -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_.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. diff --git a/docs/progres_qa_factura.md b/docs/progres_qa_factura.md new file mode 100644 index 0000000..1062929 --- /dev/null +++ b/docs/progres_qa_factura.md @@ -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 + (``), 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 -- `. **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. diff --git a/docs/qa_factura_harta.md b/docs/qa_factura_harta.md new file mode 100644 index 0000000..c2373c5 --- /dev/null +++ b/docs/qa_factura_harta.md @@ -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`. diff --git a/docs/qa_factura_unelte.md b/docs/qa_factura_unelte.md new file mode 100644 index 0000000..e26259d --- /dev/null +++ b/docs/qa_factura_unelte.md @@ -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_.txt`/`cont_.txt`). +- `-StepTimeoutSec` (implicit 130), `-ReadyTimeoutSec` (implicit 180, pt. `ready_0` = afisarea formularului). +- `-ShotsDir` (implicit `\screenshots`), `-SyncDir` (implicit `\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 ` (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_.txt` (scris de test din `ui_harness.prg`), face screenshot, scrie + `cont_.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 "" `, 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 `_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 ` 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_.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__.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/\` (`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. diff --git a/docs/test_curs_bnr_headless.md b/docs/test_curs_bnr_headless.md new file mode 100644 index 0000000..55e0d84 --- /dev/null +++ b/docs/test_curs_bnr_headless.md @@ -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!". diff --git a/docs/writeback_curs_bnr.md b/docs/writeback_curs_bnr.md new file mode 100644 index 0000000..d0de388 --- /dev/null +++ b/docs/writeback_curs_bnr.md @@ -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 + `*` (dupa `*p: clink`), in `*` (dupa `clink = ...`) si in + `_memberdata` (dupa ``), 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 \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).