Files
roafacturare/docs/cercetare/rec_dec42_proiectare.md
Marius Mutu b5a7108f34 docs: cercetarile comune trec in COMUN, reziduul #6 dispare, progres.md la zi
Curatenie ceruta dupa inchiderea lui #6. Folderul cercetare\ NU se putea sterge
in bloc: plan_13 se sprijina pe el cu 85 de trimiteri, deci e baza de dovezi a
planului aflat in lucru. Impartirea:

- 14 cercetari trans-proiect trec in COMUN\docs\cercetare\ - valuta si curs,
  TVA/VANZARI, consumatorii VANZARI din toata suita, integrarile #10/#11/#12,
  watchdog VFP, proiectarea Oracle a lui S5, view-ul VVANZARI_ARTICOLE. Nu sunt
  ale ROAFACTURARE, iar #10/#11/#12 se reiau chiar din ele.
- 20 de rapoarte de executie ale lui #6, nereferite de nimic viu, sterse.
- 91 raman, neatinse.

Trimiterile catre handoff-urile si diff-urile intermediare deja desfiintate au
fost curatate peste tot (39 de fisiere): 50 catre handoff-uri, 37 catre diff-uri
aplicate, plus caile celor mutate in COMUN. Zero trimiteri rupte ramase.

progres.md preia rolul de predare: ce ramane din #6 (cele sase documente
parazite, cele doua esecuri reale din S8 pe factura din aviz), cifrele de test
citite din log, si cele doua capcane de mediu platite - .FXP vechi executat in
locul .prg-ului, si GETFONT() care atarna un formular instantiat fara goApp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:31:42 +03:00

19 KiB

Proiectare — Decizia 42: articole factura vizibile, needitabile dupa trimiterea in eFactura

Scris 10.08.2026. Agent read-only, doar cercetare/proiectare — nicio modificare de cod facuta de acest agent.

ATENTIE — implementarea era deja IN LUCRU, de catre alt agent; bugul de mai jos e DEJA CORECTAT

Inainte de a ajunge la proiectare: la momentul cercetarii, COMUN\clase\omodificari.vc2 avea deja modificari necomise in working tree (git status in COMUN: M clase/omodificari.vc2) care implementeaza aproape exact Decizia 42 corectata (vizibil, doar needitabil). Agentul d42-efactura, activ in aceeasi sesiune, lucra chiar atunci pe exact acest subiect.

Acest raport nu descrie deci o proiectare de la zero, ci: (1) verificarea contractelor si (2) un review al diff-ului in lucru, cu un bug real gasit si dovedit pe schema Oracle — vezi §1 si §8.

UPDATE, dupa trimiterea raportului: d42-efactura a gasit acelasi bug independent, in timpul propriei lucrari (nu din briefingul primit de la team-lead), l-a corectat si scris in binar inainte sa primeasca mesajul meu. Verificat direct pe disc de acest agent (nu doar preluat din raportarea lui d42-efactura): git diff pe COMUN\clase\omodificari.vc2 arata This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0)), iar git diff pe COMUN\programe\ofacturare_editare.prg arata v.id_fact adaugat in SELECT-ul din IncarcaVanzareNota si id_fact I NULL adaugat in schema CREATE CURSOR tvanz din CreeazaCursorTvanzGol — exact varianta B recomandata la §8. Bugul e inchis, nu mai necesita actiune.

Legat de linia necomisa gasita atunci in ROAGEST\Programe\roagest.prg (SET PROCEDURE TO ofacturare_editare.prg ADDITIVE, "Not Committed Yet" la 10.08.2026 11:28): d42-efactura confirma ca nu e a lui — a atins doar omodificari.vc2 si ofacturare_editare.prg (plus fisiere de test noi). Ramane deci dintr-un alt bloc de lucru (posibil S9), de verificat separat de team-lead — nu descrie starea lui d42-efactura.


1. Cum se afla ca documentul e in eFactura

Functie: FUNCTION EsteInEFactura, globala (nu metoda de clasa), definita in COMUN\programe\ofacturare_editare.prg:16-25:

*!* parametru: id_fact
*!* verifica daca factura cu id_fact dat a fost deja trimisa in eFactura (anaf_efactura)
FUNCTION EsteInEFactura
	LPARAMETERS tnIdFact
	LOCAL lcSql, lnEFactura, llSucces
	lnEFactura = 0
	lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + Alltrim(Str(Nvl(m.tnIdFact,0)))
	llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @lnEFactura)
	RETURN (Nvl(m.lnEFactura,0) > 0)
ENDFUNC

Contract, deschis din RETURN: primeste tnIdFact — ID_FACT, nu ID_VANZARE — si intoarce .T./.F. (numar de randuri in ANAF_EFACTURA cu acel id_fact > 0). Foloseste goExecutor (disponibil global, aceeasi conventie ca restul aplicatiei).

Disponibilitate cross-project — VERIFICAT, nu presupus: ofacturare_editare.prg e inregistrat prin SET PROCEDURE ... ADDITIVE in toate cele trei aplicatii:

App Fisier Linie Stare git
ROAFACTURARE Programe\roafacturare.prg 214 comis demult
ROACONT Programe\roacont.prg 212 comis, 3af0089 (08.08.2026) — mesaj: "Fara el, pagina de articole ale facturii nu apare in registrul jurnal > modificare"
ROAGEST Programe\roagest.prg 260 necomis, adaugat azi 10.08.2026 (vezi avertismentul de mai sus)

Deci EsteInEFactura este apelabila din contextul omodificari.vc2, inclusiv din ROACONT si (dupa commit-ul in curs) ROAGEST. Comentariul din omodificari.vc2:14786-14787 ("apare doar cand ofacturare_editare.prg e incarcat (doar ROAFACTURARE - ROACONT/ROAGEST nu-l inregistreaza)") e invechit — scris inainte de commit-ul 3af0089, care a inversat exact aceasta premisa pentru ROACONT. Nu se corecteaza comentariul in acest raport (read-only), dar oricine implementeaza ar trebui sa-l actualizeze cand atinge zona.

BUG GASIT in diff-ul in lucru — id_fact confundat cu id_vanzare. Apelul din omodificari.vc2:14798 (diff necomis) e:

This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)

dar This.nIdVanzare e populat cu tvanz.id_vanzare (linia 14796), adica VANZARI.ID_VANZARE — alt camp decat VANZARI.ID_FACT, pe care EsteInEFactura il asteapta. Dovada, in trei straturi:

  1. Cod: precedentul functional existent, ofacturare_comun.vc2:3739 (lnIdFact = id_fact) si :3764 (EsteInEFactura(lnIdFact)), citeste id_fact dintr-un camp separat de id_vanzare (:3734, lnIdVanzare = id_vanzare, aceeasi cursor crsfacturi, doua LOCAL-uri distincte). Acelasi tipar in anaf_efactura.prg:3123-3124: pnIdFact = id_fact si lnIdVanzare = id_vanzare citite pe acelasi rand din crsFacturiEmise — daca ar fi acelasi numar, codul n-ar avea nevoie de doua variabile.
  2. Schema Oracle (verificat read-only, user_tab_columns, MARIUSM_AUTO): VANZARI are ambele coloane, ID_FACT si ID_VANZARE, distincte; ANAF_EFACTURA are doar ID_FACT.
  3. Date reale (verificat read-only, join anaf_efactura.id_fact = vanzari.id_fact): pentru documentele deja trimise in eFactura, cele doua valori difera constant —
    id_fact id_vanzare cod numar_act data_act
    8008013 1013 1140509 510 31-AUG-25
    8007922 1007 1140439 503 03-JAN-25
    8007836 993 1140380 490 15-AUG-24
    8007810 991 1140369 488 11-JUL-24

Cu bug-ul curent, EsteInEFactura(1013) cauta id_fact = 1013 in ANAF_EFACTURA — care nu exista cu acel numar — deci intoarce mereu .F., chiar si pentru documente reale trimise in eFactura. Garda ar fi complet inoperanta pe date reale. Corectia: §8.

Niciun document dintre cele patru de mai sus nu e din luna curenta (limitare de date deja cunoscuta din bloc — garda de editare cere luna curenta), deci un test headless pe un caz real din productie de date nu se poate face acum; testul trebuie sa foloseasca un cursor tact/tvanz mock cu id_fact setat manual (acelasi tipar folosit deja pentru id_vanzare_set in S5).

2. Unde se aseaza conditia in omodificari.vc2

Locul folosit deja de diff-ul in lucru e corect si e cel recomandat: frm_modific2024.Show() (omodificari.vc2:14748-14837 in varianta comisa 1c42ae0; extins la 14748-14856 in diff-ul necomis), imediat dupa blocul care rezolva This.nIdVanzare/This.nTipVanzare prin IncarcaVanzareDinNota('tact') si inainte de This.pgfArticole.PageCount = 3. E punctul unde formularul stie deja daca documentul curent are rand in VANZARI (This.lAreArticoleVanzari) — conditia eFactura se agata direct pe acest rezultat, fara sa duplice logica de potrivire cod/nract/serie_act/dataact -> id_vanzare.

Proprietatea noua, lArticoleReadOnly (declarata la nivelul clasei, langa lAreArticoleVanzari, omodificari.vc2:6827 si 6867 in diff), e citita apoi din .When-urile coloanelor editabile ale gridului grdArticoleFactura (mecanismul de editabilitate per rand din S5, §3-4) — deci conditia de formular compune cu mecanismul existent, nu-l inlocuieste.

3. Controale care se dezactiveaza — confirmate pe cod

Control Path Linii (comis) Ce face diff-ul
Buton adaugare pgfArticole.PAGE3.cmdAdaugaArticol Click: 16488-16529 .Enabled = !This.lArticoleReadOnly la afisare (Show); plus guard OR Thisform.lArticoleReadOnly in Click (aparare in adancime, cazul Enabled sarit programatic)
Buton stergere pgfArticole.PAGE3.cmdStergeArticol Click: 16531-16540 idem
Cantitate grdArticoleFactura.cCantitateArt.Text1.When 16549-16554 adauga Thisform.lArticoleReadOnly OR in fata conditiei existente Nvl(tvd.id_vanzare_set,0)<>0
Pret achizitie grdArticoleFactura.cPretAchizitieArt.Text1.When 16556-16561 idem
Pret grdArticoleFactura.cPretArt.Text1.When 16570-16575 idem
Pret cu TVA (checkbox) grdArticoleFactura.cPretCuTvaArt._checkbox1.When 16587-16589 RETURN !Thisform.lArticoleReadOnly AND ...
Discount pgfArticole.PAGE3.txtDiscountArt — neatins in diff — vezi nota de mai jos

Gol observat: txtDiscountArt (discountul pe factura, cu ControlSource = tvanz.discount) nu are .When si nu apare in diff. Daca discountul e considerat parte din "articolele facturii" in sensul Deciziei 42 (afecteaza totalul liniilor), ramane editabil dupa trimiterea in eFactura — de clarificat cu Marius sau de inclus explicit. Nu era in lista cmdAdaugaArticol/cmdStergeArticol ceruta explicit in brief, deci il semnalez ca gol, nu-l tratez ca bug.

4. Tipar read-only pe grid, folosit deja in proiect

Nu se inventeaza un tipar nou. Proiectul (din S5) foloseste deja exact acest idiom pentru randurile needitabile (liniile din seturi, id_vanzare_set <> 0): fiecare coloana editabila a gridului are un handler .When care intoarce .F. cand conditia de blocare e adevarata — VFP nu lasa controlul sa intre in editare daca .When intoarce .F.. Diff-ul in lucru extinde exact aceste .When-uri existente, adaugand Thisform.lArticoleReadOnly OR in fata conditiei deja acolo — e continuarea directa a mecanismului S5, nu un tipar paralel. Confirma cererea din brief: "foloseste tiparul deja folosit in proiect, nu inventa unul nou" — respectat.

5. Feedback vizual pentru utilizator

Diff-ul adauga o eticheta noua, pgfArticole.PAGE3.lblArticoleReadOnly (omodificari.vc2:12857-12871 in diff), cu Caption = "Articole needitabile - factura trimisa in eFactura", Visible legat de This.lArticoleReadOnly in Show(). E minimul cerut de brief — niciun tipar de marcaj standard read-only nu exista deja in proiect (nu s-a gasit unul in cautarea pentru §4), deci eticheta simpla e alegerea rezonabila. Singura observatie: eticheta e pozitionata Top = 4, Left = 360 — de verificat pe ecran (fara VFP IDE nu se poate confirma vizual) ca nu se suprapune cu alt control din PAGE3 la latimile de forma folosite azi.

6. Suprafata de regresie — de ce notele obisnuite din registrul jurnal nu sunt atinse

Blocul care calculeaza lArticoleReadOnly ruleaza doar in interiorul conditiei deja existente:

IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Reccount('tact') > 0
    IncarcaVanzareDinNota('tact')
    IF Reccount('tvanz') = 1
        This.lAreArticoleVanzari = .T.
        ...
        This.lArticoleReadOnly = EsteInEFactura(...)
    ENDIF
ENDIF

IncarcaVanzareDinNota cauta in VANZARI un rand cu tripletul (cod, nract, serie_act, dataact) al notei curente. Pentru o nota contabila obisnuita (nu factura de vanzare), acest rand nu exista, Reccount('tvanz') ramane 0, deci intreg blocul IF Reccount('tvanz') = 1 e sarit — This.lAreArticoleVanzari ramane .F. si This.lArticoleReadOnly ramane la valoarea implicita .F. (setata explicit chiar inainte de bloc, :14791). Consecinta directa: This.pgfArticole.PageCount = 2 (fara PAGE3), deci cmdAdaugaArticol/cmdStergeArticol/gridul de articole nici nu exista pentru documentul respectiv — nu doar ca nu sunt dezactivate, ci nu sunt instantiate in fluxul de afisare. Riscul de regresie pe notele obisnuite din ROAGEST/ROACONT e deci zero prin constructie, mostenit din gating-ul lAreArticoleVanzari deja livrat si testat in S4/S5 — Decizia 42 doar adauga o conditie suplimentara in interiorul ramurii deja izolate pentru facturi de vanzare, nu schimba gating-ul insusi. Acesta e exact argumentul cerut in brief la punctul 6, si e verificabil citind codul, nu presupus.

comun.vc2 (clasa afisjurcom, do_modifica, 2222-2572) nu e atins de acest diff — ramane neschimbat, confirmand ca intreaga logica sta in omodificari.vc2, un singur punct de intretinere.

7. Teste minime propuse (headless, in stilul suitei existente)

Suita COMUN\utile\Teste\editare_factura\ foloseste harness-ul ui_harness.prg + asserteaza, cu formular vizibil (WindowType = 0, Show(), DOEVENTS FORCE), fara input real (nici un keybd_event/SendInput — doar citire directa de proprietati si apel direct de metode). Propunere, dupa modelul test_ui_sterge_linie.prg:

test_d42_efactura_readonly.prg — cazul pozitiv, cu date mock (nu exista document real din luna curenta trimis in eFactura, cf. §1):

  1. Incarca tact/tvd pentru un document real din luna curenta cu lAreArticoleVanzari = .T. (ex. acelasi id_vanzare = 1049 mentionat in handoff intermediar (sters), daca inca in luna curenta la momentul rularii).
  2. Dupa IncarcaVanzareDinNota, forteaza tvanz.id_fact (sau tact.id_fact, dupa care varianta se alege in §8) la o valoare stiuta ca exista in ANAF_EFACTURA (mock: INSERT intr-un cursor local, nu in Oracle) — sau, mai simplu, mock-uieste direct EsteInEFactura prin SET PROCEDURE/redefinire temporara, dupa tiparul deja folosit in proiect pentru izolarea de Oracle in teste UI.
  3. assert: loForm.lArticoleReadOnly = .T.
  4. assert: loForm.pgfArticole.PAGE3.cmdAdaugaArticol.Enabled = .F.
  5. assert: loForm.pgfArticole.PAGE3.cmdStergeArticol.Enabled = .F.
  6. assert: loForm.pgfArticole.PAGE3.lblArticoleReadOnly.Visible = .T.
  7. assert: grid-ul e in continuare vizibil si populat — loForm.pgfArticole.PageCount = 3, Reccount('tvd') > 0 — confirma partea corectata a deciziei (nu se suprima pagina).
  8. assert: pozitionare pe grdArticoleFactura, SetFocus pe coloana cCantitateArt nu intra in editare — verificat prin apelul direct al handlerului .When (loForm.pgfArticole.PAGE3. grdArticoleFactura.cCantitateArt.Text1.When() trebuie sa intoarca .F.), nu prin tastare.

test_d42_efactura_editabil.prg — cazul negativ (control): acelasi document, dar EsteInEFactura mock-uit sa intoarca .F. -> toate assert-urile de mai sus inversate (Enabled = .T., .When() nu intoarce .F. din cauza flagului — poate intoarce .F. din alt motiv, ex. id_vanzare_set, testat separat).

test_d42_nota_obisnuita_neatinsa.prg — cazul de regresie cerut la §6: incarca o nota contabila fara corespondent in VANZARI (orice test existent din COMUN\utile\Teste\ care nu tine de facturare), verifica loForm.pgfArticole.PageCount = 2 si ca lArticoleReadOnly ramane .F. fara sa fi fost nevoie sa se apeleze EsteInEFactura deloc (se poate confirma indirect, verificand ca nu s-a facut nicio interogare Oracle suplimentara, sau direct daca se mock-uieste functia cu un contor de apeluri).

Toate cele trei suite: fara scriere in Oracle, QUIT la final, log langa .prg cu acelasi nume + _log.txt, conform conventiei din handoff intermediar (sters).

8. Schita de diff — corectia necesara peste diff-ul in lucru

Doua variante, cu recomandare pentru B.

Varianta A — minima, refoloseste id_fact deja prezent pe cursorul tact (confirmat: tact vine din view-ul vact_tot, care are coloana id_fact, folosita deja in ofacturare_comun.vc2:3776 ca Locate For Nvl(id_fact, 0) = lnIdFact pe acelasi cursor actactan/tact):

--- a/COMUN/clase/omodificari.vc2
+++ b/COMUN/clase/omodificari.vc2
@@ frm_modific2024.Show
 			This.nIdVanzare = tvanz.id_vanzare
 			This.nTipVanzare = tvanz.tip
-			This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
+			This.lArticoleReadOnly = EsteInEFactura(Nvl(tact.id_fact, 0))

Risc al variantei A: presupune ca recordul curent din tact (la momentul Show(), imediat dupa incarcare, deci pe Top) are id_fact valid pentru documentul gasit — valabil in cazul normal, dar nu la fel de robust ca precedentul din ofacturare_comun.vc2:3775-3783, care cauta explicit randul cu id_fact potrivit inainte sa cada pe fallback (Go Top).

Varianta B — recomandata, aduce id_fact chiar pe tvanz (randul din VANZARI deja gasit prin tripletul cod/nract/serie_act/dataact), simetric cu id_vanzare:

--- a/COMUN/programe/ofacturare_editare.prg
+++ b/COMUN/programe/ofacturare_editare.prg
@@ CreeazaCursorTvanzGol
-	CREATE CURSOR tvanz (id_vanzare I, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ;
+	CREATE CURSOR tvanz (id_vanzare I, id_fact I NULL, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ;
 		in_valuta I NULL, id_valuta I NULL, nume_val C(20) NULL)
@@ IncarcaVanzareNota
-	lcSql = [select v.id_vanzare, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ;
+	lcSql = [select v.id_vanzare, v.id_fact, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ;
 		[v.in_valuta, v.id_valuta, nv.nume_val from vanzari v left join nom_valute nv on nv.id_valuta = v.id_valuta where v.cod = ] + Transform(m.tnCod) + ;

--- a/COMUN/clase/omodificari.vc2
+++ b/COMUN/clase/omodificari.vc2
@@ frm_modific2024.Show
 			This.nIdVanzare = tvanz.id_vanzare
 			This.nTipVanzare = tvanz.tip
-			This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
+			This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact, 0))

Varianta B e mai robusta pentru ca foloseste VANZARI.ID_FACT direct — exact coloana pe care ANAF_EFACTURA o are ca cheie (verificat pe schema Oracle, §1) — fara sa depinda de pozitia curenta a cursorului tact. Costul: doua fisiere in loc de unul (ofacturare_editare.prg + omodificari.vc2), plus verificarea ca select v.id_fact nu produce eroare Oracle daca vreun rand vechi din VANZARI are id_fact NULL (coloana e deja NULL-abila judecand dupa restul cursorului, in_valuta I NULL etc., deci nu ar trebui sa fie o problema).

Neschimbat, corect asa cum e: restul diff-ului (.When-urile pe grid, cmdAdaugaArticol/ cmdStergeArticol.Enabled, eticheta lblArticoleReadOnly) — vezi §2-5.


Rezumat pentru implementare

  1. Diff-ul lui d42-efactura (necomis la momentul acestui raport, in COMUN\clase\omodificari.vc2
    • COMUN\programe\ofacturare_editare.prg) e structural corect — locul (§2), controalele (§3), tiparul de grid (§4) si feedback-ul (§5) respecta cerintele Deciziei 42 corectate.
  2. Bug gasit, dovedit pe schema si date, si INCHIS: EsteInEFactura(This.nIdVanzare) trebuia sa foloseasca id_fact, nu id_vanzare — d42-efactura l-a gasit independent si l-a corectat cu exact varianta B din §8 (EsteInEFactura(Nvl(tvanz.id_fact,0))), verificat pe disc de acest agent dupa corectie. Nu mai necesita nicio actiune.
  3. Gol de clarificat, nu bug: txtDiscountArt ramane editabil dupa eFactura — de decis cu Marius daca discountul intra sub "articole" (§3). Confirmat si de d42-efactura ca gol cunoscut, in afara scope-ului primit de la team-lead.
  4. Comentariul din omodificari.vc2:14786-14787 ("ROACONT/ROAGEST nu-l inregistreaza") e invechit de la commit-ul 3af0089 — de actualizat cand se atinge zona (§1).
  5. Teste propuse in §7, aliniate stilistic cu suita existenta — de confirmat cu d42-efactura daca au fost deja scrise (mesajul lui mentioneaza "doua fisiere noi de test").
  6. ROAGEST\Programe\roagest.prg (linia necomisa SET PROCEDURE TO ofacturare_editare.prg) nu apartine lui d42-efactura — ramane dintr-un alt bloc de lucru, de identificat separat de team-lead.