#Include "Protheus.ch" #Include "FWMVCDef.ch" /*/{Protheus.doc} PGV020 Cadastro de contratos em MVC com cabecalho e itens -- o "modelo 3" do ERP. Cabecalho na tabela ficticia Z51, itens na Z52. Alem do que o PGV010 ja mostra, este fonte tem os quatro pontos que so aparecem quando existe grid: AddGrid, SetRelation, a pos-validacao de linha e um commit proprio que soma os itens no cabecalho antes de gravar. Exemplo didatico do site. As tabelas Z51 e Z52 nao existem no dicionario padrao; crie-as no Configurador antes de compilar. @type user function @author Pragiva @since 20/09/2026 @version 1.0 @return nil @example U_PGV020() /*/ User Function PGV020() Local oBrowse := Nil oBrowse := FWMBrowse():New() oBrowse:SetAlias("Z51") oBrowse:SetDescription("Contratos") oBrowse:SetMenuDef("PGV020") oBrowse:Activate() Return Nil /*/{Protheus.doc} MenuDef As opcoes do browse. Identicas as do modelo 1: ter grid nao muda o menu. @type static function @return array, o aRotina do browse /*/ Static Function MenuDef() Local aRotina := {} ADD OPTION aRotina TITLE "Pesquisar" ACTION "AxPesqui" OPERATION 1 ACCESS 0 ADD OPTION aRotina TITLE "Visualizar" ACTION "VIEWDEF.PGV020" OPERATION 2 ACCESS 0 ADD OPTION aRotina TITLE "Incluir" ACTION "VIEWDEF.PGV020" OPERATION 3 ACCESS 0 ADD OPTION aRotina TITLE "Alterar" ACTION "VIEWDEF.PGV020" OPERATION 4 ACCESS 0 ADD OPTION aRotina TITLE "Excluir" ACTION "VIEWDEF.PGV020" OPERATION 5 ACCESS 0 Return aRotina /*/{Protheus.doc} ModelDef O modelo pai-filho. A ordem dos blocos de AddGrid nao e a mesma de AddFields: aqui os dois primeiros sao os de LINHA (bLinePre, bLinePost) e so depois vem os do formulario (bPre, bPost) e a carga. Passar a pos-validacao de linha na posicao errada compila e nao roda nunca. @type static function @return object, o modelo com cabecalho e itens /*/ Static Function ModelDef() Local oModel := Nil Local oStruCab := FWFormStruct(1, "Z51") Local oStruItem := FWFormStruct(1, "Z52") // Posicao 3: pos-validacao do modelo. Preenche-la SUBSTITUI o default // do MPFormModel, que e FWFormCanDel -- dai PGV020Pos devolver // FWFormCanDel na exclusao, e nao .T. oModel := MPFormModel():New("PGV020M", /*bPre*/, {|oMdl| PGV020Pos(oMdl)}, ; {|oMdl| PGV020Com(oMdl)}, /*bCancel*/) oModel:AddFields("Z51MASTER", /*cOwner*/, oStruCab) oModel:AddGrid("Z52DETAIL", "Z51MASTER", oStruItem, ; /*bLinePre*/, {|oGrid| PGV020Lin(oGrid)}, ; /*bPre*/, /*bPost*/, /*bLoad*/) // O lado direito de cada par e campo do PAI quando e nome de campo, e // expressao avaliada quando e string de expressao. A ordenacao e a chave // de indice da tabela filha, lida dela mesma. oModel:SetRelation("Z52DETAIL", { ; {"Z52_FILIAL", 'xFilial("Z52")'}, ; {"Z52_NUM" , "Z51_NUM"} ; }, Z52->(IndexKey(1))) oModel:SetDescription("Contratos") oModel:GetModel("Z51MASTER"):SetDescription("Cabecalho do contrato") oModel:GetModel("Z52DETAIL"):SetDescription("Itens do contrato") oModel:SetPrimaryKey({"Z51_FILIAL", "Z51_NUM"}) // Comportamento observado: .F. torna o grid obrigatorio -- o // contrato nao fecha sem pelo menos um item. oModel:GetModel("Z52DETAIL"):SetOptional(.F.) // Duas linhas do mesmo produto no mesmo contrato viram erro do // framework, sem precisar de codigo. oModel:GetModel("Z52DETAIL"):SetUniqueLine({"Z52_PROD"}) Return oModel /*/{Protheus.doc} ViewDef A tela do pai-filho: cabecalho em cima, itens embaixo. Os campos de vinculo saem da ESTRUTURA DA VIEW, nao da do modelo. Tirados do modelo, eles deixariam de ser gravados; tirados so da view, somem da tela e continuam sendo preenchidos pelo SetRelation. @type static function @return object, a view ligada ao modelo /*/ Static Function ViewDef() Local oView := Nil Local oModel := FWLoadModel("PGV020") Local oStruCab := FWFormStruct(2, "Z51") Local oStruItem := FWFormStruct(2, "Z52") // Os DOIS campos do SetRelation saem da tela. FWFormStruct(2,...) nao // descarta a filial sozinho. oStruItem:RemoveField("Z52_FILIAL") oStruItem:RemoveField("Z52_NUM") oView := FWFormView():New() oView:SetModel(oModel) oView:AddField("VIEW_CAB" , oStruCab , "Z51MASTER") oView:AddGrid ("VIEW_ITEM", oStruItem, "Z52DETAIL") // Numeracao automatica do item. E da VIEW, nao do modelo. oView:AddIncrementField("VIEW_ITEM", "Z52_ITEM") oView:CreateHorizontalBox("SUPERIOR", 35) oView:CreateHorizontalBox("INFERIOR", 65) oView:SetOwnerView("VIEW_CAB" , "SUPERIOR") oView:SetOwnerView("VIEW_ITEM", "INFERIOR") Return oView /*/{Protheus.doc} PGV020Lin Pos-validacao de LINHA do grid: roda uma vez por linha, na confirmacao. Linha marcada para exclusao nao se valida -- senao o usuario nao consegue apagar justamente a linha errada que o fez abrir a tela. FWAlertError e MsgAlert aqui fazem o usuario ver DUAS mensagens: a sua e a que o framework exibe em seguida. @type static function @param oGrid, object, o submodelo de grid, ja posicionado na linha @return logical, .F. barra a confirmacao /*/ Static Function PGV020Lin(oGrid) Local lOk := .T. If oGrid:IsDeleted() Return .T. EndIf // O submodelo de grid nao implementa SetErrorMessage; quem tem o metodo // e o modelo principal, alcancado por oGrid:GetModel(). E as posicoes 3 // e 4 sao as do erro -- e a 3a que o framework le. If oGrid:GetValue("Z52_QUANT") <= 0 oGrid:GetModel():SetErrorMessage( , , oGrid:GetId(), "Z52_QUANT", ; "PGV020", "Quantidade invalida", ; "Informe uma quantidade maior que zero.") lOk := .F. EndIf Return lOk /*/{Protheus.doc} PGV020Pos Pos-validacao do modelo inteiro. Ela roda na inclusao, na alteracao E na exclusao. Sem filtrar pela operacao, uma regra escrita para a inclusao passa a impedir que alguem apague o registro. @type static function @param oModel, object, o modelo principal @return logical, .F. barra a confirmacao /*/ Static Function PGV020Pos(oModel) Local lOk := .T. // FWFormCanDel, e nao .T.: um bPost proprio SUBSTITUI esse default do // MPFormModel, que e quem confere chave estrangeira antes de apagar. If oModel:GetOperation() == MODEL_OPERATION_DELETE Return FWFormCanDel(oModel) EndIf // Posicoes 3 e 4 sao as do erro, e e a 3a que o framework le. Preencher // 1 e 2 e deixar 3 e 4 vazios faz a mensagem se perder. If Empty(oModel:GetValue("Z51MASTER", "Z51_CLIENTE")) oModel:SetErrorMessage( , , "Z51MASTER", "Z51_CLIENTE", ; "PGV020", "Cliente obrigatorio", ; "Informe o cliente do contrato.") lOk := .F. EndIf Return lOk /*/{Protheus.doc} PGV020Com Commit proprio: soma os itens no total do cabecalho e grava. Duas coisas que o nome nao entrega. Primeira: GetValue e IsDeleted do grid aceitam o NUMERO DA LINHA, entao se varre por indice sem mover o grid -- nao e preciso GoLine. Segunda: o retorno diz GRAVOU, nao PODE GRAVAR; retornar .F. aqui deixa os dados no banco e a tela aberta. Quem barra e a pos-validacao, que roda antes. @type static function @param oModel, object, o modelo principal @return logical, resultado da gravacao /*/ Static Function PGV020Com(oModel) Local lCommit := .F. Local oGrid := oModel:GetModel("Z52DETAIL") Local nTotal := 0 Local nLinha := 0 Local nLinhas := oGrid:Length() For nLinha := 1 To nLinhas If !oGrid:IsDeleted(nLinha) nTotal += oGrid:GetValue("Z52_VALOR", nLinha) EndIf Next nLinha oModel:GetModel("Z51MASTER"):SetValue("Z51_VALOR", nTotal) lCommit := FWFormCommit(oModel) Return lCommit