Compare commits

...

4 Commits

Author SHA1 Message Date
df9698375a Merge origin/main: rulaje.vc2 rezolvat din binarul SVN curent
Conflictul era doar pe 7 valori Column*.Width din frm_rulaje.grdrulaje - doua
regenerari FoxBin2Prg ale aceleiasi forme. Rezolvat prin regenerare din
clase/rulaje.vcx (SVN = sursa de adevar), rezultat identic cu HEAD.
wincrypt.h a venit identic pe ambele parti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqJ7D5ftMbChDyz5mpVkCd
2026-07-24 11:02:29 +03:00
ca465641e8 docs: compactare depanare/testare VFP (dedup + eliminare cazuri specifice)
depanare_testare_vfp.md 226->118 linii, testare-ui-vfp.md 133->90.
- regula SAFETY/dialog modal si -A -T erau enuntate de 3 ori -> o data
- cele 3 capcane "proces agatat" -> un singur algoritm de diagnostic in 3 pasi
- studiul de caz CNRCRT si detaliile ROAGEST (jtva_coloane2, achizitie_import)
  reduse la regula generala + pointer catre scriptul de test
- reguli_lucru pct.4 ramane doar pointer, fara detaliile duplicate de mediu
- sters docs/bash.exe.stackdump (fisier de gunoi, negitat)
Masuratorile si datele concrete pastrate integral.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqJ7D5ftMbChDyz5mpVkCd
2026-07-24 10:59:30 +03:00
22e97a9225 sync SVN: rulaje.vc2 regenerat + wincrypt.h (aduse din svn update)
Nu sunt modificari locale: rulaje.vc2 reflecta binarul actualizat din SVN
(coloane cValoare* + butoane modifica_* pe Page2), iar wincrypt.h e un header
nou versionat in SVN care lipsea din git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqJ7D5ftMbChDyz5mpVkCd
2026-07-24 10:59:30 +03:00
95bb2b5b74 Borderou eFactura: import manual arhiva zip descarcata din SPV
AnafeFacturaServer.ImportZipLocal (programe/anaf_efactura.prg): dezarhiveaza
arhiva aleasa de utilizator, ia xml-ul facturii (ignora semnatura MFinante),
ParseEFactura, decide FACTURA PRIMITA/TRIMISA dupa codul fiscal al firmei
curente, copiaza arhiva in directorul local de raspunsuri si scrie in Oracle
prin acelasi lant ca descarcarea din SPV (cursor temporar canaf_efactura_temp
+ cUpdateFactura + UpdateDb -> pack_anaf.AdaugaRaspunsFactura si
anaf_efactura_detalii). Fara SQL scris de mana si fara duplicarea maparii de
campuri.

clase/anaf_efactura.vcx (clasa anaf_efactura, metoda citesteraspunsuri):
optiunea 8 in meniul butonului "Raspunsuri" - "Import arhiva zip de pe disc...".

Necesar cand mesajul nu mai e in lista de raspunsuri ANAF (expira dupa 60 zile)
si factura primita nu a ajuns in borderou.

Testare (CENTRAL/MARIUSM_AUTO, ianuarie 2026, 12 PASS / 0 FAIL):
utile/Teste/efactura_import/ - garda pe cod fiscal, import complet cu verificarea
campurilor si a liniilor in anaf_efactura_detalii, factura vizibila in grila reala
"Facturi primite in SPV".

utile/Teste/test_init_env_auto.prg: adauga goFirma.codfiscalfro ca in start_firma
(ostartfirma.prg) - proprietatea nu e coloana in v_firme, iar fara ea orice cod
care o citeste nepazit crapa in mediul headless.

docs/depanare_testare_vfp.md: verificarea de sintaxa prin compilare headless
(config.fpw cu SAFETY=OFF, copie in scratchpad) si capcana literalelor string
mai lungi de 255 de caractere (eroare de compilare, nu de runtime).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqJ7D5ftMbChDyz5mpVkCd
2026-07-24 10:43:28 +03:00
9 changed files with 594 additions and 320 deletions

View File

@@ -5228,7 +5228,7 @@ DEFINE CLASS anaf_efactura AS _frmbase OF "_frm_base.vcx"
PROCEDURE citesteraspunsuri
Local llSucces, lnOptiune, lnZile, llFortareRaspunsuri, llTest
lnOptiune = xmenu('Ultima zi;Ultimele 2 zile;Ultimele 3 zile;Ultimele 7 zile;Ultimele 15 zile;Ultimele 30 zile;Ultimele 60 zile')
lnOptiune = xmenu('Ultima zi;Ultimele 2 zile;Ultimele 3 zile;Ultimele 7 zile;Ultimele 15 zile;Ultimele 30 zile;Ultimele 60 zile;Import arhiva zip de pe disc...')
IF EMPTY(m.lnOptiune)
RETURN
ENDIF
@@ -5238,7 +5238,11 @@ DEFINE CLASS anaf_efactura AS _frmbase OF "_frm_base.vcx"
llTest = This.lTest
*!* llSucces = Thisform.oAnaf.RaspunsuriFacturi(m.lnZile, m.llFortareRaspunsuri)
llSucces = efactura_client(m.lnZile, m.llFortareRaspunsuri, m.llTest)
IF m.lnOptiune = 8
llSucces = Thisform.oAnaf.AnafeFacturaServer.ImportZipLocal()
ELSE
llSucces = efactura_client(m.lnZile, m.llFortareRaspunsuri, m.llTest)
ENDIF
Thisform.LockScreen = .T.
save_grid_tag(Thisform.pgfeFactura.page2.grdFacturiPrimite)

View File

@@ -385,18 +385,12 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cTva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoare.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoare.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareTva.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareTva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cPretv.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cPretv.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cTvav.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cTvav.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cPretvtva.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cPretvtva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareV.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareV.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareVTva.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareVTva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cProc_tva.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cProc_tva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cUtil.Header1" UniqueID="" Timestamp="" />
@@ -478,6 +472,12 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cAles._checkbox1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cIdFact.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cIdFact.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareTva.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareTva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareV.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareV.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareVTva.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.grdrulaje.cValoareVTva.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.Shape1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.Text2" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page1.Label1" UniqueID="" Timestamp="" />
@@ -527,6 +527,9 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
*< OBJECTDATA: ObjPath="pgframe.Page2.Label2" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtValoare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtFurnizor" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_sectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_lucrare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_responsabil" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_furnizor" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.Label6" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtSerieAct" UniqueID="" Timestamp="" />
@@ -534,14 +537,11 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
*< OBJECTDATA: ObjPath="pgframe.Page2.Label7" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.Label8" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtReponsabil" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.Label9" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.Label10" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtSectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtLucrare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.Label3" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.txtSectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.Label9" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_responsabil" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_lucrare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page2.but_modifica_sectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.grdbon" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.grdbon.cNrcrt.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.grdbon.cNrcrt.Text1" UniqueID="" Timestamp="" />
@@ -572,8 +572,8 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
*< OBJECTDATA: ObjPath="pgframe.Page3.txtLucrare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.txtSectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.but_modifica_responsabil" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.but_modifica_lucrare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.but_modifica_sectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.but_modifica_lucrare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.txtValoare" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.Label8" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgframe.Page3.Text4" UniqueID="" Timestamp="" />
@@ -960,7 +960,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column19.InputMask = (get_mask(12,gnPPRET)), ;
Column19.Name = "cTva", ;
Column19.ReadOnly = .T., ;
Column19.Width = 75, ;
Column19.Width = 117, ;
Column20.ColumnOrder = 27, ;
Column20.ControlSource = "round((cant+cante)*pret,gnPc)", ;
Column20.FontName = "Arial", ;
@@ -968,7 +968,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column20.InputMask = (get_mask(12,gnPa)), ;
Column20.Name = "cValoare", ;
Column20.ReadOnly = .T., ;
Column20.Width = 93, ;
Column20.Width = 119, ;
Column21.ColumnOrder = 30, ;
Column21.ControlSource = "pretv", ;
Column21.FontName = "Arial", ;
@@ -976,6 +976,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column21.InputMask = (get_mask(14,gnPPretV)), ;
Column21.Name = "cPretv", ;
Column21.ReadOnly = .T., ;
Column21.Width = 101, ;
Column22.ColumnOrder = 31, ;
Column22.ControlSource = "tvav", ;
Column22.FontName = "Arial", ;
@@ -983,7 +984,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column22.InputMask = (get_mask(14,gnPPretV)), ;
Column22.Name = "cTvav", ;
Column22.ReadOnly = .T., ;
Column22.Width = 109, ;
Column22.Width = 113, ;
Column23.ColumnOrder = 32, ;
Column23.ControlSource = "pretv+tvav", ;
Column23.FontName = "Arial", ;
@@ -991,7 +992,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column23.InputMask = (get_mask(14,gnPPretV)), ;
Column23.Name = "cPretvtva", ;
Column23.ReadOnly = .T., ;
Column23.Width = 111, ;
Column23.Width = 123, ;
Column24.ColumnOrder = 35, ;
Column24.ControlSource = "proc_tvav", ;
Column24.FontName = "Arial", ;
@@ -1228,7 +1229,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column64.InputMask = (get_mask(12,gnPa)), ;
Column64.Name = "cValoareTva", ;
Column64.ReadOnly = .T., ;
Column64.Width = 93, ;
Column64.Width = 147, ;
Column65.ColumnOrder = 33, ;
Column65.ControlSource = "round((cant+cante)*pretv,gnPc)", ;
Column65.FontName = "Arial", ;
@@ -1236,7 +1237,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column65.InputMask = (get_mask(12,gnPa)), ;
Column65.Name = "cValoareV", ;
Column65.ReadOnly = .T., ;
Column65.Width = 93, ;
Column65.Width = 108, ;
Column66.ColumnOrder = 34, ;
Column66.ControlSource = "round((cant+cante)*(pretv+tvav),gnPc)", ;
Column66.FontName = "Arial", ;
@@ -1244,7 +1245,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
Column66.InputMask = (get_mask(12,gnPa)), ;
Column66.Name = "cValoareVTva", ;
Column66.ReadOnly = .T., ;
Column66.Width = 100
Column66.Width = 143
*< END OBJECT: ClassLib="_grd_base.vcx" BaseClass="grid" />
ADD OBJECT 'pgframe.Page1.grdrulaje.cAcont.Header1' AS header WITH ;
@@ -2265,7 +2266,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
ADD OBJECT 'pgframe.Page1.grdrulaje.cTva.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "TVA achizitie", ;
Caption = "TVA unitar achizitie", ;
FontSize = 10, ;
Name = "Header1"
*< END OBJECT: BaseClass="header" />
@@ -2283,7 +2284,7 @@ DEFINE CLASS frm_rulaje AS _frmbase OF "_frm_base.vcx"
ADD OBJECT 'pgframe.Page1.grdrulaje.cTvav.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "TVA vanzare", ;
Caption = "TVA unitar vanzare", ;
FontSize = 10, ;
Name = "Header1"
*< END OBJECT: BaseClass="header" />

View File

@@ -1,174 +1,126 @@
# Depanare si testare VFP fara IDE (headless)
Metoda folosita la depanarea erorii "Property CNRCRT is not found" (ROACONT 2.11.61,
15.07.2026) - reutilizabila pentru orice bug runtime VFP. Complementara cu
`flux-editare-vfp-text.md` (editare .vcx/.scx pe text) si `cautare_vcx_vct.md` (cautare).
## 0. Reguli de lucru
Vezi `reguli_lucru.md` (diff ca fisier + aprobare inainte de write-back, comentarii
max o linie, modificari minime).
## Fisiere de testare (foloseste-le la orice depanare/testare viitoare)
- `COMUN\utile\Teste\test_init_env_auto.prg` - initializare completa de mediu FARA
interactiune (parametri: host, schema, parola; implicit CENTRAL / MARIUSM_AUTO).
Echivalentul headless al `COMUN\programe\test_init_env.prg` (care ramane varianta
interactiva, cu INPUTBOX si meniu de lansare formulare).
- `COMUN\utile\Teste\test_repro_cnrcrt_footer.prg` - exemplu complet de harness:
init mediu -> replicare flux (cursoarele din modificanote) -> instantiere forma reala ->
executie pas cu pas cu logare -> verdict OK/BUG. Copiaza-i structura pentru bug-uri noi.
- `COMUN\utile\Teste\test_repro_import_nota.prg` - harness instantiere import_nota
(ROAGEST) fara Oracle: cursori dummy + goExecutor dummy; parametrul LPARAMETERS
incarca o copie de clasa din scratchpad in loc de cea reala (bisectie A/B).
- `COMUN\utile\Teste\teste_roacont.prg` si `COMUN\utile\Teste\test.prg` - teste ad hoc istorice.
- Mediu de test standard: server `CENTRAL`, schema `MARIUSM_AUTO`, parola schemei
`ROMFASTSOFT`; utilizator de aplicatie "MARIUS M" / parola "123" (unde e nevoie de login).
## 1. Citirea log-ului aplicatiei (log.txt in radacina proiectului)
`ErrorHandler` (roacont.prg) scrie pentru fiecare eroare: `nErrror`, `cMethod`,
`line` (0 daca exe-ul e compilat fara debug info) si stack-ul complet.
Semantica ON ERROR (esentiala la interpretare):
- Dupa ce handlerul face RETURN, executia CONTINUA de la linia urmatoare din metoda
care a crapat. O singura cauza produce deci CASCADE de erori.
- Tipar clasic: eroare pe linia `WITH <expr>` => urmatoarele linii `.prop = ...` dau
1940 "Expression is not valid outside of WITH/ENDWITH", apoi `ENDWITH` da
1939 "WITH/ENDWITH mismatch". PRIMA eroare din secventa e cea reala; restul e zgomot.
- Erorile din interiorul TRY/CATCH nu ajung in log (inghitite). Un CATCH gol poate
ascunde ani de zile un defect latent, care "explodeaza" cand codul din jur se repara
(exact cazul typo-ului oMinHeaderHeight din cusfonthandler).
- In stack, liniile "ON..." sunt dispatch-uri (ON ERROR / READ EVENTS), nu metode reale.
## 2. Rulare headless a scripturilor VFP
Sablonul unui script de diagnostic (vezi `utile\Teste\test_repro_cnrcrt_footer.prg`):
- logare in fisier cu STRTOFILE (nu WAIT WINDOW / debugger - nu exista consola);
- `ON ERROR DO <handler> WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()` care logheaza
si continua - mimeaza comportamentul ErrorHandler din aplicatie;
- `QUIT` obligatoriu la final (altfel ramane vfp9.exe agatat);
- lansare din PowerShell cu timeout:
`$p = Start-Process 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe' -ArgumentList '-A','-T','"D:\...\script.prg" PARAM' -PassThru; $p.WaitForExit(120000); if (!$p.HasExited) { $p.Kill() }`
- `-A` ignora config, `-T` fara ecran de start; parametrul dupa .prg ajunge in LPARAMETERS.
- LINENO() din handler e relativ la inceputul metodei (utile pentru localizare in .vc2:
linia din metoda = linia raportata + linia PROCEDURE din .vc2).
Precautii: nu rula cu VFP IDE sau exe-ul aplicatiei deschis (lock pe binare);
sterge .fxp-ul vechi de langa .prg daca ai editat scriptul intre rulari.
## 3. Mediu complet cu Oracle, fara interactiune
`utile\Teste\test_init_env_auto.prg` = varianta neinteractiva a
`programe\test_init_env.prg` (fara INPUTBOX/xmenu): primeste host/schema/parola ca
parametri, seteaza PATH/CLASSLIB/PROCEDURE ca roacont.prg, instantiaza
goConn/goExecutor/goApp/goLog, incarca goFirma/goCalendar/optiuni si pachetele de
sesiune Oracle. Dupa apel poti instantia forme reale (`CREATEOBJECT('frm_xxx', ...)`)
si apela metodele lor punctual, cu cursoarele pregatite ca in fluxul real.
Pentru forme care se deschid modal (Show(1) blocheaza), nu apela Show: replica in
script doar liniile relevante din Show (attach, calcule) si logheaza starea intre ele.
## 4. Bisectia versiunilor de clase (regresii)
Cand un bug apare "de azi", compara comportamentul cu clasa veche fara a atinge SVN:
1. `git show <rev_vechi>:clase/fisier.vcx > temp\fisier.vcx` (+ .vct identic);
2. convertit la text pentru diff: `vcx2txt.ps1 -Source temp\fisier.vcx -CacheRoot temp\txt`;
3. in scriptul de test, `SET CLASSLIB TO temp\fisier.vcx ADDITIVE` (varianta veche)
vs. cea din COMUN (varianta noua) - A/B pe acelasi flux;
4. cand difera rezultatul, cauza e in diff-ul acelei clase (sau intr-un defect latent
pe care noul cod il activeaza - vezi capcanele de mai jos).
Varianta pe backup-uri de runda (fara git): copiaza `*.vc2.pre_rundaN.bak` din cache
intr-un folder temp ca `ointroduceri.vc2`, regenereaza binarul direct cu
`FoxBin2Prg.EXE "<temp>\fisier.vc2" "" "" "" 1 0 1` (pune `..\include\foxpro.h` langa,
altfel .ERR), apoi in harness incarca DOAR copia (nu ADDITIVE peste cea reala -
rezolvarea intre doua classlib-uri cu aceeasi clasa nu e garantata => fals negativ).
Bisectie in interiorul unei runde: neutralizeaza valori IN LOC (ControlSource ->
camp simplu, sterge linii de proprietate din mijlocul blocului) - NU sterge
obiecte/coloane din ADD OBJECT (strica structura: "Cannot add this object to a Grid").
## 5. Capcane VFP descoperite (de verificat la bug-uri asemanatoare)
- `SELECT ... INTO CURSOR` NU mosteneste indecsii cursorului sursa. Orice
`Seek(..., 'cursor', 'tag')` pe copie crapa. `update_jtva_coloane` creeaza
`INDEX ON id_jtva_coloana TAG id_jtva`; daca ii copiezi rezultatul, recreeaza indexul.
- Un grid isi pierde TOATE coloanele (ColumnCount devine 0, fara nicio eroare in log)
daca un ControlSource crapa la re-evaluare in timp ce coloanele sunt atinse
programatic (ex. FontName/FontSize per coloana in cusfonthandler.ApplyFontToGrid,
la Init-ul formei prin _frmbase.SetFont). Simptome tipice in aval:
`_grdfooter.attachtogrid` copiaza 0 coloane, apoi `calctotal` da
"Property <cColWithLabel> is not found" la `Getpem(This, This.cColWithLabel)`.
- ControlSource-uri cu campuri inexistente in RecordSource sunt tolerate la afisare,
dar orice expresie care ARUNCA eroare la evaluare (Seek pe tag lipsa, functie
nedefinita) declanseaza comportamentul de mai sus.
- Fix-ul unui typo intr-o clasa partajata poate activa defecte latente in aval
(codul care nu rulase niciodata incepe sa ruleze). La regresii dupa fix-uri
"inofensive" in COMUN, cauta defectul latent, nu doar diff-ul.
- **Coloana de grid cu ControlSource EXPRESIE (nu camp) cere `Bound = .F.`**.
Fara el: eroare 9 "Data type mismatch" la CREATEOBJECT-ul formei (faza de
constructie, raportata la linia apelanta, Details gol). Caz: import_nota.GridArt
Column7/8/9 cu `round(...)` (ROAGEST 16.07.2026); model corect: Grid1.cTaxCode
(`Bound = .F.` + expresie, functional de ani).
## 6. Capcane la SCRIEREA scriptului de test (nu ale bug-ului investigat)
Descoperite depanand harness-ul pentru "Data type mismatch" la `CREATEOBJECT('IMPORT_nota',...)`
(ROAGEST, 16.07.2026) - de verificat oricand un script de test nou pare "agatat".
- **`DEFINE CLASS ... ENDDEFINE` NU poate sta la mijlocul programului principal.**
Daca il pui intre alte instructiuni de top-level (ex. clasa dummy pentru un
`goExecutor` de test, definita devreme "ca sa fie la indemana"), VFP compileaza
gresit: TOATE liniile de DUPA `ENDDEFINE` dau "Statement is not in a procedure"
(vezi `<script>.ERR` generat la compilare). Simptom in productie: `vfp9.exe`
pare agatat la nesfarsit - CPU ~0, nicio fereastra de dialog vizibila (verificat
si prin enumerare Win32 a ferestrelor procesului), pentru ca de fapt a rulat
doar liniile de dinaintea `DEFINE CLASS` si s-a oprit silentios dupa eroarea de
compilare, fara sa ajunga la `QUIT`. Fix: muta orice `DEFINE CLASS` auxiliar
(dummy executor, helpere) la FINALUL fisierului, dupa `QUIT`, langa celelalte
`PROCEDURE` - e locul standard pentru cod suport intr-un script de test.
- **Diagnostic rapid compilare vs blocaj real**: cand un script "nu se termina",
verifica INTAI daca a aparut `<script>.ERR` langa `.prg` (log de compilare,
vezi si `roagest.ERR`/`log.txt` in radacina proiectului) - inseamna ca s-a
oprit la compilare, nu ca ruleaza/asteapta ceva. Daca nu exista `.ERR`, verifica
CPU-ul procesului (`Get-Process | select CPU`): ~0 dupa cateva secunde inseamna
ca asteapta un dialog modal (foloseste enumerarea Win32 de ferestre de mai jos
ca sa confirmi), nu ca proceseaza ceva lent.
- **Enumerare ferestre Win32 pentru un proces "agatat"** (utila cand nu esti sigur
daca exista un dialog modal ascuns): `Add-Type` cu `EnumWindows`/`GetWindowText`/
`GetWindowThreadProcessId` din `user32.dll`, filtrat pe PID-ul lui `vfp9.exe`.
Daca vezi doar fereastra principala + "Command", nu e dialog - cauta alta cauza
(vezi punctul de mai sus).
- **`STRTOFILE` inainte de `SET SAFETY OFF` = dialog modal "Overwrite?"** daca
fisierul de log exista deja (sub `-A`, SAFETY porneste ON). Pune `SET SAFETY OFF`
PRIMA linie sau sterge log-ul inainte de fiecare rulare.
- **`-A -T` sunt obligatorii impreuna** la lansare headless (`-A` ignora config.fpw,
`-T` sare peste splash screen-ul de start) - fara `-T` procesul ramane agatat pe
ecranul de intampinare, identic ca simptom cu o eroare de compilare (CPU ~0,
fara log). Confirma mereu care e cauza inainte de a schimba scriptul.
- **`goExecutor`/alte dependinte "dummy"**: `CREATEOBJECT('Custom')` + `AddProperty()`
poate adauga doar PROPRIETATI, nu METODE. Daca clasa testata apeleaza
`goExecutor.oExecute(...)`/`oExecuta(...)`, dummy-ul trebuie sa fie o clasa
proprie (`DEFINE CLASS dummyexecutor AS Custom` cu `PROCEDURE oExecute`/
`oExecuta` reale), nu un `Custom` cu proprietati adaugate.
- **Erorile de instantiere (`CREATEOBJECT`) raportate la linia CREATEOBJECT, nu la
`.Init`**: cand `ON ERROR`/`CATCH` arata `Procedure` = scriptul apelant (nu
`NUMECLASA.INIT`), inseamna ca eroarea vine din evaluarea proprietatilor/
obiectelor din DEFINITIA clasei (`ADD OBJECT ... WITH RowSource = ...`,
`ControlSource` de coloana grid evaluat ca expresie, etc.) - controale native
VFP (ex. combobox cu `RowSourceType=6` Fields) isi rezolva bind-ul la
CONSTRUCTIE, inainte sa apuce sa ruleze `PROCEDURE Init` a formei. Cand
reproduci un astfel de bug cu un mediu minimal, o "Alias is not found" pe un
cursor pe care nu l-ai deschis tu confirma mecanismul (faza de instantiere),
chiar daca nu e mesajul EXACT din bug-ul original.
## 7. Studiu de caz: "Property CNRCRT is not found" (15.07.2026)
Lant complet: `frm_import_extrase_banca.modificanote` copia `crsJTVATemp` fara index
(defect latent) -> coloana `cExplicatieTVA` din `frm_modific2024.grdRulaje` face
`Seek(trul.id_jtva_coloana,'crsJtvaTemp','id_jtva')` in ControlSource -> fix-ul
typo-ului `oMinHeaderHeight`->`nMinHeaderHeight` in `cusfonthandler` (r17869) a facut
ca ApplyFontToGrid sa atinga coloanele la Init -> grid-ul a ramas cu 0 coloane ->
footer-ul a crapat in calctotal. Fix: `INDEX ON id_jtva_coloana TAG id_jtva` dupa
SELECT INTO in modificanote. Repro/regresie: `utile\Teste\test_repro_cnrcrt_footer.prg`
(mod FARAINDEX reproduce bug-ul, mod CUINDEX valideaza fix-ul).
# Depanare si testare VFP fara IDE (headless)
Metoda de depanare a bug-urilor runtime VFP, valabila pentru toate proiectele ROA.
Complementara cu `flux-editare-vfp-text.md` (editare .vcx/.scx pe text), `cautare_vcx_vct.md`
(cautare in binare) si `testare-ui-vfp.md` (teste cu formular vizibil + screenshots).
Reguli de livrare: `reguli_lucru.md`.
## 1. Fisiere de mediu si exemple
- `COMUN\utile\Teste\test_init_env_auto.prg` - initializare completa de mediu FARA
interactiune (parametri: host, schema, parola; implicit CENTRAL / MARIUSM_AUTO, parola
schemei `ROMFASTSOFT`; user aplicatie "MARIUS M" / "123"). Seteaza PATH/CLASSLIB/PROCEDURE
ca programul principal si instantiaza goConn/goExecutor/goApp/goLog/goFirma/goCalendar +
pachetele de sesiune Oracle. Dupa apel poti face `CREATEOBJECT('frm_xxx')` si apela metodele
punctual. Varianta interactiva (INPUTBOX + meniu): `COMUN\programe\test_init_env.prg`.
- `COMUN\utile\Teste\test_repro_cnrcrt_footer.prg` - harness model: init mediu -> replicare
flux -> instantiere forma reala -> executie pas cu pas cu logare -> verdict OK/BUG.
- `COMUN\utile\Teste\test_repro_import_nota.prg` - harness fara Oracle (cursori + goExecutor
dummy) cu parametru care incarca o copie de clasa din scratchpad, pentru bisectie A/B.
## 2. Rulare headless
`vfp9.exe -c<config.fpw> -A -T "<script.prg>" <param>`, lansat din PowerShell cu timeout:
```
$p = Start-Process 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe' -ArgumentList '-A','-T','"D:\...\script.prg" PARAM' -PassThru
$p.WaitForExit(120000); if (!$p.HasExited) { $p.Kill() }
```
- `-A` ignora config.fpw implicit, `-T` sare peste splash; **ambele obligatorii** - fara `-T`
procesul ramane pe ecranul de intampinare (simptom identic cu o eroare de compilare).
Un `-c<cale>` explicit (lipit de flag) e preluat chiar si impreuna cu `-A`.
- **SAFETY**: sub `-A` porneste ON, deci primul `STRTOFILE` peste un log existent (sau
`COMPILE` peste un `.err`/`.fxp` existent) scoate dialogul modal "overwrite?" si procesul
pare agatat. Pune `SET SAFETY OFF` ca PRIMA linie din script si/sau `SAFETY=OFF` +
`RESOURCE=OFF` intr-un config.fpw propriu pasat cu `-c`. Verificare: logheaza
`SET("SAFETY")`/`SET("RESOURCE")` la start.
- In script: logare cu STRTOFILE (nu WAIT WINDOW / debugger - nu exista consola);
`ON ERROR DO <handler> WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()` care logheaza si
continua (mimeaza ErrorHandler); `QUIT` obligatoriu la final.
- `LINENO()` din handler e relativ la inceputul metodei: linia in `.vc2` = linia raportata +
linia `PROCEDURE`.
- Nu rula cu IDE-ul sau exe-ul aplicatiei deschis (lock pe binare); sterge `.fxp`-ul vechi
daca ai editat scriptul intre rulari.
- Forme modale: `Show(1)` blocheaza. Replica in script doar liniile relevante din Show, sau
seteaza `WindowType = 0`.
## 3. Citirea log-ului aplicatiei (log.txt din radacina proiectului)
`ErrorHandler` scrie `nErrror`, `cMethod`, `line` (0 daca exe-ul e fara debug info) si stack-ul.
Semantica ON ERROR, esentiala la interpretare:
- Dupa RETURN din handler executia CONTINUA de la linia urmatoare => o singura cauza produce
CASCADE de erori. Tipar clasic: eroare pe `WITH <expr>` => liniile `.prop=` dau 1940
"Expression is not valid outside of WITH/ENDWITH", `ENDWITH` da 1939. **Prima eroare din
secventa e cea reala**, restul e zgomot.
- Erorile din TRY/CATCH nu ajung in log. Un CATCH gol poate ascunde ani un defect latent care
"explodeaza" cand codul din jur se repara.
- Liniile "ON..." din stack sunt dispatch-uri (ON ERROR / READ EVENTS), nu metode reale.
## 4. Verificare de sintaxa prin compilare headless (dupa orice editare de `.prg`)
Cel mai ieftin test dupa o modificare: `COMPILE` headless, fara aplicatie si fara Oracle.
- Compileaza o COPIE a fisierului in scratchpad, nu sursa din working copy (`COMPILE` scrie
`.fxp` langa `.prg`, care ar aparea ca modificare in SVN). VFP nu rezolva simboluri externe
la compilare, deci copia izolata e suficienta pentru sintaxa.
- Sterge `.err`-ul vechi INAINTE de `COMPILE`; verdictul = existenta lui `<nume>.err` dupa
(exista => logheaza continutul; nu exista => compilare curata).
- Nu inlocuieste testul functional: prinde sintaxa, nu apeluri catre metode inexistente.
## 5. Bisectia versiunilor de clase (regresii)
Cand un bug apare "de azi", compara cu clasa veche fara a atinge SVN:
1. `git show <rev>:clase\fisier.vcx > temp\fisier.vcx` (+ `.vct`); pentru diff lizibil,
`vcx2txt.ps1 -Source temp\fisier.vcx -CacheRoot temp\txt`.
2. In harness incarca DOAR copia (`SET CLASSLIB TO temp\fisier.vcx`), **nu ADDITIVE peste cea
reala** - rezolvarea intre doua classlib-uri cu aceeasi clasa nu e garantata => fals negativ.
3. Fara git, din backup-urile de runda: copiaza `*.vc2.pre_rundaN.bak` intr-un temp si
regenereaza binarul cu `FoxBin2Prg.EXE "<temp>\fisier.vc2" "" "" "" 1 0 1` (pune
`..\include\foxpro.h` langa, altfel `.ERR`).
4. Bisectie in interiorul unei runde: neutralizeaza valori IN LOC (ControlSource -> camp
simplu, sterge linii de proprietate) - NU sterge obiecte/coloane din `ADD OBJECT` (strica
structura: "Cannot add this object to a Grid").
5. Cand diferenta apare dupa un fix "inofensiv" in COMUN, cauza poate fi un defect latent
activat de fix (cod care nu rulase niciodata incepe sa ruleze), nu diff-ul in sine.
## 6. Capcane VFP (de verificat la bug-uri asemanatoare)
- **`SELECT ... INTO CURSOR` NU mosteneste indecsii sursei.** Orice `Seek(...,'cursor','tag')`
pe copie crapa; recreeaza indexul dupa copiere. Acelasi lucru pentru cursorii creati intr-un
test: trebuie sa aiba EXACT indecsii pe care ii creeaza fluxul real inainte de instantiere,
altfel ordinea/logica difera si testul da rezultat fals.
- **Un grid isi pierde TOATE coloanele** (`ColumnCount` devine 0, fara eroare in log) daca un
ControlSource crapa la re-evaluare in timp ce coloanele sunt atinse programatic (ex. font per
coloana la Init). Simptome in aval: `attachtogrid` copiaza 0 coloane, apoi `calctotal` da
"Property <cColWithLabel> is not found". Similar, un cursor legat la grid recreat de Init
lasa grid-ul fara coloane (eroare 1925).
- **ControlSource-uri cu campuri inexistente sunt tolerate la afisare**, dar orice expresie
care ARUNCA eroare la evaluare (Seek pe tag lipsa, functie nedefinita) declanseaza cazul
de mai sus. Repro/regresie: `utile\Teste\test_repro_cnrcrt_footer.prg`.
- **Coloana de grid cu ControlSource EXPRESIE (nu camp) cere `Bound = .F.`** Fara el: eroare 9
"Data type mismatch" la `CREATEOBJECT`-ul formei (faza de constructie, raportata la linia
apelanta, Details gol).
- **Erorile de instantiere sunt raportate la linia `CREATEOBJECT`, nu la `.Init`**: daca
`Procedure` = scriptul apelant, eroarea vine din evaluarea definitiei clasei (`ADD OBJECT ...
WITH RowSource`, ControlSource-expresie), pentru ca bind-ul controalelor native se rezolva la
CONSTRUCTIE, inainte de `Init`.
- **Literal string > 255 caractere = eroare de COMPILARE, nu de runtime** (masurat 24.07.2026:
255 trece, 256 pica). Mesaj: `Unrecognized command verb` daca linia incepe cu un apel de
metoda, `Command contains unrecognized phrase/keyword` la o atribuire. Insidios: restul
programului ruleaza, doar linia aceea lipseste din `.fxp` si da eroare 16 cand se ajunge la
ea. De aceea SQL-ul lung se scrie cu `TEXT TO <var> [TEXTMERGE] NOSHOW ... ENDTEXT` sau
concatenat. Verificare: `awk 'length($0) > 260 {print NR": "length($0)}' fisier.prg`.
## 7. Capcane la SCRIEREA scriptului de test
- **`DEFINE CLASS ... ENDDEFINE` nu poate sta la mijlocul programului principal.** Toate liniile
de DUPA `ENDDEFINE` dau "Statement is not in a procedure" (vezi `<script>.ERR`); scriptul
ruleaza doar pana acolo si se opreste silentios, fara sa ajunga la `QUIT` - simptom: vfp9.exe
pare agatat la nesfarsit. Pune orice `DEFINE CLASS` auxiliar la FINALUL fisierului.
- **Dummy-uri cu metode**: `CREATEOBJECT('Custom')` + `AddProperty()` adauga doar PROPRIETATI.
Daca clasa testata apeleaza `goExecutor.oExecute(...)`, dummy-ul trebuie sa fie
`DEFINE CLASS dummyexecutor AS Custom` cu `PROCEDURE oExecute` reala.
- **Diagnostic "script agatat"**, in ordine: (1) exista `<script>.ERR` langa `.prg`? => s-a
oprit la compilare; (2) CPU-ul procesului ~0 dupa cateva secunde (`Get-Process | select CPU`)
=> asteapta un dialog modal, nu proceseaza; confirma enumerand ferestrele procesului cu
`EnumWindows`/`GetWindowText`/`GetWindowThreadProcessId` (`Add-Type`, user32) filtrat pe PID -
daca vezi doar fereastra principala + "Command", nu e dialog, cauta alta cauza (tipic `-T`
lipsa). Confirma mereu cauza inainte de a schimba scriptul.

View File

@@ -11,11 +11,8 @@
Changelog (`changelog_<aplicatie>.txt`): text minimal, pe limba utilizatorului,
nu tehnic - o fraza scurta per intrare `:nou:`/`:modificare:`/`:eroare:`.
3. Modificari minime: doar ce s-a cerut; fara refactorizari sau curatenie din oficiu.
4. Testare headless (fara IDE): `vfp9.exe -A -T "<script.prg>" <param>` din PowerShell,
logare cu STRTOFILE, `ON ERROR` care logheaza si continua, QUIT la final.
Init mediu neinteractiv: `utile\Teste\test_init_env_auto.prg` (CENTRAL / MARIUSM_AUTO;
user aplicatie "MARIUS M"/"123"). Exemplu harness complet: `utile\Teste\test_repro_cnrcrt_footer.prg`.
Metoda de depanare (log.txt, cascade ON ERROR, bisectie clase): `depanare_testare_vfp.md`.
4. Testare headless (fara IDE): `vfp9.exe -A -T "<script.prg>" <param>` din PowerShell.
Mediu, sabloane, capcane si metoda de depanare: `depanare_testare_vfp.md`.
5. Editare .vcx/.scx pe text (cache, cp1252 byte-safe, fidelity-check): `flux-editare-vfp-text.md`.
Cautare in binare: `cautare_vcx_vct.md`.
6. Misiuni lungi (teste, investigatii, sarcini inlantuite): NU un singur subagent cu context

View File

@@ -1,132 +1,95 @@
# Testare UI VFP headless cu harness + screenshots
Harness reutilizabil pentru teste care afiseaza un formular REAL (fara Oracle/UI complet),
executa actiuni si valideaza vizual prin capturi de ecran. Valabil pentru toate proiectele
VFP din suita ROA; exemplele sunt pe ROAGEST. Alternativa la
`testare-vfp-mcp.md` (care ruleaza programul principal intreg + windows-mcp): aici instantiem o
singura clasa cu dependinte dummy, deci pornire rapida si izolata.
Harness reutilizabil pentru teste care afiseaza un formular REAL (cu dependinte dummy, fara
Oracle), executa actiuni si valideaza vizual prin capturi. Valabil pentru toate proiectele VFP
din suita ROA. Alternativa la `testare-vfp-mcp.md` (care porneste programul principal intreg);
capcanele generale de rulare headless (`-A -T`, SAFETY, script agatat) sunt in
`depanare_testare_vfp.md`.
Fisiere in `COMUN\utile\Teste\`: `vfp_ui_harness.ps1` (orchestrator generic, parametrizat),
`_precompile.ps1` (precompilare izolata), `ui_harness.prg` (proceduri VFP de handshake),
`mock_amessagebox.prg` (mock dialog) — infrastructura sta in radacina `Teste\`; suitele de
teste stau grupate pe subiect in subfoldere (ex. `achizitie_import\`). Exemplu complet:
`achizitie_import\test_import_nota_ui.prg` + `.ps1` (wrapper).
`_precompile.ps1` (precompilare izolata), `ui_harness.prg` (proceduri de handshake),
`mock_amessagebox.prg` (mock dialog) — infrastructura sta in radacina `Teste\`; suitele stau in
subfoldere pe subiect. Exemplu complet: `achizitie_import\test_import_nota_ui.prg` + `.ps1`.
**REGULA (Marius, 17/07/2026): testele pe aplicatii frontend exerseaza fluxul REAL al
utilizatorului** — formularul/butonul/metoda pe care o apeleaza aplicatia (ex. "Adauga repere",
do_adauga_factura prin dialogul real), NU introducerea directa a randurilor in cursoare/tabele
(ex. Append in rul_temp/introdc). Popularea directa sare peste validari/filtre/efecte laterale
si da PASS fals (caz real: articol cont 212 "adaugat" direct in rul_temp trecea testul, dar prin
fluxul real nu se adauga deloc). Mock-urile raman permise doar pe INFRASTRUCTURA (Oracle,
utilizatorului** — formularul/butonul/metoda pe care o apeleaza aplicatia, NU introducerea
directa a randurilor in cursoare/tabele. Popularea directa sare peste validari/filtre/efecte
laterale si da PASS fals (caz real: un articol "adaugat" direct in cursor trecea testul, dar
prin fluxul real nu se adauga deloc). Mock-urile raman permise doar pe INFRASTRUCTURA (Oracle,
dialoguri modale), nu pe pasii fluxului testat.
## Test UI nou in 5 pasi
1. `.prg` de test: `PUBLIC gcUILog, gcSyncDir`; `SET TALK OFF` (obligatoriu, vezi cap. c);
`SET PATH/CLASSLIB/PROCEDURE` ca in aplicatie; incarca ULTIMELE
`SET PROCEDURE TO ...\ui_harness.prg ADDITIVE` si (daca ai dialoguri)
`...\mock_amessagebox.prg ADDITIVE`.
2. Creeaza dummy-urile minime (`goExecutor`, `goApp`, etc.) + cursoarele pe care le cere
Init-ul clasei (RecordSource-urile grilelor).
3. Instantiaza clasa, `WindowType=0`, `Show()`, `WindowState=2`.
4. Pe fiecare pas: fa actiunea, verifica efectul in date (loghezi PASS/FAIL cu un helper
local), apoi `DO HarnessStep WITH <n>, '<mesaj>'` (scrie ready_n, asteapta cont_n de la
orchestrator sau auto-continua dupa ~30s). La final `DO HarnessDone WITH 'done'`.
5. Ruleaza: `powershell -File vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0',...)`.
PNG-urile ies in `screenshots\step_<n>_<eticheta>.png`; log in `<test>_log.txt`.
1. `.prg` de test: `PUBLIC gcUILog, gcSyncDir`; `SET SAFETY OFF` + `SET TALK OFF` ca PRIMA
linie (vezi capcana a); `SET PATH/CLASSLIB/PROCEDURE` ca in aplicatie; incarca ULTIMELE
`ui_harness.prg` si (daca ai dialoguri) `mock_amessagebox.prg`, cu `ADDITIVE`.
2. Creeaza dummy-urile minime (`goExecutor` cu metode reale, `goApp` pt. `ReadIni`, `poAct`
pt. curs valutar) + cursoarele cerute de Init (RecordSource-urile grilelor).
3. Instantiaza clasa, `WindowType=0` (modeless - `Show()` pe modal nu returneaza), `Show()`,
`WindowState=2`.
4. Pe fiecare pas: fa actiunea, verifica efectul in date (PASS/FAIL cu helper local), apoi
`DO HarnessStep WITH <n>, '<mesaj>'` (scrie ready_n, asteapta cont_n de la orchestrator sau
auto-continua dupa ~30s). La final `DO HarnessDone WITH 'done'`.
5. `powershell -File vfp_ui_harness.ps1 -TestPrg <test.prg> -Steps @('pas0',...)`.
PNG-uri in `screenshots\step_<n>_<eticheta>.png`; log in `<test>_log.txt`.
## Capcane / deblocari (toate confirmate in sesiune)
a. **Lansarea orchestratorului**: prin tool cu timeout implicit (~120s) e omorat inainte de
finalul pasilor. Ruleaza-l in background (run_in_background) + asteapta `done.txt`, sau
timeout mare (300-600s). NU porni VFP separat de orchestrator - si-l lanseaza singur.
b. **Asteptarea ready_0 prea scurta** omoara VFP exact inainte sa scrie semaforul. Cu `.fxp`
cald START apare in ~2s, la rece mult mai mult (zeci de classlib-uri). Foloseste
`-ReadyTimeoutSec` generos (implicit 180s). Retry NUMAI la "start ratat", nu la "start lent".
c. **`SET SAFETY OFF` + `SET TALK OFF` ca PRIMUL lucru, inainte de primul `STRTOFILE`.**
Cu SAFETY ON, prima scriere a log-ului (daca fisierul exista deja) scoate dialogul modal
"...already exists, overwrite it?" care BLOCHEAZA rularea headless (VFP pare "instanta moarta"
- proces viu, blocat pe dialog). Cu TALK ON, `SUM`/`CALCULATE` (ex. din `recalculeaza`) isi
echo-eaza rezultatele pe desktop, ca text parazit peste formular in screenshots. Harness-ul
are `HarnessInit`/`SET SAFETY OFF` defensiv in procedurile de scriere, dar testul trebuie
sa faca `SET SAFETY OFF` oricum INAINTE de scrierea lui proprie de "START".
d. **`vfp9 -A test.prg` deschide INTERMITENT editorul** in loc sa ruleze (log ramane gol).
Lanseaza `.fxp`-ul PRECOMPILAT (`vfp9 -A test.fxp`) - se executa mereu.
e. **Precompilarea (`COMPILE`+`QUIT`) atarna** dupa COMPILE; rulata in ACEEASI sesiune
powershell care lanseaza apoi testul, otraveste lansarile (nu executa). Ruleaza precompilarea
intr-un proces copil izolat (`Start-Process -Wait _precompile.ps1`).
f. **Detectia "a pornit" pe mtime-ul log-ului > momentul lansarii**, nu pe "log ne-gol": un log
stale tinut deschis de un `vfp9` zombi pacaleste verificarea. Omoara zombii inainte de fiecare
lansare.
g. **Formulare modale** (`WindowType`) - `Show()` nu returneaza si blocheaza testul. Seteaza
`goObj.WindowType = 0` (modeless) pentru afisare non-modala automatizata.
h. **Dummy minimali**: `goExecutor` (oExecute/oExecuta creeaza cursorul cerut), `goApp`
(`ReadIni` pt. font din settings.ini), `poAct`/`oact` (curs valutar). Init-ul recreeaza
cursoarele goale din Oracle daca nu-s pre-populate - pre-populeaza-le INAINTE de instantiere.
i. **`LOCATE FOR camp == 'literal'` pe camp `C(n)` padded cu spatii nu gaseste nimic** (`==` e
exact) - foloseste `ALLTRIM(camp) == 'literal'`.
j. **Messagebox-uri de confirmare blocheaza testul headless**. Mock: `FUNCTION amessagebox`
care returneaza direct valoarea butonului (6=Da). Incarca-l cu `SET PROCEDURE ... ADDITIVE`
**PRIMUL** (inaintea `oproceduri_comune.prg`): la nume duplicat de procedura, VFP foloseste
fisierul cautat PRIMUL in lista (verificat empiric: incarcat ultimul PIERDE, primul CASTIGA).
Mock-ul umbreste `amessagebox` si din apelurile din metodele de clasa (ex. `do_sterge_articol`).
k. **Cursor bound la o grila, recreat de Init -> grila pierde coloanele (eroare 1925).** Ex.:
`import_nota.Init` recreeaza `rul_temp` din Oracle daca e gol la instantiere; GridArt ramane
fara coloanele denumite. Fix: pune 1 rand placeholder in cursor INAINTE de instantiere (ca Init
sa nu-l recreeze), apoi **`ZAP`** dupa `Show()` (NU `DELETE ALL`: acesta lasa un rand fizic
sters si pointerul la EOF, iar cod gen `do_calculeaza_diferente` face `GO recno_EOF` -> eroare 5).
l. **CREATEOBJECT/ADDPROPERTY se comporta gresit apelate DINTR-O PROCEDURA in acest runtime headless**
(obiect returnat ca string de 30 spatii; `ADDPROPERTY` da eroare 11). Din programul PRINCIPAL
merg. Construieste obiectele (ex. `toDlg` pt. `do_adauga_factura`) INLINE in main, nu intr-un
helper. (Cauza exacta neclara; reproductibil.)
m. **`gl406=.T.` (SAFT) activeaza cod care are nevoie de mock-uri suplimentare**, altfel crapa sau
taxcode ramane mereu NULL: `GetTaxCode`/`GetTaxCodeIdPart` (oproceduri_comune.prg) apeleaza
`update_jtva_coloane()` care ruleaza `goExecutor.oExecute("select * from vjtva_coloane ...", <cursor>)`
- mock-ul de `oExecute` trebuie sa intercepteze `'VJTVA_COLOANE' $ UPPER(tcSql)` si sa returneze
un cursor cu structura reala a view-ului (`id_jtva_coloana, denumire, jc, jv, coloana_jc,
coloana_jv, cota_tva, id_tva, taxcode, taxcode_neexigibil, taxcode_n50, taxcode_n50_tvai,
taxcode_n100, taxcode_regularizare, taxcode_tvai, afisat, ordine`) - codul apelant face singur
`INDEX ON id_jtva_coloana TAG id_jtva` dupa, nu trebuie indexat in mock. Cand `id_partc`/`id_partd`
pe rand nu e gol, `GetTaxCodeIdPart` cauta si codul fiscal al partenerului prin
`goExecutor.oSelect2Value(sql, @toValue)` (`GetCodFiscalPartenerById`) - metoda lipseste din
dummyexecutor-ul de baza, trebuie adaugata (returneaza cod fiscal gol -> `VERIFICA_RTVAI` sare
peste apelul Oracle real de validare CIF).
n. **Explicatia TVA de pe randul T ("in tandem" cu baza) NU se scrie prin cod separat** - e pur si
simplu o A DOUA intrare in `jtva_coloane2`/`vjtva_coloane` (coloana `id_tva` de pe randul bazei
punctand spre `id_jtva_coloana` al randului TVA); `aplica_explicatie_tva` (ointroduceri.prg
~9807) citeste acea a doua intrare si o scrie pe rand T. Populeaza mock-ul cu ambele randuri
(baza + TVA), nu doar cu unul si nu concatena manual "TVA " + denumirea bazei in test.
o. **FARA FURT DE FOCUS (17/07/2026)**: `Graphics.CopyFromScreen` (captura ecranului intreg) fura
focus si se corupe daca utilizatorul lucreaza in paralel - NU se mai foloseste. Capturile se fac
cu `PrintWindow` (user32, P/Invoke) pe HANDLE-ul ferestrei vfp9 (`Process.MainWindowHandle`),
flag `2` = `PW_RENDERFULLCONTENT` (fallback flag `0` daca iese goala); merge cu fereastra
acoperita/in fundal, dar NU minimizata. Fereastra e mutata OFF-SCREEN (`SetWindowPos` cu
`HWND_BOTTOM`, x=-4000, `SWP_NOACTIVATE`) imediat ce apare handle-ul si RE-IMPINSA la fiecare
pas (VFP isi reactiveaza singur fereastra la `Show()`/dialoguri modale - o singura impingere la
lansare nu e suficienta). Toate consolele powershell copil (precompilare) sunt `-WindowStyle
Hidden` (sigur pentru consola; NU si pentru GUI VFP, care trebuie doar off-screen, nu ascuns -
PrintWindow pe fereastra minimizata/ascunsa poate reda gol). Implementat in
a. **`SET SAFETY OFF` + `SET TALK OFF` inaintea primului `STRTOFILE`.** Cu SAFETY ON, scrierea
log-ului existent scoate un dialog modal care blocheaza rularea (proces viu, "instanta
moarta"). Cu TALK ON, `SUM`/`CALCULATE` isi echo-eaza rezultatele peste formular in
screenshots.
b. **Lansarea orchestratorului**: prin tool cu timeout implicit (~120s) e omorat inainte de
final. Ruleaza-l in background + asteapta `done.txt`, sau timeout 300-600s. NU porni VFP
separat de orchestrator - si-l lanseaza singur.
c. **Asteptarea `ready_0`**: cu `.fxp` cald START apare in ~2s, la rece mult mai mult (zeci de
classlib-uri). `-ReadyTimeoutSec` generos (implicit 180s); retry NUMAI la "start ratat",
nu la "start lent". Detectia "a pornit" se face pe mtime-ul log-ului > momentul lansarii
(un log stale tinut de un vfp9 zombi pacaleste verificarea "log ne-gol") - omoara zombii
inainte de fiecare lansare.
d. **`vfp9 -A test.prg` deschide INTERMITENT editorul** in loc sa ruleze (log gol). Lanseaza
`.fxp`-ul PRECOMPILAT. Precompilarea (`COMPILE`+`QUIT`) atarna dupa COMPILE si, rulata in
ACEEASI sesiune powershell, otraveste lansarile ulterioare - ruleaza-o intr-un proces copil
izolat (`Start-Process -Wait _precompile.ps1`).
e. **Suitele NU se ruleaza in paralel**: `_precompile.ps1` face `Get-Process vfp9 | Stop-Process
-Force` (omoara toate instantele), deci un test lansat in paralel il ucide pe cel in curs.
f. **FARA FURT DE FOCUS (17/07/2026)**: `Graphics.CopyFromScreen` fura focus si se corupe daca
utilizatorul lucreaza in paralel - nu se mai foloseste. Capturile se fac cu `PrintWindow`
(user32, P/Invoke) pe `Process.MainWindowHandle`, flag `2` = `PW_RENDERFULLCONTENT`
(fallback `0` daca iese goala); merge cu fereastra acoperita, dar NU minimizata. Fereastra
se muta OFF-SCREEN (`SetWindowPos`, `HWND_BOTTOM`, x=-4000, `SWP_NOACTIVATE`) imediat ce
apare handle-ul si SE RE-IMPINGE la fiecare pas (VFP se reactiveaza singur la
`Show()`/dialoguri). Consolele powershell copil sunt `-WindowStyle Hidden` (sigur pentru
consola, NU pentru GUI VFP - ascunsa, `PrintWindow` poate reda gol). Implementat in
`vfp_ui_harness.ps1` (Take-Screenshot, Push-Offscreen, Wait-MainWindowHandle) si
`_precompile.ps1` (acelasi Push-Offscreen pe fereastra tranzitorie de compilare).
p. **Cursorul `introdc` FARA indexul `ord_doc` schimba documentul "principal" la sincronizari
repetate** (sincronizeaza() apelat de mai multe ori, ex. la adaugarea a 2+ articole succesiv
prin fluxul real): identificarea documentului principal (`Go Top` + `Locate For
Inlist(tip_rand,'B','S') And !Empty(doc_key)`) e stabila in productie doar pentru ca
`achizitie_import` (COMUN\programe\ointroduceri.prg:1616-1618) creeaza, INAINTE de
`CREATEOBJECT('import_nota')`, indexul `ord_doc` (`Str(Nvl(nr_doc,0),8) +
Icase(tip_rand='B','1', tip_rand='S','2', tip_rand='T','3', tip_rand='D','4', '5') +
Padr(Nvl(scd,''),10)`) + `SET ORDER TO ord_doc`, care ordoneaza dupa `nr_doc` (S-urile
principalei mostenesc nr_doc-ul ei) - documentul cu `nr_doc` minim ramane mereu acelasi. Un
test care creeaza `introdc` fara acest index ramane pe ordinea FIZICA (recno): dupa ce
principala e sparta (randul B sters, randurile S adaugate la coada), urmatorul apel de
sincronizeaza() gaseste gresit ca "principal" documentul cu cel mai mic recno ramas si il
sparge si pe el - cascada se repeta la fiecare apel ulterior. Fix: dupa popularea `introdc`,
INAINTE de `CREATEOBJECT('import_nota', ...)`, adauga exact acelasi `INDEX ON ... TAG ord_doc`
+ `SET ORDER TO ord_doc` ca in `achizitie_import`.
q. **Suitele de teste NU se ruleaza in paralel**: `_precompile.ps1` face `Get-Process vfp9 |
Stop-Process -Force` (omoara TOATE instantele vfp9 de pe masina), deci un test lansat in
paralel ucide testul aflat in curs si corupe rezultatele. Ruleaza suitele strict secvential.
r. **Fara SELECT-SQL pe cursorul legat de grid**: SELECT ... FROM introdc INTO CURSOR intr-un
test, cat timp introdc e RecordSource-ul unui grid viu, poate omori procesul vfp9 silentios
(fara eroare catchabila, fara semafor). Foloseste xBase nativ (COUNT FOR ... TO,
CALCULATE, LOCATE) care nu comuta zona de lucru a grid-ului.
`_precompile.ps1`.
g. **Mock de messagebox**: `FUNCTION amessagebox` care returneaza direct valoarea butonului
(6=Da), incarcat cu `SET PROCEDURE ... ADDITIVE` **PRIMUL**, inaintea fisierelor aplicatiei:
la nume duplicat de procedura VFP foloseste fisierul cautat PRIMUL (verificat empiric).
Astfel umbreste `amessagebox` si in apelurile din metodele de clasa.
h. **Cursorii ceruti de Init**: pre-populeaza-i INAINTE de instantiere (Init-ul recreeaza din
Oracle cursoarele goale, iar recrearea lasa grid-ul fara coloane, eroare 1925). Pune 1 rand
placeholder inainte, apoi **`ZAP`** dupa `Show()` — NU `DELETE ALL`, care lasa un rand fizic
sters si pointerul la EOF (cod care face `GO <recno>` da eroare 5, vezi
`conventie_go_recno.md`).
i. **Cursorii trebuie sa aiba EXACT indecsii creati de fluxul real** inainte de instantiere
(fluxul real ii creeaza in metoda apelanta, ex. un `INDEX ON ... TAG ord_doc` +
`SET ORDER TO` inainte de `CREATEOBJECT`). Fara ei cursorul ramane pe ordinea FIZICA, iar
logica de tip `GO TOP` + `LOCATE FOR ...` alege alt rand decat in productie - rezultat fals,
uneori in cascada la apeluri repetate. Verifica in codul apelant ce indecsi se creeaza si
replica-i identic.
j. **Mock-urile de date trebuie sa reproduca structura si semantica REALA a sursei** (view
Oracle: toate coloanele; relatii intre randuri: toate randurile implicate, nu unul singur cu
valori "compuse" manual in test). Un mock incomplet lasa campuri mereu NULL sau ascunde
pasul care se testeaza. Cand un flag global activeaza cod suplimentar (ex. `gl406`), acel
cod cere mock-uri in plus - verifica ce apeleaza si adauga metodele lipsa din dummy.
k. **`LOCATE FOR camp == 'literal'` pe camp `C(n)` padded nu gaseste nimic** (`==` e exact) -
foloseste `ALLTRIM(camp) == 'literal'`.
l. **`CREATEOBJECT`/`ADDPROPERTY` se comporta gresit apelate DINTR-O PROCEDURA in acest runtime
headless** (obiect returnat ca string de 30 spatii; `ADDPROPERTY` da eroare 11). Din
programul PRINCIPAL merg. Construieste obiectele-parametru INLINE in main, nu intr-un helper.
(Cauza neclara; reproductibil.)
m. **Fara `SELECT-SQL` pe cursorul legat de grid**: `SELECT ... FROM <cursor> INTO CURSOR` cat
timp cursorul e RecordSource-ul unui grid viu poate omori procesul vfp9 silentios (fara
eroare catchabila, fara semafor). Foloseste xBase nativ (`COUNT FOR ... TO`, `CALCULATE`,
`LOCATE`), care nu comuta zona de lucru a grid-ului.

View File

@@ -1135,6 +1135,109 @@ ENDTEXT
RETURN m.llSucces
ENDPROC && ProcesareRaspunsuri
*********************************
*!* 24.07.2026 marius.mutu - import manual arhiva zip eFactura descarcata din SPV
*********************************
PROCEDURE ImportZipLocal
LPARAMETERS tcZipFile
* tcZipFile: (optional) arhiva zip descarcata din SPV. Daca lipseste, se alege cu Getfile
Local laFiles[1], lcCodFiscalClient, lcCodFiscalFurnizor, lcDir, lcErrorMessage, lcFilePath
Local lcId, lcSemnatura, lcTip, lcXmlFile, lcZipFile, llSucces, lnFile, lnFiles, loFactura
lcZipFile = IIF(TYPE('tcZipFile') = 'C' AND !EMPTY(NVL(m.tcZipFile, '')), ALLTRIM(m.tcZipFile), GETFILE('zip', 'Arhiva zip eFactura', 'Import'))
IF EMPTY(m.lcZipFile) OR !FILE(m.lcZipFile)
RETURN .F.
ENDIF
lcDir = ADDBS(SYS(2023)) + SYS(2015) + '\'
lcErrorMessage = ''
llSucces = This.UnzipFile(m.lcZipFile, @lcDir, @lcErrorMessage)
IF !m.llSucces
AMESSAGEBOX('Eroare la dezarhivarea ' + m.lcZipFile + CHR(13) + CHR(10) + m.lcErrorMessage, 0+48, _SCREEN.Caption)
RETURN .F.
ENDIF
* xml-ul facturii este fisierul xml care nu este semnatura MFinante
lcXmlFile = ''
lnFiles = ADIR(laFiles, ADDBS(m.lcDir) + '*.xml')
FOR lnFile = 1 TO m.lnFiles
IF !('SEMNATURA' $ UPPER(laFiles[m.lnFile, 1]))
lcXmlFile = ADDBS(m.lcDir) + ALLTRIM(laFiles[m.lnFile, 1])
EXIT
ENDIF
ENDFOR
IF EMPTY(m.lcXmlFile)
AMESSAGEBOX('Arhiva nu contine fisierul xml al facturii!', 0+48, _SCREEN.Caption)
RETURN .F.
ENDIF
lcId = ALLTRIM(JUSTSTEM(m.lcXmlFile))
loFactura = ParseEFactura(m.lcXmlFile)
IF TYPE('loFactura') <> 'O' OR loFactura.Eroare
AMESSAGEBOX('Eroare la citirea facturii din ' + m.lcXmlFile, 0+48, _SCREEN.Caption)
RETURN .F.
ENDIF
lcCodFiscalFurnizor = UPPER(ALLTRIM(GetNrFromString(NVL(loFactura.CodFiscalFurnizor, ''))))
lcCodFiscalClient = UPPER(ALLTRIM(GetNrFromString(NVL(loFactura.CodFiscalClient, ''))))
DO CASE
CASE !EMPTY(m.lcCodFiscalClient) AND m.lcCodFiscalClient == ALLTRIM(This.cCodFiscal)
lcTip = 'FACTURA PRIMITA'
CASE !EMPTY(m.lcCodFiscalFurnizor) AND m.lcCodFiscalFurnizor == ALLTRIM(This.cCodFiscal)
lcTip = 'FACTURA TRIMISA'
OTHERWISE
AMESSAGEBOX('Factura nu apartine firmei curente (cod fiscal ' + ALLTRIM(This.cCodFiscal) + ')!' + CHR(13) + CHR(10) + ;
'Emitent: ' + m.lcCodFiscalFurnizor + ' Beneficiar: ' + m.lcCodFiscalClient, 0+48, _SCREEN.Caption)
RETURN .F.
ENDCASE
* Salvez arhiva in directorul local de raspunsuri, ca la descarcarea din SPV
lcFilePath = GetPdfPath('EFACTURA', IIF(m.lcTip == 'FACTURA PRIMITA', 'PRIMITE', 'TRIMISE')) + m.lcId + '_' + m.lcId + '.zip'
IF !FILE(m.lcFilePath)
This.SaveFile(m.lcZipFile, .F., JUSTPATH(m.lcFilePath), JUSTFNAME(m.lcFilePath))
ENDIF
* Cursor temporar cu structura din InitDB, cu factura importata
llSucces = goExecutor.oExecuta("select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat from anaf_efactura a where 1 = 0", "canaf_efactura_temp")
IF !m.llSucces
AMESSAGEBOX('Eroare la citirea structurii anaf_efactura!', 0+48, _SCREEN.Caption)
RETURN .F.
ENDIF
INSERT INTO canaf_efactura_temp (id_incarcare, id_descarcare, data_raspuns, mesaj_raspuns, tip_mesaj_raspuns, ;
cod_fiscal_emitent, cod_fiscal_beneficiar, factura_emisa, test, descarcat, filepath, detalii, modificat) ;
VALUES (m.lcId, m.lcId, DATETIME(), 'Import manual ' + JUSTFNAME(m.lcZipFile), m.lcTip, ;
m.lcCodFiscalFurnizor, m.lcCodFiscalClient, IIF(m.lcTip == 'FACTURA PRIMITA', 0, 1), IIF(This.lTest, 1, 0), ;
0, m.lcFilePath, FILETOSTR(m.lcXmlFile), 1)
TABLEUPDATE(1, .F., 'canaf_efactura_temp')
ADDPROPERTY(loFactura, 'cIdIncarcare', m.lcId)
This.cUpdateFactura(loFactura, 'canaf_efactura_temp')
llSucces = This.UpdateDb('canaf_efactura_temp')
USE IN (SELECT('canaf_efactura_temp'))
lcSemnatura = ADDBS(m.lcDir) + 'semnatura_' + m.lcId + '.xml'
DELETE FILE (m.lcXmlFile)
IF FILE(m.lcSemnatura)
DELETE FILE (m.lcSemnatura)
ENDIF
IF m.llSucces
AMESSAGEBOX('Factura ' + ALLTRIM(loFactura.NumarAct) + ' din ' + DTOC(loFactura.DataAct) + ' a fost importata in borderoul eFactura!', 0+64, _SCREEN.Caption)
ENDIF
RETURN m.llSucces
ENDPROC && ImportZipLocal
*********************************
* RASPUNSURI FACTURI

View File

@@ -0,0 +1,237 @@
* test_import_zip_efactura.prg
* Test UI pentru importul manual al unei arhive zip eFactura in borderou
* (COMUN\clase\anaf_efactura.vcx > clasa anaf_efactura + AnafeFacturaServer.ImportZipLocal).
* Mediu: CENTRAL / MARIUSM_AUTO, perioada ianuarie 2026 (data facturii din xml).
*
* Ce acopera:
* T1 - garda pe cod fiscal: arhiva nu apartine firmei de test => import respins, 0 randuri
* T2 - import complet cu codul fiscal al beneficiarului din xml => anaf_efactura + detalii
* T3 - factura apare in grila "Facturi primite" a borderoului real (captura de ecran)
* GETFILE (alegerea fisierului) NU poate fi automatizat headless - functie nativa VFP,
* nu poate fi umbrita de un mock - de aceea arhiva se transmite ca parametru.
*
* Rulare: powershell -ExecutionPolicy Bypass -File test_import_zip_efactura.ps1
SET SAFETY OFF
SET TALK OFF
PUBLIC gcUILog, gcSyncDir, gnMockRaspuns, gcTestDir, gcZipFile, gcIdInc, gcCifBenef
PUBLIC poFacturiEmise, poFacturiPrimite, poFacturiTrimise, poFacturiDetalii
PUBLIC loAnaf, loForm, gnPass, gnFail
gcTestDir = 'D:\ROA\ROACONT\COMUN\utile\Teste\efactura_import\'
gcUILog = gcTestDir + 'test_import_zip_efactura_log.txt'
gcSyncDir = gcTestDir + 'uisync_importzip\'
gcZipFile = 'D:\ROA\ROACONT\5967557693.zip'
gcIdInc = '5967557693'
gcCifBenef = '29923675'
gnMockRaspuns = 6
gnPass = 0
gnFail = 0
STRTOFILE('START ' + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog)
ON ERROR DO TErr WITH ERROR(), MESSAGE(), PROGRAM(), LINENO()
ON SHUTDOWN QUIT
* Mock-urile se incarca INAINTE de test_init_env_auto: la nume duplicat de procedura
* castiga fisierul incarcat PRIMUL (vezi docs\testare-ui-vfp.md, punctul j)
SET PROCEDURE TO ('D:\ROA\ROACONT\COMUN\utile\Teste\mock_amessagebox.prg') ADDITIVE
SET PROCEDURE TO ('D:\ROA\ROACONT\COMUN\utile\Teste\ui_harness.prg') ADDITIVE
DO ('D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg') WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT', 2026, 1
DO HarnessLog WITH 'INIT gnHandle=' + TRANSFORM(gnHandle) + ' gcS=' + TRANSFORM(gcS) + ' perioada=' + TRANSFORM(gnLuna) + '/' + TRANSFORM(gnAn)
DO HarnessLog WITH 'FIRMA=' + TRANSFORM(gcFirma) + ' cod fiscal=' + TRANSFORM(goFirma.codfiscalfro)
* Curatenie: sterg importul unei rulari anterioare, ca testul sa fie repetabil
goExecutor.oExecuta("delete from anaf_efactura_detalii where id_efactura in (select id from anaf_efactura where id_incarcare = '" + gcIdInc + "')")
goExecutor.oExecuta("delete from anaf_efactura where id_incarcare = '" + gcIdInc + "'")
DO HarnessLog WITH 'CURATENIE randuri anterioare OK'
loAnaf = CREATEOBJECT('ANAFeFactura')
DO HarnessLog WITH 'ANAFeFactura creat; cod fiscal server=' + TRANSFORM(loAnaf.AnafeFacturaServer.cCodFiscal) + ' lTest=' + TRANSFORM(loAnaf.AnafeFacturaServer.lTest)
* ---------- cursoarele borderoului, copie exacta din ANAFeFactura.vizeFactura (anaf_efactura.prg:2867-2942) ----------
* FACTURI PRIMITE
lcData1 = '01' + PADL(INT(m.gnLuna),2,'0') + ALLTRIM(STR(m.gnAn))
Text To lcSchema Noshow
ales N(1), id N(20), id_fact N(20), data_act D, data_scad D, numar_act C(30), xfurnizor C(200), xclient C(200), id_incarcare C(36), id_descarcare c(36) null, tip_mesaj_raspuns C(50) null,mesaj_raspuns C(250) null, data_raspuns D null, trimis N(1) null, data_trimis T null, cod_fiscal_emitent C(30) null, cod_fiscal_beneficiar C(30) null, detalii M null, total_fara_tva N(16,4), total_tva N(16,4), total_tva_ron N(16,4), total_cu_tva N(16,4), discount_fara_tva N(16,4), taxe_fara_tva N(16,4), valoare_fara_tva N(16,4), total_de_plata N(16,4), nume_valuta C(5), test N(1) null, jtotctva N(16,4), descriere M null, detalii_plata M null, diferenta N(16,4), procesat N(1), tip_persoana N(1) null, descarcat N(1) null, filepath c(250) null, procesat_server N(1) null, creditnote N(1)
Endtext
Text To lcSelect Noshow
SELECT 0 as ales, id, id_fact, data_act, data_scad, numar_act, xfurnizor, xclient, id_incarcare, id_descarcare, tip_mesaj_raspuns, mesaj_raspuns, data_raspuns, NVL(trimis,0) as trimis, data_trimis, cod_fiscal_emitent, cod_fiscal_beneficiar, '' as detalii, total_fara_tva, total_tva, total_tva_ron, total_cu_tva, discount_fara_tva, taxe_fara_tva, valoare_fara_tva, total_de_plata, nume_valuta, test, jtotctva, descriere, detalii_plata, decode(creditnote,0,1,-1) * NVL(total_cu_tva, 0.00)-NVL(jtotctva, 0.00) as diferenta, procesat, tip_persoana, descarcat, filepath, procesat_server, creditnote FROM anaf_vefactura_primit
Endtext
lcOrder = [data_act,numar_act,data_raspuns]
lcgroup = []
* Aduc inregistrarile cand se activeaza prima data pagina cu facturi primite
lcFiltru = [1=2] && [(extract(year from data_act) = ?gnAn and extract(month from data_act) = ?gnLuna) or (data_act is null and data_raspuns >= to_date('] + m.lcData1 + [','ddmmyyyy'))]
lcFiltruOriginal = []
llModParam = .T.
llAfiseaza = .F.
gencursor('poFacturiPrimite', 'crsFacturiPrimite', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
poFacturiPrimite.ca_baza1.afisare()
Select crsFacturiPrimite
Go Top
* FACTURI TRIMISE
Text To lcSchema Noshow
ales N(1), id N(20), id_fact N(20), data_act D, data_scad D, numar_act C(30), xfurnizor C(200), xclient C(200), id_incarcare C(36), id_descarcare c(36) null, mesaj_trimis C(250) null, tip_mesaj_raspuns C(50) null,mesaj_raspuns C(250) null, data_raspuns D null, trimis N(1) null, data_trimis T null, cod_fiscal_emitent C(30) null, cod_fiscal_beneficiar C(30) null, detalii M null, total_fara_tva N(16,4), total_tva N(16,4), total_tva_ron N(16,4), total_cu_tva N(16,4), discount_fara_tva N(16,4), taxe_fara_tva N(16,4), valoare_fara_tva N(16,4), total_de_plata N(16,4), nume_valuta C(5), test N(1) null, jtotctva N(16,4), descriere M null, detalii_plata M null, diferenta N(16,4), procesat N(1), tip_persoana N(1) null, descarcat N(1) null, filepath c(250) null, procesat_server N(1) null, creditnote N(1)
Endtext
Text To lcSelect Noshow
SELECT 0 as ales, id, id_fact, data_act, data_scad, numar_act, xfurnizor, xclient, id_incarcare, id_descarcare, mesaj_trimis, tip_mesaj_raspuns, mesaj_raspuns, data_raspuns, NVL(trimis,0) as trimis, data_trimis, cod_fiscal_emitent, cod_fiscal_beneficiar, '' as detalii, total_fara_tva, total_tva, total_tva_ron, total_cu_tva, discount_fara_tva, taxe_fara_tva, valoare_fara_tva, total_de_plata, nume_valuta, test, jtotctva, descriere, detalii_plata, decode(creditnote,0,1,-1) * NVL(total_cu_tva, 0.00)-NVL(jtotctva, 0.00) as diferenta, procesat, tip_persoana, descarcat, filepath, procesat_server, creditnote FROM anaf_vefactura_trimis
Endtext
lcOrder = [data_act,numar_act,data_trimis,data_raspuns]
lcgroup = []
* Aduc inregistrarile cand se activeaza prima data pagina cu facturi trimise
lcFiltru = [1=2] && [(extract(year from data_act) = ?gnAn and extract(month from data_act) = ?gnLuna) or (data_act is null and data_raspuns >= to_date('] + m.lcData1 + [','ddmmyyyy'))]
lcFiltruOriginal = []
llModParam = .T.
llAfiseaza = .F.
gencursor('poFacturiTrimise', 'crsFacturiTrimise', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
poFacturiTrimise.ca_baza1.afisare()
Select crsFacturiTrimise
Go Top
* FACTURI EMISE
Text To lcSchema Noshow
ales N(1), id N(20), id_fact N(20), id_vanzare N(10), data_act D, numar_act N(14), in_valuta N(1) null, curs N(10,4) null, valval N(16,2), tvaval N(16,2),totval N(16,2), total_fara_tva N(16,2), total_tva N(16,2), total_cu_tva N(16,2), nume_valuta C(5) null, client C(100), id_client N(10), cod_fiscal C(30), tip_persoana N(1) null, trimis N(1) null, data_trimis T null, stare C(100) null, id_incarcare C(36) null, mesaj_trimis C(250) null, id_descarcare c(36) null, tip_mesaj_raspuns C(50) null,mesaj_raspuns C(250) null, data_raspuns D null, cod_fiscal_emitent C(30) null, cod_fiscal_beneficiar C(30) null, detalii M null, xfurnizor C(200), xclient C(200), xdata_act D, xnumar_act C(30), xtotal_fara_tva N(16,4), xtotal_tva N(16,4), xtotal_tva_ron N(16,4), xtotal_cu_tva N(16,4), xdiscount_fara_tva N(16,4), xtaxe_fara_tva N(16,4), xvaloare_fara_tva N(16,4), xtotal_de_plata N(16,4), xnume_valuta C(5), test N(1) null, id_util N(10), utilizator C(100), jtotctva N(16,4), diferenta N(16,4), procesat N(1), procesat_server N(1), creditnote N(1)
Endtext
Text To lcSelect Noshow
SELECT 0 as ales, id, id_fact, id_vanzare, data_act, numar_act, in_valuta, curs, valval, tvaval, totval, total_fara_tva, total_tva,total_cu_tva, nume_valuta, client, id_client, cod_fiscal, NVL(tip_persoana,1) as tip_persoana, NVL(trimis,0) as trimis, data_trimis, stare, id_incarcare, mesaj_trimis, id_descarcare, tip_mesaj_raspuns, mesaj_raspuns, data_raspuns, cod_fiscal_emitent, cod_fiscal_beneficiar, '' as detalii, xfurnizor, xclient, xdata_act, xnumar_act, xtotal_fara_tva, xtotal_tva, xtotal_tva_ron, xtotal_cu_tva, xdiscount_fara_tva, xtaxe_fara_tva, xvaloare_fara_tva, xtotal_de_plata, xnume_valuta, test, id_util, utilizator, jtotctva, decode(creditnote,0,1,-1) * ROUND(NVL(xtotal_cu_tva , 0.00) * decode(in_valuta,1,curs,1), 2)-NVL(jtotctva, 0.00) as diferenta, procesat, procesat_server,creditnote FROM anaf_vefactura_emis
Endtext
lcOrder = [data_act,numar_act]
lcgroup = []
lcFiltru = [(extract(year from data_act)=?gnAn and extract(month from data_act)=?gnLuna) OR (trimis = 1 and tip_mesaj_raspuns is null and extract(year from data_act) >=2023)]
lcFiltruOriginal = []
llModParam = .T.
llAfiseaza = .F.
* Afisez toate facturile emise, ca sa le pot vizualiza pe cele netrimise sau trimise cu starea lor (trimis, eroare etc.)
gencursor('poFacturiEmise', 'crsFacturiEmise', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
poFacturiEmise.ca_baza1.afisare()
* DETALII FACTURI PRIMITE/TRIMISE/EMISE
Text To lcSchema Noshow
id N(20), id_efactura N(20), nr N(5), articol V(250), descriere V(250), detalii M, cantitate N(18,6), um V(20), pret N(20,6), proctva N(7,2), valoarefaratva N(20,6), discountfaratva N(20,6)
Endtext
Text To lcSelect Noshow
select id, id_efactura, nr, articol, descriere, detalii, cantitate, um, pret, proctva, valoarefaratva, discountfaratva from anaf_vefactura_detalii
Endtext
lcOrder = [nr]
lcgroup = []
lcFiltru = [1=2] && IIF(!EMPTY(NVL(crsFacturiPrimite.id,0)), [id_efactura=] + ALLTRIM(STR(crsFacturiPrimite.id)), [1=2])
lcFiltruOriginal = []
llModParam = .T.
llAfiseaza = .F.
gencursor('poFacturiDetalii', 'crsDetaliiFacturi', lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcgroup, llModParam, lcFiltruOriginal)
poFacturiDetalii.ca_baza1.afisare()
* ---------- Formularul REAL al borderoului, non-modal (Show(1) ar bloca testul) ----------
Select crsFacturiEmise
loForm = CREATEOBJECT('anaf_efactura', loAnaf)
loForm.WindowType = 0
loForm.Show()
loForm.WindowState = 2
DO HarnessLog WITH 'BORDEROU deschis: [' + TRANSFORM(loForm.Caption) + ']'
DO HarnessStep WITH 0, 'borderou_deschis'
* ---------- T1: garda pe cod fiscal (firma de test nu e nici emitent, nici beneficiar) ----------
llRezultat = loForm.oAnaf.AnafeFacturaServer.ImportZipLocal(gcZipFile)
lnRanduri = 0
goExecutor.oSelecteaza2Value("select count(*) from anaf_efactura where id_incarcare = '" + gcIdInc + "'", @lnRanduri)
DO TVerdict WITH !llRezultat AND NVL(lnRanduri, 0) = 0, 'T1 garda cod fiscal', 'import respins=' + TRANSFORM(!llRezultat) + ' randuri in anaf_efactura=' + TRANSFORM(lnRanduri)
DO HarnessStep WITH 1, 'garda_cod_fiscal'
* ---------- T2: import complet, cu codul fiscal al beneficiarului din xml ----------
loForm.oAnaf.AnafeFacturaServer.cCodFiscal = gcCifBenef
llRezultat = loForm.oAnaf.AnafeFacturaServer.ImportZipLocal(gcZipFile)
DO TVerdict WITH llRezultat, 'T2 import', 'ImportZipLocal a returnat ' + TRANSFORM(llRezultat)
* SQL in TEXT/ENDTEXT: un literal string mai lung de 255 caractere nu compileaza
TEXT TO lcSqlVerif NOSHOW
select id, xnumar_act, xdata_act, xdata_scad, xtotal_fara_tva, xtotal_tva, xtotal_cu_tva, xnume_valuta,
xfurnizor, xclient, tip_mesaj_raspuns, factura_emisa, cod_fiscal_emitent, cod_fiscal_beneficiar,
descarcat, procesat, creditnote, filepath,
nvl(length(detalii),0) as len_detalii, nvl(length(detalii_zip),0) as len_zip
from anaf_efactura
where id_incarcare = ?gcIdInc
ENDTEXT
goExecutor.oExecuta(lcSqlVerif, 'cVerif')
IF USED('cVerif') AND RECCOUNT('cVerif') = 1
SELECT cVerif
GO TOP
DO HarnessLog WITH 'ANAF_EFACTURA id=' + TRANSFORM(cVerif.id) + ' numar=[' + TRANSFORM(cVerif.xnumar_act) + '] data=' + TRANSFORM(cVerif.xdata_act) + ' scadenta=' + TRANSFORM(cVerif.xdata_scad)
DO HarnessLog WITH ' furnizor=[' + TRANSFORM(cVerif.xfurnizor) + '] client=[' + TRANSFORM(cVerif.xclient) + ']'
DO HarnessLog WITH ' fara tva=' + TRANSFORM(cVerif.xtotal_fara_tva) + ' tva=' + TRANSFORM(cVerif.xtotal_tva) + ' cu tva=' + TRANSFORM(cVerif.xtotal_cu_tva) + ' valuta=' + TRANSFORM(cVerif.xnume_valuta)
DO HarnessLog WITH ' tip=[' + TRANSFORM(cVerif.tip_mesaj_raspuns) + '] emisa=' + TRANSFORM(cVerif.factura_emisa) + ' cif emitent=' + TRANSFORM(cVerif.cod_fiscal_emitent) + ' cif benef=' + TRANSFORM(cVerif.cod_fiscal_beneficiar)
DO HarnessLog WITH ' descarcat=' + TRANSFORM(cVerif.descarcat) + ' procesat=' + TRANSFORM(cVerif.procesat) + ' creditnote=' + TRANSFORM(cVerif.creditnote) + ' len(detalii)=' + TRANSFORM(cVerif.len_detalii) + ' len(zip)=' + TRANSFORM(cVerif.len_zip)
DO HarnessLog WITH ' filepath=[' + TRANSFORM(cVerif.filepath) + '] exista pe disc=' + TRANSFORM(FILE(ALLTRIM(NVL(cVerif.filepath, ''))))
DO TVerdict WITH ALLTRIM(NVL(cVerif.xnumar_act, '')) == 'SRC/0000809', 'T2.1 numar factura', TRANSFORM(cVerif.xnumar_act)
DO TVerdict WITH cVerif.xdata_act = {^2026-01-28}, 'T2.2 data factura', TRANSFORM(cVerif.xdata_act)
DO TVerdict WITH ROUND(NVL(cVerif.xtotal_cu_tva, 0), 2) = -5166.63, 'T2.3 total cu tva', TRANSFORM(cVerif.xtotal_cu_tva)
DO TVerdict WITH ROUND(NVL(cVerif.xtotal_fara_tva, 0), 2) = -4269.94, 'T2.4 total fara tva', TRANSFORM(cVerif.xtotal_fara_tva)
DO TVerdict WITH ALLTRIM(UPPER(NVL(cVerif.tip_mesaj_raspuns, ''))) == 'FACTURA PRIMITA' AND NVL(cVerif.factura_emisa, -1) = 0, 'T2.5 tip factura primita', TRANSFORM(cVerif.tip_mesaj_raspuns)
DO TVerdict WITH ALLTRIM(NVL(cVerif.cod_fiscal_emitent, '')) == '17043227' AND ALLTRIM(NVL(cVerif.cod_fiscal_beneficiar, '')) == gcCifBenef, 'T2.6 coduri fiscale', TRANSFORM(cVerif.cod_fiscal_emitent) + '/' + TRANSFORM(cVerif.cod_fiscal_beneficiar)
DO TVerdict WITH NVL(cVerif.len_detalii, 0) > 4000, 'T2.7 xml salvat in detalii', TRANSFORM(cVerif.len_detalii) + ' caractere'
DO TVerdict WITH FILE(ALLTRIM(NVL(cVerif.filepath, ''))), 'T2.8 arhiva copiata local', TRANSFORM(cVerif.filepath)
lnDetalii = 0
goExecutor.oSelecteaza2Value("select count(*) from anaf_efactura_detalii where id_efactura = " + ALLTRIM(STR(cVerif.id)), @lnDetalii)
DO TVerdict WITH NVL(lnDetalii, 0) > 0, 'T2.9 linii in anaf_efactura_detalii', TRANSFORM(lnDetalii) + ' linii'
goExecutor.oExecuta("select nr, articol, cantitate, um, pret, proctva, valoarefaratva from anaf_efactura_detalii where id_efactura = " + ALLTRIM(STR(cVerif.id)) + " order by nr", 'cLinii')
IF USED('cLinii')
SELECT cLinii
SCAN
DO HarnessLog WITH ' linia ' + TRANSFORM(cLinii.nr) + ': [' + TRANSFORM(cLinii.articol) + '] cant=' + TRANSFORM(cLinii.cantitate) + ' ' + TRANSFORM(cLinii.um) + ' pret=' + TRANSFORM(cLinii.pret) + ' tva=' + TRANSFORM(cLinii.proctva) + ' valoare=' + TRANSFORM(cLinii.valoarefaratva)
ENDSCAN
USE IN (SELECT('cLinii'))
ENDIF
ELSE
DO TVerdict WITH .F., 'T2.1-T2.9 verificare in baza de date', 'nu am gasit exact 1 rand in anaf_efactura'
ENDIF
USE IN (SELECT('cVerif'))
DO HarnessStep WITH 2, 'dupa_import'
* ---------- T3: factura in grila reala "Facturi primite" din borderou ----------
* ATENTIE: pagina "Facturi primite in SPV" e obiectul Page2, dar are PageOrder = 3,
* iar ActivePage merge pe PageOrder - deci activarea ei se face cu 3, nu cu 2.
loForm.pgfeFactura.ActivePage = 3
loForm.Refresh()
lnGasit = 0
IF USED('crsFacturiPrimite')
SELECT crsFacturiPrimite
COUNT FOR ALLTRIM(NVL(numar_act, '')) == 'SRC/0000809' TO lnGasit
DO HarnessLog WITH 'crsFacturiPrimite: ' + TRANSFORM(RECCOUNT('crsFacturiPrimite')) + ' randuri, din care factura importata: ' + TRANSFORM(lnGasit)
ENDIF
DO TVerdict WITH lnGasit = 1, 'T3 factura vizibila in grila Facturi primite', TRANSFORM(lnGasit) + ' randuri gasite'
DO HarnessStep WITH 3, 'grila_facturi_primite'
DO HarnessLog WITH 'REZULTAT FINAL: PASS=' + TRANSFORM(gnPass) + ' FAIL=' + TRANSFORM(gnFail)
DO HarnessDone WITH IIF(gnFail = 0, 'done OK', 'done CU ESECURI')
QUIT
***********************
PROCEDURE TVerdict
LPARAMETERS tlOk, tcNume, tcDetalii
IF tlOk
gnPass = gnPass + 1
ELSE
gnFail = gnFail + 1
ENDIF
DO HarnessLog WITH IIF(tlOk, 'PASS ', 'FAIL ') + tcNume + ' | ' + TRANSFORM(tcDetalii)
ENDPROC
***********************
PROCEDURE TErr
LPARAMETERS tnError, tcMessage, tcProgram, tnLine
DO HarnessLog WITH 'EROARE ' + TRANSFORM(tnError) + ' [' + TRANSFORM(tcMessage) + '] in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLine)
ENDPROC

View File

@@ -0,0 +1,12 @@
# test_import_zip_efactura.ps1 - wrapper peste harness-ul generic pentru testul de import
# arhiva zip eFactura in borderou (clasa anaf_efactura + AnafeFacturaServer.ImportZipLocal).
# Rulare: powershell -ExecutionPolicy Bypass -File test_import_zip_efactura.ps1
$ErrorActionPreference = 'Stop'
$Teste = $PSScriptRoot
& (Join-Path (Split-Path $Teste -Parent) 'vfp_ui_harness.ps1') `
-TestPrg (Join-Path $Teste 'test_import_zip_efactura.prg') `
-Steps @('borderou_deschis','garda_cod_fiscal','dupa_import','grila_facturi_primite') `
-ShotsDir (Join-Path $Teste 'screenshots_importzip') `
-SyncDir (Join-Path $Teste 'uisync_importzip') `
-ReadyTimeoutSec 300 `
-StepTimeoutSec 180

View File

@@ -253,11 +253,16 @@ IF m.llSucces
gnIdSucursala = null
gcFirma = ALLTRIM(firma)
SCATTER NAME goFirma
*!* 24.07.2026 marius.mutu - ca in start_firma (ostartfirma.prg): codfiscalfro nu e coloana in v_firme
IF TYPE('goFirma.codfiscalfro') = 'U'
ADDPROPERTY(goFirma, 'codfiscalfro', ALLTRIM(STRTRAN(ALLTRIM(NVL(goFirma.cod_fiscal, '')), 'RO', '')))
ENDIF
ELSE
goFirma = CREATEOBJECT('custom')
goFirma.addproperty('cod_fiscal', '')
goFirma.addproperty('schema', m.gcS)
goFirma.addproperty('firma', m.gcS)
goFirma.addproperty('codfiscalfro', '')
ENDIF
USE IN (SELECT('cFirma'))