sync SVN r18202

This commit is contained in:
2026-09-24 23:07:59 +03:00
parent c8aac857a3
commit e13b0e928e
17 changed files with 730 additions and 2 deletions

View File

@@ -0,0 +1,29 @@
# Editarea iterativa a unui raport existent
Doua cai; alege una si tine-o ca **sursa unica de adevar**.
## A. Generator `.prg` reutilizabil (reasezare / raport construit programatic)
Un singur `.prg` cu `COPY FILE <model>.frx TO <nou>.frx` (+ `.frt`), `USE ... EXCLUSIVE`, `ZAP`,
apoi `APPEND BLANK` + `REPLACE` per obiect printr-un singur `FUNCTION addrec` (valori implicite,
suprascrise per tip). Sursa de adevar e `.prg`-ul: il re-rulezi si regenereaza `.frx`/`.frt`.
Potrivit cand layout-ul se schimba mult (latimi, benzi, orientare) sau raportul se construieste de la
zero; fiecare runda porneste de la generatorul rundei precedente (r6: `build_r6.prg` derivat din
`build_final.prg`).
## B. Editarea `.fr2` + `frx_convert`
Pentru modificari mici de text/camp pe un raport existent:
`frx_convert.ps1 -Path r.frx` -> editezi `r.fr2` -> `frx_convert.ps1 -Path r.fr2` regenereaza
`r.frx`+`r.frt`. Editezi doar textul vizat (nu rescrie antetul), altfel risti sa strici formatul.
## Reguli
- **Nu edita ambele**: daca rulezi generatorul peste un `.frx` editat manual din `.fr2`, pierzi
editarea; daca editezi `.fr2` dupa ce ai rulat generatorul, `.frx` nu mai corespunde. Alege o
sursa si lucreaza numai in ea.
- `FoxBin2Prg.EXE` (prin `frx_convert.ps1`) lasa un `r.fr2.bak` cand `r.fr2` exista deja; nu face
parte din livrare - sterge-l.
- Modelul de pornire trebuie sa aiba toate benzile (Title/PageHeader/GroupHeader/Detail/GroupFooter/
PageFooter). `Rapoarte\listmfcota.frx` e valid; `COPY FILE` pastreaza si formatul de raport, pe
cand `COPY STRUCTURE` produce un `.frx` "invalid" (lipseste formatul).

View File

@@ -0,0 +1,43 @@
# Orientare si latimi (portret / landscape)
## Campurile din .frx
In inregistrarea REPORT (`OBJTYPE=1`, `OBJCODE=53`), memo-ul `expr`:
- `ORIENTATION`: 0 = portret, 1 = landscape.
- `PAPERSIZE=9` (A4), `COLOR=2`.
- Referinta portret valid: `Rapoarte\listmf_inv.fr2` (`ORIENTATION=0 PAPERSIZE=9 COLOR=2`, fara
`PAPERLENGTH`/`PAPERWIDTH`). pvcasare_mf portret a pastrat si `PAPERLENGTH=2794 PAPERWIDTH=2159`;
`ORIENTATION` decide oricum orientarea.
## Latimea utila
- Portret A4: ~**74000 FRU** (pvcasare_mf r5 foloseste W raport 74000; `listmf_inv` are 74667, cu
max `HPOS+WIDTH=74166.666`).
- Landscape: ~**80000 FRU** (pvcasare_mf r2-r4).
- La schimbarea orientarii reaseaza TOATE HPOS/WIDTH pe latimea noua (antet, casete, titlu, cap de
tabel, detaliu, GF), apoi verifica `max(HPOS+WIDTH) <= latimea paginii` (r5: max 73600 <= 74000),
cu un mic program care parcurge obiectele si tipareste `HPOS+WIDTH` per obiect.
## Trunchierea de camp: W (FRU) vs FONT, nu valoarea
Un camp trunchiat cu "..." (nu `***`) are W prea mic pentru **fontul** lui, nu pentru valoare (r6):
TOTAL amortizare avea W=9900 ca detaliul, dar font 9 bold fata de font 8 in detaliu, iar
`transform(...)` -> "3..." nu incapea. Latimea in FRU e independenta de font; un font cu un punct mai
mare cere ~12% mai mult W. Remediu: mareste coloana si **aliniaza W-ul TOTAL la W-ul din detaliu**
(r6: W=14800). Verifica pe PNG - ASCII-ul nu trunchiaza la fel.
## Cap de coloana care nu incape
Un LABEL de cap de coloana care nu incape in W-ul coloanei **NU se taie la marginea coloanei**:
textul iese peste linia verticala si peste `RECT`-ul gri, pana la marginea paginii, cu aspect de
"fundal gri dincolo de linie" (r6). Rupe textul manual pe 2-3 randuri (VPOS diferit) care incap in W.
Arial font 8 ~540 FRU/caracter, font 9 ~600 (masurat: "scoaterea din functiune", 23 caractere
~12400 FRU).
## Blocuri care nu incap in subsol
Cand un bloc din Group Footer (ex. COMISIA) nu incape, asaza-l pe mai multe randuri si/sau sub tabel
si **mareste HEIGHT-ul GF** cat sa-l acopere (r5: H=15300 -> r6: H=16800), altfel continutul se taie
pe pagina. `RECT`-ul gri se opreste la marginea tabelului (r6: W=73000, X max 73500), nu dincolo de
linia verticala.

View File

@@ -0,0 +1,43 @@
# Preview PNG (REPORTBEHAVIOR 90)
Fereastra de capturat este **"Report Preview - <raport>.frx [- Page N]"**, copil al shell-ului VFP.
NU "Report Designer" si NU fereastra Command Window. Captura pe fereastra gresita = certifici
designerul/desktop-ul, nu pagina.
## Cum gasesti fereastra
- Preview-ul NU e top-level: `EnumWindows` pe proces intoarce doar shell-ul VFP ("Microsoft Visual
FoxPro"), iar fereastra de pagina e un **copil** al lui. Captura corecta = `EnumChildWindows` pe
shell si filtrare dupa titlul care contine `Report Preview`.
- Fereastra de pagina are titlul `Report Preview - <raport>.frx - Page N`. Bara de unelte e o
fereastra mica (aceeasi radacina de titlu, inaltime mica) si se exclude prin filtrul de dimensiune
(`>200x200`); daca umbreste pagina, muta-o cu `MoveWindow`.
## Runner
`.prg` (precompilat) cu: cursor de test + `SET REPORTBEHAVIOR 90` +
`REPORT FORM <frx> PREVIEW NOWAIT` + `INKEY(<sec>)` + `QUIT`; `ON ERROR` scrie in log si `QUIT`.
Apoi:
```
powershell -File scripts\randare_preview.ps1 -Fxp <ui.FXP> -Png <png> [-TitleMatch <nume>]
```
Scriptul lanseaza vfp9, capteaza fereastra copil de preview si **opreste procesul** la final (nu lasa
vfp9 viu).
## Watchdog
`randare_preview.ps1` este runnerul controlat pentru preview: are nevoie de procesul VIV in timpul
`INKEY`, deci nu poate fi asteptat de watchdog-ul care asteapta iesirea procesului. Toate celelalte
rulari vfp9 (build, randare ASCII, structura) trec prin
`COMUN\utile\Teste\watchdog_vfp.ps1 -Script <prg> -TimeoutSec 120 -AutoDismiss -OutDir <dir>`.
## Capcane de preview
- `_REPORTPREVIEW = "ZOOM 100"` NU e zoom: VFP cauta un fisier `zoom 100.prg` (eroare "File 'zoom
100.prg' does not exist"). Zoom-ul e doar buton owner-drawn in toolbar, nu se seteaza programatic.
- Preview-ul nativ la 100% afiseaza **pagina intreaga** (r6): marginea paginii se vede, restul e
desktop gri. Un cap de coloana care pare taiat e text care iese din coloana, nu pagina (nota
inversa din r5 era inselatoare). Confirma intai `max(HPOS+WIDTH) <= latimea paginii`, apoi citeste
PNG-ul.

View File

@@ -0,0 +1,28 @@
# REPORTBEHAVIOR 80 vs 90 (rapoarte ROA)
Aplicatia randeaza rapoartele cu **REPORTBEHAVIOR 90**: `FoxyPreviewerSimple`
(`COMUN\programe\oexport.prg:1733-1738`) seteaza `gnReportBehaviour` daca e numeric, altfel 90,
apoi lanseaza `REPORT FORM ... PREVIEW` (`oexport.prg:1746-1747`). **Verificarea finala se face la
90**, pentru ca la 90 randeaza aplicatia.
## Diferente verificate (aceeasi sursa .frx, r3)
- **Obiectele din Group Header cu `SUPRPCOL=0/SUPGROUP=0` NU se randeaza la 90** (pe primul grup),
dar se randeaza la 80. Cu `SUPRPCOL=3` + `SUPGROUP=6` (grupul 1), cum le scrie Designer-ul, GH
randeaza normal la 90 (dovedit pe `pvcasare_mf`, 24.09.2026). Continutul per grup poate sta in GH
cu aceste valori, sau in Page Header cu GH `HEIGHT=0`, `PAGEBREAK=.T.`.
- **Obiectele plasate in regiunea separatorului** (VPOS intre `START+HEIGHT` al benzii si `START`-ul
benzii urmatoare, adica in cei 2083.333 FRU) se randeaza la 80, dar NU la 90. La 90 motorul taie
strict la continutul benzii.
- Test izolat: label la VPOS 29500 (in separatorul de dupa Page Header) - apare la 80, dispare la 90.
## Dovada numerica vs dovada de aspect
- **La 90 NU exista cale text curata headless**: `TO FILE ... ASCII` trece prin listener si scrie
BINAR (nu agata, dar nu e text lizibil); `OBJECT TYPE 1` se BLOCHEAZA headless.
- Dovada numerica (randuri, totaluri) se face cu **80** +
`REPORT FORM ... TO FILE <txt> ASCII NOCONSOLE`; dovada de aspect si verificarea finala se face cu
**90** + `PREVIEW` -> PNG (vezi `preview-png.md`).
- `TO FILE ... ASCII` (80) **trunchiaza la 80 de caractere/linie** (r6): capetele din dreapta pot
aparea taiate ("Amortizarea pana l") fara ca raportul sa fie defect - e limita grilei ASCII, nu a
paginii. Nu confunda cu trunchierea de camp ("..."/"***"), care se vede doar pe PNG.

View File

@@ -0,0 +1,49 @@
# Stilul rapoartelor ROA (aprobat de Marius pe `Rapoarte/pvcasare_mf`)
Model de referinta: `Rapoarte/pvcasare_mf.fr2` (portret, grupare per document, tabel cu linii).
Aplica stilul de la inceput; fiecare punct de mai jos a costat o runda de corectura.
## Pagina si fonturi
- Portret (`ORIENTATION=0`, A4) daca incape; tabelul pe ~74000 FRU, toate obiectele cu acelasi X max.
- Firma (`goFirma.Antet`): Arial 10 bold. Titlul: Arial 12 bold, centrat.
- Restul (texte, cap de tabel, detaliu, total, comisie): **Arial Narrow 10** (bold la capete si total).
Subsolul de pagina: Arial Narrow 8.
- Texte fixe fara diacritice, sentence case ("Numar document", nu "NUMAR DOCUMENT").
## Grupare
- Un document (PV, factura etc.) per grup, cu pagina noua: GH cu `EXPR` = cheia documentului
(ex. `ALLTRIM(NVL(nrdoc,'')) + '|' + DTOS(NVL(datadoc,{}))`), `PAGEBREAK=.T.`.
- Continutul per document (numar/data, texte, cap de tabel) sta in **Group Header**, nu in Page
Header; toate obiectele din GH cu `SUPRPCOL=3`, `SUPGROUP=6`. Page Header doar firma + titlu.
- Nr. crt. si totalurile se reseteaza pe grup: variabile de raport (Count / Sum, `RESETTOTAL=6`),
nu precalcul in cursor, nu `recno()` (`totaluri.md`).
## Ordonare
- Ordinea o da cursorul, nu raportul: `INDEX ON <cheia grupului> + <sortarea din grup>` (ex.
`Alltrim(nrdoc) + '|' + Dtos(datadoc) + denumire`). Cheia din INDEX = exact `EXPR`-ul GH, altfel
grupul se rupe in mai multe pagini.
- Campuri din Oracle: NULL si tipuri reale (nrdoc numeric, datadoc datetime) -> `Nvl`, `Iif(Isnull...)`,
`Str()`; testeaza cu tipurile reale, nu doar cu cursori sintetici.
## Tabel
- Cap de tabel pe **un singur rand**, texte scurte ("Amortizare", nu "Amortizarea pana la ..."),
centrate (`@I`), fundal gri (`FILL*=200`, `MODE=0`) doar pana la marginea tabelului, linie sub cap.
- Coloana de text lunga (Denumirea) lata; coloanele numerice cat valoarea maxima cu masca.
Text la stanga, numere la dreapta (`OFFSET=1`), masca `get_mask(14,gnpa)` peste tot (si la total).
- Linii verticale in detaliu: `OBJTYPE=6`, `STRETCH=.T.`, aceleasi `HPOS` ca in capul de tabel.
- Campul de text care se poate lungi (Denumirea): `STRETCH=.T.` (se rupe pe mai multe randuri,
nu se trunchiaza).
- **Linie orizontala sub fiecare rand de detaliu**, pe toata latimea tabelului, la baza benzii
Detail, cu **`FLOAT=.T.`** (`STRETCH=.F.`, `TOP=.F.`) - altfel ramane sus cand randul se lungeste.
- Tabelul se inchide doar cu linia ultimului rand: **fara linie suplimentara la inceputul GF**
(se dubleaza si iese o linie groasa).
- Fara spatiu gol intre capul de tabel si primul rand (detaliul incepe imediat sub linia capului).
## Total si subsol de document (Group Footer)
- Randul "TOTAL" fara chenar, sub tabel, valorile aliniate sub coloanele lor, aceeasi masca.
- Comisia: tabel cu perechi "Nume si prenume | Semnatura" alaturate, pe latimea tabelului, un
singur rand gol pentru semnaturi (nu 3).
## Verificare specifica stilului
- Test cu o denumire lunga (3-4 randuri): linia orizontala coboara sub rand, verticalele se intind.
- Doua documente -> doua pagini, nr. crt. reincepe de la 1, totalurile per document.

View File

@@ -0,0 +1,50 @@
# Totaluri pe grup: variabile de raport (standard ROA)
In rapoartele ROA totalurile se calculeaza **in raport**, cu variabile de raport resetate pe grup -
nu se precalculeaza in cursor. Randurile numerice: `EXPR = transform(camp, get_mask(14, gnpa))`
(masca cu SPATIU, ca rapoartele existente).
## Reteta (dovedita pe `Rapoarte\pvcasare_mf.frx`, 24.09.2026, beh 90, 2 randuri/PV)
Variabila = inregistrare `OBJTYPE=18` in .frx (creata de Marius in IDE, Report > Variables):
| camp | valoare | sens |
|---|---|---|
| `NAME` | `tot_valoare` | numele variabilei |
| `EXPR` | `valoare` | expresia NUMERICA acumulata (camp brut, nu `transform`) |
| `TOTALTYPE` | `2` Sum / `1` Count | Calculate |
| `RESETTOTAL` | `6` | reset la grupul 1 (= nr. grup + 5); `1` = sfarsit raport, `2` = pagina |
| `TAG` | `0` | valoarea initiala |
| `PENRED` | `0` | (asa o scrie IDE-ul) |
Afisarea in Group Footer: camp `OBJTYPE=8` cu `EXPR = transform(tot_valoare, get_mask(14,gnpa))`,
**`TOTALTYPE=0`** (variabila deja acumuleaza).
**Nr. crt. pe grup**: variabila Count (`TOTALTYPE=1`, `RESETTOTAL=6`, `EXPR=id_mf`), afisata cu
`alltrim(str(nrcrt_pv))`. **NU `recno()`**: cursorul se parcurge pe index (`SET ORDER`), deci
`recno()` e ordinea fizica (a dat 5,3 / 4,1 in loc de 1,2 / 1,2) si nici nu se reseteaza pe grup.
## Capcane
- **Nume de variabila = nume de camp din cursor**: campul castiga (rezolvarea VFP a numelor
necalificate), deci raportul afiseaza campul, nu variabila. Scoate campul din cursor sau da-i
variabilei alt nume.
- **Creare programatica**: `APPEND BLANK` + `REPLACE` pe campuri alese de mana a dat **Fatal error
C0000005** la `REPORT FORM` (r4, `verify80b.prg`). Ce merge: copiaza o variabila facuta in IDE
(`LOCATE FOR objtype=18` + `SCATTER MEMO NAME lo` + modifici `name/expr/totaltype` + `APPEND BLANK`
+ `GATHER MEMO NAME lo`) - randat OK. Daca raportul nu are inca o variabila, cere-o facuta in IDE
sau copiaz-o din `COMUN\Rapoarte\factura.frx`.
- `EXPR = transform(camp, ...)` + `TOTALTYPE=2` pe campul de afisare -> `****` (motorul nu suma
string-uri). Suma se face pe variabila numerica, `transform` doar la afisare.
- `PICTURE` e un **sir literal de format**, nu expresie: `PICTURE = "get_mask(14,gnpa)"` tipareste
textul. Nu pune masca in PICTURE.
- Test obligatoriu cu **cel putin 2 randuri pe grup** si randuri inserate fizic in afara ordinii
indexului - un singur rand pe grup nu dovedeste nici suma, nici resetul, nici nr. crt.
## Masca numerica `get_mask`
`get_mask` real (`COMUN\programe\oproceduri_comune.prg:1719`; `proceduri_comune.prg:1090`)
construieste masca cu **SPATIU**: `transform(3000, "99 999 999 999 999.99")` => `"3 000.00"`.
`SET SEPARATOR` nu schimba asta. **Stub-ul din harness trebuie sa intoarca masca reala** (cu spatiu);
un stub cu virgula (runda 2) a facut preview-ul sa arate virgule - adica preview-ul minte. Definiti
`FUNCTION get_mask` identic cu cel real (vezi sablonul din SKILL.md, la Scripturi).