@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,519 @@
|
|
|
1
|
+
# Protheus Jobs and Background Processing Patterns
|
|
2
|
+
|
|
3
|
+
Complete reference for implementing background jobs, multi-threading, and scheduled tasks in TOTVS Protheus.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview
|
|
8
|
+
|
|
9
|
+
Protheus supports background processing through Jobs configured in `appserver.ini` and programmatic thread creation via `StartJob`. Jobs run without a user interface and are used for batch processing, integrations, scheduled tasks, and parallel operations.
|
|
10
|
+
|
|
11
|
+
Key functions:
|
|
12
|
+
- **StartJob()**: Executes a function in a secondary thread without interface.
|
|
13
|
+
- **RpcSetEnv()**: Opens/prepares the Protheus environment (company, branch, tables) in a new thread.
|
|
14
|
+
- **RpcClearEnv()**: Closes/releases the environment and connections opened by `RpcSetEnv`.
|
|
15
|
+
- **LockByName()**: Creates a named semaphore on the license server for concurrency control.
|
|
16
|
+
- **UnlockByName()**: Releases a named semaphore created by `LockByName`.
|
|
17
|
+
- **ConOut()**: Writes messages to the AppServer console log.
|
|
18
|
+
- **FWLogMsg()**: Standardized logging API (recommended replacement for `ConOut` in newer versions).
|
|
19
|
+
|
|
20
|
+
Required includes:
|
|
21
|
+
```advpl
|
|
22
|
+
#Include "TOTVS.CH"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. OnStart - AppServer.ini Configuration
|
|
28
|
+
|
|
29
|
+
### 2.1 Basic Job Configuration
|
|
30
|
+
|
|
31
|
+
Jobs are configured in the `appserver.ini` file to run automatically when the Application Server starts.
|
|
32
|
+
|
|
33
|
+
```ini
|
|
34
|
+
[OnStart]
|
|
35
|
+
JOBS=MYJOB
|
|
36
|
+
REFRESHRATE=120
|
|
37
|
+
|
|
38
|
+
[MYJOB]
|
|
39
|
+
MAIN=U_zJobMain
|
|
40
|
+
ENVIRONMENT=PRODUCAO
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Key | Description |
|
|
44
|
+
|-----|-------------|
|
|
45
|
+
| `JOBS` | Comma-separated list of job section names to execute |
|
|
46
|
+
| `REFRESHRATE` | Interval in seconds to re-execute jobs if not running (default: 60) |
|
|
47
|
+
|
|
48
|
+
### 2.2 Job Section Keys
|
|
49
|
+
|
|
50
|
+
| Key | Description |
|
|
51
|
+
|-----|-------------|
|
|
52
|
+
| `MAIN` | Function name to execute (e.g., `U_zJobMain`) |
|
|
53
|
+
| `ENVIRONMENT` | Server environment name |
|
|
54
|
+
|
|
55
|
+
### 2.3 Multiple Jobs
|
|
56
|
+
|
|
57
|
+
```ini
|
|
58
|
+
[OnStart]
|
|
59
|
+
JOBS=JOB_INTEG,JOB_LOTE,JOB_HTTP
|
|
60
|
+
REFRESHRATE=300
|
|
61
|
+
|
|
62
|
+
[JOB_INTEG]
|
|
63
|
+
MAIN=U_zIntegJob
|
|
64
|
+
ENVIRONMENT=PRODUCAO
|
|
65
|
+
|
|
66
|
+
[JOB_LOTE]
|
|
67
|
+
MAIN=U_zLoteJob
|
|
68
|
+
ENVIRONMENT=PRODUCAO
|
|
69
|
+
|
|
70
|
+
[JOB_HTTP]
|
|
71
|
+
MAIN=HTTP_START
|
|
72
|
+
ENVIRONMENT=PRODUCAO
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 3. StartJob - Thread Creation
|
|
78
|
+
|
|
79
|
+
### 3.1 Syntax
|
|
80
|
+
|
|
81
|
+
```advpl
|
|
82
|
+
StartJob(cFuncName, cEnvironment, lWait, parm1, parm2, ..., parm25)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Parameter | Type | Description |
|
|
86
|
+
|-----------|------|-------------|
|
|
87
|
+
| `cFuncName` | Character | Function name to execute in the new thread |
|
|
88
|
+
| `cEnvironment` | Character | Server environment (use `GetEnvServer()` for current) |
|
|
89
|
+
| `lWait` | Logical | `.T.` waits for thread to finish, `.F.` runs in background |
|
|
90
|
+
| `parm1..parm25` | Any | Up to 25 optional parameters passed to the function |
|
|
91
|
+
|
|
92
|
+
### 3.2 Basic Usage
|
|
93
|
+
|
|
94
|
+
```advpl
|
|
95
|
+
// Executa em background sem esperar
|
|
96
|
+
StartJob("U_zProcessa", GetEnvServer(), .F.)
|
|
97
|
+
|
|
98
|
+
// Executa e aguarda conclusao
|
|
99
|
+
StartJob("U_zProcessa", GetEnvServer(), .T.)
|
|
100
|
+
|
|
101
|
+
// Passa parametros
|
|
102
|
+
StartJob("U_zProcessa", GetEnvServer(), .F., "99", "01", "PARAM3")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. RpcSetEnv - Environment Setup
|
|
108
|
+
|
|
109
|
+
### 4.1 Syntax
|
|
110
|
+
|
|
111
|
+
```advpl
|
|
112
|
+
lRet := RpcSetEnv(cCodEmp, cCodFil, cUsuario, cSenha, cModulo, cFunName, aTables)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Parameter | Type | Description |
|
|
116
|
+
|-----------|------|-------------|
|
|
117
|
+
| `cCodEmp` | Character | Company code (e.g., `"99"`) |
|
|
118
|
+
| `cCodFil` | Character | Branch code (e.g., `"01"`) |
|
|
119
|
+
|
|
120
|
+
> **IMPORTANT:** Never use `cFilial`, `cFilAnt` or `cEmpAnt` as variable names — these are **reserved Private variables** maintained by the Protheus framework to track company/branch context. Using them as Local variables will shadow the system variables and cause unpredictable behavior. Use `cCodFil`, `cCodEmp` or similar names instead.
|
|
121
|
+
| `cUsuario` | Character | User login (optional) |
|
|
122
|
+
| `cSenha` | Character | User password (optional) |
|
|
123
|
+
| `cModulo` | Character | Module code, e.g., `"FAT"`, `"CTB"` (default: `"FAT"`) |
|
|
124
|
+
| `cFunName` | Character | Function name for `FunName()` (default: `"RPC"`) |
|
|
125
|
+
| `aTables` | Array | Array of table aliases to open (optional) |
|
|
126
|
+
| **Return** | Logical | `.T.` if environment was prepared successfully |
|
|
127
|
+
|
|
128
|
+
### 4.2 Basic Usage
|
|
129
|
+
|
|
130
|
+
```advpl
|
|
131
|
+
If RpcSetEnv("99", "01", "admin", " ", "FAT")
|
|
132
|
+
// Ambiente preparado, executar logica de negocio
|
|
133
|
+
// ...
|
|
134
|
+
RpcClearEnv()
|
|
135
|
+
EndIf
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 5. RpcClearEnv - Environment Cleanup
|
|
141
|
+
|
|
142
|
+
### 5.1 Syntax
|
|
143
|
+
|
|
144
|
+
```advpl
|
|
145
|
+
RpcClearEnv()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Closes the environment opened by `RpcSetEnv`, releasing connections and resources. Must always be called after processing is complete.
|
|
149
|
+
|
|
150
|
+
**Important**: Do not call `RpcSetEnv` or `RpcClearEnv` in routines executed from the Protheus menu, as the environment is already open and managed by the system.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 6. LockByName / UnlockByName - Semaphores
|
|
155
|
+
|
|
156
|
+
### 6.1 LockByName Syntax
|
|
157
|
+
|
|
158
|
+
```advpl
|
|
159
|
+
lLocked := LockByName(cName, lByCompany, lByBranch)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Parameter | Type | Description |
|
|
163
|
+
|-----------|------|-------------|
|
|
164
|
+
| `cName` | Character | Semaphore name |
|
|
165
|
+
| `lByCompany` | Logical | `.T.` considers company code in the lock |
|
|
166
|
+
| `lByBranch` | Logical | `.T.` considers branch code in the lock |
|
|
167
|
+
| **Return** | Logical | `.T.` if lock was acquired successfully |
|
|
168
|
+
|
|
169
|
+
### 6.2 UnlockByName Syntax
|
|
170
|
+
|
|
171
|
+
```advpl
|
|
172
|
+
UnlockByName(cName, lByCompany, lByBranch)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Parameters are the same as `LockByName`. Releases the named semaphore.
|
|
176
|
+
|
|
177
|
+
### 6.3 Usage Pattern
|
|
178
|
+
|
|
179
|
+
```advpl
|
|
180
|
+
// Tenta adquirir o semaforo
|
|
181
|
+
If LockByName("PROC_LOTE_001", .F., .F.)
|
|
182
|
+
// Executa processamento exclusivo
|
|
183
|
+
ProcessaLote()
|
|
184
|
+
|
|
185
|
+
// Libera o semaforo
|
|
186
|
+
UnlockByName("PROC_LOTE_001", .F., .F.)
|
|
187
|
+
Else
|
|
188
|
+
ConOut("Processamento ja em execucao por outra thread.")
|
|
189
|
+
EndIf
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 7. Logging
|
|
195
|
+
|
|
196
|
+
### 7.1 ConOut
|
|
197
|
+
|
|
198
|
+
Writes a message to the Application Server console log:
|
|
199
|
+
|
|
200
|
+
```advpl
|
|
201
|
+
ConOut("Inicio do processamento: " + DToC(Date()) + " " + Time())
|
|
202
|
+
ConOut("Registro processado: " + cCodigo)
|
|
203
|
+
ConOut("Erro: " + cMsgErro)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 7.2 FWLogMsg (Recommended)
|
|
207
|
+
|
|
208
|
+
Standardized logging API that replaces `ConOut` in newer Protheus versions:
|
|
209
|
+
|
|
210
|
+
```advpl
|
|
211
|
+
FWLogMsg("INFO", /*cTransactionId*/, "MYJOB", /*cCategory*/, /*cStep*/, "001", "Inicio do processamento", /*aMessage*/, /*nLogLevel*/)
|
|
212
|
+
FWLogMsg("ERROR", /*cTransactionId*/, "MYJOB", /*cCategory*/, /*cStep*/, "002", "Erro no processamento: " + cMsgErro, /*aMessage*/, /*nLogLevel*/)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| Parameter | Description |
|
|
216
|
+
|-----------|-------------|
|
|
217
|
+
| 1st | Severity: `"INFO"`, `"ERROR"`, `"WARNING"` |
|
|
218
|
+
| 2nd | Transaction ID (optional) |
|
|
219
|
+
| 3rd | Message grouper/module ID |
|
|
220
|
+
| 4th | Category (optional) |
|
|
221
|
+
| 5th | Step (optional) |
|
|
222
|
+
| 6th | Message code |
|
|
223
|
+
| 7th | Message text |
|
|
224
|
+
| 8th | Array of additional messages (optional) |
|
|
225
|
+
| 9th | Log level (optional) |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 8. Complete Example - Batch Processing Job
|
|
230
|
+
|
|
231
|
+
This example implements a job that processes pending records in batches, with environment setup, semaphore control, and logging.
|
|
232
|
+
|
|
233
|
+
```advpl
|
|
234
|
+
#Include "TOTVS.CH"
|
|
235
|
+
|
|
236
|
+
/*/{Protheus.doc} zJobLote
|
|
237
|
+
Job de processamento em lote
|
|
238
|
+
@type User Function
|
|
239
|
+
@author Autor
|
|
240
|
+
@since 01/01/2026
|
|
241
|
+
@version 1.0
|
|
242
|
+
/*/
|
|
243
|
+
User Function zJobLote()
|
|
244
|
+
Local cCodEmp := "99"
|
|
245
|
+
Local cCodFil := "01"
|
|
246
|
+
|
|
247
|
+
ConOut("[zJobLote] Iniciando job: " + DToC(Date()) + " " + Time())
|
|
248
|
+
|
|
249
|
+
// Prepara o ambiente
|
|
250
|
+
If !RpcSetEnv(cCodEmp, cCodFil, "admin", " ", "FAT", "zJobLote")
|
|
251
|
+
ConOut("[zJobLote] ERRO: Falha ao preparar ambiente.")
|
|
252
|
+
Return Nil
|
|
253
|
+
EndIf
|
|
254
|
+
|
|
255
|
+
// Controle de concorrencia via semaforo
|
|
256
|
+
If !LockByName("JOB_PROC_LOTE", .F., .F.)
|
|
257
|
+
ConOut("[zJobLote] Job ja em execucao por outra thread. Saindo.")
|
|
258
|
+
RpcClearEnv()
|
|
259
|
+
Return Nil
|
|
260
|
+
EndIf
|
|
261
|
+
|
|
262
|
+
// Executa o processamento
|
|
263
|
+
Begin Sequence
|
|
264
|
+
fProcessaLote()
|
|
265
|
+
Recover
|
|
266
|
+
ConOut("[zJobLote] ERRO: Excecao durante processamento.")
|
|
267
|
+
End Sequence
|
|
268
|
+
|
|
269
|
+
// Libera semaforo e ambiente
|
|
270
|
+
UnlockByName("JOB_PROC_LOTE", .F., .F.)
|
|
271
|
+
RpcClearEnv()
|
|
272
|
+
|
|
273
|
+
ConOut("[zJobLote] Job finalizado: " + DToC(Date()) + " " + Time())
|
|
274
|
+
|
|
275
|
+
Return Nil
|
|
276
|
+
|
|
277
|
+
// =============================================================
|
|
278
|
+
// Processamento em lote
|
|
279
|
+
// =============================================================
|
|
280
|
+
Static Function fProcessaLote()
|
|
281
|
+
Local cAlias := "ZA9"
|
|
282
|
+
Local aArea := GetArea()
|
|
283
|
+
Local nCount := 0
|
|
284
|
+
Local nErrors := 0
|
|
285
|
+
|
|
286
|
+
DbSelectArea(cAlias)
|
|
287
|
+
DbSetOrder(1)
|
|
288
|
+
DbGoTop()
|
|
289
|
+
|
|
290
|
+
While !Eof()
|
|
291
|
+
If xFilial(cAlias) == ZA9->ZA9_FILIAL .And. ZA9->ZA9_STATUS == "1" // Pendente
|
|
292
|
+
|
|
293
|
+
Begin Transaction
|
|
294
|
+
RecLock(cAlias, .F.)
|
|
295
|
+
ZA9->ZA9_STATUS := "2" // Em processamento
|
|
296
|
+
MsUnlock()
|
|
297
|
+
|
|
298
|
+
// Executa logica de negocio
|
|
299
|
+
If fProcessaRegistro(ZA9->ZA9_CODIGO)
|
|
300
|
+
RecLock(cAlias, .F.)
|
|
301
|
+
ZA9->ZA9_STATUS := "3" // Processado
|
|
302
|
+
ZA9->ZA9_DTPROC := Date()
|
|
303
|
+
ZA9->ZA9_HRPROC := Time()
|
|
304
|
+
MsUnlock()
|
|
305
|
+
nCount++
|
|
306
|
+
Else
|
|
307
|
+
RecLock(cAlias, .F.)
|
|
308
|
+
ZA9->ZA9_STATUS := "9" // Erro
|
|
309
|
+
MsUnlock()
|
|
310
|
+
nErrors++
|
|
311
|
+
EndIf
|
|
312
|
+
End Transaction
|
|
313
|
+
|
|
314
|
+
EndIf
|
|
315
|
+
|
|
316
|
+
DbSkip()
|
|
317
|
+
EndWh
|
|
318
|
+
|
|
319
|
+
ConOut("[zJobLote] Processados: " + cValToChar(nCount) + " | Erros: " + cValToChar(nErrors))
|
|
320
|
+
|
|
321
|
+
RestArea(aArea)
|
|
322
|
+
|
|
323
|
+
Return Nil
|
|
324
|
+
|
|
325
|
+
// =============================================================
|
|
326
|
+
// Processa um registro individual
|
|
327
|
+
// =============================================================
|
|
328
|
+
Static Function fProcessaRegistro(cCodigo)
|
|
329
|
+
Local lRet := .T.
|
|
330
|
+
|
|
331
|
+
// Logica de negocio especifica
|
|
332
|
+
ConOut("[zJobLote] Processando registro: " + cCodigo)
|
|
333
|
+
|
|
334
|
+
// ... implementar logica aqui ...
|
|
335
|
+
|
|
336
|
+
Return lRet
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## 9. Multi-Thread Processing with StartJob
|
|
342
|
+
|
|
343
|
+
### 9.1 Dispatcher Pattern
|
|
344
|
+
|
|
345
|
+
A common pattern is to have a dispatcher function that creates multiple worker threads:
|
|
346
|
+
|
|
347
|
+
```advpl
|
|
348
|
+
#Include "TOTVS.CH"
|
|
349
|
+
|
|
350
|
+
/*/{Protheus.doc} zDispatcher
|
|
351
|
+
Dispatcher que cria multiplas threads de processamento
|
|
352
|
+
@type User Function
|
|
353
|
+
@author Autor
|
|
354
|
+
@since 01/01/2026
|
|
355
|
+
@version 1.0
|
|
356
|
+
/*/
|
|
357
|
+
User Function zDispatcher()
|
|
358
|
+
Local nThreads := 5
|
|
359
|
+
Local nI := 0
|
|
360
|
+
|
|
361
|
+
ConOut("[zDispatcher] Iniciando " + cValToChar(nThreads) + " threads")
|
|
362
|
+
|
|
363
|
+
// Prepara ambiente para o dispatcher
|
|
364
|
+
If !RpcSetEnv("99", "01", "admin", " ", "FAT", "zDispatcher")
|
|
365
|
+
ConOut("[zDispatcher] ERRO: Falha ao preparar ambiente.")
|
|
366
|
+
Return Nil
|
|
367
|
+
EndIf
|
|
368
|
+
|
|
369
|
+
// Inicia threads de processamento
|
|
370
|
+
For nI := 1 To nThreads
|
|
371
|
+
StartJob("U_zWorker", GetEnvServer(), .F., cValToChar(nI))
|
|
372
|
+
ConOut("[zDispatcher] Thread " + cValToChar(nI) + " iniciada.")
|
|
373
|
+
Next nI
|
|
374
|
+
|
|
375
|
+
RpcClearEnv()
|
|
376
|
+
|
|
377
|
+
Return Nil
|
|
378
|
+
|
|
379
|
+
/*/{Protheus.doc} zWorker
|
|
380
|
+
Worker thread para processamento paralelo
|
|
381
|
+
@type User Function
|
|
382
|
+
@author Autor
|
|
383
|
+
@since 01/01/2026
|
|
384
|
+
@version 1.0
|
|
385
|
+
/*/
|
|
386
|
+
User Function zWorker(cThreadId)
|
|
387
|
+
Default cThreadId := "0"
|
|
388
|
+
|
|
389
|
+
ConOut("[zWorker-" + cThreadId + "] Inicio: " + Time())
|
|
390
|
+
|
|
391
|
+
// Cada thread prepara seu proprio ambiente
|
|
392
|
+
If !RpcSetEnv("99", "01", "admin", " ", "FAT", "zWorker")
|
|
393
|
+
ConOut("[zWorker-" + cThreadId + "] ERRO: Falha ao preparar ambiente.")
|
|
394
|
+
Return Nil
|
|
395
|
+
EndIf
|
|
396
|
+
|
|
397
|
+
// Processamento da thread
|
|
398
|
+
fWorkerProcess(cThreadId)
|
|
399
|
+
|
|
400
|
+
RpcClearEnv()
|
|
401
|
+
|
|
402
|
+
ConOut("[zWorker-" + cThreadId + "] Fim: " + Time())
|
|
403
|
+
|
|
404
|
+
Return Nil
|
|
405
|
+
|
|
406
|
+
Static Function fWorkerProcess(cThreadId)
|
|
407
|
+
Local nI := 0
|
|
408
|
+
|
|
409
|
+
// Exemplo: cada thread processa um lote de registros
|
|
410
|
+
For nI := 1 To 100
|
|
411
|
+
// Processamento individual
|
|
412
|
+
ConOut("[zWorker-" + cThreadId + "] Processando item " + cValToChar(nI))
|
|
413
|
+
Next nI
|
|
414
|
+
|
|
415
|
+
Return Nil
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## 10. Job with Interval Processing
|
|
421
|
+
|
|
422
|
+
Pattern for a job that runs periodically and processes data in intervals:
|
|
423
|
+
|
|
424
|
+
```advpl
|
|
425
|
+
#Include "TOTVS.CH"
|
|
426
|
+
|
|
427
|
+
/*/{Protheus.doc} zJobLoop
|
|
428
|
+
Job com processamento em intervalo (controlado por OnStart/RefreshRate)
|
|
429
|
+
@type User Function
|
|
430
|
+
@author Autor
|
|
431
|
+
@since 01/01/2026
|
|
432
|
+
@version 1.0
|
|
433
|
+
/*/
|
|
434
|
+
User Function zJobLoop()
|
|
435
|
+
|
|
436
|
+
ConOut("[zJobLoop] Executando ciclo: " + DToC(Date()) + " " + Time())
|
|
437
|
+
|
|
438
|
+
If !RpcSetEnv("99", "01", "admin", " ", "FAT", "zJobLoop")
|
|
439
|
+
ConOut("[zJobLoop] ERRO: Falha ao preparar ambiente.")
|
|
440
|
+
Return Nil
|
|
441
|
+
EndIf
|
|
442
|
+
|
|
443
|
+
// Controle de concorrencia
|
|
444
|
+
If !LockByName("JOB_LOOP", .F., .F.)
|
|
445
|
+
ConOut("[zJobLoop] Outra instancia em execucao.")
|
|
446
|
+
RpcClearEnv()
|
|
447
|
+
Return Nil
|
|
448
|
+
EndIf
|
|
449
|
+
|
|
450
|
+
// Processa pendencias
|
|
451
|
+
fProcessaPendencias()
|
|
452
|
+
|
|
453
|
+
UnlockByName("JOB_LOOP", .F., .F.)
|
|
454
|
+
RpcClearEnv()
|
|
455
|
+
|
|
456
|
+
ConOut("[zJobLoop] Ciclo finalizado: " + DToC(Date()) + " " + Time())
|
|
457
|
+
// O RefreshRate do appserver.ini controlara a proxima execucao
|
|
458
|
+
|
|
459
|
+
Return Nil
|
|
460
|
+
|
|
461
|
+
Static Function fProcessaPendencias()
|
|
462
|
+
Local cQuery := ""
|
|
463
|
+
Local cAlias := GetNextAlias()
|
|
464
|
+
Local nCount := 0
|
|
465
|
+
|
|
466
|
+
cQuery := "SELECT ZA9_CODIGO, ZA9_DESCRI "
|
|
467
|
+
cQuery += "FROM " + RetSqlName("ZA9") + " ZA9 "
|
|
468
|
+
cQuery += "WHERE ZA9.D_E_L_E_T_ = ' ' "
|
|
469
|
+
cQuery += "AND ZA9_FILIAL = '" + xFilial("ZA9") + "' "
|
|
470
|
+
cQuery += "AND ZA9_STATUS = '1' "
|
|
471
|
+
cQuery += "ORDER BY ZA9_CODIGO "
|
|
472
|
+
|
|
473
|
+
TCQuery cQuery New Alias (cAlias)
|
|
474
|
+
|
|
475
|
+
While !(cAlias)->(Eof())
|
|
476
|
+
ConOut("[zJobLoop] Processando: " + AllTrim((cAlias)->ZA9_CODIGO))
|
|
477
|
+
// ... logica de processamento ...
|
|
478
|
+
nCount++
|
|
479
|
+
(cAlias)->(DbSkip())
|
|
480
|
+
EndWh
|
|
481
|
+
|
|
482
|
+
(cAlias)->(DbCloseArea())
|
|
483
|
+
|
|
484
|
+
ConOut("[zJobLoop] Total processado: " + cValToChar(nCount))
|
|
485
|
+
|
|
486
|
+
Return Nil
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## 11. Best Practices
|
|
492
|
+
|
|
493
|
+
### 11.1 Environment Management
|
|
494
|
+
|
|
495
|
+
- **Always** call `RpcClearEnv()` after `RpcSetEnv()`, even if an error occurs (use `Begin Sequence` / `Recover`).
|
|
496
|
+
- **Never** call `RpcSetEnv()` or `RpcClearEnv()` in routines executed from the Protheus menu (the environment is already managed by the system).
|
|
497
|
+
- Each thread started by `StartJob` must call its own `RpcSetEnv()` to prepare its environment.
|
|
498
|
+
|
|
499
|
+
### 11.2 Concurrency Control
|
|
500
|
+
|
|
501
|
+
- Use `LockByName()` / `UnlockByName()` to prevent multiple instances of the same job from running simultaneously.
|
|
502
|
+
- Always release the lock in a `Recover` block or ensure it is released even on errors.
|
|
503
|
+
|
|
504
|
+
### 11.3 Logging
|
|
505
|
+
|
|
506
|
+
- Use `ConOut()` for basic console logging.
|
|
507
|
+
- Prefer `FWLogMsg()` for standardized, structured logging in production environments.
|
|
508
|
+
- Include timestamps and identifiers in log messages for easier debugging.
|
|
509
|
+
|
|
510
|
+
### 11.4 Error Handling
|
|
511
|
+
|
|
512
|
+
```advpl
|
|
513
|
+
Begin Sequence
|
|
514
|
+
// Logica de negocio
|
|
515
|
+
fProcessa()
|
|
516
|
+
Recover
|
|
517
|
+
ConOut("[JOB] Erro critico durante processamento: " + DToC(Date()) + " " + Time())
|
|
518
|
+
End Sequence
|
|
519
|
+
```
|