@netoalmanca/advpl-sensei 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/changelog-generator.md +63 -0
- package/agents/code-generator.md +215 -0
- package/agents/code-reviewer.md +145 -0
- package/agents/debugger.md +83 -0
- package/agents/doc-generator.md +67 -0
- package/agents/docs-reference.md +86 -0
- package/agents/migrator.md +84 -0
- package/agents/process-consultant.md +97 -0
- package/agents/refactorer.md +75 -0
- package/agents/sx-configurator.md +67 -0
- package/commands/changelog.md +66 -0
- package/commands/diagnose.md +67 -0
- package/commands/docs.md +81 -0
- package/commands/document.md +67 -0
- package/commands/explain.md +60 -0
- package/commands/generate.md +111 -0
- package/commands/migrate.md +81 -0
- package/commands/process.md +111 -0
- package/commands/refactor.md +65 -0
- package/commands/review.md +60 -0
- package/commands/sxgen.md +98 -0
- package/commands/test.md +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
- package/skills/advpl-code-generation/SKILL.md +163 -0
- package/skills/advpl-code-generation/patterns-fwformbrowse.md +485 -0
- package/skills/advpl-code-generation/patterns-jobs.md +519 -0
- package/skills/advpl-code-generation/patterns-mvc.md +765 -0
- package/skills/advpl-code-generation/patterns-pontos-entrada.md +708 -0
- package/skills/advpl-code-generation/patterns-rest.md +974 -0
- package/skills/advpl-code-generation/patterns-soap.md +639 -0
- package/skills/advpl-code-generation/patterns-treport.md +481 -0
- package/skills/advpl-code-generation/patterns-workflow.md +779 -0
- package/skills/advpl-code-generation/templates-classes.md +1096 -0
- package/skills/advpl-code-review/SKILL.md +72 -0
- package/skills/advpl-code-review/rules-best-practices.md +444 -0
- package/skills/advpl-code-review/rules-modernization.md +290 -0
- package/skills/advpl-code-review/rules-performance.md +333 -0
- package/skills/advpl-code-review/rules-security.md +302 -0
- package/skills/advpl-debugging/SKILL.md +265 -0
- package/skills/advpl-debugging/common-errors.md +1124 -0
- package/skills/advpl-debugging/performance-tips.md +768 -0
- package/skills/advpl-refactoring/SKILL.md +139 -0
- package/skills/advpl-to-tlpp-migration/SKILL.md +293 -0
- package/skills/advpl-to-tlpp-migration/migration-checklist.md +122 -0
- package/skills/advpl-to-tlpp-migration/migration-rules.md +265 -0
- package/skills/changelog-patterns/SKILL.md +99 -0
- package/skills/code-explanation/SKILL.md +66 -0
- package/skills/documentation-patterns/SKILL.md +172 -0
- package/skills/embedded-sql/SKILL.md +379 -0
- package/skills/probat-testing/SKILL.md +226 -0
- package/skills/probat-testing/patterns-unit-tests.md +614 -0
- package/skills/protheus-business/SKILL.md +92 -0
- package/skills/protheus-business/modulo-compras.md +780 -0
- package/skills/protheus-business/modulo-contabilidade.md +874 -0
- package/skills/protheus-business/modulo-estoque.md +876 -0
- package/skills/protheus-business/modulo-faturamento.md +800 -0
- package/skills/protheus-business/modulo-financeiro.md +1015 -0
- package/skills/protheus-business/modulo-fiscal.md +749 -0
- package/skills/protheus-business/modulo-manutencao.md +848 -0
- package/skills/protheus-business/modulo-pcp.md +743 -0
- package/skills/protheus-reference/SKILL.md +119 -0
- package/skills/protheus-reference/native-functions.md +7029 -0
- package/skills/protheus-reference/rest-api-reference.md +1758 -0
- package/skills/protheus-reference/restricted-functions.md +265 -0
- package/skills/protheus-reference/sx-dictionary.md +854 -0
- package/skills/sx-configuration/SKILL.md +184 -0
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
# Protheus Workflow and Approval Patterns
|
|
2
|
+
|
|
3
|
+
Complete reference for implementing approval workflows, automatic routines (MsExecAuto), and email notifications in TOTVS Protheus.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview
|
|
8
|
+
|
|
9
|
+
TOTVS Protheus provides built-in support for document approval workflows through the approval control system (Alcada), automatic routine execution (MsExecAuto/FWMVCRotAuto), and email notifications.
|
|
10
|
+
|
|
11
|
+
Key components:
|
|
12
|
+
- **MsExecAuto**: Executes standard Protheus routines (ExecAuto) programmatically without user interface.
|
|
13
|
+
- **FWMVCRotAuto**: Executes MVC-based routines programmatically.
|
|
14
|
+
- **SCR Table**: Stores documents pending approval (Documentos com Alcada).
|
|
15
|
+
- **SAK Table**: Stores approver definitions and limits.
|
|
16
|
+
- **TMailManager**: Email server communication class (SMTP/POP3).
|
|
17
|
+
- **TMailMessage**: Email message composition and sending.
|
|
18
|
+
|
|
19
|
+
Required includes:
|
|
20
|
+
```advpl
|
|
21
|
+
#Include "TOTVS.CH"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For MVC operations:
|
|
25
|
+
```advpl
|
|
26
|
+
#Include "TOTVS.CH"
|
|
27
|
+
#Include "FWMVCDef.ch"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. MsExecAuto - Automatic Routine Execution
|
|
33
|
+
|
|
34
|
+
### 2.1 Syntax
|
|
35
|
+
|
|
36
|
+
```advpl
|
|
37
|
+
MsExecAuto({|x,y,z| ROUTINE(x,y,z)}, aCabec, aItens, nOpc)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Parameter | Type | Description |
|
|
41
|
+
|-----------|------|-------------|
|
|
42
|
+
| Block | Block | Code block calling the target routine |
|
|
43
|
+
| `aCabec` | Array | Header fields array `{{"FIELD", value, Nil}, ...}` |
|
|
44
|
+
| `aItens` | Array | Item lines array (for master-detail routines) |
|
|
45
|
+
| `nOpc` | Numeric | Operation: `3`=Include, `4`=Update, `5`=Delete |
|
|
46
|
+
|
|
47
|
+
### 2.2 Standard Variables
|
|
48
|
+
|
|
49
|
+
Before calling MsExecAuto, declare these Private variables:
|
|
50
|
+
|
|
51
|
+
```advpl
|
|
52
|
+
Private lMsErroAuto := .F. // Error flag
|
|
53
|
+
Private lMsHelpAuto := .T. // Suppress Help dialogs
|
|
54
|
+
Private lAutoErrNoFile := .T. // Errors to memory (not file)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2.3 Error Handling
|
|
58
|
+
|
|
59
|
+
```advpl
|
|
60
|
+
If lMsErroAuto
|
|
61
|
+
// Erro ocorreu
|
|
62
|
+
Local cErro := MostraErro(.F.) // .F. retorna string em vez de exibir dialog
|
|
63
|
+
ConOut("Erro na rotina automatica: " + cErro)
|
|
64
|
+
Else
|
|
65
|
+
ConOut("Rotina executada com sucesso.")
|
|
66
|
+
EndIf
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2.4 Include Purchase Request (MATA110)
|
|
70
|
+
|
|
71
|
+
```advpl
|
|
72
|
+
#Include "TOTVS.CH"
|
|
73
|
+
|
|
74
|
+
/*/{Protheus.doc} zIncSC
|
|
75
|
+
Inclusao de Solicitacao de Compras via MsExecAuto
|
|
76
|
+
@type User Function
|
|
77
|
+
@author Autor
|
|
78
|
+
@since 01/01/2026
|
|
79
|
+
@version 1.0
|
|
80
|
+
/*/
|
|
81
|
+
User Function zIncSC()
|
|
82
|
+
Local aArea := GetArea()
|
|
83
|
+
Local aCabec := {}
|
|
84
|
+
Local aItens := {}
|
|
85
|
+
Local aLinha := {}
|
|
86
|
+
|
|
87
|
+
Private lMsErroAuto := .F.
|
|
88
|
+
Private lMsHelpAuto := .T.
|
|
89
|
+
Private lAutoErrNoFile := .T.
|
|
90
|
+
|
|
91
|
+
// Cabecalho (pode nao ser necessario para todas as rotinas)
|
|
92
|
+
// MATA110 trabalha diretamente nos itens
|
|
93
|
+
|
|
94
|
+
// Item 1
|
|
95
|
+
aLinha := {}
|
|
96
|
+
aAdd(aLinha, {"C1_PRODUTO", "000001", Nil})
|
|
97
|
+
aAdd(aLinha, {"C1_QUANT", 10, Nil})
|
|
98
|
+
aAdd(aLinha, {"C1_DESCRI", "PRODUTO TESTE", Nil})
|
|
99
|
+
aAdd(aLinha, {"C1_UM", "UN", Nil})
|
|
100
|
+
aAdd(aLinha, {"C1_LOCAL", "01", Nil})
|
|
101
|
+
aAdd(aLinha, {"C1_DATPRF", Date() + 30, Nil})
|
|
102
|
+
aAdd(aItens, aLinha)
|
|
103
|
+
|
|
104
|
+
// Item 2
|
|
105
|
+
aLinha := {}
|
|
106
|
+
aAdd(aLinha, {"C1_PRODUTO", "000002", Nil})
|
|
107
|
+
aAdd(aLinha, {"C1_QUANT", 5, Nil})
|
|
108
|
+
aAdd(aLinha, {"C1_DESCRI", "PRODUTO TESTE 2", Nil})
|
|
109
|
+
aAdd(aLinha, {"C1_UM", "UN", Nil})
|
|
110
|
+
aAdd(aLinha, {"C1_LOCAL", "01", Nil})
|
|
111
|
+
aAdd(aLinha, {"C1_DATPRF", Date() + 30, Nil})
|
|
112
|
+
aAdd(aItens, aLinha)
|
|
113
|
+
|
|
114
|
+
// Executa inclusao (opcao 3)
|
|
115
|
+
MsExecAuto({|x, y, z| MATA110(x, y, z)}, aCabec, aItens, 3)
|
|
116
|
+
|
|
117
|
+
If lMsErroAuto
|
|
118
|
+
ConOut("Erro na inclusao da SC: " + MostraErro(.F.))
|
|
119
|
+
Else
|
|
120
|
+
ConOut("Solicitacao de compras incluida com sucesso.")
|
|
121
|
+
EndIf
|
|
122
|
+
|
|
123
|
+
RestArea(aArea)
|
|
124
|
+
|
|
125
|
+
Return Nil
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2.5 Include Sales Order (MATA410)
|
|
129
|
+
|
|
130
|
+
```advpl
|
|
131
|
+
User Function zIncPV()
|
|
132
|
+
Local aCabec := {}
|
|
133
|
+
Local aItens := {}
|
|
134
|
+
Local aLinha := {}
|
|
135
|
+
Local aArea := GetArea()
|
|
136
|
+
|
|
137
|
+
Private lMsErroAuto := .F.
|
|
138
|
+
Private lMsHelpAuto := .T.
|
|
139
|
+
Private lAutoErrNoFile := .T.
|
|
140
|
+
|
|
141
|
+
// Cabecalho do pedido de venda
|
|
142
|
+
aAdd(aCabec, {"C5_TIPO", "N", Nil})
|
|
143
|
+
aAdd(aCabec, {"C5_CLIENTE", "000001", Nil})
|
|
144
|
+
aAdd(aCabec, {"C5_LOJACLI", "01", Nil})
|
|
145
|
+
aAdd(aCabec, {"C5_CONDPAG", "001", Nil})
|
|
146
|
+
|
|
147
|
+
// Item 1
|
|
148
|
+
aLinha := {}
|
|
149
|
+
aAdd(aLinha, {"C6_ITEM", "01", Nil})
|
|
150
|
+
aAdd(aLinha, {"C6_PRODUTO", "000001", Nil})
|
|
151
|
+
aAdd(aLinha, {"C6_QTDVEN", 10, Nil})
|
|
152
|
+
aAdd(aLinha, {"C6_PRCVEN", 50.00, Nil})
|
|
153
|
+
aAdd(aLinha, {"C6_PRUNIT", 50.00, Nil})
|
|
154
|
+
aAdd(aLinha, {"C6_VALOR", 500.00, Nil})
|
|
155
|
+
aAdd(aLinha, {"C6_TES", "501", Nil})
|
|
156
|
+
aAdd(aItens, aLinha)
|
|
157
|
+
|
|
158
|
+
// Executa inclusao
|
|
159
|
+
MsExecAuto({|x, y, z| MATA410(x, y, z)}, aCabec, aItens, 3)
|
|
160
|
+
|
|
161
|
+
If lMsErroAuto
|
|
162
|
+
ConOut("Erro na inclusao do PV: " + MostraErro(.F.))
|
|
163
|
+
Else
|
|
164
|
+
ConOut("Pedido de venda incluido com sucesso.")
|
|
165
|
+
EndIf
|
|
166
|
+
|
|
167
|
+
RestArea(aArea)
|
|
168
|
+
|
|
169
|
+
Return Nil
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 3. FWMVCRotAuto - MVC Automatic Execution
|
|
175
|
+
|
|
176
|
+
### 3.1 Syntax
|
|
177
|
+
|
|
178
|
+
```advpl
|
|
179
|
+
lRet := FWMVCRotAuto(oModel, cSource, nOperation, aParams)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| Parameter | Type | Description |
|
|
183
|
+
|-----------|------|-------------|
|
|
184
|
+
| `oModel` | Object | Model object (usually Nil, framework creates internally) |
|
|
185
|
+
| `cSource` | Character | Source program name with ModelDef |
|
|
186
|
+
| `nOperation` | Numeric | Operation constant (MODEL_OPERATION_INSERT, etc.) |
|
|
187
|
+
| `aParams` | Array | Data array: `{{"ModelId", aFieldValues}, {"GridId", aGridLines}}` |
|
|
188
|
+
| **Return** | Logical | `.T.` if operation succeeded |
|
|
189
|
+
|
|
190
|
+
### 3.2 Include via FWMVCRotAuto
|
|
191
|
+
|
|
192
|
+
```advpl
|
|
193
|
+
#Include "TOTVS.CH"
|
|
194
|
+
#Include "FWMVCDef.ch"
|
|
195
|
+
|
|
196
|
+
User Function zIncMVC()
|
|
197
|
+
Local aHeader := {}
|
|
198
|
+
Local aItems := {}
|
|
199
|
+
Local aLine := {}
|
|
200
|
+
Local lRet := .F.
|
|
201
|
+
Local oModel := Nil
|
|
202
|
+
|
|
203
|
+
// Cabecalho
|
|
204
|
+
aAdd(aHeader, {"ZA1_NUM", "000001", Nil})
|
|
205
|
+
aAdd(aHeader, {"ZA1_CLIENT", "000001", Nil})
|
|
206
|
+
aAdd(aHeader, {"ZA1_LOJA", "01", Nil})
|
|
207
|
+
aAdd(aHeader, {"ZA1_EMISSA", Date(), Nil})
|
|
208
|
+
|
|
209
|
+
// Item 1
|
|
210
|
+
aLine := {}
|
|
211
|
+
aAdd(aLine, {"ZA2_ITEM", "01", Nil})
|
|
212
|
+
aAdd(aLine, {"ZA2_PROD", "000001", Nil})
|
|
213
|
+
aAdd(aLine, {"ZA2_QUANT", 10, Nil})
|
|
214
|
+
aAdd(aLine, {"ZA2_PRCUNI", 25.50, Nil})
|
|
215
|
+
aAdd(aItems, aLine)
|
|
216
|
+
|
|
217
|
+
// Executa
|
|
218
|
+
lRet := FWMVCRotAuto(oModel, "FATA090", MODEL_OPERATION_INSERT, {;
|
|
219
|
+
{"ZA1MASTER", aHeader}, ;
|
|
220
|
+
{"ZA2DETAIL", aItems} ;
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
If lRet
|
|
224
|
+
ConOut("Registro incluido com sucesso!")
|
|
225
|
+
Else
|
|
226
|
+
ConOut("Erro na inclusao.")
|
|
227
|
+
MostraErro()
|
|
228
|
+
EndIf
|
|
229
|
+
|
|
230
|
+
Return lRet
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 4. Approval Flow - SCR Table
|
|
236
|
+
|
|
237
|
+
### 4.1 SCR Table Structure (Documentos com Alcada)
|
|
238
|
+
|
|
239
|
+
The SCR table stores documents pending approval in the Protheus approval system.
|
|
240
|
+
|
|
241
|
+
| Field | Type | Description |
|
|
242
|
+
|-------|------|-------------|
|
|
243
|
+
| `CR_FILIAL` | C(2) | Branch |
|
|
244
|
+
| `CR_NUM` | C(50) | Document number |
|
|
245
|
+
| `CR_TIPO` | C(2) | Document type |
|
|
246
|
+
| `CR_USER` | C(6) | User code who created the document |
|
|
247
|
+
| `CR_APROV` | C(6) | Approver code |
|
|
248
|
+
| `CR_GRUPO` | C(6) | Approval group |
|
|
249
|
+
| `CR_NIVEL` | C(2) | Approval level |
|
|
250
|
+
| `CR_STATUS` | C(2) | Approval status |
|
|
251
|
+
| `CR_EMISSAO` | D | Issue date |
|
|
252
|
+
| `CR_TOTAL` | N(14,2) | Total value |
|
|
253
|
+
| `CR_DATALIB` | D | Release/approval date |
|
|
254
|
+
| `CR_OBS` | M(50) | Approval observations |
|
|
255
|
+
| `CR_USERLIB` | C(6) | User who approved |
|
|
256
|
+
| `CR_LIBAPRO` | C(6) | Effective approver of the document |
|
|
257
|
+
| `CR_VALLIB` | N(14,2) | Approved value |
|
|
258
|
+
| `CR_TIPOLIM` | C(1) | Approver limit type |
|
|
259
|
+
| `CR_MOEDA` | N(2) | Currency |
|
|
260
|
+
|
|
261
|
+
### 4.2 SAK Table Structure (Aprovadores)
|
|
262
|
+
|
|
263
|
+
The SAK table stores approver definitions.
|
|
264
|
+
|
|
265
|
+
| Field | Type | Description |
|
|
266
|
+
|-------|------|-------------|
|
|
267
|
+
| `AK_FILIAL` | C(2) | Branch |
|
|
268
|
+
| `AK_COD` | C(6) | Approver code |
|
|
269
|
+
| `AK_USER` | C(6) | User code |
|
|
270
|
+
| `AK_NOME` | C(40) | Full name |
|
|
271
|
+
| `AK_LIMMIN` | N(14,2) | Minimum approval limit |
|
|
272
|
+
| `AK_LIMMAX` | N(14,2) | Maximum approval limit |
|
|
273
|
+
| `AK_APROSUP` | C(6) | Superior approver code |
|
|
274
|
+
| `AK_LIMITE` | N(14,2) | Approver limit |
|
|
275
|
+
| `AK_TIPO` | C(1) | Limit type |
|
|
276
|
+
| `AK_LOGIN` | C(25) | User login |
|
|
277
|
+
|
|
278
|
+
### 4.3 Querying Pending Approvals
|
|
279
|
+
|
|
280
|
+
```advpl
|
|
281
|
+
Static Function fGetPendentes(cAprovador)
|
|
282
|
+
Local cQuery := ""
|
|
283
|
+
Local cAlias := GetNextAlias()
|
|
284
|
+
Local aResult := {}
|
|
285
|
+
|
|
286
|
+
cQuery := "SELECT CR_NUM, CR_TIPO, CR_TOTAL, CR_EMISSAO, CR_USER "
|
|
287
|
+
cQuery += "FROM " + RetSqlName("SCR") + " SCR "
|
|
288
|
+
cQuery += "WHERE SCR.D_E_L_E_T_ = ' ' "
|
|
289
|
+
cQuery += "AND CR_FILIAL = '" + xFilial("SCR") + "' "
|
|
290
|
+
cQuery += "AND CR_APROV = '" + cAprovador + "' "
|
|
291
|
+
cQuery += "AND CR_STATUS = '01' " // Pendente de aprovacao
|
|
292
|
+
cQuery += "ORDER BY CR_EMISSAO, CR_NUM "
|
|
293
|
+
|
|
294
|
+
TCQuery cQuery New Alias (cAlias)
|
|
295
|
+
|
|
296
|
+
While !(cAlias)->(Eof())
|
|
297
|
+
aAdd(aResult, { ;
|
|
298
|
+
AllTrim((cAlias)->CR_NUM), ;
|
|
299
|
+
AllTrim((cAlias)->CR_TIPO), ;
|
|
300
|
+
(cAlias)->CR_TOTAL, ;
|
|
301
|
+
(cAlias)->CR_EMISSAO, ;
|
|
302
|
+
AllTrim((cAlias)->CR_USER) ;
|
|
303
|
+
})
|
|
304
|
+
(cAlias)->(DbSkip())
|
|
305
|
+
EndWh
|
|
306
|
+
|
|
307
|
+
(cAlias)->(DbCloseArea())
|
|
308
|
+
|
|
309
|
+
Return aResult
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 5. Email - TMailManager and TMailMessage
|
|
315
|
+
|
|
316
|
+
### 5.1 TMailManager - Server Connection
|
|
317
|
+
|
|
318
|
+
**Constructor and methods confirmed on TDN:**
|
|
319
|
+
|
|
320
|
+
| Method | Description |
|
|
321
|
+
|--------|-------------|
|
|
322
|
+
| `TMailManager():New()` | Creates a new mail manager instance |
|
|
323
|
+
| `Init(cServer, cSmtpServer, cUser, cPass, nPop3Port, nSmtpPort)` | Initializes server parameters |
|
|
324
|
+
| `SetUseTLS(lUseTLS)` | Enables TLS encryption |
|
|
325
|
+
| `SetUseSSL(lUseSSL)` | Enables SSL encryption |
|
|
326
|
+
| `SetSmtpTimeOut(nTimeout)` | Sets SMTP timeout in seconds |
|
|
327
|
+
| `SmtpConnect()` | Establishes SMTP connection |
|
|
328
|
+
| `SmtpAuth(cUser, cPass)` | Authenticates on the SMTP server |
|
|
329
|
+
| `SmtpDisconnect()` | Closes the SMTP connection |
|
|
330
|
+
| `GetErrorString(nError)` | Returns error description for the given error code |
|
|
331
|
+
|
|
332
|
+
### 5.2 TMailMessage - Email Composition
|
|
333
|
+
|
|
334
|
+
**Properties:**
|
|
335
|
+
|
|
336
|
+
| Property | Type | Description |
|
|
337
|
+
|----------|------|-------------|
|
|
338
|
+
| `cFrom` | Character | Sender email address |
|
|
339
|
+
| `cTo` | Character | Recipient email address |
|
|
340
|
+
| `cCc` | Character | CC recipients |
|
|
341
|
+
| `cBcc` | Character | BCC recipients |
|
|
342
|
+
| `cSubject` | Character | Email subject |
|
|
343
|
+
| `cBody` | Character | Email body (HTML or plain text) |
|
|
344
|
+
|
|
345
|
+
**Methods:**
|
|
346
|
+
|
|
347
|
+
| Method | Description |
|
|
348
|
+
|--------|-------------|
|
|
349
|
+
| `TMailMessage():New()` | Creates a new message instance |
|
|
350
|
+
| `Clear()` | Clears all message fields |
|
|
351
|
+
| `Send(oServer)` | Sends the message via the TMailManager server object |
|
|
352
|
+
| `AttachFile(cFilePath)` | Attaches a file to the message |
|
|
353
|
+
|
|
354
|
+
### 5.3 Complete Email Sending Example
|
|
355
|
+
|
|
356
|
+
```advpl
|
|
357
|
+
#Include "TOTVS.CH"
|
|
358
|
+
|
|
359
|
+
/*/{Protheus.doc} zSendMail
|
|
360
|
+
Envia email via SMTP usando TMailManager e TMailMessage
|
|
361
|
+
@type User Function
|
|
362
|
+
@author Autor
|
|
363
|
+
@since 01/01/2026
|
|
364
|
+
@version 1.0
|
|
365
|
+
/*/
|
|
366
|
+
User Function zSendMail(cDestinatario, cAssunto, cCorpo)
|
|
367
|
+
Local oServer := Nil
|
|
368
|
+
Local oMessage := Nil
|
|
369
|
+
Local nRet := 0
|
|
370
|
+
Local cSmtp := "smtp.empresa.com"
|
|
371
|
+
Local cUser := "sistema@empresa.com"
|
|
372
|
+
Local cPass := "senha123"
|
|
373
|
+
Local nPort := 587
|
|
374
|
+
Local lRet := .F.
|
|
375
|
+
|
|
376
|
+
// Cria o gerenciador de email
|
|
377
|
+
oServer := TMailManager():New()
|
|
378
|
+
oServer:SetUseTLS(.T.)
|
|
379
|
+
|
|
380
|
+
// Inicializa com dados do servidor SMTP
|
|
381
|
+
nRet := oServer:Init("", cSmtp, cUser, cPass, 0, nPort)
|
|
382
|
+
If nRet <> 0
|
|
383
|
+
ConOut("Erro ao inicializar email: " + oServer:GetErrorString(nRet))
|
|
384
|
+
Return .F.
|
|
385
|
+
EndIf
|
|
386
|
+
|
|
387
|
+
// Configura timeout
|
|
388
|
+
oServer:SetSmtpTimeOut(60)
|
|
389
|
+
|
|
390
|
+
// Conecta ao servidor SMTP
|
|
391
|
+
nRet := oServer:SmtpConnect()
|
|
392
|
+
If nRet <> 0
|
|
393
|
+
ConOut("Erro ao conectar SMTP: " + oServer:GetErrorString(nRet))
|
|
394
|
+
Return .F.
|
|
395
|
+
EndIf
|
|
396
|
+
|
|
397
|
+
// Autentica
|
|
398
|
+
nRet := oServer:SmtpAuth(cUser, cPass)
|
|
399
|
+
If nRet <> 0
|
|
400
|
+
ConOut("Erro na autenticacao SMTP: " + oServer:GetErrorString(nRet))
|
|
401
|
+
oServer:SmtpDisconnect()
|
|
402
|
+
Return .F.
|
|
403
|
+
EndIf
|
|
404
|
+
|
|
405
|
+
// Monta a mensagem
|
|
406
|
+
oMessage := TMailMessage():New()
|
|
407
|
+
oMessage:Clear()
|
|
408
|
+
oMessage:cFrom := cUser
|
|
409
|
+
oMessage:cTo := cDestinatario
|
|
410
|
+
oMessage:cSubject := cAssunto
|
|
411
|
+
oMessage:cBody := cCorpo
|
|
412
|
+
|
|
413
|
+
// Envia
|
|
414
|
+
nRet := oMessage:Send(oServer)
|
|
415
|
+
If nRet <> 0
|
|
416
|
+
ConOut("Erro ao enviar email: " + oServer:GetErrorString(nRet))
|
|
417
|
+
Else
|
|
418
|
+
ConOut("Email enviado com sucesso para: " + cDestinatario)
|
|
419
|
+
lRet := .T.
|
|
420
|
+
EndIf
|
|
421
|
+
|
|
422
|
+
// Desconecta
|
|
423
|
+
oServer:SmtpDisconnect()
|
|
424
|
+
|
|
425
|
+
Return lRet
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 5.4 Email with Attachment
|
|
429
|
+
|
|
430
|
+
```advpl
|
|
431
|
+
// Monta mensagem com anexo
|
|
432
|
+
oMessage := TMailMessage():New()
|
|
433
|
+
oMessage:Clear()
|
|
434
|
+
oMessage:cFrom := "sistema@empresa.com"
|
|
435
|
+
oMessage:cTo := "gestor@empresa.com"
|
|
436
|
+
oMessage:cSubject := "Relatorio Diario"
|
|
437
|
+
oMessage:cBody := "<html><body><h2>Relatorio em anexo</h2></body></html>"
|
|
438
|
+
|
|
439
|
+
// Anexa arquivo
|
|
440
|
+
oMessage:AttachFile("/reports/relatorio_diario.pdf")
|
|
441
|
+
|
|
442
|
+
// Envia
|
|
443
|
+
nRet := oMessage:Send(oServer)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 6. TMailMng - Modern Email Class
|
|
449
|
+
|
|
450
|
+
TMailMng is the newer replacement for TMailManager, offering greater configuration flexibility and not depending on the `appserver.ini` Protocol key.
|
|
451
|
+
|
|
452
|
+
```advpl
|
|
453
|
+
// TMailMng usa o protocolo definido diretamente no construtor
|
|
454
|
+
// Consulte a documentacao TDN para sintaxe atualizada:
|
|
455
|
+
// https://tdn.totvs.com/display/tec/Classe+TMailMng
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Note**: TMailMng is confirmed on TDN as a valid replacement class. For production implementations, consult the TDN documentation for the most current constructor syntax and method signatures, as they may vary by Protheus version.
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## 7. Complete Example - Purchase Order Approval Workflow
|
|
463
|
+
|
|
464
|
+
This example implements a complete approval workflow: creating a purchase order, checking approval requirements, and sending email notifications.
|
|
465
|
+
|
|
466
|
+
```advpl
|
|
467
|
+
#Include "TOTVS.CH"
|
|
468
|
+
|
|
469
|
+
/*/{Protheus.doc} zAprovPC
|
|
470
|
+
Fluxo de aprovacao de Pedido de Compras
|
|
471
|
+
@type User Function
|
|
472
|
+
@author Autor
|
|
473
|
+
@since 01/01/2026
|
|
474
|
+
@version 1.0
|
|
475
|
+
/*/
|
|
476
|
+
User Function zAprovPC(cNumPC)
|
|
477
|
+
Local aArea := GetArea()
|
|
478
|
+
Local lAprov := .F.
|
|
479
|
+
Local cAprov := ""
|
|
480
|
+
Local nTotal := 0
|
|
481
|
+
|
|
482
|
+
// Busca dados do pedido de compras
|
|
483
|
+
DbSelectArea("SC7")
|
|
484
|
+
DbSetOrder(1)
|
|
485
|
+
|
|
486
|
+
If !DbSeek(xFilial("SC7") + cNumPC)
|
|
487
|
+
ConOut("[zAprovPC] Pedido nao encontrado: " + cNumPC)
|
|
488
|
+
RestArea(aArea)
|
|
489
|
+
Return .F.
|
|
490
|
+
EndIf
|
|
491
|
+
|
|
492
|
+
nTotal := fTotalPC(cNumPC)
|
|
493
|
+
|
|
494
|
+
// Busca aprovador com alcada suficiente
|
|
495
|
+
cAprov := fGetAprovador(nTotal)
|
|
496
|
+
|
|
497
|
+
If Empty(cAprov)
|
|
498
|
+
ConOut("[zAprovPC] Nenhum aprovador encontrado para o valor: " + cValToChar(nTotal))
|
|
499
|
+
RestArea(aArea)
|
|
500
|
+
Return .F.
|
|
501
|
+
EndIf
|
|
502
|
+
|
|
503
|
+
// Verifica se ja existe aprovacao pendente na SCR
|
|
504
|
+
If fTemAprovPendente(cNumPC, "PC")
|
|
505
|
+
ConOut("[zAprovPC] Pedido ja possui aprovacao pendente.")
|
|
506
|
+
RestArea(aArea)
|
|
507
|
+
Return .F.
|
|
508
|
+
EndIf
|
|
509
|
+
|
|
510
|
+
// Registra solicitacao de aprovacao na SCR
|
|
511
|
+
fRegistraAprovacao(cNumPC, "PC", cAprov, nTotal)
|
|
512
|
+
|
|
513
|
+
// Envia notificacao por email ao aprovador
|
|
514
|
+
fNotificaAprovador(cAprov, cNumPC, nTotal)
|
|
515
|
+
|
|
516
|
+
ConOut("[zAprovPC] Solicitacao de aprovacao registrada para PC: " + cNumPC)
|
|
517
|
+
|
|
518
|
+
RestArea(aArea)
|
|
519
|
+
|
|
520
|
+
Return .T.
|
|
521
|
+
|
|
522
|
+
// =============================================================
|
|
523
|
+
// Calcula o valor total do pedido de compras
|
|
524
|
+
// =============================================================
|
|
525
|
+
Static Function fTotalPC(cNumPC)
|
|
526
|
+
Local cQuery := ""
|
|
527
|
+
Local cAlias := GetNextAlias()
|
|
528
|
+
Local nTotal := 0
|
|
529
|
+
|
|
530
|
+
cQuery := "SELECT SUM(C7_QUANT * C7_PRECO) AS TOTAL "
|
|
531
|
+
cQuery += "FROM " + RetSqlName("SC7") + " SC7 "
|
|
532
|
+
cQuery += "WHERE SC7.D_E_L_E_T_ = ' ' "
|
|
533
|
+
cQuery += "AND C7_FILIAL = '" + xFilial("SC7") + "' "
|
|
534
|
+
cQuery += "AND C7_NUM = '" + cNumPC + "' "
|
|
535
|
+
|
|
536
|
+
TCQuery cQuery New Alias (cAlias)
|
|
537
|
+
|
|
538
|
+
If !(cAlias)->(Eof())
|
|
539
|
+
nTotal := (cAlias)->TOTAL
|
|
540
|
+
EndIf
|
|
541
|
+
|
|
542
|
+
(cAlias)->(DbCloseArea())
|
|
543
|
+
|
|
544
|
+
Return nTotal
|
|
545
|
+
|
|
546
|
+
// =============================================================
|
|
547
|
+
// Busca aprovador com alcada suficiente
|
|
548
|
+
// =============================================================
|
|
549
|
+
Static Function fGetAprovador(nValor)
|
|
550
|
+
Local cQuery := ""
|
|
551
|
+
Local cAlias := GetNextAlias()
|
|
552
|
+
Local cAprov := ""
|
|
553
|
+
|
|
554
|
+
cQuery := "SELECT AK_COD, AK_NOME, AK_LIMITE "
|
|
555
|
+
cQuery += "FROM " + RetSqlName("SAK") + " SAK "
|
|
556
|
+
cQuery += "WHERE SAK.D_E_L_E_T_ = ' ' "
|
|
557
|
+
cQuery += "AND AK_FILIAL = '" + xFilial("SAK") + "' "
|
|
558
|
+
cQuery += "AND AK_LIMITE >= " + cValToChar(nValor) + " "
|
|
559
|
+
cQuery += "ORDER BY AK_LIMITE "
|
|
560
|
+
|
|
561
|
+
TCQuery cQuery New Alias (cAlias)
|
|
562
|
+
|
|
563
|
+
If !(cAlias)->(Eof())
|
|
564
|
+
cAprov := AllTrim((cAlias)->AK_COD)
|
|
565
|
+
EndIf
|
|
566
|
+
|
|
567
|
+
(cAlias)->(DbCloseArea())
|
|
568
|
+
|
|
569
|
+
Return cAprov
|
|
570
|
+
|
|
571
|
+
// =============================================================
|
|
572
|
+
// Verifica se existe aprovacao pendente
|
|
573
|
+
// =============================================================
|
|
574
|
+
Static Function fTemAprovPendente(cNumDoc, cTipoDoc)
|
|
575
|
+
Local lPendente := .F.
|
|
576
|
+
|
|
577
|
+
DbSelectArea("SCR")
|
|
578
|
+
DbSetOrder(1)
|
|
579
|
+
|
|
580
|
+
If DbSeek(xFilial("SCR") + cNumDoc)
|
|
581
|
+
If AllTrim(SCR->CR_TIPO) == cTipoDoc .And. AllTrim(SCR->CR_STATUS) < "03"
|
|
582
|
+
lPendente := .T.
|
|
583
|
+
EndIf
|
|
584
|
+
EndIf
|
|
585
|
+
|
|
586
|
+
Return lPendente
|
|
587
|
+
|
|
588
|
+
// =============================================================
|
|
589
|
+
// Registra solicitacao de aprovacao
|
|
590
|
+
// =============================================================
|
|
591
|
+
Static Function fRegistraAprovacao(cNumDoc, cTipoDoc, cAprov, nTotal)
|
|
592
|
+
|
|
593
|
+
DbSelectArea("SCR")
|
|
594
|
+
|
|
595
|
+
RecLock("SCR", .T.)
|
|
596
|
+
SCR->CR_FILIAL := xFilial("SCR")
|
|
597
|
+
SCR->CR_NUM := cNumDoc
|
|
598
|
+
SCR->CR_TIPO := cTipoDoc
|
|
599
|
+
SCR->CR_USER := cUserID // Usuario logado
|
|
600
|
+
SCR->CR_APROV := cAprov
|
|
601
|
+
SCR->CR_STATUS := "01" // Pendente
|
|
602
|
+
SCR->CR_EMISSAO := Date()
|
|
603
|
+
SCR->CR_TOTAL := nTotal
|
|
604
|
+
MsUnlock()
|
|
605
|
+
|
|
606
|
+
Return Nil
|
|
607
|
+
|
|
608
|
+
// =============================================================
|
|
609
|
+
// Notifica aprovador por email
|
|
610
|
+
// =============================================================
|
|
611
|
+
Static Function fNotificaAprovador(cAprov, cNumPC, nTotal)
|
|
612
|
+
Local cEmail := ""
|
|
613
|
+
Local cCorpo := ""
|
|
614
|
+
|
|
615
|
+
// Busca email do aprovador
|
|
616
|
+
DbSelectArea("SAK")
|
|
617
|
+
DbSetOrder(1)
|
|
618
|
+
|
|
619
|
+
If DbSeek(xFilial("SAK") + cAprov)
|
|
620
|
+
// Busca email do usuario vinculado ao aprovador
|
|
621
|
+
cEmail := fGetEmailUsuario(AllTrim(SAK->AK_USER))
|
|
622
|
+
EndIf
|
|
623
|
+
|
|
624
|
+
If Empty(cEmail)
|
|
625
|
+
ConOut("[zAprovPC] Email do aprovador nao encontrado: " + cAprov)
|
|
626
|
+
Return Nil
|
|
627
|
+
EndIf
|
|
628
|
+
|
|
629
|
+
// Monta corpo do email
|
|
630
|
+
cCorpo := "<html><body>"
|
|
631
|
+
cCorpo += "<h2>Solicitacao de Aprovacao - Pedido de Compras</h2>"
|
|
632
|
+
cCorpo += "<p>Numero do Pedido: <strong>" + AllTrim(cNumPC) + "</strong></p>"
|
|
633
|
+
cCorpo += "<p>Valor Total: <strong>R$ " + AllTrim(Transform(nTotal, "@E 999,999,999.99")) + "</strong></p>"
|
|
634
|
+
cCorpo += "<p>Data: " + DToC(Date()) + "</p>"
|
|
635
|
+
cCorpo += "<p>Acesse o Protheus para aprovar ou rejeitar este documento.</p>"
|
|
636
|
+
cCorpo += "</body></html>"
|
|
637
|
+
|
|
638
|
+
// Envia email (usando funcao do exemplo anterior)
|
|
639
|
+
U_zSendMail(cEmail, "Aprovacao Pendente - PC " + AllTrim(cNumPC), cCorpo)
|
|
640
|
+
|
|
641
|
+
Return Nil
|
|
642
|
+
|
|
643
|
+
// =============================================================
|
|
644
|
+
// Busca email de um usuario
|
|
645
|
+
// =============================================================
|
|
646
|
+
Static Function fGetEmailUsuario(cUserId)
|
|
647
|
+
Local cEmail := ""
|
|
648
|
+
Local cQuery := ""
|
|
649
|
+
Local cAlias := GetNextAlias()
|
|
650
|
+
|
|
651
|
+
cQuery := "SELECT USR_EMAIL "
|
|
652
|
+
cQuery += "FROM " + RetSqlName("SYS_USR") + " "
|
|
653
|
+
cQuery += "WHERE D_E_L_E_T_ = ' ' "
|
|
654
|
+
cQuery += "AND USR_ID = '" + cUserId + "' "
|
|
655
|
+
|
|
656
|
+
TCQuery cQuery New Alias (cAlias)
|
|
657
|
+
|
|
658
|
+
If !(cAlias)->(Eof())
|
|
659
|
+
cEmail := AllTrim((cAlias)->USR_EMAIL)
|
|
660
|
+
EndIf
|
|
661
|
+
|
|
662
|
+
(cAlias)->(DbCloseArea())
|
|
663
|
+
|
|
664
|
+
Return cEmail
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## 8. MsExecAuto with Approval Blocking
|
|
670
|
+
|
|
671
|
+
When a document requires approval, MsExecAuto may block the operation if the approval flow is configured. To handle this:
|
|
672
|
+
|
|
673
|
+
```advpl
|
|
674
|
+
// Antes do MsExecAuto, verificar se o documento necessita aprovacao
|
|
675
|
+
// e tratar o retorno adequadamente
|
|
676
|
+
|
|
677
|
+
Private lMsErroAuto := .F.
|
|
678
|
+
Private lMsHelpAuto := .T.
|
|
679
|
+
Private lAutoErrNoFile := .T.
|
|
680
|
+
|
|
681
|
+
MsExecAuto({|x, y, z| MATA120(x, y, z)}, aCabec, aItens, 3)
|
|
682
|
+
|
|
683
|
+
If lMsErroAuto
|
|
684
|
+
Local cErro := MostraErro(.F.)
|
|
685
|
+
|
|
686
|
+
// Verifica se o erro e relacionado a aprovacao/alcada
|
|
687
|
+
If "ALCADA" $ Upper(cErro) .Or. "APROV" $ Upper(cErro)
|
|
688
|
+
ConOut("Documento gerado e encaminhado para aprovacao.")
|
|
689
|
+
Else
|
|
690
|
+
ConOut("Erro na geracao do documento: " + cErro)
|
|
691
|
+
EndIf
|
|
692
|
+
EndIf
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## 9. FWMVCRotAuto with Approver Validation
|
|
698
|
+
|
|
699
|
+
Example of using FWMVCRotAuto with pre-validation that checks approver permissions:
|
|
700
|
+
|
|
701
|
+
```advpl
|
|
702
|
+
#Include "TOTVS.CH"
|
|
703
|
+
#Include "FWMVCDef.ch"
|
|
704
|
+
|
|
705
|
+
User Function zMVCAprov()
|
|
706
|
+
Local aHeader := {}
|
|
707
|
+
Local lRet := .F.
|
|
708
|
+
Local oModel := Nil
|
|
709
|
+
|
|
710
|
+
// Verifica se o usuario atual e aprovador
|
|
711
|
+
If !fIsAprovador(cUserID)
|
|
712
|
+
ConOut("Usuario nao possui permissao de aprovador.")
|
|
713
|
+
Return .F.
|
|
714
|
+
EndIf
|
|
715
|
+
|
|
716
|
+
// Posiciona no registro a ser aprovado
|
|
717
|
+
DbSelectArea("ZA1")
|
|
718
|
+
DbSetOrder(1)
|
|
719
|
+
DbSeek(xFilial("ZA1") + "000001")
|
|
720
|
+
|
|
721
|
+
// Campos de aprovacao
|
|
722
|
+
aAdd(aHeader, {"ZA1_STATUS", "A", Nil}) // A = Aprovado
|
|
723
|
+
aAdd(aHeader, {"ZA1_DTAPRO", Date(), Nil})
|
|
724
|
+
aAdd(aHeader, {"ZA1_USRAPR", cUserID, Nil})
|
|
725
|
+
|
|
726
|
+
lRet := FWMVCRotAuto(oModel, "FATA090", MODEL_OPERATION_UPDATE, {;
|
|
727
|
+
{"ZA1MASTER", aHeader} ;
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
If lRet
|
|
731
|
+
ConOut("Documento aprovado com sucesso!")
|
|
732
|
+
// Notifica solicitante
|
|
733
|
+
fNotificaSolicitante("000001", "APROVADO")
|
|
734
|
+
Else
|
|
735
|
+
ConOut("Erro na aprovacao.")
|
|
736
|
+
MostraErro()
|
|
737
|
+
EndIf
|
|
738
|
+
|
|
739
|
+
Return lRet
|
|
740
|
+
|
|
741
|
+
Static Function fIsAprovador(cUser)
|
|
742
|
+
Local lAprov := .F.
|
|
743
|
+
|
|
744
|
+
DbSelectArea("SAK")
|
|
745
|
+
DbSetOrder(1)
|
|
746
|
+
|
|
747
|
+
// Busca aprovador pelo codigo do usuario
|
|
748
|
+
DbGoTop()
|
|
749
|
+
While !Eof()
|
|
750
|
+
If xFilial("SAK") == SAK->AK_FILIAL .And. AllTrim(SAK->AK_USER) == AllTrim(cUser)
|
|
751
|
+
lAprov := .T.
|
|
752
|
+
Exit
|
|
753
|
+
EndIf
|
|
754
|
+
DbSkip()
|
|
755
|
+
EndWh
|
|
756
|
+
|
|
757
|
+
Return lAprov
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
## 10. Best Practices
|
|
763
|
+
|
|
764
|
+
### 10.1 MsExecAuto
|
|
765
|
+
- Always declare `lMsErroAuto`, `lMsHelpAuto`, and `lAutoErrNoFile` as Private before calling.
|
|
766
|
+
- Always check `lMsErroAuto` after execution.
|
|
767
|
+
- Use `MostraErro(.F.)` to capture error messages as strings.
|
|
768
|
+
- Use `GetArea()` / `RestArea()` to preserve table positioning.
|
|
769
|
+
|
|
770
|
+
### 10.2 Approval Flows
|
|
771
|
+
- Use the standard SCR/SAK tables for approval control when possible.
|
|
772
|
+
- Always validate approver limits before allowing approval operations.
|
|
773
|
+
- Log all approval actions for auditing purposes.
|
|
774
|
+
|
|
775
|
+
### 10.3 Email Notifications
|
|
776
|
+
- Always check the return value of each TMailManager method (0 = success).
|
|
777
|
+
- Always call `SmtpDisconnect()` even when errors occur.
|
|
778
|
+
- Use TLS/SSL for secure email transmission.
|
|
779
|
+
- For high-volume email sending, consider using a background job.
|