Vídeo Aula – AdvPL 020 – Modelo 3 em MVC

Olá pessoal…

Na aula de hoje irei mostrar como fazer um cadastro do tipo Modelo 3 em MVC, que basicamente é um cadastro que possui 2 tabelas, uma cabeçalho e outra itens (similar ao Pedido de Venda).

Abaixo o código fonte completo usado na aula:

//Bibliotecas
#Include 'Protheus.ch'
#Include 'FWMVCDef.ch'

//Variáveis Estáticas
Static cTitulo := "Composição de CDs"

/*/{Protheus.doc} zModel3
Função para cadastro de Composição de CDs (Exemplo de Modelo 3 - ZZ2 x ZZ3)
@author Atilio
@since 03/09/2016
@version 1.0
	@return Nil, Função não tem retorno
	@example
	u_zModel3()
/*/

User Function zModel3()
	Local aArea   := GetArea()
	Local oBrowse
	
	//Instânciando FWMBrowse - Somente com dicionário de dados
	oBrowse := FWMBrowse():New()
	
	//Setando a tabela de cadastro de CDs
	oBrowse:SetAlias("ZZ2")

	//Setando a descrição da rotina
	oBrowse:SetDescription(cTitulo)
	
	//Ativa a Browse
	oBrowse:Activate()
	
	RestArea(aArea)
Return Nil

/*---------------------------------------------------------------------*
 | Func:  MenuDef                                                      |
 | Autor: Daniel Atilio                                                |
 | Data:  03/09/2016                                                   |
 | Desc:  Criação do menu MVC                                          |
 *---------------------------------------------------------------------*/

Static Function MenuDef()
	Local aRot := {}
	
	//Adicionando opções
	ADD OPTION aRot TITLE 'Visualizar' ACTION 'VIEWDEF.zModel3' OPERATION MODEL_OPERATION_VIEW   ACCESS 0 //OPERATION 1
	ADD OPTION aRot TITLE 'Incluir'    ACTION 'VIEWDEF.zModel3' OPERATION MODEL_OPERATION_INSERT ACCESS 0 //OPERATION 3
	ADD OPTION aRot TITLE 'Alterar'    ACTION 'VIEWDEF.zModel3' OPERATION MODEL_OPERATION_UPDATE ACCESS 0 //OPERATION 4
	ADD OPTION aRot TITLE 'Excluir'    ACTION 'VIEWDEF.zModel3' OPERATION MODEL_OPERATION_DELETE ACCESS 0 //OPERATION 5

Return aRot

/*---------------------------------------------------------------------*
 | Func:  ModelDef                                                     |
 | Autor: Daniel Atilio                                                |
 | Data:  03/09/2016                                                   |
 | Desc:  Criação do modelo de dados MVC                               |
 *---------------------------------------------------------------------*/

Static Function ModelDef()
	Local oModel 		:= Nil
	Local oStPai 		:= FWFormStruct(1, 'ZZ2')
	Local oStFilho 	:= FWFormStruct(1, 'ZZ3')
	Local aZZ3Rel		:= {}
	
	//Definições dos campos
	oStPai:SetProperty('ZZ2_CODCD',    MODEL_FIELD_WHEN,    FwBuildFeature(STRUCT_FEATURE_WHEN,    '.F.'))                                 //Modo de Edição
	oStPai:SetProperty('ZZ2_CODCD',    MODEL_FIELD_INIT,    FwBuildFeature(STRUCT_FEATURE_INIPAD,  'GetSXENum("ZZ2", "ZZ2_CODCD")'))       //Ini Padrão
	oStPai:SetProperty('ZZ2_CODART',   MODEL_FIELD_VALID,   FwBuildFeature(STRUCT_FEATURE_VALID,   'ExistCpo("ZZ1", M->ZZ2_CODART)'))      //Validação de Campo
	oStFilho:SetProperty('ZZ3_CODCD',  MODEL_FIELD_WHEN,    FwBuildFeature(STRUCT_FEATURE_WHEN,    '.F.'))                                 //Modo de Edição
	oStFilho:SetProperty('ZZ3_CODCD',  MODEL_FIELD_OBRIGAT, .F. )                                                                          //Campo Obrigatório
	oStFilho:SetProperty('ZZ3_CODART', MODEL_FIELD_OBRIGAT, .F. )                                                                          //Campo Obrigatório
	oStFilho:SetProperty('ZZ3_CODMUS', MODEL_FIELD_INIT,    FwBuildFeature(STRUCT_FEATURE_INIPAD,  'u_zIniMus()'))                         //Ini Padrão
	
	//Criando o modelo e os relacionamentos
	oModel := MPFormModel():New('zModel3M')
	oModel:AddFields('ZZ2MASTER',/*cOwner*/,oStPai)
	oModel:AddGrid('ZZ3DETAIL','ZZ2MASTER',oStFilho,/*bLinePre*/, /*bLinePost*/,/*bPre - Grid Inteiro*/,/*bPos - Grid Inteiro*/,/*bLoad - Carga do modelo manualmente*/)  //cOwner é para quem pertence
	
	//Fazendo o relacionamento entre o Pai e Filho
	aAdd(aZZ3Rel, {'ZZ3_FILIAL','ZZ2_FILIAL'} )
	aAdd(aZZ3Rel, {'ZZ3_CODCD',	'ZZ2_CODCD'})
	aAdd(aZZ3Rel, {'ZZ3_CODART','ZZ2_CODART'}) 
	
	oModel:SetRelation('ZZ3DETAIL', aZZ3Rel, ZZ3->(IndexKey(1))) //IndexKey -> quero a ordenação e depois filtrado
	oModel:GetModel('ZZ3DETAIL'):SetUniqueLine({"ZZ3_DESC"})	//Não repetir informações ou combinações {"CAMPO1","CAMPO2","CAMPOX"}
	oModel:SetPrimaryKey({})
	
	//Setando as descrições
	oModel:SetDescription("Grupo de Produtos - Mod. 3")
	oModel:GetModel('ZZ2MASTER'):SetDescription('Cadastro')
	oModel:GetModel('ZZ3DETAIL'):SetDescription('CDs')
Return oModel

/*---------------------------------------------------------------------*
 | Func:  ViewDef                                                      |
 | Autor: Daniel Atilio                                                |
 | Data:  03/09/2016                                                   |
 | Desc:  Criação da visão MVC                                         |
 *---------------------------------------------------------------------*/

Static Function ViewDef()
	Local oView		:= Nil
	Local oModel		:= FWLoadModel('zModel3')
	Local oStPai		:= FWFormStruct(2, 'ZZ2')
	Local oStFilho	:= FWFormStruct(2, 'ZZ3')
	
	//Criando a View
	oView := FWFormView():New()
	oView:SetModel(oModel)
	
	//Adicionando os campos do cabeçalho e o grid dos filhos
	oView:AddField('VIEW_ZZ2',oStPai,'ZZ2MASTER')
	oView:AddGrid('VIEW_ZZ3',oStFilho,'ZZ3DETAIL')
	
	//Setando o dimensionamento de tamanho
	oView:CreateHorizontalBox('CABEC',30)
	oView:CreateHorizontalBox('GRID',70)
	
	//Amarrando a view com as box
	oView:SetOwnerView('VIEW_ZZ2','CABEC')
	oView:SetOwnerView('VIEW_ZZ3','GRID')
	
	//Habilitando título
	oView:EnableTitleView('VIEW_ZZ2','Cabeçalho - Cadastro')
	oView:EnableTitleView('VIEW_ZZ3','Grid - CDs')
	
	//Força o fechamento da janela na confirmação
	oView:SetCloseOnOk({||.T.})
	
	//Remove os campos de Código do Artista e CD
	oStFilho:RemoveField('ZZ3_CODART')
	oStFilho:RemoveField('ZZ3_CODCD')
Return oView

/*/{Protheus.doc} zIniMus
Função que inicia o código sequencial da grid
@type function
@author Atilio
@since 03/09/2016
@version 1.0
/*/

User Function zIniMus()
	Local aArea := GetArea()
	Local cCod  := StrTran(Space(TamSX3('ZZ3_CODMUS')[1]), ' ', '0')
	Local oModelPad  := FWModelActive()
	Local oModelGrid := oModelPad:GetModel('ZZ3DETAIL')
	Local nOperacao  := oModelPad:nOperation
	Local nLinAtu    := oModelGrid:nLine
	Local nPosCod    := aScan(oModelGrid:aHeader, {|x| AllTrim(x[2]) == AllTrim("ZZ3_CODMUS")})
	
	//Se for a primeira linha
	If nLinAtu < 1
		cCod := Soma1(cCod)
	
	//Senão, pega o valor da última linha
	Else
		cCod := oModelGrid:aCols[nLinAtu][nPosCod]
		cCod := Soma1(cCod)
	EndIf
	
	RestArea(aArea)
Return cCod

Bom pessoal, por hoje é só.
Abraços e até a próxima.

Dan Atilio (Daniel Atilio)
Especialista em Engenharia de Software pela FIB. Entusiasta de soluções Open Source. E blogueiro nas horas vagas.

54 Responses

  1. Artur Lima disse:

    Parabéns!

  2. Josuel Silva disse:

    Olá Dan_Atilio, ótima aula.
    No caso do ZZ3_CODMUS porque ao invés de usar essa função para incrementar o proximo numero, porque não usar o AddIncrementField ?

    Eu costumo usar dessa forma que é mais fácil.

    Abçs.

  3. Leonardo Palmieri disse:

    Boa tarde,
    Vi sua video aula e fiz o codigo, mas acontece que ele não está abrindo duas telas igual ao seu

  4. Wagner Lima de Aguiar disse:

    Olá Dan_Atilio!
    Parabéns pelo vídeo, são muito bons!
    Estou com um erro, espero que possa me ajudar
    Quando eu compilo aparece esse erro abaixo nas Static function, MenuDef(), ModelDef() e ViewDef().

    warning W0010 Static Function MENUDEF never called

    Se puder me ajudar eu agradeço!

    • Dan_Atilio disse:

      Boa tarde Wagner, tudo bem?
      Na verdade isso não é um erro, é apenas um Warning, é uma tratativa nova dos repositórios mais novos do Protheus.
      Quando se usa uma função estática e não tem chamada dela, ele dá esse Warning mesmo, mas não se preocupe, não impacta em nada na rotina.
      Abraços.

  5. Wagner Lima de Aguiar disse:

    Dan_Atilio muito obrigado mais uma vez pela ajuda!
    E mais uma vez parabéns pelos vídeos!

  6. Wagner Lima de Aguiar disse:

    Boa Tarde!
    Dan_Atilio, estou com um problema.
    Não está aparecendo a Grid, somente o cabecalho!
    Se puder ajudar eu agradeço.
    Peguei o código que você postou e criei as tabelas conforme você descreveu
    Desde já, Obrigado!

  7. Wagner Lima de Aguiar disse:

    Já resolvi o problema acima com a ajuda de um amigo.
    Só que a grid aparece cinza e quando dou seta para baixo da erro!

  8. Wagner Lima de Aguiar disse:

    Boa noite Dan_Atilio!
    Desculpa não ter respondido antes, estava ocupado!
    Muito obrigado pela atenção!
    Acho que o problema está na minha versão do protheus 12, deve ter algum erro.
    Testei no protheus 11 e funcionou tudo certinho.
    Mais uma vez muito obrigado pela atenção!

  9. Wagner Lima de Aguiar disse:

    Bom dia Dan_Atilio!
    Aqui no meu cabeçalho tenho um campo, valor total, que deve ter a soma dos valores do campo, valor, da minha grid.
    Com a função addcalc não consegui.
    Existe alguma função que faça isso por mim ou tenho que criar algum método para implementar essa funcionalidade?

  10. helton rodrigo disse:

    Josuel Realmente funciona com o oView:AddIncrementField(‘FORM3’ , ‘ZL0_ITEM’ ).

  11. Artur Ferrari disse:

    Boa tarde.

    Adaptei sua rotina para a minha 2 tabelas e só aparece o Browse, sem os menus.

    • Dan_Atilio disse:

      Bom dia Artur, tudo bem?
      Verifique esses pontos:
      – O nome do prw, deve ter no máximo 7 caracteres, por exemplo, arquivo.prw
      – O nome da user function, deve ter no máximo 7 caracteres, e ser igual ao nome do prw, por exemplo, User Function Arquivo
      – A função não deve ser executada no Fórmulas, se for, deve ser usado as funções de SetFunName para alterar o nome
      Abraços.

  12. Alan disse:

    Boa tarde,
    Parabéns pelo vídeo, segui o exemplo criei tabela SZF – cabeçalho e SZG – itens, deu tudo certo exceto 1 único campo que não grava a filial, o campo ZG_FILIAL fica em branco. Alguma dica ou sugestão?

  13. Valmir disse:

    Boa tarde!

    Desde já quero agradecer pelo material disponível para aprendizado, queria tirar uma duvida, ao rolar na próxima linha do grid da o seguinte erro

    THREAD ERROR ([9160], valmir.rodrigues, GERA24) 21/12/2018 15:03:07

    array out of bounds ( 1 of 0 ) on U_ZINIMUS(ZMODEL3.PRW) 21/12/2018 14:40:31 line : 160

    [TOTVS build: 7.00.131227A-20180920 NG]

    Called from U_ZINIMUS(ZMODEL3.PRW) 21/12/2018 14:40:31 line : 160

    Called from {|a,b,c| FWInitCpo(a,b,c),xRet:=(u_zIniMus()),FWCloseCpo(a,b,c,.T.),FwSetVarMem(a,b,xRet),xRet }(PROTHEUSFUNCTIONMVC.PRX) 23/10/2018 18:06:52 line : 160

    Called from INITVALUE(FWFORMGRIDMODEL.PRX) 23/10/2018 18:06:50

  14. bruno frederico disse:

    Pois é, no meu também não grava a filial na tabela filho, apenas a do cabeçalho… Então, quando vamos visualizar o cadastro, ele vem com o grid em branco, olhando no banco, o campo filial esta vazio, não foi gravado… não sei também o que pode ser ;/

    • Dan_Atilio disse:

      Bom dia Bruno.
      No setRelation, deixe o campo da sua grid, e a relação dele com FWxFilial, por exemplo:

      ...
      aAdd(aZ04Rel, {'Z04_FILIAL', 'Iif(!INCLUI, Z04->Z04_FILIAL, FWxFilial("Z04"))'} )
      ...
      

      Abraços.

  15. PAULO JOSE ALCANTARA BORGES DE QUEIROZ disse:

    Primeiramente, parabéns por esse inciativa que certamente ajuda muito a comunidade ADVPL.
    Gostaria do seu apoio, criei o modelo 3, conforme sua aula, revisei n vezes e ao acessar a rotina NÃO APARECE os menus de trabalho, incluir, visualizar etc…. nada só impressão em browser que é padrão do Protheus. seja o código:

    Static Function Menu()

    Local aRot := {}
    //Adicionando opções
    ADD OPTION aRot TITLE ‘Visualizar’ ACTION ‘VIEWDEF.qprogra’ OPERATION MODEL_OPERATION_VIEW ACCESS 0 //OPERATION 1
    ADD OPTION aRot TITLE ‘Incluir’ ACTION ‘VIEWDEF.qprogra’ OPERATION MODEL_OPERATION_INSERT ACCESS 0 //OPERATION 2
    ADD OPTION aRot TITLE ‘Alterar’ ACTION ‘VIEWDEF.qprogra’ OPERATION MODEL_OPERATION_UPDATE ACCESS 0 //OPERATION 3
    ADD OPTION aRot TITLE ‘Excluir’ ACTION ‘VIEWDEF.qprogra’ OPERATION MODEL_OPERATION_DELETE ACCESS 0 //OPERATION 4

    Return aRot

    Pode dar um apoio Daniel.

    • Dan_Atilio disse:

      Bom dia Paulo, obrigado pelo feedback.
      Então, você está executando direto pelo menu ou fórmulas? Se for pelo fórmulas tente no Menu.
      E como disse nas aulas anteriores, a User Function tem que ter no máximo 7 caracteres, e o nome do prw tem que ser o mesmo nome da User Function. Por exemplo, zBeluga.prw e User Function zBeluga().
      Abraços.

      • PAULO JOSE ALCANTARA BORGES DE QUEIROZ disse:

        Perfeito Atilio, resolveu.
        Tinha nome de menu com mais de 7, “Visualizar”.

        Perdoe a insistência, deu um erro que eu acho que ta ligado ao sistema e não ao fonte/programa. Pode ver abaixo:

        THREAD ERROR ([8044], paulo, LAPTOP_PAULO) 07/08/2019 11:01:36
        argumento #0 , parâmetro oObj erro, previsto O->U on FWFORMVIEW:SETMODEL(FWFORMVIEW.PRW) 12/07/2019 17:33:43 line : 715

        Estou com o ambiente todo atualizado.
        Muito grato amigo, fico lhe devendo 2 com essa.

        Grande abraço, Paulo Queiroz.

        • Dan_Atilio disse:

          Boa tarde Paulo.
          Vixe, esse erro parece meio vago, precisaria ver o fonte mesmo.
          Entre no grupo do Discord, lá nós podemos te ajudar.
          Abraços.

  16. Fabrício Stefanini disse:

    Muito bom seus vídeos! Por favor faça mais vídeos!

  17. Leandro Lemos disse:

    Olá Dan, parabéns pelo conteúdo.
    Me orientando pelo seu material desenvolvi uma tela em que é digitado o Código do produto e já chamo uma função para preencher o resto dos campos e pular a linha, até ai tudo bem.
    Mas quando pula a linha o Foco sai do código do produto e na nova linha avança como se eu não tivesse pulado.
    Após preencher os campos eu estou fazendo seguinte

    oModel:AddLine()
    oView:Refresh()

    Estou tentando com SetFocus como vi no stackoverflow (https://pt.stackoverflow.com/questions/150088/como-setar-o-foco-em-um-componente-do-mvc), mas sem sucesso.
    Tem ideia do que pode ser feito?

    • Dan_Atilio disse:

      Bom dia Leandro, tudo bem?
      O trecho que passou parece certo, talvez o que você pode fazer, é na sua função de validar o código do produto, após adicionar a linha, você retornar um .F. para ele não prosseguir para o próximo campo.
      Qualquer coisa, me mande o fonte no Discord para analisarmos a situação.
      Abraços.

  18. Jonathan Oliveira disse:

    Maravilha,ajudou muito!!

  19. Bruno Laviano disse:

    Olá Dan, parabéns pelo conteúdo.
    Eu tenho o mesmo problema que “PAULO JOSE ALCANTARA”. Não consigo exibir as opções definidas no MenuDef ().
    O código é o mesmo que você publicou. Você me pode ajudar?
    Preciso de uma configuração extra em outro lugar? Obrigado

    • Dan_Atilio disse:

      Bom dia Bruno, obrigado pelo feedback.
      Então, o nome da user function tem que ser igual o nome do prw.
      O ideal é que tenha 7 caracteres.
      Porém pode ser algo do MenuDef, se possível nos mande o código fonte para avaliarmos.

  20. Jose Carlos disse:

    Boa Tarde, qual seria o motivo mais obvio para ao clicar em incluir o programa nao fazer nada ?

    • Dan_Atilio disse:

      Bom dia, segue abaixo as possibilidades.
      1. Nome da User Function difere do .prw
      2. Nome do MPFormModel é igual o da User Function
      3. Você estar executando no Mini Fórmulas / Lanç. Padrão sem estar usando SetFunName

  21. Jose de Aguiar Ferreira Real Neto disse:

    Gostei do seu video e estou tentando adaptar a minha necessidade, mas estou com um problema. Nesse modelo 3 que estou refazendo ele só vai alterar um campo do grid o que devo alterar no setProperty para deixar que os campos em questão fiquem apenas para visualização?

    • Dan_Atilio disse:

      Boa tarde José, obrigado pelo comentário.
      No caso, você precisa manipular o FWFormStruct, na posição que indica se o campo será alterável sim (.T.) ou não (.F.), dentro do seu ViewDef.
      Ai você teria duas opções, ou debugar e ver como está a variável e encontrar a posição, ou criar a estrutura, por exemplo:

      Static Function ViewDef()
          Local oModel     := FWLoadModel("zModel2")
          Local oStTmp     := FWFormViewStruct():New()
          Local oStFilho   := FWFormStruct(2, 'SX5')
          Local oView      := Nil
            
          //Adicionando o campo Chave para ser exibido
          oStTmp:AddField(;
              "X5_CHAVE",;                // [01]  C   Nome do Campo
              "01",;                      // [02]  C   Ordem
              "Tabela",;                  // [03]  C   Titulo do campo
              X3Descric('X5_TABELA'),;    // [04]  C   Descricao do campo
              Nil,;                       // [05]  A   Array com Help
              "C",;                       // [06]  C   Tipo do campo
              X3Picture("X5_TABELA"),;    // [07]  C   Picture
              Nil,;                       // [08]  B   Bloco de PictTre Var
              Nil,;                       // [09]  C   Consulta F3
              Iif(INCLUI, .T., .F.),;     // [10]  L   Indica se o campo é alteravel
              Nil,;                       // [11]  C   Pasta do campo
              Nil,;                       // [12]  C   Agrupamento do campo
              Nil,;                       // [13]  A   Lista de valores permitido do campo (Combo)
              Nil,;                       // [14]  N   Tamanho maximo da maior opção do combo
              Nil,;                       // [15]  C   Inicializador de Browse
              Nil,;                       // [16]  L   Indica se o campo é virtual
              Nil,;                       // [17]  C   Picture Variavel
              Nil)                        // [18]  L   Indica pulo de linha após o campo
      

      [/code]

  22. Jose de Aguiar Ferreira Real Neto disse:

    Bom dia. Grato pela resposta isso ajuda muito. Se vc puder fazer mais uma gentileza de me responder mais uma pergunta eu agradeço. Levando-se em conta que estou usando uma tabela do banco de dados. Exemplo SC5, a onde esta os campo do SX5 bastaria substituir pelos campo que quero do SC5?

    • Dan_Atilio disse:

      Bom dia José.
      Sim, só tome como atenção que a SC5 é cheia de gatilhos e validações internas.
      Então o campo que você quer alterar, se for campo customizado, daria certo, se for campo padrão, ai pode dar algum problema em validações internas.

  23. Aramis disse:

    Boa tarde, primeiramente obrigado pelo material !
    Estou tento um problema que ao clicar em qualquer botão seja incluir, alterar, visualizar ou excluir nada acontece.
    Também não está permetindo fechar a aba, aparece um alert escrito problema, sem texto

  24. Aramis disse:

    Perfeitamente isso mesmo, muito obrigado !
    Estou enfrentando outro problema agora, está dando o seguinte erro:
    erro no parâmetroFWFormField: O modeloZZEMASTERnão contem referência com o campoZZD_CODPRO on FWFORMFIELD:PREPAREFIELDS(FWFORMFIELD.PRW) 13/10/2020 16:57:59 line : 368

    Pelo o que eu entendi ele está pedindo um relacionamento da tabela pai com o codpro da filho, mas realmente não tem esse relacionamento !

    O que será que faço pra solucionar?

    • Dan_Atilio disse:

      Opa por nada.
      Quanto a esse erro, se você estiver montando uma tela com cabeçalho e grid (modelo 3), é obrigatório ter um relacionamento entre as duas tabelas.
      Por exemplo, imagine a tela de pedidos de venda, temos o cabeçalho (SC5) e os itens do pedido (SC6), e o relacionamento entre ambas é o número do pedido (C5_NUM e C6_NUM).

  25. Aramis disse:

    Entendi, o relacionamento existe com outros campos zzd_cod e zze_cod, mas ai informa este erro

  26. Aramis disse:

    oModel := MPFormModel():New(‘OCOA’)
    oModel:AddFields(‘ZZEMASTER’,/*cOwner*/,oStPai)
    oModel:AddGrid(‘ZZDDETAIL’,’ZZEMASTER’,oStFilho,/*bLinePre*/,/*bLinePost*/,/*bPre – Grid Inteiro*/,/*bPos – Grid Inteiro*/,/*bLoad – Carga do modelo manualmente*/)

    //Fazendo Relacionamento entre pai e filho
    aAdd(aZZDRel, {‘ZZD_FILIAL’ , ‘xFilial(“ZZE”)’})
    aAdd(aZZDRel, {‘ZZD_COD’ , ‘ZZE_COD’})

    oModel:SetRelation(‘ZZDDETAIL’,aZZDRel,ZZD->(IndexKey(1)))
    oModel:SetPrimaryKey({})

Deixe uma resposta