@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,639 @@
|
|
|
1
|
+
# Protheus SOAP Web Service Patterns
|
|
2
|
+
|
|
3
|
+
Patterns for exposing and consuming SOAP Web Services in TOTVS Protheus. SOAP in Protheus is exclusive to `.prw` files — there is no TLPP equivalent.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Exposing SOAP Web Services (WsService)
|
|
8
|
+
|
|
9
|
+
The `WsService` approach defines SOAP services with typed attributes and methods. The Protheus AppServer automatically generates the WSDL.
|
|
10
|
+
|
|
11
|
+
### 1.1 Includes
|
|
12
|
+
|
|
13
|
+
```advpl
|
|
14
|
+
#Include "TOTVS.CH"
|
|
15
|
+
#Include "APWebSrv.ch"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Both includes are required** for exposing SOAP services.
|
|
19
|
+
|
|
20
|
+
### 1.2 Service Declaration
|
|
21
|
+
|
|
22
|
+
```advpl
|
|
23
|
+
#Include "TOTVS.CH"
|
|
24
|
+
#Include "APWebSrv.ch"
|
|
25
|
+
|
|
26
|
+
/*/{Protheus.doc} zWSClientes
|
|
27
|
+
Web Service SOAP para operacoes com clientes
|
|
28
|
+
@type WsService
|
|
29
|
+
@author Autor
|
|
30
|
+
@since 01/01/2026
|
|
31
|
+
@version 1.0
|
|
32
|
+
/*/
|
|
33
|
+
WsService zWSClientes Description "WebService de Clientes"
|
|
34
|
+
|
|
35
|
+
// Atributos de entrada/saida
|
|
36
|
+
WsData cViewRece As String
|
|
37
|
+
WsData cViewSend As String
|
|
38
|
+
WsData cNewRece As String
|
|
39
|
+
WsData cNewSend As String
|
|
40
|
+
WsData cDelRece As String
|
|
41
|
+
WsData cDelSend As String
|
|
42
|
+
|
|
43
|
+
// Metodos expostos
|
|
44
|
+
WsMethod ViewCli Description "Consultar cliente por codigo ou CGC"
|
|
45
|
+
WsMethod NewCli Description "Incluir novo cliente"
|
|
46
|
+
WsMethod DelCli Description "Excluir cliente por codigo"
|
|
47
|
+
|
|
48
|
+
EndWsService
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 1.3 WsData Types
|
|
52
|
+
|
|
53
|
+
| Type | ADVPL Equivalent | Example |
|
|
54
|
+
|------|-----------------|---------|
|
|
55
|
+
| `String` | Character | `WsData cNome As String` |
|
|
56
|
+
| `Integer` | Numeric (integer) | `WsData nQtd As Integer` |
|
|
57
|
+
| `Float` | Numeric (decimal) | `WsData nValor As Float` |
|
|
58
|
+
| `Boolean` | Logical | `WsData lAtivo As Boolean` |
|
|
59
|
+
| `Date` | Date | `WsData dEmissao As Date` |
|
|
60
|
+
|
|
61
|
+
### 1.4 WsMethod Syntax
|
|
62
|
+
|
|
63
|
+
```advpl
|
|
64
|
+
WsMethod <MethodName> WsReceive <param1>, <param2> WsSend <paramOut> WsService <ServiceName>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `WsReceive` — attributes that the method receives (input)
|
|
68
|
+
- `WsSend` — attribute that the method returns (output)
|
|
69
|
+
- `WsService` — name of the service this method belongs to
|
|
70
|
+
- All parameters must be declared as `WsData` in the service
|
|
71
|
+
|
|
72
|
+
### 1.5 Method Implementation: Query (ViewCli)
|
|
73
|
+
|
|
74
|
+
```advpl
|
|
75
|
+
/*/{Protheus.doc} WsMethod ViewCli
|
|
76
|
+
Consulta informacoes de um cliente por codigo ou CGC
|
|
77
|
+
@type WsMethod
|
|
78
|
+
@author Autor
|
|
79
|
+
@since 01/01/2026
|
|
80
|
+
@param cViewRece, Caractere, Codigo ou CGC do cliente
|
|
81
|
+
@return cViewSend, Caractere, JSON com dados do cliente
|
|
82
|
+
/*/
|
|
83
|
+
WsMethod ViewCli WsReceive cViewRece WsSend cViewSend WsService zWSClientes
|
|
84
|
+
Local aArea := FWGetArea()
|
|
85
|
+
Local lRet := .T.
|
|
86
|
+
Local cBusca := AllTrim(::cViewRece)
|
|
87
|
+
Local nIndice := 0
|
|
88
|
+
Local cCGC := ""
|
|
89
|
+
Local cMascCPF := "@R 999.999.999-99"
|
|
90
|
+
Local cMascCNPJ := "@R 99.999.999/9999-99"
|
|
91
|
+
Local jResponse := JsonObject():New()
|
|
92
|
+
|
|
93
|
+
// Normalizar entrada (remover pontuacao)
|
|
94
|
+
cBusca := StrTran(cBusca, ".", "")
|
|
95
|
+
cBusca := StrTran(cBusca, "/", "")
|
|
96
|
+
cBusca := StrTran(cBusca, "-", "")
|
|
97
|
+
|
|
98
|
+
// Determinar indice de busca
|
|
99
|
+
If Len(cBusca) == 14 .Or. Len(cBusca) == 11
|
|
100
|
+
nIndice := 3 // A1_FILIAL + A1_CGC
|
|
101
|
+
Else
|
|
102
|
+
nIndice := 1 // A1_FILIAL + A1_COD + A1_LOJA
|
|
103
|
+
EndIf
|
|
104
|
+
|
|
105
|
+
DbSelectArea("SA1")
|
|
106
|
+
SA1->(DbSetOrder(nIndice))
|
|
107
|
+
|
|
108
|
+
If SA1->(MsSeek(FWxFilial("SA1") + cBusca))
|
|
109
|
+
cCGC := AllTrim(SA1->A1_CGC)
|
|
110
|
+
|
|
111
|
+
jResponse["status"] := "Cliente encontrado"
|
|
112
|
+
jResponse["codigo"] := AllTrim(SA1->A1_COD) + AllTrim(SA1->A1_LOJA)
|
|
113
|
+
jResponse["nome"] := AllTrim(SA1->A1_NOME)
|
|
114
|
+
jResponse["email"] := AllTrim(SA1->A1_EMAIL)
|
|
115
|
+
|
|
116
|
+
If Len(cCGC) == 14
|
|
117
|
+
jResponse["cnpj"] := AllTrim(Transform(cCGC, cMascCNPJ))
|
|
118
|
+
ElseIf Len(cCGC) == 11
|
|
119
|
+
jResponse["cpf"] := AllTrim(Transform(cCGC, cMascCPF))
|
|
120
|
+
EndIf
|
|
121
|
+
Else
|
|
122
|
+
jResponse["status"] := "Cliente nao encontrado com a chave fornecida"
|
|
123
|
+
EndIf
|
|
124
|
+
|
|
125
|
+
::cViewSend := jResponse:ToJson()
|
|
126
|
+
|
|
127
|
+
FreeObj(jResponse)
|
|
128
|
+
FWRestArea(aArea)
|
|
129
|
+
Return lRet
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 1.6 Method Implementation: Create (NewCli)
|
|
133
|
+
|
|
134
|
+
```advpl
|
|
135
|
+
/*/{Protheus.doc} WsMethod NewCli
|
|
136
|
+
Inclui um novo cliente via JSON
|
|
137
|
+
@type WsMethod
|
|
138
|
+
@author Autor
|
|
139
|
+
@since 01/01/2026
|
|
140
|
+
@param cNewRece, Caractere, JSON com dados do cliente
|
|
141
|
+
@return cNewSend, Caractere, JSON com resultado da inclusao
|
|
142
|
+
/*/
|
|
143
|
+
WsMethod NewCli WsReceive cNewRece WsSend cNewSend WsService zWSClientes
|
|
144
|
+
Local aArea := FWGetArea()
|
|
145
|
+
Local lRet := .T.
|
|
146
|
+
Local jRecebe := JsonObject():New()
|
|
147
|
+
Local jResp := JsonObject():New()
|
|
148
|
+
Local cError := ""
|
|
149
|
+
Local aDados := {}
|
|
150
|
+
Local aLogAuto := {}
|
|
151
|
+
Local cErrorLog := ""
|
|
152
|
+
Local nLinha := 0
|
|
153
|
+
Private lMsHelpAuto := .T.
|
|
154
|
+
Private lAutoErrNoFile := .T.
|
|
155
|
+
Private lMsErroAuto := .F.
|
|
156
|
+
|
|
157
|
+
// Parse do JSON de entrada
|
|
158
|
+
cError := jRecebe:FromJson(::cNewRece)
|
|
159
|
+
|
|
160
|
+
If !Empty(cError)
|
|
161
|
+
jResp["errorId"] := "NEW001"
|
|
162
|
+
jResp["error"] := "Erro no parse do JSON"
|
|
163
|
+
jResp["solution"] := "Verifique a estrutura do JSON enviado"
|
|
164
|
+
::cNewSend := jResp:ToJson()
|
|
165
|
+
FreeObj(jRecebe)
|
|
166
|
+
FreeObj(jResp)
|
|
167
|
+
FWRestArea(aArea)
|
|
168
|
+
Return lRet
|
|
169
|
+
EndIf
|
|
170
|
+
|
|
171
|
+
// Validacao de campos obrigatorios
|
|
172
|
+
If Empty(jRecebe:GetJsonObject("cod")) .Or. ;
|
|
173
|
+
Empty(jRecebe:GetJsonObject("loja")) .Or. ;
|
|
174
|
+
Empty(jRecebe:GetJsonObject("nome")) .Or. ;
|
|
175
|
+
Empty(jRecebe:GetJsonObject("tipo"))
|
|
176
|
+
|
|
177
|
+
jResp["errorId"] := "NEW002"
|
|
178
|
+
jResp["error"] := "Campos obrigatorios ausentes"
|
|
179
|
+
jResp["solution"] := "Envie: cod, loja, nome, tipo"
|
|
180
|
+
::cNewSend := jResp:ToJson()
|
|
181
|
+
FreeObj(jRecebe)
|
|
182
|
+
FreeObj(jResp)
|
|
183
|
+
FWRestArea(aArea)
|
|
184
|
+
Return lRet
|
|
185
|
+
EndIf
|
|
186
|
+
|
|
187
|
+
// Montar array para MsExecAuto
|
|
188
|
+
aAdd(aDados, {"A1_COD", jRecebe:GetJsonObject("cod"), Nil})
|
|
189
|
+
aAdd(aDados, {"A1_LOJA", jRecebe:GetJsonObject("loja"), Nil})
|
|
190
|
+
aAdd(aDados, {"A1_NOME", jRecebe:GetJsonObject("nome"), Nil})
|
|
191
|
+
aAdd(aDados, {"A1_TIPO", jRecebe:GetJsonObject("tipo"), Nil})
|
|
192
|
+
|
|
193
|
+
If !Empty(jRecebe:GetJsonObject("end"))
|
|
194
|
+
aAdd(aDados, {"A1_END", jRecebe:GetJsonObject("end"), Nil})
|
|
195
|
+
EndIf
|
|
196
|
+
If !Empty(jRecebe:GetJsonObject("mun"))
|
|
197
|
+
aAdd(aDados, {"A1_MUN", jRecebe:GetJsonObject("mun"), Nil})
|
|
198
|
+
EndIf
|
|
199
|
+
If !Empty(jRecebe:GetJsonObject("est"))
|
|
200
|
+
aAdd(aDados, {"A1_EST", jRecebe:GetJsonObject("est"), Nil})
|
|
201
|
+
EndIf
|
|
202
|
+
|
|
203
|
+
// Executar inclusao via ExecAuto
|
|
204
|
+
MsExecAuto({|x, y| CRMA980(x, y)}, aDados, 3)
|
|
205
|
+
|
|
206
|
+
If lMsErroAuto
|
|
207
|
+
aLogAuto := GetAutoGrLog()
|
|
208
|
+
For nLinha := 1 To Len(aLogAuto)
|
|
209
|
+
cErrorLog += aLogAuto[nLinha] + CRLF
|
|
210
|
+
Next nLinha
|
|
211
|
+
|
|
212
|
+
Conout("zWSClientes:NewCli - Erro: " + cErrorLog)
|
|
213
|
+
|
|
214
|
+
jResp["errorId"] := "NEW003"
|
|
215
|
+
jResp["error"] := "Erro na inclusao do registro"
|
|
216
|
+
jResp["solution"] := cErrorLog
|
|
217
|
+
Else
|
|
218
|
+
jResp["note"] := "Registro incluido com sucesso"
|
|
219
|
+
EndIf
|
|
220
|
+
|
|
221
|
+
::cNewSend := jResp:ToJson()
|
|
222
|
+
|
|
223
|
+
FreeObj(jRecebe)
|
|
224
|
+
FreeObj(jResp)
|
|
225
|
+
FWRestArea(aArea)
|
|
226
|
+
Return lRet
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 1.7 Method Implementation: Delete (DelCli)
|
|
230
|
+
|
|
231
|
+
```advpl
|
|
232
|
+
/*/{Protheus.doc} WsMethod DelCli
|
|
233
|
+
Exclui um cliente por codigo
|
|
234
|
+
@type WsMethod
|
|
235
|
+
@author Autor
|
|
236
|
+
@since 01/01/2026
|
|
237
|
+
@param cDelRece, Caractere, Codigo do cliente (cod+loja)
|
|
238
|
+
@return cDelSend, Caractere, JSON com resultado da exclusao
|
|
239
|
+
/*/
|
|
240
|
+
WsMethod DelCli WsReceive cDelRece WsSend cDelSend WsService zWSClientes
|
|
241
|
+
Local aArea := FWGetArea()
|
|
242
|
+
Local lRet := .T.
|
|
243
|
+
Local cBusca := AllTrim(::cDelRece)
|
|
244
|
+
Local jResp := JsonObject():New()
|
|
245
|
+
Local aDados := {}
|
|
246
|
+
Private lMsHelpAuto := .T.
|
|
247
|
+
Private lAutoErrNoFile := .T.
|
|
248
|
+
Private lMsErroAuto := .F.
|
|
249
|
+
|
|
250
|
+
DbSelectArea("SA1")
|
|
251
|
+
SA1->(DbSetOrder(1))
|
|
252
|
+
|
|
253
|
+
If !SA1->(MsSeek(FWxFilial("SA1") + cBusca))
|
|
254
|
+
jResp["errorId"] := "DEL001"
|
|
255
|
+
jResp["error"] := "Cliente nao encontrado: " + cBusca
|
|
256
|
+
::cDelSend := jResp:ToJson()
|
|
257
|
+
FreeObj(jResp)
|
|
258
|
+
FWRestArea(aArea)
|
|
259
|
+
Return lRet
|
|
260
|
+
EndIf
|
|
261
|
+
|
|
262
|
+
aAdd(aDados, {"A1_COD", SA1->A1_COD, Nil})
|
|
263
|
+
aAdd(aDados, {"A1_LOJA", SA1->A1_LOJA, Nil})
|
|
264
|
+
|
|
265
|
+
MsExecAuto({|x, y| CRMA980(x, y)}, aDados, 5)
|
|
266
|
+
|
|
267
|
+
If lMsErroAuto
|
|
268
|
+
jResp["errorId"] := "DEL002"
|
|
269
|
+
jResp["error"] := "Erro na exclusao do registro"
|
|
270
|
+
Else
|
|
271
|
+
jResp["note"] := "Registro excluido com sucesso"
|
|
272
|
+
EndIf
|
|
273
|
+
|
|
274
|
+
::cDelSend := jResp:ToJson()
|
|
275
|
+
|
|
276
|
+
FreeObj(jResp)
|
|
277
|
+
FWRestArea(aArea)
|
|
278
|
+
Return lRet
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 1.8 Accessing the WSDL
|
|
282
|
+
|
|
283
|
+
After compiling the service and starting the AppServer, the WSDL is available at:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
http://<server>:<port>/ws/<ServiceName>.apw?WSDL
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
```
|
|
291
|
+
http://localhost:8091/ws/ZWSCLIENTES.apw?WSDL
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The WSDL index page listing all services:
|
|
295
|
+
```
|
|
296
|
+
http://localhost:8091/ws/WSINDEX.apw
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 1.9 appserver.ini Configuration
|
|
300
|
+
|
|
301
|
+
```ini
|
|
302
|
+
[HTTPSERVER]
|
|
303
|
+
Enable=1
|
|
304
|
+
Port=8091
|
|
305
|
+
|
|
306
|
+
[HTTPENV]
|
|
307
|
+
Environment=ENVIRONMENT_NAME
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 2. Consuming SOAP Web Services (TWsdlManager)
|
|
313
|
+
|
|
314
|
+
Use `TWsdlManager` to call external SOAP services from ADVPL.
|
|
315
|
+
|
|
316
|
+
### 2.1 Include
|
|
317
|
+
|
|
318
|
+
```advpl
|
|
319
|
+
#Include "TOTVS.CH"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Only `TOTVS.CH` is needed — `TWsdlManager` is a native Protheus class.
|
|
323
|
+
|
|
324
|
+
### 2.2 Basic Flow: Load WSDL, Call Operation, Read Response
|
|
325
|
+
|
|
326
|
+
```advpl
|
|
327
|
+
#Include "TOTVS.CH"
|
|
328
|
+
|
|
329
|
+
/*/{Protheus.doc} ConsumeWS
|
|
330
|
+
Exemplo de consumo de Web Service SOAP via TWsdlManager
|
|
331
|
+
@type User Function
|
|
332
|
+
@author Autor
|
|
333
|
+
@since 01/01/2026
|
|
334
|
+
@version 1.0
|
|
335
|
+
/*/
|
|
336
|
+
User Function ConsumeWS()
|
|
337
|
+
Local oWsdl := TWsdlManager():New()
|
|
338
|
+
Local cUrl := "http://127.0.0.1:8091/ws/ZWSCLIENTES.apw?WSDL"
|
|
339
|
+
Local cResp := ""
|
|
340
|
+
Local cMsgWs := ""
|
|
341
|
+
Local lRet := .F.
|
|
342
|
+
|
|
343
|
+
// Configurar timeout (segundos)
|
|
344
|
+
oWsdl:nTimeout := 120
|
|
345
|
+
|
|
346
|
+
// Desabilitar verificacao de certificado SSL (apenas para testes)
|
|
347
|
+
oWsdl:bNoCheckPeerCert := .T.
|
|
348
|
+
|
|
349
|
+
// Carregar WSDL
|
|
350
|
+
If !oWsdl:ParseURL(cUrl)
|
|
351
|
+
Conout("Erro ao carregar WSDL: " + oWsdl:cError)
|
|
352
|
+
Return lRet
|
|
353
|
+
EndIf
|
|
354
|
+
|
|
355
|
+
// Selecionar operacao
|
|
356
|
+
If !oWsdl:SetOperation("VIEWCLI")
|
|
357
|
+
Conout("Erro ao selecionar operacao: " + oWsdl:cError)
|
|
358
|
+
Return lRet
|
|
359
|
+
EndIf
|
|
360
|
+
|
|
361
|
+
// Montar envelope SOAP
|
|
362
|
+
cMsgWs := '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://localhost/">'
|
|
363
|
+
cMsgWs += ' <soapenv:Header/>'
|
|
364
|
+
cMsgWs += ' <soapenv:Body>'
|
|
365
|
+
cMsgWs += ' <ws:VIEWCLI>'
|
|
366
|
+
cMsgWs += ' <ws:CVIEWRECE>000001</ws:CVIEWRECE>'
|
|
367
|
+
cMsgWs += ' </ws:VIEWCLI>'
|
|
368
|
+
cMsgWs += ' </soapenv:Body>'
|
|
369
|
+
cMsgWs += '</soapenv:Envelope>'
|
|
370
|
+
|
|
371
|
+
// Enviar requisicao
|
|
372
|
+
If oWsdl:SendSoapMsg(cMsgWs)
|
|
373
|
+
cResp := oWsdl:GetParsedResponse()
|
|
374
|
+
|
|
375
|
+
If !Empty(cResp)
|
|
376
|
+
Conout("Resposta: " + cResp)
|
|
377
|
+
lRet := .T.
|
|
378
|
+
Else
|
|
379
|
+
Conout("Resposta vazia")
|
|
380
|
+
EndIf
|
|
381
|
+
Else
|
|
382
|
+
Conout("Erro ao enviar SOAP: " + oWsdl:cError)
|
|
383
|
+
EndIf
|
|
384
|
+
|
|
385
|
+
Return lRet
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 2.3 Listing Available Operations
|
|
389
|
+
|
|
390
|
+
```advpl
|
|
391
|
+
User Function ListOps()
|
|
392
|
+
Local oWsdl := TWsdlManager():New()
|
|
393
|
+
Local cUrl := "http://127.0.0.1:8091/ws/ZWSCLIENTES.apw?WSDL"
|
|
394
|
+
Local aOps := {}
|
|
395
|
+
Local nI := 0
|
|
396
|
+
|
|
397
|
+
oWsdl:nTimeout := 60
|
|
398
|
+
|
|
399
|
+
If !oWsdl:ParseURL(cUrl)
|
|
400
|
+
Conout("Erro: " + oWsdl:cError)
|
|
401
|
+
Return
|
|
402
|
+
EndIf
|
|
403
|
+
|
|
404
|
+
// Listar operacoes do servico
|
|
405
|
+
Conout("Operacoes disponiveis:")
|
|
406
|
+
aOps := oWsdl:ListOperations()
|
|
407
|
+
For nI := 1 To Len(aOps)
|
|
408
|
+
Conout(" " + aOps[nI])
|
|
409
|
+
Next nI
|
|
410
|
+
|
|
411
|
+
Return
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 2.4 Error Handling
|
|
415
|
+
|
|
416
|
+
```advpl
|
|
417
|
+
User Function ConsumeWSSafe()
|
|
418
|
+
Local oWsdl := TWsdlManager():New()
|
|
419
|
+
Local cUrl := "http://api.externa.com/service?WSDL"
|
|
420
|
+
Local cResp := ""
|
|
421
|
+
Local cFault := ""
|
|
422
|
+
Local cMsgWs := ""
|
|
423
|
+
|
|
424
|
+
oWsdl:nTimeout := 30
|
|
425
|
+
oWsdl:bNoCheckPeerCert := .T.
|
|
426
|
+
|
|
427
|
+
// Carregar WSDL com tratamento
|
|
428
|
+
If !oWsdl:ParseURL(cUrl)
|
|
429
|
+
FWLogMsg("ERROR", , "ConsumeWS", "ParseURL", , , ;
|
|
430
|
+
"Falha ao carregar WSDL: " + cUrl + " - " + oWsdl:cError)
|
|
431
|
+
Return .F.
|
|
432
|
+
EndIf
|
|
433
|
+
|
|
434
|
+
If !oWsdl:SetOperation("OPERACAO")
|
|
435
|
+
FWLogMsg("ERROR", , "ConsumeWS", "SetOperation", , , ;
|
|
436
|
+
"Operacao nao encontrada: OPERACAO")
|
|
437
|
+
Return .F.
|
|
438
|
+
EndIf
|
|
439
|
+
|
|
440
|
+
// Montar e enviar
|
|
441
|
+
cMsgWs := fMontaEnvelope()
|
|
442
|
+
|
|
443
|
+
If !oWsdl:SendSoapMsg(cMsgWs)
|
|
444
|
+
FWLogMsg("ERROR", , "ConsumeWS", "SendSoapMsg", , , ;
|
|
445
|
+
"Falha no envio SOAP: " + oWsdl:cError)
|
|
446
|
+
Return .F.
|
|
447
|
+
EndIf
|
|
448
|
+
|
|
449
|
+
// Verificar SOAP Fault via propriedades
|
|
450
|
+
If !Empty(oWsdl:cFaultCode)
|
|
451
|
+
FWLogMsg("ERROR", , "ConsumeWS", "SoapFault", , , ;
|
|
452
|
+
"SOAP Fault: [" + oWsdl:cFaultCode + "] " + oWsdl:cFaultString)
|
|
453
|
+
Return .F.
|
|
454
|
+
EndIf
|
|
455
|
+
|
|
456
|
+
// Ler resposta
|
|
457
|
+
cResp := oWsdl:GetParsedResponse()
|
|
458
|
+
|
|
459
|
+
If Empty(cResp)
|
|
460
|
+
FWLogMsg("WARN", , "ConsumeWS", "Response", , , ;
|
|
461
|
+
"Resposta vazia do servico")
|
|
462
|
+
Return .F.
|
|
463
|
+
EndIf
|
|
464
|
+
|
|
465
|
+
// Processar resposta (pode ser XML ou texto)
|
|
466
|
+
Conout("Resposta: " + cResp)
|
|
467
|
+
|
|
468
|
+
Return .T.
|
|
469
|
+
|
|
470
|
+
Static Function fMontaEnvelope()
|
|
471
|
+
Local cMsg := ""
|
|
472
|
+
cMsg := '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://target/">'
|
|
473
|
+
cMsg += ' <soapenv:Header/>'
|
|
474
|
+
cMsg += ' <soapenv:Body>'
|
|
475
|
+
cMsg += ' <ws:OPERACAO>'
|
|
476
|
+
cMsg += ' <ws:PARAMETRO>valor</ws:PARAMETRO>'
|
|
477
|
+
cMsg += ' </ws:OPERACAO>'
|
|
478
|
+
cMsg += ' </soapenv:Body>'
|
|
479
|
+
cMsg += '</soapenv:Envelope>'
|
|
480
|
+
Return cMsg
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### 2.5 TWsdlManager Key Properties and Methods
|
|
484
|
+
|
|
485
|
+
| Property/Method | Type | Description |
|
|
486
|
+
|-----------------|------|-------------|
|
|
487
|
+
| `nTimeout` | Property | Timeout in seconds (default: 120) |
|
|
488
|
+
| `bNoCheckPeerCert` | Property | `.T.` to skip SSL certificate validation |
|
|
489
|
+
| `cError` | Property | Last error message |
|
|
490
|
+
| `ParseURL(cUrl)` | Method | Load WSDL from URL. Returns `.T.`/`.F.` |
|
|
491
|
+
| `ParseFile(cPath)` | Method | Load WSDL from local file. Returns `.T.`/`.F.` |
|
|
492
|
+
| `SetOperation(cOp)` | Method | Select the operation to call. Returns `.T.`/`.F.` |
|
|
493
|
+
| `SendSoapMsg(cXml)` | Method | Send SOAP envelope. Returns `.T.`/`.F.` |
|
|
494
|
+
| `GetParsedResponse()` | Method | Get the parsed response body |
|
|
495
|
+
| `GetSoapResponse()` | Method | Get the raw SOAP XML response |
|
|
496
|
+
| `GetSoapMsg()` | Method | Get the SOAP message that will be/was sent |
|
|
497
|
+
| `ListOperations()` | Method | List operations for the current service |
|
|
498
|
+
| `SetPort(cPort)` | Method | Select a service port from the WSDL |
|
|
499
|
+
| `SetValue(cParam, cValue)` | Method | Set input parameter value |
|
|
500
|
+
| `SimpleInput(cField)` | Method | Navigate to a simple input field |
|
|
501
|
+
| `cFaultCode` | Property | SOAP Fault code (check after SendSoapMsg) |
|
|
502
|
+
| `cFaultSubCode` | Property | SOAP Fault sub-code |
|
|
503
|
+
| `cFaultString` | Property | SOAP Fault description message |
|
|
504
|
+
| `cFaultActor` | Property | SOAP Fault actor |
|
|
505
|
+
| `lProcResp` | Property | `.T.` to auto-process response (default `.T.`) |
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## 3. SOAP vs REST — When to Use Each
|
|
510
|
+
|
|
511
|
+
| Aspect | SOAP (WsService) | REST (WsRestFul / TLPP) |
|
|
512
|
+
|--------|-------------------|------------------------|
|
|
513
|
+
| Protocol | XML over HTTP | JSON over HTTP |
|
|
514
|
+
| Contract | WSDL (strict) | Documentation (flexible) |
|
|
515
|
+
| Use case | Enterprise integration, legacy systems, NFe/NFSe | Modern APIs, mobile, SPA |
|
|
516
|
+
| Complexity | Higher (XML, envelope, WSDL) | Lower (JSON, HTTP verbs) |
|
|
517
|
+
| Performance | Heavier (XML parsing) | Lighter (JSON) |
|
|
518
|
+
| TLPP support | No (`.prw` only) | Yes (`.prw` and `.tlpp`) |
|
|
519
|
+
| Error format | SOAP Fault (XML) | HTTP status + JSON |
|
|
520
|
+
| When to choose | Integrating with systems that require SOAP (banks, government, NFe) | New APIs, internal integration, mobile apps |
|
|
521
|
+
|
|
522
|
+
**Rule of thumb:** Use REST for new development. Use SOAP only when the external system requires it.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## 4. Common Errors and Troubleshooting
|
|
527
|
+
|
|
528
|
+
### 4.1 WSDL Not Found (404)
|
|
529
|
+
|
|
530
|
+
**Error:** `http://server:port/ws/SERVICE.apw?WSDL` returns 404
|
|
531
|
+
|
|
532
|
+
**Causes:**
|
|
533
|
+
- Service not compiled into RPO
|
|
534
|
+
- `[HTTPSERVER]` not configured in `appserver.ini`
|
|
535
|
+
- Service name in URL does not match `WsService` declaration (case-sensitive)
|
|
536
|
+
|
|
537
|
+
**Solution:**
|
|
538
|
+
```ini
|
|
539
|
+
; appserver.ini
|
|
540
|
+
[HTTPSERVER]
|
|
541
|
+
Enable=1
|
|
542
|
+
Port=8091
|
|
543
|
+
```
|
|
544
|
+
Recompile the `.prw` file and restart the AppServer.
|
|
545
|
+
|
|
546
|
+
### 4.2 ParseURL Fails
|
|
547
|
+
|
|
548
|
+
**Error:** `oWsdl:ParseURL()` returns `.F.`
|
|
549
|
+
|
|
550
|
+
**Causes:**
|
|
551
|
+
- Network unreachable or timeout
|
|
552
|
+
- Invalid WSDL URL
|
|
553
|
+
- SSL certificate issue
|
|
554
|
+
|
|
555
|
+
**Solution:**
|
|
556
|
+
```advpl
|
|
557
|
+
// Increase timeout
|
|
558
|
+
oWsdl:nTimeout := 300
|
|
559
|
+
|
|
560
|
+
// Disable SSL check for testing
|
|
561
|
+
oWsdl:bNoCheckPeerCert := .T.
|
|
562
|
+
|
|
563
|
+
// Try loading from local file instead
|
|
564
|
+
If !oWsdl:ParseURL(cUrl)
|
|
565
|
+
Conout("ParseURL failed: " + oWsdl:cError)
|
|
566
|
+
// Save WSDL manually and use ParseFile
|
|
567
|
+
// oWsdl:ParseFile("C:\temp\service.wsdl")
|
|
568
|
+
EndIf
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### 4.3 SOAP Fault Response
|
|
572
|
+
|
|
573
|
+
**Error:** The service returns a SOAP Fault instead of the expected response.
|
|
574
|
+
|
|
575
|
+
**Solution:**
|
|
576
|
+
```advpl
|
|
577
|
+
If oWsdl:SendSoapMsg(cMsgWs)
|
|
578
|
+
// Check SOAP Fault via properties (NOT via GetSoapFault which does not exist)
|
|
579
|
+
If !Empty(oWsdl:cFaultCode)
|
|
580
|
+
Conout("SOAP Fault Code: " + oWsdl:cFaultCode)
|
|
581
|
+
Conout("SOAP Fault Msg: " + oWsdl:cFaultString)
|
|
582
|
+
// Also check the raw response for details
|
|
583
|
+
Conout("Raw XML: " + oWsdl:GetSoapResponse())
|
|
584
|
+
Else
|
|
585
|
+
cResp := oWsdl:GetParsedResponse()
|
|
586
|
+
EndIf
|
|
587
|
+
EndIf
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 4.4 Empty Response
|
|
591
|
+
|
|
592
|
+
**Error:** `GetParsedResponse()` returns empty string.
|
|
593
|
+
|
|
594
|
+
**Causes:**
|
|
595
|
+
- Wrong operation name
|
|
596
|
+
- Missing or wrong parameters in the envelope
|
|
597
|
+
- Server error not returned as SOAP Fault
|
|
598
|
+
|
|
599
|
+
**Solution:**
|
|
600
|
+
```advpl
|
|
601
|
+
// Check the raw SOAP response
|
|
602
|
+
Local cRawXml := oWsdl:GetSoapMsg()
|
|
603
|
+
Conout("Raw response: " + cRawXml)
|
|
604
|
+
|
|
605
|
+
// Verify operation name matches exactly (case-sensitive)
|
|
606
|
+
// Verify namespace in envelope matches the WSDL target namespace
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### 4.5 SSL Certificate Error
|
|
610
|
+
|
|
611
|
+
**Error:** Connection fails with SSL/TLS error.
|
|
612
|
+
|
|
613
|
+
**Solution:**
|
|
614
|
+
```advpl
|
|
615
|
+
// For testing only — disable certificate verification
|
|
616
|
+
oWsdl:bNoCheckPeerCert := .T.
|
|
617
|
+
|
|
618
|
+
// For production — configure certificates in appserver.ini:
|
|
619
|
+
// [SSLConfigure]
|
|
620
|
+
// CertificateClient=cert\client.pem
|
|
621
|
+
// KeyClient=cert\client.key
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## 5. Best Practices
|
|
627
|
+
|
|
628
|
+
| Practice | Description |
|
|
629
|
+
|----------|-------------|
|
|
630
|
+
| Always use `FWGetArea()` / `FWRestArea()` | Preserve work area state in every WsMethod |
|
|
631
|
+
| Validate input before processing | Check required fields and data format |
|
|
632
|
+
| Return structured responses | Use JSON via `JsonObject` for consistent output |
|
|
633
|
+
| Log errors with context | Use `FWLogMsg` or `Conout` with service/method name |
|
|
634
|
+
| Set appropriate timeout | Default 120s may be too long or short — adjust per service |
|
|
635
|
+
| Handle SOAP Faults explicitly | Always check `cFaultCode`/`cFaultString` properties after `SendSoapMsg()` |
|
|
636
|
+
| Use `MsExecAuto` for data operations | Triggers standard Protheus validations and workflows |
|
|
637
|
+
| Do NOT use `Private` variables in new code | Exception: `lMsHelpAuto`, `lAutoErrNoFile`, `lMsErroAuto` required by `MsExecAuto` |
|
|
638
|
+
| Name services with `z` prefix | Convention for custom services: `zWSClientes`, `zWSPedidos` |
|
|
639
|
+
| Keep methods focused | One operation per method — avoid methods that do multiple things |
|