#6 editare factura emisa: S4b etapa 2 - butonul, dialogul si tipul lui id_articol

Enumerarea diferentelor dintre rulaje si articole capata interfata: buton pe
pagina de articole, dialog cu grid si comutator de directie, plus verificarea la
salvare. Dialogul propune, nu decide - aplicarea ramane o apasare explicita.

- omodificari.vc2: cmdSincronizeazaArticole pe PAGE3, dezactivat odata cu restul
  paginii cand documentul e blocat in eFactura; frm_sincronizare_articole,
  derivata din frm_termin_renunt. Gridul e legat de propunere_afisata, cursor
  stabil creat in Load si doar golit si reumplut - propunere_sincronizare se
  recreeaza la fiecare apel, deci o legare directa s-ar rupe la prima comutare
  de directie.
- id_articol trece de la I la N(20), in toate cele 6 locuri scrise pentru acest
  punct. Restul codebase-ului foloseste dintotdeauna N(20): valorile reale trec
  de 2^31 pe 89.8% din NOM_ARTICOLE, iar un camp I le trunchia tacit, asa incat
  potrivirea pe id_articol cadea si iesea perechea Adaugare+Semnalare in locul
  unei Modificari. Nu e o schimbare de design, e revenire la conventia casei.
- doua accesari This. in loc de Thisform. in Click-ul butonului de adaugare
  articol: proprietatile sunt ale formularului, iar scurtcircuitul OR le
  evalua exact cand documentul avea articole - orice click real dadea eroare.
- teste: test_s4b_dialog (35/0) acopera instantierea, comutarea directiei si
  aplicarea la salvare; test_s4b_sincronizare (42/0) capata cazul id_articol
  3598545102, care ar fi picat inainte de schimbarea de tip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
This commit is contained in:
2026-08-11 20:05:14 +03:00
parent d3268a69bc
commit 40a112a42d
4 changed files with 901 additions and 8 deletions

View File

@@ -6661,6 +6661,7 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.grdArticoleFactura.cValoareArt.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.cmdStergeArticol" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.cmdAdaugaArticol" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.cmdSincronizeazaArticole" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.lblTotalLiniiArt" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.txtTotalLiniiArt" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="pgfArticole.PAGE3.lblDiscountArt" UniqueID="" Timestamp="" />
@@ -6797,6 +6798,7 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
*<DefinedPropArrayMethod>
*m: actualizeazabaratotaluri
*m: actualizeazaverdictactrul
*m: afiseazadialogsincronizarearticole
*m: afiseaza_rulaje
*m: alege_partener
*m: aleg_document_pereche
@@ -12304,6 +12306,22 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
ZOrderSet = 0
*< END OBJECT: BaseClass="commandbutton" />
ADD OBJECT 'pgfArticole.PAGE3.cmdSincronizeazaArticole' AS commandbutton WITH ;
Anchor = 8, ;
AutoSize = .F., ;
Caption = "Sincronizeaza cu rulaje...", ;
FontCharSet = 238, ;
FontSize = 9, ;
ForeColor = 0,0,64, ;
Height = 22, ;
Left = 350, ;
Name = "cmdSincronizeazaArticole", ;
TabIndex = 6, ;
Top = 0, ;
Width = 250, ;
ZOrderSet = 0
*< END OBJECT: BaseClass="commandbutton" />
ADD OBJECT 'pgfArticole.PAGE3.cmdStergeArticol' AS commandbutton WITH ;
Anchor = 8, ;
AutoSize = .F., ;
@@ -13170,6 +13188,20 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
Thisform.calculeaza_valori_articol()
ENDPROC
PROCEDURE AfiseazaDialogSincronizareArticole
*!* dialogul compara rulajele cu articolele si aplica diferentele doar in cursoare, la Aplica
LOCAL loDlg
IF !Used('tvd') OR !Used('trul')
RETURN
ENDIF
PUBLIC gnButon
gnButon = 2
loDlg = Createobject('frm_sincronizare_articole')
loDlg.oFormArticole = This
loDlg.Show(1)
Release loDlg
ENDPROC
PROCEDURE afiseaza_rulaje
* COLAPSEAZA SAU MARESTE PAGEFRAME-UL RULAJE
* Daca butonul are imaginea sus, inseamna ca pageframe-ul cu rulaje este ascuns
@@ -14401,6 +14433,24 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
ENDIF
SELECT (m.lnAreaTvd)
ENDIF
*!* al doilea punct de declansare al sincronizarii: enumerarea diferentelor la salvare, cu
*!* continuarea salvarii daca utilizatorul renunta - de aceea llRet nu se schimba aici
IF m.llRet AND This.lAreArticoleVanzari AND !This.lArticoleReadOnly
LOCAL lnAreaInainte, lnDivergente
lnAreaInainte = Select()
lnDivergente = 0
IF ConstruiestePropunereSincronizare('RUL_SURSA')
SELECT propunere_sincronizare
COUNT FOR Inlist(Alltrim(actiune),'Modificare','Adaugare','Semnalare') TO lnDivergente
ENDIF
IF m.lnAreaInainte > 0
SELECT (m.lnAreaInainte)
ENDIF
*!* liniile N-A singure nu opresc salvarea: sunt "nu se poate compara", nu divergente
IF m.lnDivergente > 0
This.AfiseazaDialogSincronizareArticole()
ENDIF
ENDIF
RETURN m.llRet
@@ -14676,7 +14726,7 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
IF Used('tvd')
Use In tvd
ENDIF
CREATE CURSOR tvd (id_vanzare I NULL, id_vanzare_det I NULL, id_articol I NULL, cantitate N(12,3) NULL, pret N(14,4) NULL, pret_cu_tva N(14,4) NULL, proc_tvav N(6,4) NULL, discount_unitar N(14,4) NULL, id_gestiune I NULL, cont C(20) NULL, id_valuta I NULL, id_jtva_coloana I NULL, serie C(20) NULL, explicatie C(60) NULL, taxcode I NULL, lot C(20) NULL, sters I NULL, id_vanzare_set I NULL, pret_achizitie N(14,4) NULL, denumire C(100) NULL, codmat C(30) NULL, nume_gestiune C(60) NULL, nume_val C(20) NULL, lmodificat L, valoare N(14,2), in_stoc I NULL)
CREATE CURSOR tvd (id_vanzare I NULL, id_vanzare_det I NULL, id_articol N(20) NULL, cantitate N(12,3) NULL, pret N(14,4) NULL, pret_cu_tva N(14,4) NULL, proc_tvav N(6,4) NULL, discount_unitar N(14,4) NULL, id_gestiune I NULL, cont C(20) NULL, id_valuta I NULL, id_jtva_coloana I NULL, serie C(20) NULL, explicatie C(60) NULL, taxcode I NULL, lot C(20) NULL, sters I NULL, id_vanzare_set I NULL, pret_achizitie N(14,4) NULL, denumire C(100) NULL, codmat C(30) NULL, nume_gestiune C(60) NULL, nume_val C(20) NULL, lmodificat L, valoare N(14,2), in_stoc I NULL)
ENDIF
*!* cursorul tvanz - placeholder gol, ca bara de totaluri (ControlSource pe tvanz.discount) sa se lege la construire
@@ -14812,6 +14862,7 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
This.pgfArticole.PAGE3.cmdStergeArticol.Enabled = !This.lArticoleReadOnly
This.pgfArticole.PAGE3.txtDiscountArt.ReadOnly = This.lArticoleReadOnly
This.pgfArticole.PAGE3.lblArticoleReadOnly.Visible = This.lArticoleReadOnly
This.pgfArticole.PAGE3.cmdSincronizeazaArticole.Enabled = !This.lArticoleReadOnly
This.ActualizeazaBaraTotaluri()
ELSE
This.pgfArticole.PageCount = 2
@@ -16492,7 +16543,7 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
*!* daca documentul e in valuta (tvanz.in_valuta=1), poArticol.tip_valuta/Curs/multiplicator/
*!* nume_val/id_valuta se populeaza cu valorile documentului, ca dialogul sa lucreze in valuta
LOCAL loArticolAles, lnIdArticol, llInValutaDoc, lnCursDoc, lnMultDoc, lcNumeValDoc, lnIdValutaDoc
IF !Used('tvd') OR !This.lAreArticoleVanzari OR Thisform.lArticoleReadOnly
IF !Used('tvd') OR !Thisform.lAreArticoleVanzari OR Thisform.lArticoleReadOnly
RETURN
ENDIF
loArticolAles = caut_articol()
@@ -16511,7 +16562,7 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
poArticol = CreeazaPoArticolNouTvd(m.lnIdArticol, loArticolAles.codmat, loArticolAles.denumire, loArticolAles.um, loArticolAles.cont, ;
m.llInValutaDoc, m.lnCursDoc, m.lnMultDoc, m.lcNumeValDoc, m.lnIdValutaDoc)
poDate = Createobject('Empty')
AddProperty(poDate, 'tip', This.nTipVanzare)
AddProperty(poDate, 'tip', Thisform.nTipVanzare)
AddProperty(poDate, 'in_valuta', 0)
AddProperty(poDate, 'dataact', Iif(Used('tact') And Reccount('tact') > 0, Nvl(tact.dataact, Date()), Date()))
gnScadereStoc = 0
@@ -16529,6 +16580,14 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
Release loDlg
ENDPROC
PROCEDURE pgfArticole.PAGE3.cmdSincronizeazaArticole.Click
*!* primul punct de declansare: comparatia ceruta explicit de utilizator, de pe pagina de articole
IF !Used('tvd') OR !Thisform.lAreArticoleVanzari OR Thisform.lArticoleReadOnly
RETURN
ENDIF
Thisform.AfiseazaDialogSincronizareArticole()
ENDPROC
PROCEDURE pgfArticole.PAGE3.cmdStergeArticol.Click
*!* comuta flagul sters pe linia curenta din tvd - stergerea e logica, ramane vizibila (grizata) pana la salvare
IF !Used('tvd') OR Reccount('tvd') = 0 OR Thisform.lArticoleReadOnly
@@ -16594,3 +16653,286 @@ DEFINE CLASS frm_modific2024 AS _frmbase OF "_frm_base.vcx"
ENDPROC
ENDDEFINE
DEFINE CLASS frm_sincronizare_articole AS frm_termin_renunt OF "_frm_child.vcx"
*< CLASSDATA: Baseclass="form" Timestamp="" Scale="Pixels" Uniqueid="" />
*-- OBJECTDATA items order determines ZOrder / El orden de los items OBJECTDATA determina el ZOrder
*< OBJECTDATA: ObjPath="lblDirectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="optDirectie" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="lblAvertisment" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere.cArticolProp.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere.cCantVecheProp.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere.cCantNouaProp.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere.cPretVechiProp.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere.cPretNouProp.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grdPropunere.cActiuneProp.Header1" UniqueID="" Timestamp="" />
*<DefinedPropArrayMethod>
*m: construiesteenumerare
*p: cdirectie
*p: oformarticole
*</DefinedPropArrayMethod>
*<PropValue>
BorderStyle = 1
cDirectie = "RUL_SURSA"
DoCreate = .T.
Height = 480
Name = "frm_sincronizare_articole"
oFormArticole = .F.
Width = 700
_shape1.Name = "_shape1"
_shape1.ZOrderSet = 1
_shape2.Height = 29
_shape2.Left = 636
_shape2.Name = "_shape2"
_shape2.Top = 0
_shape2.Width = 64
_shape2.ZOrderSet = 2
Lb_titlu_alb_b121.Caption = "Sincronizare articole factura - rulaje"
Lb_titlu_alb_b121.Name = "Lb_titlu_alb_b121"
Lb_titlu_alb_b121.TabIndex = 5
Lb_titlu_alb_b121.ZOrderSet = 3
BUT_TERMIN1.Left = 668
BUT_TERMIN1.Name = "BUT_TERMIN1"
BUT_TERMIN1.TabIndex = 3
BUT_TERMIN1.ToolTipText = "Aplica sincronizarea propusa"
BUT_TERMIN1.Top = 1
BUT_TERMIN1.ZOrderSet = 4
Gridsort1.Name = "Gridsort1"
But_renunt1.Left = 639
But_renunt1.Name = "But_renunt1"
But_renunt1.TabIndex = 4
But_renunt1.Top = 1
But_renunt1.ZOrderSet = 6
*</PropValue>
ADD OBJECT 'grdPropunere' AS _grdrow WITH ;
ColumnCount = 6, ;
DeleteMark = .F., ;
FontSize = 9, ;
GridLineColor = 128,128,128, ;
HeaderHeight = 22, ;
Height = 300, ;
Left = 16, ;
Name = "grdPropunere", ;
ReadOnly = .T., ;
RecordSource = "propunere_afisata", ;
RowHeight = 18, ;
Top = 108, ;
Width = 668, ;
ZOrderSet = 2, ;
Column1.ControlSource = "ALLTRIM(propunere_afisata.denumire) + ' (' + ALLTRIM(propunere_afisata.codmat) + ')'", ;
Column1.DynamicForeColor = "IIF(ALLTRIM(propunere_afisata.actiune)='N-A',RGB(150,150,150),RGB(0,0,0))", ;
Column1.Name = "cArticolProp", ;
Column1.ReadOnly = .T., ;
Column1.Width = 220, ;
Column2.ControlSource = "propunere_afisata.cantitate_veche", ;
Column2.DynamicForeColor = "IIF(ALLTRIM(propunere_afisata.actiune)='N-A',RGB(150,150,150),RGB(0,0,0))", ;
Column2.Format = "R", ;
Column2.InputMask = (get_mask(12,gnPCANT)), ;
Column2.Name = "cCantVecheProp", ;
Column2.ReadOnly = .T., ;
Column2.Width = 88, ;
Column3.ControlSource = "propunere_afisata.cantitate_noua", ;
Column3.DynamicForeColor = "IIF(ALLTRIM(propunere_afisata.actiune)='N-A',RGB(150,150,150),RGB(0,0,0))", ;
Column3.Format = "R", ;
Column3.InputMask = (get_mask(12,gnPCANT)), ;
Column3.Name = "cCantNouaProp", ;
Column3.ReadOnly = .T., ;
Column3.Width = 88, ;
Column4.ControlSource = "propunere_afisata.pret_vechi", ;
Column4.DynamicForeColor = "IIF(ALLTRIM(propunere_afisata.actiune)='N-A',RGB(150,150,150),RGB(0,0,0))", ;
Column4.Format = "R", ;
Column4.InputMask = (get_mask(12,gnPPRET)), ;
Column4.Name = "cPretVechiProp", ;
Column4.ReadOnly = .T., ;
Column4.Width = 88, ;
Column5.ControlSource = "propunere_afisata.pret_nou", ;
Column5.DynamicForeColor = "IIF(ALLTRIM(propunere_afisata.actiune)='N-A',RGB(150,150,150),RGB(0,0,0))", ;
Column5.Format = "R", ;
Column5.InputMask = (get_mask(12,gnPPRET)), ;
Column5.Name = "cPretNouProp", ;
Column5.ReadOnly = .T., ;
Column5.Width = 88, ;
Column6.ControlSource = "propunere_afisata.actiune", ;
Column6.DynamicForeColor = "IIF(ALLTRIM(propunere_afisata.actiune)='N-A',RGB(150,150,150),RGB(0,0,0))", ;
Column6.Name = "cActiuneProp", ;
Column6.ReadOnly = .T., ;
Column6.Width = 96
*< END OBJECT: ClassLib="_grd_base.vcx" BaseClass="grid" />
ADD OBJECT 'grdPropunere.cActiuneProp.Header1' AS header WITH ;
Caption = "Actiune", ;
FontName = "Arial", ;
FontSize = 9, ;
Name = "Header1", ;
WordWrap = .T.
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grdPropunere.cArticolProp.Header1' AS header WITH ;
Caption = "Articol", ;
FontName = "Arial", ;
FontSize = 9, ;
Name = "Header1", ;
WordWrap = .T.
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grdPropunere.cCantNouaProp.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Cantitate noua", ;
FontName = "Arial", ;
FontSize = 9, ;
Name = "Header1", ;
WordWrap = .T.
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grdPropunere.cCantVecheProp.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Cantitate veche", ;
FontName = "Arial", ;
FontSize = 9, ;
Name = "Header1", ;
WordWrap = .T.
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grdPropunere.cPretNouProp.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Pret nou", ;
FontName = "Arial", ;
FontSize = 9, ;
Name = "Header1", ;
WordWrap = .T.
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grdPropunere.cPretVechiProp.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Pret vechi", ;
FontName = "Arial", ;
FontSize = 9, ;
Name = "Header1", ;
WordWrap = .T.
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'lblAvertisment' AS _label WITH ;
AutoSize = .T., ;
Caption = "Propunere calculata automat - verificati valorile inainte de aplicare.", ;
ForeColor = 153,0,0, ;
Left = 16, ;
Name = "lblAvertisment", ;
Top = 420, ;
ZOrderSet = 3
*< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
ADD OBJECT 'lblDirectie' AS _label WITH ;
AutoSize = .T., ;
Caption = "Directie sincronizare:", ;
Left = 16, ;
Name = "lblDirectie", ;
Top = 38, ;
ZOrderSet = 0
*< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
ADD OBJECT 'optDirectie' AS _optiongrup WITH ;
AutoSize = .F., ;
ButtonCount = 2, ;
Height = 46, ;
Left = 16, ;
Name = "optDirectie", ;
Top = 56, ;
Value = 1, ;
Width = 660, ;
ZOrderSet = 1, ;
OPTION1.AutoSize = .T., ;
OPTION1.Caption = "Rulajul e sursa (actualizeaza articolele facturii)", ;
OPTION1.FontSize = 10, ;
OPTION1.Left = 5, ;
OPTION1.Name = "OPTION1", ;
OPTION1.Top = 4, ;
OPTION1.Value = 1, ;
OPTION2.AutoSize = .T., ;
OPTION2.Caption = "Articolele sunt sursa (actualizeaza rulajul)", ;
OPTION2.FontSize = 10, ;
OPTION2.Left = 5, ;
OPTION2.Name = "OPTION2", ;
OPTION2.Top = 24
*< END OBJECT: ClassLib="_baza.vcx" BaseClass="optiongroup" />
PROCEDURE ConstruiesteEnumerare
*!* propunere_sincronizare se recreeaza la fiecare apel, deci gridul nu poate sta legat de el;
*!* continutul se copiaza in propunere_afisata, cursorul stabil creat in Load
LOCAL lnAreaOrigine, lnAplicabile
lnAreaOrigine = Select()
lnAplicabile = 0
ConstruiestePropunereSincronizare(This.cDirectie)
SELECT propunere_afisata
ZAP
IF Used('propunere_sincronizare')
SELECT propunere_sincronizare
SCAN
SCATTER MEMVAR
INSERT INTO propunere_afisata FROM MEMVAR
SELECT propunere_sincronizare
ENDSCAN
ENDIF
SELECT propunere_afisata
COUNT FOR Inlist(Alltrim(actiune),'Modificare','Adaugare') TO lnAplicabile
GO TOP
IF m.lnAreaOrigine > 0
SELECT (m.lnAreaOrigine)
ENDIF
This.grdPropunere.Refresh()
This.But_termin1.Enabled = (m.lnAplicabile > 0)
ENDPROC
PROCEDURE inainte_de_do_termin
*!* "Aplica" muta valorile doar in cursoarele tvd/trul - scrierea in Oracle ramane la salvare
LOCAL lnAplicate
lnAplicate = AplicaSincronizareArticole(This.cDirectie, This.oFormArticole)
IF Vartype(This.oFormArticole) == 'O'
IF m.lnAplicate > 0 AND Pemstatus(This.oFormArticole,'ActualizeazaBaraTotaluri',5)
This.oFormArticole.ActualizeazaBaraTotaluri()
ENDIF
IF Pemstatus(This.oFormArticole,'pgfArticole',5)
This.oFormArticole.pgfArticole.PAGE3.grdArticoleFactura.Refresh()
ENDIF
ENDIF
RETURN .T.
ENDPROC
PROCEDURE Init
DoDefault()
This.ConstruiesteEnumerare()
ENDPROC
PROCEDURE Load
*!* Load ruleaza inaintea controalelor: cursorul trebuie sa existe cand gridul se leaga de el
LOCAL lnAreaOrigine
DoDefault()
lnAreaOrigine = Select()
IF Used('propunere_afisata')
Use In propunere_afisata
ENDIF
CREATE CURSOR propunere_afisata (id_articol N(20), denumire C(100), codmat C(30), ;
cantitate_veche N(12,3), cantitate_noua N(12,3), pret_vechi N(14,4), pret_nou N(14,4), ;
actiune C(12), motiv C(80))
IF m.lnAreaOrigine > 0
SELECT (m.lnAreaOrigine)
ENDIF
ENDPROC
PROCEDURE Unload
IF Used('propunere_afisata')
Use In propunere_afisata
ENDIF
DoDefault()
ENDPROC
PROCEDURE optDirectie.Click
Thisform.cDirectie = Iif(This.Value = 2, 'ARTICOLE_SURSA', 'RUL_SURSA')
Thisform.ConstruiesteEnumerare()
ENDPROC
ENDDEFINE