Continuando o desenvolvimento do WorkFlow de Produtos via link, nesse post vou desenvolver o fonte responsável pelo envio e retorno do WF e também o layout html.
Algumas alterações foram feitas nos fontes da parte 1, então é importante dar um pull no repositório e baixar as atualizações, link do repositório no final do post
Todo o material da serie foi desenvolvido especialmente para as postagens, os fontes estão de fácil entendimento e bem comentados o que facilita a adaptação a outros processos, e também tem espaço para lapidar o processo criado incluindo barra de processamento quando altera ou clica em aprovação, alterando o status do produto após o disparo do e-mail, gravar o usuário solicitante, etc.
Qualquer dificuldade ou sugestão deixe nos comentários.
Vídeo do processo
Fonte responsável pelo envio retorno do WorkFlow
Atenção com os parâmetros criados e com folder onde irão os layouts html.
#Include "TOTVS.ch"
/*/{Protheus.doc} User Function wfProdutos
(Fonte para envio de WorkFlow para Fluxo de Cadastro de Produto)
@type Function
@author Leandro Lemos
@since 16/12/2020
@version P12
@param cProduto, param_type, param_descr
@return lOk, bool, confirmação ou não da operação
@example
(examples)
@see (links_or_references)
/*/
User Function wfProdutos(cProduto)
Local lOk := .T.
Local cFolderLyt := 'workflow\html\'
Local cLayoutHTML := 'wfProdutos.html'
Local lAtivWFPro := SUPERGETMV("MV_ZATIVWFP", .F., .F.)
Local cUserAprov := SUPERGETMV("MV_ZUSRAPRO", .T., '000006') //Usuário aprovador do processo, código e não nome de usuário
Local cMailAprov := UsrRetMail(cUserAprov)
Local cContaWF := SUPERGETMV("MV_YCONTA", .T., "workflow@erplabs.com.br") //Conta de e-mail para recebimento da resposta do WF
Local cUsrProcess := '000000'
Local cLink := ''
Local cWSLink := 'http://localhost:8090/'
Private oHtml
//Se o e-mail do aprovador for valido entra na condição
IF ISEMAIL(cMailAprov) .and. lAtivWFPro
oProcess := TWFProcess():New( "WFWPRO", "WorkFLow Produtos" )
oProcess:NewTask( "WorkFLow aprovação de Cadastro de Produtos", cFolderLyt+cLayoutHTML )
oProcess:cTo := cUsrProcess //Codigo do usuario ou email
oProcess:bReturn := "U_wfPrdRet()"
oProcess:cSubject := "WorkFLow aprovação de Cadastro de Produto, Produto " + alltrim(SB1->B1_COD) + '-' + alltrim(SB1->B1_DESC)
oProcess:UserSiga := cUserAprov //"000000"
oProcess:NewVersion(.T.)
oHtml := oProcess:oHTML
oHtml:ValByName( "cCodPro" , SB1->B1_COD )
oHtml:ValByName( "cProDesc" , EncodeUtf8(alltrim(SB1->B1_DESC)))
oHtml:ValByName( "cProUmc" , SB1->B1_UM )
oHtml:ValByName( "cProTipo" , SB1->B1_TIPO )
oProcess:nEncodeMime := 0
//Iniciando e gravando aquivo do processo
cProcess := oProcess:Start("\workflow\messenger\emp" +cEmpAnt + "\" + cUsrProcess + "\")
//Carregando nome do arquivo html gerado
cHtmlFile := cProcess + ".htm"
cMailTo := "mailto:" + cContaWF
//Lendo arquivo e armazenando na variavel
cHtml := wfloadfile("\workflow\messenger\emp" +cEmpAnt + "\" + cUsrProcess + "\" + cHtmlFile )
//Substituido o email no corpo do form pelo WFHTTPRET.APL
cHtml := strtran( cHtml, cMailTo, "WFHTTPRET.APL" )
//Gerando HTML para ser acessado via Link
wfsavefile("\workflow\messenger\emp" +cEmpAnt + "\" + cUsrProcess + "\" + cHtmlFile+"l", cHtml)
//Apagando o arquivo gerado
fErase("\workflow\messenger\emp" +cEmpAnt + "\" + cUsrProcess + "\" + cHtmlFile)
//Link do processo, ainda faltando o endereço/dominio do WS
cLink := cWSLink+'/workflow/messenger/emp' +cEmpAnt + '/' + cUsrProcess + '/' + alltrim(cProcess) + '.html
//Notificando aprovador
wfNotifica(cUserAprov,cMailAprov,oProcess:cSubject,cLink)
Else
MsgInfo('E-mail cadastrado para o usuario '+FwGetUserName(cUserAprov)+' é invalido, favor verificar')
EndIF
Return lOk
/*/{Protheus.doc} wfNotifica
(Função responsável por notificar ao usuario que existe alçada pendente de ação)
@type Static Function
@author Leandro Lemos
@since 29/12/2020
@version P12
@param param_name, param_type, param_descr
@return return_var, return_type, return_description
@example
(examples)
@see (links_or_references)
/*/
Static Function wfNotifica(cUserAprov,cTo,cSubject,cLink)
Local lOk := .T.
Local cHtml := ''
Local cFolderLyt := 'workflow\html\'
Local cLayoutHTML := 'notificacao.html'
//Carregando arquivo
cHtml := wfloadfile(cFolderLyt+cLayoutHTML)
cHtml := strtran( cHtml, '%cLink%', cLink ) //Link para aprovaçao
cHtml := strtran( cHtml, '%cNomeAprov%',FwGetUserName(cUserAprov) ) //Nome do Usuario
cHtml := strtran( cHtml, '%dDataAlc%', cValToChar(dDatabase) ) //Data da notificação
WFNotifyAdmin( cTo , cSubject, cHtml )
Return lOk
/*/{Protheus.doc} User Function wfPrdRet
(Fonte responsável pelo retorno do WF)
@type Function
@author Leandro Lemos
@since 16/12/2020
@version P12
@param , ,
@return lOk, bool, confirmação ou não da operação
@example
(examples)
@see (links_or_references)
/*/
User Function wfPrdRet(oProcess)
Local cMessage := ""
Local cStatusRet:= IIF(alltrim(oProcess:oHtml:RetByName("Aprovacao")) == 'S','03','04')
Local cCodPro := alltrim(oProcess:oHtml:RetByName("cCodPro"))
Local nX := 0
Local lOk := .T.
//variável de controle interno da rotina automatica que informa se houve erro durante o processamento
Private lMsErroAuto := .F.
//força a gravação das informações de erro em array para manipulação da gravação ao invés de gravar direto no arquivo temporário
Private lAutoErrNoFile := .T.
DBSelectArea('Sb1')
DBSetOrder(1)
IF (DBSeek(xFilial('SB1')+cCodPro))
//Para o exemplo do post, vou usar o execauto para alterar o cadastro, mas dependendo do cadastro pode
//não haver rotina automatica disponivel
aProd:= { {"B1_COD" , cCodPro,NIL},;
{"B1_ZSITUA" ,cStatusRet,NIL}}
MSExecAuto({|x,y| Mata010(x,y)},aProd,4)
//Tratando erros caso ocorram
IF lMsErroAuto
aLog := GetAutoGRLog()
//Tratamento para o retorno do erro
For nX := 1 to len(aLog)
cErrorAuto += alltrim(EncodeUTF8(aLog[nX]))+'</br>'
Next
cSubject := 'Erro alçada produto '+ cCodPro
wfNoticaError("suporte@erplabs.com.br",cSubject,cErrorAuto)
EndIF
Else
//Notificação caso haja algum problema ao posicionar o produto no retorno
cSubject := 'Erro alçada produto '+ cCodPro
cMessage := "Não foi possivel posicionar no produto para atualização</br>"
wfNoticaError("suporte@erplabs.com.br",cSubject,cMessage)
EndIF
Return lOk
/*/{Protheus.doc} NotifaErro
(Notifica adm de erro no processo)
@type Static Function
@author Leandro Lemos
@since 30/12/2020
@version P12
@param param_name, param_type, param_descr
@return return_var, return_type, return_description
@example
(examples)
@see (links_or_references)
/*/
Static Function wfNoticaError(cTo , cSubject, cMessage)
Local aHtml := {}
Local cHtml := ''
Local nI := 0
aAdd(aHtml,"<html>")
aAdd(aHtml,"<head>")
aAdd(aHtml,"<meta charset='utf-8' />")
aAdd(aHtml,"<title>Notificação WorkFlow alçada de produtos - ERPLabs</title>")
aAdd(aHtml,"</head>")
aAdd(aHtml,"<body>")
aAdd(aHtml,"<table>")
aAdd(aHtml,"<tr>")
aAdd(aHtml,"<td>")
aAdd(aHtml,cSubject)
aAdd(aHtml,"</td>")
aAdd(aHtml,"</tr>")
aAdd(aHtml,"<tr>")
aAdd(aHtml,"<td>")
aAdd(aHtml,cMessage)
aAdd(aHtml,"</td>")
aAdd(aHtml,"</tr>")
aAdd(aHtml,"</body>")
aAdd(aHtml,"</html>")
For nI := 1 to len(aHtml)
cHtml += aHtml[nI]
Next
WFNotifyAdmin( 'suporte@erplabs.com.br' , , cHtml )
Return return_var
Layouts HTML
Notificação
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Notificação WorkFlow alçada de produtos - ERPLabs</title>
<style>
.invoice-box {
max-width: 800px;
margin: auto;
padding: 30px;
border: 1px solid #eee;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
font-size: 16px;
line-height: 24px;
font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
color: #555;
}
.invoice-box table {
width: 100%;
line-height: inherit;
text-align: left;
}
.invoice-box table td {
padding: 5px;
vertical-align: top;
}
.invoice-box table tr td:nth-child(2) {
text-align: right;
}
.invoice-box table tr.top table td {
padding-bottom: 20px;
}
.invoice-box table tr.top table td.title {
font-size: 45px;
line-height: 45px;
color: #333;
}
.invoice-box table tr.information table td {
padding-bottom: 40px;
}
.invoice-box table tr.heading td {
background: #eee;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
.invoice-box table tr.details td {
padding-bottom: 20px;
}
.invoice-box table tr.item td {
border-bottom: 1px solid #eee;
}
.invoice-box table tr.item.last td {
border-bottom: none;
}
.invoice-box table tr.total td:nth-child(2) {
border-top: 2px solid #eee;
font-weight: bold;
}
.invoice-box a {
background-color: #ffffff;
border: solid 1px #eb0684;
border-radius: 5px;
box-sizing: border-box;
color: #eb0684;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
}
@media only screen and (max-width: 600px) {
.invoice-box table tr.top table td {
width: 100%;
display: block;
text-align: center;
}
.invoice-box table tr.information table td {
width: 100%;
display: block;
text-align: center;
}
}
/** RTL **/
.rtl {
direction: rtl;
font-family: Tahoma, 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
}
.rtl table {
text-align: right;
}
.rtl table tr td:nth-child(2) {
text-align: left;
}
</style>
</head>
<body>
<div class='invoice-box'>
<table cellpadding='0' cellspacing='0'>
<tr class='top'>
<td colspan='4'>
<table>
<tr>
<td class='title'>
<img src='https://bit.ly/3psVrhU' />
</td>
<td>
Alçada Cadastro de Produtos<br /> Data: %dDataAlc%
</td>
</tr>
</table>
</td>
</tr>
<tr class='information'>
<td colspan='2'>
<table>
<tr>
<td>
Prezado %cNomeAprov%<br /> Existe um produto aguardando sua ação para seguir com cadastro.</br>
Clique abaixo para visualizar.
</td>
</tr>
<tr>
<td>
<a href='%cLink%' target='_blank'>Visualizar</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</body>
</html>
Aprovação
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WorkFlow alçada de produtos - ERPLabs</title>
<style>
.invoice-box {
max-width: 800px;
margin: auto;
padding: 30px;
border: 1px solid #eee;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
font-size: 16px;
line-height: 24px;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
color: #555;
}
.invoice-box table {
width: 100%;
line-height: inherit;
text-align: left;
}
.invoice-box table td {
padding: 5px;
vertical-align: top;
}
.invoice-box table tr td:nth-child(2) {
text-align: right;
}
.invoice-box table tr.top table td {
padding-bottom: 20px;
}
.invoice-box table tr.top table td.title {
font-size: 45px;
line-height: 45px;
color: #333;
}
.invoice-box table tr.information table td {
padding-bottom: 40px;
}
.invoice-box table tr.heading td {
background: #eee;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
.invoice-box table tr.details td {
padding-bottom: 20px;
}
.invoice-box table tr.item td {
border-bottom: 1px solid #eee;
}
.invoice-box table tr.item.last td {
border-bottom: none;
}
.invoice-box table tr.total td:nth-child(2) {
border-top: 2px solid #eee;
font-weight: bold;
}
@media only screen and (max-width: 600px) {
.invoice-box table tr.top table td {
width: 100%;
display: block;
text-align: center;
}
.invoice-box table tr.information table td {
width: 100%;
display: block;
text-align: center;
}
}
/** RTL **/
.rtl {
direction: rtl;
font-family: Tahoma, "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
}
.rtl table {
text-align: right;
}
.rtl table tr td:nth-child(2) {
text-align: left;
}
</style>
</head>
<body>
<form method="post" action="mailto:%WFMailTo%" name="FrontPage_Form1" onSubmit="return FrontPage_Form1_Validator(this)">
<div class="invoice-box">
<table cellpadding="0" cellspacing="0">
<tr class="top">
<td colspan="4">
<table>
<tr>
<td class="title">
<img src="https://bit.ly/3psVrhU" />
</td>
<td>
Alçada Cadastro de Produtos<br />Produto #: %cCodPro% <br /> Data: %dDataAlc%
</td>
</tr>
</table>
</td>
</tr>
<tr class="information">
<td colspan="2">
<table>
<tr>
</tr>
<tr>
<td>
Prezado %cNomeAprov%<br /> O produto %cCodPro% (%cProDesc%) aguarda sua ação.
</td>
</tr>
</table>
</td>
</tr>
<tr class="heading">
<td>Codigo</td>
<td>Descrição</td>
<td>UM</td>
<td>Tipo</td>
</tr>
<tr class="item">
<td>%cCodPro%</td>
<td>%cProDesc%</td>
<td>%cProUmc%</td>
<td>%cProTipo%</td>
</tr>
<tr class="information">
<td colspan="2">
<table>
<tr>
</tr>
<tr>
<td>
<br/>Esse documento elimina a necessidade de formulario impresso para aprovação
</td>
</tr>
</table>
</td>
</tr>
</table>
<tr class="information">
<td colspan="2">
<table>
<tr>
</tr>
<tr>
<td>Aprovado?</br>
<input type="radio" checked name="Aprovacao" value="%Aprovacao%S" /> Sim
<input type="radio" name="Aprovacao" value="%Aprovacao%N" />Não
</br><input type="submit" name="B1" value="Enviar" />
</td>
</tr>
</table>
</td>
</tr>
</div>
</form>
</body>
</html>
Links
Parte 1
https://erplabs.com.br/workflow-de-produtos-advpl-via-link-parte-01/
Repositório
http://bit.ly/2KDHZJp
Referencias
https://tdn.totvs.com/display/public/PROT/WF0016_Rastreabilidade_do_Workflow
https://tdn.totvs.com/display/public/PROT/TWFProcess
Template HTML
https://github.com/sparksuite/simple-html-invoice-template