docs: conventii COMUN (cautare vcx/vct, encoding cp1252, goExecutor ALTER TABLE, UX formulare)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 13:29:09 +03:00
parent e6a386e68d
commit 02c54473eb
4 changed files with 334 additions and 0 deletions

139
docs/cautare_vcx_vct.md Normal file
View File

@@ -0,0 +1,139 @@
# Căutare în codul din librăriile/formele VFP (`.vcx`/`.vct`, `.scx`/`.sct`, ...)
Partajat între proiectele ROA* (companion narativ la `PROMPT_cautare_vfp.md` din acest folder,
care e varianta "prompt de copiat" — acest fișier e ghidul de referință complet, cu exemple).
Cum se caută cod-sursă (proceduri, metode, parametri) care **nu** stă în fișiere `.prg`, ci
în interiorul fișierelor binare VFP: librării de clase (`.vcx`+`.vct`), forme (`.scx`+`.sct`),
rapoarte (`.frx`+`.frt`), meniuri (`.mnx`+`.mnt`), baze de date (`.dbc`+`.dct`).
## De ce e nevoie de asta
Un `.vcx` este de fapt o **tabelă DBF**: codul metodelor stă în câmpuri memo în `.vct`. Un
`grep` direct pe `.vct` *găsește* textul, dar îl vede ca fișier binar (octeți `\0` între linii),
fără numere de linie utile și greu de citit. Soluția: convertim binarul în reprezentarea lui
**TEXT** (`.vc2`, `.sc2`, …) — un PRG-style lizibil, cu metode sortate alfabetic — și căutăm
în text cu `grep`/`Grep`.
> **Regula de aur:** textul generat e **doar pentru citire/căutare**, CU O EXCEPȚIE: pentru
> `.vcx`/`.scx` se poate și edita pe text, cu write-back în binar (`txt2vcx.ps1`) — vezi
> `PROMPT_cautare_vfp.md` pasul 3 pentru fluxul complet (ordine obligatorie, encoding,
> fidelity check). Pentru `.frx`/`.mnx`/`.dbc` nu există write-back — modificările se fac tot
> în **IDE-ul VFP**.
## Unelte disponibile (verificate)
| Unealtă | Cale | Rol | Recomandare |
|---|---|---|---|
| **FoxBin2Prg** | `D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE` | Binar VFP ⇄ text bidirecțional (`.vc2/.sc2/.fr2/.mn2/.dc2/.lb2`) | **De folosit.** Complet, fidel, include corpul metodelor, parametri, comentarii |
| vcx2prg | `D:\ROA\UTIL\vcx2prg\x2prg.prg` | Doar `.vcx``.prg` *compilabil* | Alternativă; mai vechi, doar clase, necesită IDE VFP deschis pentru a rula forma |
| prgtovcx | `D:\ROA\UTIL\prgtovcx\prgtovcx.PRG` | Invers: `.prg``.vcx` | **Nu** pentru căutare (e direcția opusă) |
FoxBin2Prg e prima alegere: rulează din linia de comandă fără IDE deschis (EXE compilat), acoperă
toate tipurile de container, și produce text mult mai fidel decât vcx2prg.
> Dacă `FoxBin2Prg.EXE` lipsește, se compilează o singură dată: deschide
> `D:\ROA\UTIL\foxbin2prg\foxbin2prg.pj2` în VFP 9 și *Build > Build Executable*. Lângă EXE trebuie
> să existe `filename_caps.exe` și fișierele `props*.txt` (sunt deja în folder).
## Mod recomandat: scriptul `vcx2txt.ps1`
`D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1` automatizează tot: copiază binarul + memo-ul într-un
**cache** care oglindește structura proiectului (ca să nu murdărească arborele SVN), rulează
FoxBin2Prg, păstrează doar fișierul text. E **incremental** (sare peste ce e deja la zi).
Parametrizează scriptul cu proiectul curent și un cache separat per proiect (nu amesteca
cache-urile mai multor proiecte ROA):
```powershell
# DOAR fișierele proiectului curent (recomandat, rapid) -> cache text propriu
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 `
-Project '<RADACINA_PROIECT>\<proiect>.pjx' -ProjectRoot '<RADACINA_PROIECT>' `
-CacheRoot 'D:\ROA\UTIL\foxbin2prg\_textcache_<nume_proiect>'
# include și rapoartele (.frx) și meniurile (.mnx)
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 `
-Project '<RADACINA_PROIECT>\<proiect>.pjx' -ProjectRoot '<RADACINA_PROIECT>' `
-CacheRoot 'D:\ROA\UTIL\foxbin2prg\_textcache_<nume_proiect>' -Types vcx,scx,frx,mnx
# conversie punctuală a unui fișier sau folder (chiar dacă NU e în proiect, ex. o clasă din COMUN)
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 -Source '<RADACINA_PROIECT>\COMUN\clase\caut.vcx'
# rebuild curat (șterge cache-ul întâi) / forțează reconversia
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 `
-Project '<RADACINA_PROIECT>\<proiect>.pjx' -ProjectRoot '<RADACINA_PROIECT>' `
-CacheRoot 'D:\ROA\UTIL\foxbin2prg\_textcache_<nume_proiect>' -Clean
```
Exemplu concret (ROAAUTO):
```powershell
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 `
-Project D:\ROA\ROAAUTO\roaauto.pjx -ProjectRoot D:\ROA\ROAAUTO `
-CacheRoot D:\ROA\UTIL\foxbin2prg\_textcache_roaauto
```
Rezultatul ajunge în cache-ul indicat, cu aceeași structură de directoare ca proiectul (ex.:
`_textcache_roaauto\Clase\odevize.vc2`, `_textcache_roaauto\comun\clase\_frm_base.vc2`). Apoi:
```bash
# cu unealta Grep (preferat) sau grep:
grep -rn "PROCEDURE do_calcul_tarife" "D:\ROA\UTIL\foxbin2prg\_textcache_<nume_proiect>"
grep -rln "factura_salvare" "D:\ROA\UTIL\foxbin2prg\_textcache_<nume_proiect>" # ce clase ating subiectul
```
Flux tipic pentru o problemă: rulează scriptul cu parametrii de mai sus o dată (populează
cache-ul), apoi caută cu `Grep` în cache. La sesiuni următoare rescrie doar ce s-a schimbat
(incremental). Dacă ai nevoie de o librărie din `COMUN\` care nu e în proiect, convertește-o
punctual cu `-Source`.
## Alternativă manuală (fără script)
FoxBin2Prg scrie textul **lângă sursă**. Ca să nu murdărești folderul de clase al proiectului,
copiază perechea binar+memo într-un folder temporar și convertește acolo:
```powershell
# .vcx -> .vc2 (cType gol = extensia decide direcția BIN2PRG)
Copy-Item D:\ROA\<proiect>\Clase\odevize.vcx, D:\ROA\<proiect>\Clase\odevize.vct D:\tmp\
& 'D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE' 'D:\tmp\odevize.vcx' '' '' '' '1' '0' '1'
# => D:\tmp\odevize.vc2
```
Pozițiile parametrilor folosite: `fișier, cType, cTextName, lGenText, cDontShowErrors=1,
cDebug=0, cDontShowProgress=1`. **Lasă `cType` gol** — extensia (`.vcx`) determină direcția.
(Nu da explicit `"BIN2PRG"` pentru un singur fișier: în acel mod EXE-ul așteaptă un *director*.)
Detalii complete de parametri: `D:\ROA\UTIL\foxbin2prg\docs\FoxBin2Prg_Run.md`.
## Soluție rapidă „murdară" (un singur fișier, fără conversie)
Pentru o verificare rapidă „există metoda X undeva în librărie?", `grep` merge direct pe `.vct`
(o vede ca binar, dar găsește potrivirile):
```bash
grep -c "PROCEDURE" "D:\ROA\<proiect>\Clase\odevize.VCT"
grep -a -o "PROCEDURE [a-zA-Z0-9_]*" "D:\ROA\<proiect>\Clase\odevize.VCT"
```
Bun pentru „da/nu" și nume de metode; pentru citit corpul/contextul, folosește conversia în text.
## Corespondență extensii binar → text
| Container | Binar (+memo) | Text |
|---|---|---|
| Librărie clase | `.vcx` + `.vct` | `.vc2` |
| Formă | `.scx` + `.sct` | `.sc2` |
| Raport | `.frx` + `.frt` | `.fr2` |
| Etichetă | `.lbx` + `.lbt` | `.lb2` |
| Meniu | `.mnx` + `.mnt` | `.mn2` |
| Bază de date | `.dbc` + `.dct` | `.dc2` |
## Note
- Codul aplicației stă mai ales în `Clase\*.vcx`; o mare parte din `.vcx`-uri sunt în
`COMUN\` (framework partajat între toate aplicațiile ROA — vezi `CLAUDE.md` per proiect).
- Cache-ul text e regenerabil și e în afara arborelui SVN/git al proiectului — poate fi șters
oricând.
- Conversia **nu** necesită IDE-ul VFP deschis (folosește EXE-ul compilat).
- Vezi și [conventie_encoding_cp1252.md](conventie_encoding_cp1252.md) — capcană de encoding la
editarea `.sc2`/`.vc2` (nu doar la căutare).

View File

@@ -0,0 +1,82 @@
# Capcană: corupere diacritice la editarea `.sc2`/`.vc2` (encoding cp1252)
Partajat între proiectele ROA* (similar cu `PROMPT_cautare_vfp.md` din acest folder) — capcana
ține de formatul `.sc2`/`.vc2` produs de FoxBin2Prg, nu de un proiect anume.
Descoperit de 2 ori în aceeași sesiune (2026-07-10, în ROAAUTO): odată în
`COMUN/clase/cmd_butoane.vc2` (clasa `cmd_select_adv`), odată în `Clase/ofundal_dev.vc2`
(meniul principal) — de fiecare dată un agent anterior a editat text-cache-ul cu un tool care
nu scrie cp1252 nativ, și a corupt TOATE caracterele cu diacritice din fișier, nu doar linia
pe care voia s-o schimbe.
## De ce se întâmplă
Fișierele `.sc2`/`.vc2` generate de FoxBin2Prg sunt **cp1252 (Windows-1252)**, nu UTF-8
(vezi antetul fiecărui fișier: `CPID="1252"`). Dacă un editor/tool citește fișierul ca UTF-8
(sau alt encoding), fiecare byte >= 0x80 (diacritic) devine invalid ca secvență UTF-8 și e
înlocuit cu caracterul de replacement U+FFFD. Dacă apoi tool-ul **salvează rezultatul tot ca
UTF-8**, acel U+FFFD e scris ca 3 octeți UTF-8 (`0xEF 0xBF 0xBD`) — care, citiți înapoi ca
cp1252 (cum face FoxBin2Prg la write-back), apar ca `�`. Rezultatul: caracterul original e
**ireversibil pierdut** (nu mai există în fișier sub nicio formă) — recuperabil DOAR din altă
sursă (git history, backup).
Codebase-ul ROA folosește un hack vechi de "reciclare" a unor caractere cp1252 needependente
de Ș/ă/ț (care nu au echivalent direct în cp1252) pentru diacritice românești:
| Literă românească | Caracter folosit (cp1252) | Byte |
|---|---|---|
| ă | ã (a-tilde) | `0xE3` |
| Ă | Ã | `0xC3` |
| ț | þ (thorn minuscul) | `0xFE` |
| Ș (majusculă) | ª (ordinal masculin) | `0xAA` |
| ș (minusculă) | º (ordinal feminin) | `0xBA` |
| î, â | î, â (existente nativ în cp1252) | `0xEE`, `0xE2` |
Aceste caractere par "gunoi"/typo la prima vedere într-un editor UTF-8 (`ã`, `þ`, `ª`, `º`) —
**sunt corecte, nu le "corecta" fără să verifici encoding-ul.**
## Cum se verifică dacă un fișier `.sc2`/`.vc2` e corupt
```powershell
$bytes = [System.IO.File]::ReadAllBytes("<path>.vc2")
$text = [System.Text.Encoding]::GetEncoding(1252).GetString($bytes)
$text -split "`r`n" | Select-String ([char]0xFFFD) # cauta caracterul de replacement
```
Dacă apare `�` (sau `$text` conține U+FFFD) în output — fișierul are corupere de encoding.
## Cum se repară
1. **Nu edita orbește peste corupere** — caracterul original e deja pierdut din fișierul
curent.
2. Dacă fișierul e sub control de versiune (git) și corupera s-a întâmplat **într-o sesiune
anterioară necomisă** (deci HEAD-ul git încă are versiunea curată): recuperează originalul
convertind binarul din HEAD în text și comparând:
```bash
git show HEAD:<cale>.vcx > /tmp/head.vcx
git show HEAD:<cale>.vct > /tmp/head.vct # (sau .VCT, dupa caz)
powershell -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 -Source /tmp/head.vcx -CacheRoot /tmp/head_out
```
apoi decodează ambele fișiere (HEAD curat + curent corupt) ca cp1252 și compară linie cu
linie (PowerShell, nu `diff` direct — `diff`/Bash nu decodează cp1252 corect pentru afișare,
deși detectează totuși diferența de octeți).
3. Reconstruiește fișierul text pornind de la versiunea HEAD (curată), și reaplică DOAR
modificările intenționate din sesiunea curentă peste ea (nu invers — nu încerca să "cârpești"
fișierul corupt caracter cu caracter, e mai sigur să repornești de la baza curată).
4. Scrie rezultatul înapoi ca bytes cp1252 (`[System.Text.Encoding]::GetEncoding(1252).GetBytes(...)`),
NU ca string UTF-8/`Out-File` implicit (care scrie UTF-8 sau UTF-16 după caz).
5. Rulează write-back (`txt2vcx.ps1 -Force`) și verifică fidelity check = zero diferențe.
## Cum se evită pe viitor
Când editezi `.sc2`/`.vc2` cu tool-uri text (Read/Edit/Write), verifică întâi dacă fișierul
conține diacritice (`ã`, `þ`, `ª`, `º`, `î`, `â` etc.) — dacă da, **orice rescrie completă a
fișierului trebuie făcută la nivel de octeți cu encoding cp1252 explicit**, nu presupunând
UTF-8 implicit. Editările punctuale (Edit tool, string replace) sunt sigure DOAR dacă tool-ul
păstrează restul fișierului byte-identic (nu rescrie tot fișierul reîncodat).
## Vezi și
`PROMPT_cautare_vfp.md` din acest folder, pasul 3.c — menționează deja riscul ("Headerul
CPID="1252" poate minți...") pe scurt; acest fișier e detalierea completă (tabel de caractere,
pași de recuperare din git).

View File

@@ -0,0 +1,65 @@
# Capcană: `ALTER TABLE` pe cursorul întors de `goExecutor.oExecute()`
Partajat între proiectele ROA* — `goExecutor` (clasa `oExecutor`, definită în
`COMUN/programe/oproceduri_comune.prg`, metoda `oExecute`) e utilitarul standard pentru
interogări SQL pass-through (`SQLExec`) către Oracle, folosit peste tot în codul ROA*.
## Ce nu funcționează
```foxpro
lnSucces = goExecutor.oExecute(lcSql, lcCursor)
...
ALTER TABLE (lcCursor) ADD COLUMN coloananoua L && <- pică
```
Cursorul întors de `oExecute()` (rezultatul unui `SQLExec`) poate refuza comenzi DDL
ulterioare, cu erori în lanț (descoperite empiric, în această ordine, în ROAAUTO
`autopass_cursor_comenzi`/`autopass_cursor_operatii`):
1. `Function is not supported on remote tables.` — pe `ALTER TABLE` direct pe cursorul
original.
2. Chiar și după o încercare de rematerializare (`SELECT * FROM (cursor) INTO CURSOR
(acelasi_nume) READWRITE`) → `Alias name is already in use.` (sursa și ținta nu pot
avea același nume într-un `SELECT...INTO CURSOR`).
3. Cu un alias scratch diferit ca țintă (`SELECT * FROM (cursor) INTO CURSOR
(nume_final_diferit_de_sursa) READWRITE`) → tot `Invalid operation for the cursor.` pe
`ALTER TABLE`, chiar dacă acum cursorul final teoretic ar trebui să fie unul local
obișnuit.
Nu există niciun alt loc în codul ROA* care să facă `ALTER TABLE` imediat după
`goExecutor.oExecute()` — deci acest pattern nu avea niciun precedent testat înainte de
funcțiile AUTOPASS.
## Ce funcționează
**Nu folosi `ALTER TABLE` deloc pe rezultatul unui `oExecute()`.** Include coloanele
suplimentare direct în `SELECT`-ul de rematerializare, ca expresii literale alături de `*`
(VFP suportă `SELECT *, expr AS coloana FROM ...`):
```foxpro
lcCursorRaw = lcCursor + '_raw'
IF USED(lcCursorRaw)
USE IN (lcCursorRaw)
ENDIF
lnSucces = goExecutor.oExecute(lcSql, lcCursorRaw)
IF lnSucces < 0
* tratare eroare, RETURN
ENDIF
SELECT *, .F. AS coloananoua, SPACE(100) AS altacoloana ;
FROM (lcCursorRaw) INTO CURSOR (lcCursor) READWRITE
USE IN (lcCursorRaw)
* de-aici incolo doar REPLACE (date), NICIODATA ALTER TABLE (structura) pe (lcCursor)
```
După acest pas, cursorul (`lcCursor`) e un cursor local obișnuit — `ALTER TABLE` funcționează
normal pe el DUPĂ ACEEA (ex. dintr-o altă funcție care primește cursorul deja gata construit,
nu direct rezultatul unui `oExecute`), doar comanda imediat următoare unui `oExecute()` e
restricționată.
## Vezi și
Exemplu complet aplicat: ROAAUTO, `Programe/oproceduri_autopass.prg`,
`autopass_cursor_comenzi()`/`autopass_cursor_operatii()`.

View File

@@ -0,0 +1,48 @@
# Convenție UX pentru formulare VFP (preferința lui Marius, valabilă pe toate proiectele ROA*)
Partajat între proiectele ROA* (similar cu `PROMPT_cautare_vfp.md`, `prompt_project_insights_docs.md`
din acest folder) — pentru că `COMUN\` e clonat/sincronizat în fiecare aplicație ROA, e locul
corect pentru o convenție de design care nu ține de un singur proiect.
Origine: confirmat de Marius pe `frm_autopass.scx` (ROAAUTO) după o rundă de regrupare vizuală
(2026-07-10) — "imi place designul acesta" pe layout-ul cu rânduri clar separate pe funcție,
butoane grupate la margini, fără elemente lipite unele de altele.
## Ce înseamnă in practică, pe proprietăți VFP
- **Fiecare grup funcțional pe rândul lui** — nu înghesui controale fără legătură directă pe
același `Top`. Ex: rândul de perioadă (date + Actualizează) separat de rândul de
statistici (Declarabile/Transmise/Blocate), separat de panoul de configurare.
- **Spațiere vizibilă între grupuri, strânsă în interiorul unui grup** — butoane din
aceeași acțiune (ex. Validează dry-run → Transmite → Renunță) la 6-10px unele de altele;
gap de 20-30px față de următorul grup funcțional (ex. Selectează tot vs. grupul
Validare/Transmitere vs. Actualizează statusuri).
- **Butoanele secundare/de configurare aliniate la marginea din dreapta a containerului**,
nu lipite imediat după textbox-ul pe care îl controlează — altfel nu se disting vizual ca
acțiune separată de câmpul de input.
- **Acțiunea principală + oprirea ei alăturate** — un buton de tip "Renunță/Oprește" trebuie
poziționat lângă acțiunea pe care o anulează (ex. Transmite + Renunță), nu împrăștiat în
altă parte a rândului.
- **Un singur rând de acțiuni cât se poate** — dacă mai multe grupuri de butoane încap pe
lățimea formei, preferă un singur rând orizontal în loc să adaugi rânduri noi dedesubt;
păstrează formele compacte pe verticală.
- **Caption-uri scurte, sentence case, nu ALL CAPS** — ex. "Transmite" nu "TRANSMITE";
"Dashboard AutoPass" nu "Deschide dashboard AUTOPASS". ALL CAPS/text lung pe un buton
citește ca zgomot, nu ca ierarhie.
- **Culoare = semnal, nu decor** — folosește culoare distinctă doar pentru acțiuni cu
semnificație clară (verde = acțiune pozitivă/transmite, roșu = oprire/pericol, albastru =
navigare/deschidere link extern); restul butoanelor rămân stilul implicit din `_baza.vcx`.
## Vocabular
Funcțional, compact, aerisit, minimalist, grupat, ușor de înțeles de utilizatori, aer aerisit,
modern — asta e reperul calitativ pentru orice formular nou sau refacere de layout, dincolo de
regulile mecanice de mai sus, pe orice aplicație ROA*.
## Vezi și
- `PROMPT_cautare_vfp.md` din acest folder — cum se caută/editează cod în binarele VFP text-cache
(relevant când aplici aceste reguli prin fluxul `.sc2`/`.vc2` + write-back).
- Capcana de encoding cp1252 la editarea `.sc2`/`.vc2` (corupere diacritice) — documentată în
proiectul unde a fost descoperită (ROAAUTO `docs/conventie_encoding_cp1252.md`); relevantă ori
de câte ori editezi text-cache-ul unui formular/librărie pentru a aplica aceste reguli de UX.