@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.
Files changed (70) hide show
  1. package/agents/changelog-generator.md +63 -0
  2. package/agents/code-generator.md +215 -0
  3. package/agents/code-reviewer.md +145 -0
  4. package/agents/debugger.md +83 -0
  5. package/agents/doc-generator.md +67 -0
  6. package/agents/docs-reference.md +86 -0
  7. package/agents/migrator.md +84 -0
  8. package/agents/process-consultant.md +97 -0
  9. package/agents/refactorer.md +75 -0
  10. package/agents/sx-configurator.md +67 -0
  11. package/commands/changelog.md +66 -0
  12. package/commands/diagnose.md +67 -0
  13. package/commands/docs.md +81 -0
  14. package/commands/document.md +67 -0
  15. package/commands/explain.md +60 -0
  16. package/commands/generate.md +111 -0
  17. package/commands/migrate.md +81 -0
  18. package/commands/process.md +111 -0
  19. package/commands/refactor.md +65 -0
  20. package/commands/review.md +60 -0
  21. package/commands/sxgen.md +98 -0
  22. package/commands/test.md +103 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +143 -0
  26. package/dist/index.js.map +1 -0
  27. package/package.json +30 -0
  28. package/skills/advpl-code-generation/SKILL.md +163 -0
  29. package/skills/advpl-code-generation/patterns-fwformbrowse.md +485 -0
  30. package/skills/advpl-code-generation/patterns-jobs.md +519 -0
  31. package/skills/advpl-code-generation/patterns-mvc.md +765 -0
  32. package/skills/advpl-code-generation/patterns-pontos-entrada.md +708 -0
  33. package/skills/advpl-code-generation/patterns-rest.md +974 -0
  34. package/skills/advpl-code-generation/patterns-soap.md +639 -0
  35. package/skills/advpl-code-generation/patterns-treport.md +481 -0
  36. package/skills/advpl-code-generation/patterns-workflow.md +779 -0
  37. package/skills/advpl-code-generation/templates-classes.md +1096 -0
  38. package/skills/advpl-code-review/SKILL.md +72 -0
  39. package/skills/advpl-code-review/rules-best-practices.md +444 -0
  40. package/skills/advpl-code-review/rules-modernization.md +290 -0
  41. package/skills/advpl-code-review/rules-performance.md +333 -0
  42. package/skills/advpl-code-review/rules-security.md +302 -0
  43. package/skills/advpl-debugging/SKILL.md +265 -0
  44. package/skills/advpl-debugging/common-errors.md +1124 -0
  45. package/skills/advpl-debugging/performance-tips.md +768 -0
  46. package/skills/advpl-refactoring/SKILL.md +139 -0
  47. package/skills/advpl-to-tlpp-migration/SKILL.md +293 -0
  48. package/skills/advpl-to-tlpp-migration/migration-checklist.md +122 -0
  49. package/skills/advpl-to-tlpp-migration/migration-rules.md +265 -0
  50. package/skills/changelog-patterns/SKILL.md +99 -0
  51. package/skills/code-explanation/SKILL.md +66 -0
  52. package/skills/documentation-patterns/SKILL.md +172 -0
  53. package/skills/embedded-sql/SKILL.md +379 -0
  54. package/skills/probat-testing/SKILL.md +226 -0
  55. package/skills/probat-testing/patterns-unit-tests.md +614 -0
  56. package/skills/protheus-business/SKILL.md +92 -0
  57. package/skills/protheus-business/modulo-compras.md +780 -0
  58. package/skills/protheus-business/modulo-contabilidade.md +874 -0
  59. package/skills/protheus-business/modulo-estoque.md +876 -0
  60. package/skills/protheus-business/modulo-faturamento.md +800 -0
  61. package/skills/protheus-business/modulo-financeiro.md +1015 -0
  62. package/skills/protheus-business/modulo-fiscal.md +749 -0
  63. package/skills/protheus-business/modulo-manutencao.md +848 -0
  64. package/skills/protheus-business/modulo-pcp.md +743 -0
  65. package/skills/protheus-reference/SKILL.md +119 -0
  66. package/skills/protheus-reference/native-functions.md +7029 -0
  67. package/skills/protheus-reference/rest-api-reference.md +1758 -0
  68. package/skills/protheus-reference/restricted-functions.md +265 -0
  69. package/skills/protheus-reference/sx-dictionary.md +854 -0
  70. package/skills/sx-configuration/SKILL.md +184 -0
@@ -0,0 +1,974 @@
1
+ # Protheus REST API Patterns
2
+
3
+ Patterns for implementing REST API endpoints in TOTVS Protheus using both WsRestFul and TLPP annotations.
4
+
5
+ ---
6
+
7
+ ## 1. WsRestFul Pattern (Class-Based)
8
+
9
+ The `WsRestFul` approach uses class declarations to define REST services. This is the traditional Protheus REST pattern.
10
+
11
+ ### 1.1 Service Declaration
12
+
13
+ ```advpl
14
+ #Include "TOTVS.CH"
15
+ #Include "RestFul.ch"
16
+
17
+ /*/{Protheus.doc} CUSTOMERS
18
+ API REST para cadastro de clientes
19
+ @type WsRestFul
20
+ @author Autor
21
+ @since 01/01/2026
22
+ @version 1.0
23
+ /*/
24
+ WsRestFul CUSTOMERS Description "API de Clientes" Format APPLICATION_JSON
25
+
26
+ WsData nPage As Integer Optional
27
+ WsData nPageSize As Integer Optional
28
+ WsData cCodigo As Character Optional
29
+ WsData cLoja As Character Optional
30
+
31
+ WsMethod GET Description "Retorna clientes" WsSyntax "/customers" Path "/customers"
32
+ WsMethod POST Description "Inclui cliente" WsSyntax "/customers" Path "/customers"
33
+ WsMethod PUT Description "Altera cliente" WsSyntax "/customers/{codigo}/{loja}" Path "/customers/{codigo}/{loja}"
34
+ WsMethod DELETE Description "Exclui cliente" WsSyntax "/customers/{codigo}/{loja}" Path "/customers/{codigo}/{loja}"
35
+
36
+ End WsRestFul
37
+ ```
38
+
39
+ ### 1.2 GET Method - List/Search
40
+
41
+ ```advpl
42
+ WsMethod GET WsReceive nPage, nPageSize WsService CUSTOMERS
43
+ Local lRet := .T.
44
+ Local oJsonArr := JsonObject():New()
45
+ Local oJsonObj := Nil
46
+ Local aArea := GetArea()
47
+ Local cAlias := "SA1"
48
+ Local nCount := 0
49
+ Local nSkip := 0
50
+ Local nPageLoc := IIf(::nPage == Nil, 1, ::nPage)
51
+ Local nSizeLoc := IIf(::nPageSize == Nil, 20, ::nPageSize)
52
+ Local aResult := {}
53
+
54
+ // Autenticacao
55
+ If !ValidToken(Self)
56
+ SetRestFault(401, "Token invalido ou ausente")
57
+ lRet := .F.
58
+ RestArea(aArea)
59
+ Return lRet
60
+ EndIf
61
+
62
+ nSkip := (nPageLoc - 1) * nSizeLoc
63
+
64
+ DbSelectArea(cAlias)
65
+ DbSetOrder(1)
66
+ DbGoTop()
67
+
68
+ While !Eof() .And. nCount < nSizeLoc
69
+ If xFilial(cAlias) == SA1->A1_FILIAL
70
+ nSkip--
71
+ If nSkip < 0
72
+ oJsonObj := JsonObject():New()
73
+ oJsonObj["codigo"] := AllTrim(SA1->A1_COD)
74
+ oJsonObj["loja"] := AllTrim(SA1->A1_LOJA)
75
+ oJsonObj["nome"] := AllTrim(SA1->A1_NOME)
76
+ oJsonObj["cnpj"] := AllTrim(SA1->A1_CGC)
77
+ oJsonObj["email"] := AllTrim(SA1->A1_EMAIL)
78
+ oJsonObj["telefone"] := AllTrim(SA1->A1_TEL)
79
+
80
+ aAdd(aResult, oJsonObj)
81
+ nCount++
82
+ EndIf
83
+ EndIf
84
+ DbSkip()
85
+ EndWh
86
+
87
+ oJsonArr["items"] := aResult
88
+ oJsonArr["page"] := nPageLoc
89
+ oJsonArr["pageSize"] := nSizeLoc
90
+ oJsonArr["hasNext"] := !Eof()
91
+
92
+ ::SetContentType("application/json")
93
+ ::SetResponse(oJsonArr:ToJson())
94
+
95
+ FreeObj(oJsonArr)
96
+ RestArea(aArea)
97
+
98
+ Return lRet
99
+ ```
100
+
101
+ ### 1.3 POST Method - Create
102
+
103
+ ```advpl
104
+ WsMethod POST WsService CUSTOMERS
105
+ Local lRet := .T.
106
+ Local oJson := JsonObject():New()
107
+ Local oResp := Nil
108
+ Local cBody := ::GetContent()
109
+ Local nParsed := 0
110
+ Local aArea := GetArea()
111
+
112
+ // Autenticacao
113
+ If !ValidToken(Self)
114
+ SetRestFault(401, "Token invalido ou ausente")
115
+ lRet := .F.
116
+ RestArea(aArea)
117
+ Return lRet
118
+ EndIf
119
+
120
+ // Parse JSON body
121
+ nParsed := oJson:FromJson(cBody)
122
+ If nParsed <> 0
123
+ SetRestFault(400, "JSON invalido na posicao: " + cValToChar(nParsed))
124
+ lRet := .F.
125
+ FreeObj(oJson)
126
+ RestArea(aArea)
127
+ Return lRet
128
+ EndIf
129
+
130
+ // Validacao dos campos obrigatorios
131
+ If Empty(oJson["nome"]) .Or. Empty(oJson["cnpj"])
132
+ SetRestFault(422, "Campos obrigatorios: nome, cnpj")
133
+ lRet := .F.
134
+ FreeObj(oJson)
135
+ RestArea(aArea)
136
+ Return lRet
137
+ EndIf
138
+
139
+ // Inclusao
140
+ DbSelectArea("SA1")
141
+
142
+ Begin Transaction
143
+ RecLock("SA1", .T.)
144
+ SA1->A1_FILIAL := xFilial("SA1")
145
+ SA1->A1_COD := GetSXENum("SA1", "A1_COD")
146
+ SA1->A1_LOJA := "01"
147
+ SA1->A1_NOME := oJson["nome"]
148
+ SA1->A1_CGC := oJson["cnpj"]
149
+ SA1->A1_EMAIL := IIf(oJson["email"] <> Nil, oJson["email"], "")
150
+ SA1->A1_TEL := IIf(oJson["telefone"] <> Nil, oJson["telefone"], "")
151
+ MsUnlock()
152
+ ConfirmSX8()
153
+ End Transaction
154
+
155
+ // Response
156
+ oResp := JsonObject():New()
157
+ oResp["codigo"] := AllTrim(SA1->A1_COD)
158
+ oResp["loja"] := AllTrim(SA1->A1_LOJA)
159
+ oResp["nome"] := AllTrim(SA1->A1_NOME)
160
+ oResp["message"] := "Cliente incluido com sucesso"
161
+
162
+ ::SetContentType("application/json")
163
+ ::SetStatus(201)
164
+ ::SetResponse(oResp:ToJson())
165
+
166
+ FreeObj(oJson)
167
+ FreeObj(oResp)
168
+ RestArea(aArea)
169
+
170
+ Return lRet
171
+ ```
172
+
173
+ ### 1.4 PUT Method - Update
174
+
175
+ ```advpl
176
+ WsMethod PUT WsReceive cCodigo, cLoja WsService CUSTOMERS
177
+ Local lRet := .T.
178
+ Local oJson := JsonObject():New()
179
+ Local oResp := Nil
180
+ Local cBody := ::GetContent()
181
+ Local nParsed := 0
182
+ Local aArea := GetArea()
183
+
184
+ If !ValidToken(Self)
185
+ SetRestFault(401, "Token invalido ou ausente")
186
+ lRet := .F.
187
+ RestArea(aArea)
188
+ Return lRet
189
+ EndIf
190
+
191
+ nParsed := oJson:FromJson(cBody)
192
+ If nParsed <> 0
193
+ SetRestFault(400, "JSON invalido")
194
+ lRet := .F.
195
+ FreeObj(oJson)
196
+ RestArea(aArea)
197
+ Return lRet
198
+ EndIf
199
+
200
+ DbSelectArea("SA1")
201
+ DbSetOrder(1)
202
+
203
+ If !DbSeek(xFilial("SA1") + PadR(::cCodigo, TamSX3("A1_COD")[1]) + PadR(::cLoja, TamSX3("A1_LOJA")[1]))
204
+ SetRestFault(404, "Cliente nao encontrado: " + ::cCodigo + "/" + ::cLoja)
205
+ lRet := .F.
206
+ FreeObj(oJson)
207
+ RestArea(aArea)
208
+ Return lRet
209
+ EndIf
210
+
211
+ Begin Transaction
212
+ RecLock("SA1", .F.)
213
+ If oJson["nome"] <> Nil
214
+ SA1->A1_NOME := oJson["nome"]
215
+ EndIf
216
+ If oJson["cnpj"] <> Nil
217
+ SA1->A1_CGC := oJson["cnpj"]
218
+ EndIf
219
+ If oJson["email"] <> Nil
220
+ SA1->A1_EMAIL := oJson["email"]
221
+ EndIf
222
+ If oJson["telefone"] <> Nil
223
+ SA1->A1_TEL := oJson["telefone"]
224
+ EndIf
225
+ MsUnlock()
226
+ End Transaction
227
+
228
+ oResp := JsonObject():New()
229
+ oResp["codigo"] := AllTrim(SA1->A1_COD)
230
+ oResp["loja"] := AllTrim(SA1->A1_LOJA)
231
+ oResp["message"] := "Cliente alterado com sucesso"
232
+
233
+ ::SetContentType("application/json")
234
+ ::SetResponse(oResp:ToJson())
235
+
236
+ FreeObj(oJson)
237
+ FreeObj(oResp)
238
+ RestArea(aArea)
239
+
240
+ Return lRet
241
+ ```
242
+
243
+ ### 1.5 DELETE Method
244
+
245
+ ```advpl
246
+ WsMethod DELETE WsReceive cCodigo, cLoja WsService CUSTOMERS
247
+ Local lRet := .T.
248
+ Local oResp := Nil
249
+ Local aArea := GetArea()
250
+
251
+ If !ValidToken(Self)
252
+ SetRestFault(401, "Token invalido ou ausente")
253
+ lRet := .F.
254
+ RestArea(aArea)
255
+ Return lRet
256
+ EndIf
257
+
258
+ DbSelectArea("SA1")
259
+ DbSetOrder(1)
260
+
261
+ If !DbSeek(xFilial("SA1") + PadR(::cCodigo, TamSX3("A1_COD")[1]) + PadR(::cLoja, TamSX3("A1_LOJA")[1]))
262
+ SetRestFault(404, "Cliente nao encontrado")
263
+ lRet := .F.
264
+ RestArea(aArea)
265
+ Return lRet
266
+ EndIf
267
+
268
+ Begin Transaction
269
+ RecLock("SA1", .F.)
270
+ SA1->D_E_L_E_T_ := "*"
271
+ MsUnlock()
272
+ End Transaction
273
+
274
+ oResp := JsonObject():New()
275
+ oResp["message"] := "Cliente excluido com sucesso"
276
+
277
+ ::SetContentType("application/json")
278
+ ::SetResponse(oResp:ToJson())
279
+
280
+ FreeObj(oResp)
281
+ RestArea(aArea)
282
+
283
+ Return lRet
284
+ ```
285
+
286
+ ### 1.6 Authentication Helper
287
+
288
+ ```advpl
289
+ Static Function ValidToken(oSelf)
290
+ Local lValid := .F.
291
+ Local cAuthHeader := ""
292
+ Local cToken := ""
293
+
294
+ cAuthHeader := oSelf:GetHeader("Authorization")
295
+
296
+ If !Empty(cAuthHeader)
297
+ // Exemplo: validar Bearer token
298
+ If Left(cAuthHeader, 7) == "Bearer "
299
+ cToken := SubStr(cAuthHeader, 8)
300
+ // Valide o token contra sua base/cache
301
+ lValid := ValidateJWT(cToken) // Implementar conforme necessidade
302
+ EndIf
303
+ EndIf
304
+
305
+ Return lValid
306
+ ```
307
+
308
+ ---
309
+
310
+ ## 2. TLPP REST Pattern (Annotation-Based)
311
+
312
+ The TLPP approach uses annotations (decorators) to define REST endpoints. This is the modern pattern available in newer Protheus versions.
313
+
314
+ ### 2.1 Complete TLPP REST Service
315
+
316
+ ```tlpp
317
+ #Include "tlpp-core.th"
318
+ #Include "tlpp-rest.th"
319
+
320
+ /*/{Protheus.doc} ProductsAPI
321
+ API REST de Produtos usando TLPP
322
+ @type Namespace tlpp.rest
323
+ @author Autor
324
+ @since 01/01/2026
325
+ @version 1.0
326
+ /*/
327
+
328
+ // =============================================================
329
+ // GET /api/v1/products - Lista produtos
330
+ // =============================================================
331
+ @RestService("/api/v1/products")
332
+ @Get("")
333
+ Function getProducts()
334
+ Local oJson := JsonObject():New()
335
+ Local oItem := Nil
336
+ Local aResult := {}
337
+ Local aArea := GetArea()
338
+ Local cPage := oRest:getQueryString():getValue("page")
339
+ Local cSize := oRest:getQueryString():getValue("pageSize")
340
+ Local nPage := IIf(Empty(cPage), 1, Val(cPage))
341
+ Local nSize := IIf(Empty(cSize), 20, Val(cSize))
342
+ Local nSkip := (nPage - 1) * nSize
343
+ Local nCount := 0
344
+
345
+ DbSelectArea("SB1")
346
+ DbSetOrder(1)
347
+ DbGoTop()
348
+
349
+ While !Eof() .And. nCount < nSize
350
+ If xFilial("SB1") == SB1->B1_FILIAL
351
+ nSkip--
352
+ If nSkip < 0
353
+ oItem := JsonObject():New()
354
+ oItem["codigo"] := AllTrim(SB1->B1_COD)
355
+ oItem["descricao"] := AllTrim(SB1->B1_DESC)
356
+ oItem["tipo"] := AllTrim(SB1->B1_TIPO)
357
+ oItem["unidade"] := AllTrim(SB1->B1_UM)
358
+ oItem["preco"] := SB1->B1_PRV1
359
+
360
+ aAdd(aResult, oItem)
361
+ nCount++
362
+ EndIf
363
+ EndIf
364
+ DbSkip()
365
+ EndWh
366
+
367
+ oJson["items"] := aResult
368
+ oJson["page"] := nPage
369
+ oJson["pageSize"] := nSize
370
+ oJson["hasNext"] := !Eof()
371
+
372
+ oRest:setResponse(oJson:ToJson())
373
+ oRest:setStatus(200)
374
+
375
+ FreeObj(oJson)
376
+ RestArea(aArea)
377
+
378
+ Return .T.
379
+
380
+ // =============================================================
381
+ // GET /api/v1/products/{codigo} - Busca produto por codigo
382
+ // =============================================================
383
+ @RestService("/api/v1/products")
384
+ @Get("/{codigo}")
385
+ Function getProductById()
386
+ Local oJson := Nil
387
+ Local aArea := GetArea()
388
+ Local cCodigo := oRest:getPathParamsRequest():getValue("codigo")
389
+
390
+ DbSelectArea("SB1")
391
+ DbSetOrder(1)
392
+
393
+ If !DbSeek(xFilial("SB1") + PadR(cCodigo, TamSX3("B1_COD")[1]))
394
+ oRest:setStatus(404)
395
+ oRest:setResponse('{"error":"Produto nao encontrado"}')
396
+ RestArea(aArea)
397
+ Return .T.
398
+ EndIf
399
+
400
+ oJson := JsonObject():New()
401
+ oJson["codigo"] := AllTrim(SB1->B1_COD)
402
+ oJson["descricao"] := AllTrim(SB1->B1_DESC)
403
+ oJson["tipo"] := AllTrim(SB1->B1_TIPO)
404
+ oJson["unidade"] := AllTrim(SB1->B1_UM)
405
+ oJson["grupo"] := AllTrim(SB1->B1_GRUPO)
406
+ oJson["preco"] := SB1->B1_PRV1
407
+ oJson["custo"] := SB1->B1_CUSTO1
408
+
409
+ oRest:setResponse(oJson:ToJson())
410
+ oRest:setStatus(200)
411
+
412
+ FreeObj(oJson)
413
+ RestArea(aArea)
414
+
415
+ Return .T.
416
+
417
+ // =============================================================
418
+ // POST /api/v1/products - Cria produto
419
+ // =============================================================
420
+ @RestService("/api/v1/products")
421
+ @Post("")
422
+ Function createProduct()
423
+ Local oJson := JsonObject():New()
424
+ Local oResp := Nil
425
+ Local cBody := oRest:getBody()
426
+ Local nParsed := oJson:FromJson(cBody)
427
+ Local aArea := GetArea()
428
+
429
+ If nParsed <> 0
430
+ oRest:setStatus(400)
431
+ oRest:setResponse('{"error":"JSON invalido"}')
432
+ FreeObj(oJson)
433
+ Return .T.
434
+ EndIf
435
+
436
+ If Empty(oJson["descricao"])
437
+ oRest:setStatus(422)
438
+ oRest:setResponse('{"error":"Campo descricao e obrigatorio"}')
439
+ FreeObj(oJson)
440
+ Return .T.
441
+ EndIf
442
+
443
+ DbSelectArea("SB1")
444
+ Begin Transaction
445
+ RecLock("SB1", .T.)
446
+ SB1->B1_FILIAL := xFilial("SB1")
447
+ SB1->B1_COD := GetSXENum("SB1", "B1_COD")
448
+ SB1->B1_DESC := oJson["descricao"]
449
+ SB1->B1_TIPO := IIf(oJson["tipo"] <> Nil, oJson["tipo"], "PA")
450
+ SB1->B1_UM := IIf(oJson["unidade"] <> Nil, oJson["unidade"], "UN")
451
+ SB1->B1_PRV1 := IIf(oJson["preco"] <> Nil, oJson["preco"], 0)
452
+ MsUnlock()
453
+ ConfirmSX8()
454
+ End Transaction
455
+
456
+ oResp := JsonObject():New()
457
+ oResp["codigo"] := AllTrim(SB1->B1_COD)
458
+ oResp["descricao"] := AllTrim(SB1->B1_DESC)
459
+ oResp["message"] := "Produto criado com sucesso"
460
+
461
+ oRest:setResponse(oResp:ToJson())
462
+ oRest:setStatus(201)
463
+
464
+ FreeObj(oJson)
465
+ FreeObj(oResp)
466
+ RestArea(aArea)
467
+
468
+ Return .T.
469
+
470
+ // =============================================================
471
+ // PUT /api/v1/products/{codigo} - Atualiza produto
472
+ // =============================================================
473
+ @RestService("/api/v1/products")
474
+ @Put("/{codigo}")
475
+ Function updateProduct()
476
+ Local oJson := JsonObject():New()
477
+ Local oResp := Nil
478
+ Local cBody := oRest:getBody()
479
+ Local nParsed := oJson:FromJson(cBody)
480
+ Local cCodigo := oRest:getPathParamsRequest():getValue("codigo")
481
+ Local aArea := GetArea()
482
+
483
+ If nParsed <> 0
484
+ oRest:setStatus(400)
485
+ oRest:setResponse('{"error":"JSON invalido"}')
486
+ FreeObj(oJson)
487
+ Return .T.
488
+ EndIf
489
+
490
+ DbSelectArea("SB1")
491
+ DbSetOrder(1)
492
+
493
+ If !DbSeek(xFilial("SB1") + PadR(cCodigo, TamSX3("B1_COD")[1]))
494
+ oRest:setStatus(404)
495
+ oRest:setResponse('{"error":"Produto nao encontrado"}')
496
+ FreeObj(oJson)
497
+ RestArea(aArea)
498
+ Return .T.
499
+ EndIf
500
+
501
+ Begin Transaction
502
+ RecLock("SB1", .F.)
503
+ If oJson["descricao"] <> Nil
504
+ SB1->B1_DESC := oJson["descricao"]
505
+ EndIf
506
+ If oJson["tipo"] <> Nil
507
+ SB1->B1_TIPO := oJson["tipo"]
508
+ EndIf
509
+ If oJson["unidade"] <> Nil
510
+ SB1->B1_UM := oJson["unidade"]
511
+ EndIf
512
+ If oJson["preco"] <> Nil
513
+ SB1->B1_PRV1 := oJson["preco"]
514
+ EndIf
515
+ MsUnlock()
516
+ End Transaction
517
+
518
+ oResp := JsonObject():New()
519
+ oResp["message"] := "Produto atualizado com sucesso"
520
+
521
+ oRest:setResponse(oResp:ToJson())
522
+ oRest:setStatus(200)
523
+
524
+ FreeObj(oJson)
525
+ FreeObj(oResp)
526
+ RestArea(aArea)
527
+
528
+ Return .T.
529
+
530
+ // =============================================================
531
+ // DELETE /api/v1/products/{codigo} - Remove produto
532
+ // =============================================================
533
+ @RestService("/api/v1/products")
534
+ @Delete("/{codigo}")
535
+ Function deleteProduct()
536
+ Local cCodigo := oRest:getPathParamsRequest():getValue("codigo")
537
+ Local aArea := GetArea()
538
+
539
+ DbSelectArea("SB1")
540
+ DbSetOrder(1)
541
+
542
+ If !DbSeek(xFilial("SB1") + PadR(cCodigo, TamSX3("B1_COD")[1]))
543
+ oRest:setStatus(404)
544
+ oRest:setResponse('{"error":"Produto nao encontrado"}')
545
+ RestArea(aArea)
546
+ Return .T.
547
+ EndIf
548
+
549
+ Begin Transaction
550
+ RecLock("SB1", .F.)
551
+ SB1->D_E_L_E_T_ := "*"
552
+ MsUnlock()
553
+ End Transaction
554
+
555
+ oRest:setResponse('{"message":"Produto excluido com sucesso"}')
556
+ oRest:setStatus(200)
557
+
558
+ RestArea(aArea)
559
+
560
+ Return .T.
561
+ ```
562
+
563
+ ---
564
+
565
+ ## 3. JSON Handling
566
+
567
+ ### 3.1 Creating JSON from ADVPL Data
568
+
569
+ ```advpl
570
+ Local oJson := JsonObject():New()
571
+ Local oItem1 := Nil
572
+ Local oItem2 := Nil
573
+ Local aItens := {}
574
+ Local cJson := ""
575
+
576
+ // Simple values
577
+ oJson["nome"] := "Produto Teste"
578
+ oJson["codigo"] := 123
579
+ oJson["ativo"] := .T.
580
+ oJson["preco"] := 99.90
581
+ oJson["dataCriacao"] := DToS(Date())
582
+
583
+ // Nested object
584
+ oJson["endereco"] := JsonObject():New()
585
+ oJson["endereco"]["rua"] := "Rua Principal"
586
+ oJson["endereco"]["numero"] := "100"
587
+ oJson["endereco"]["cidade"] := "Sao Paulo"
588
+
589
+ // Array of objects
590
+ oItem1 := JsonObject():New()
591
+ oItem1["produto"] := "PROD001"
592
+ oItem1["quant"] := 10
593
+ aAdd(aItens, oItem1)
594
+
595
+ oItem2 := JsonObject():New()
596
+ oItem2["produto"] := "PROD002"
597
+ oItem2["quant"] := 5
598
+ aAdd(aItens, oItem2)
599
+
600
+ oJson["itens"] := aItens
601
+
602
+ // Convert to string
603
+ cJson := oJson:ToJson()
604
+ // Result: {"nome":"Produto Teste","codigo":123,"ativo":true,...}
605
+
606
+ FreeObj(oJson)
607
+ ```
608
+
609
+ ### 3.2 Parsing JSON Request Body
610
+
611
+ ```advpl
612
+ Local oJson := JsonObject():New()
613
+ Local cBody := '{"nome":"Teste","valor":100,"itens":[{"cod":"001"},{"cod":"002"}]}'
614
+ Local nResult := 0
615
+ Local cNome := ""
616
+ Local nValor := 0
617
+ Local aItens := {}
618
+ Local nI := 0
619
+
620
+ nResult := oJson:FromJson(cBody)
621
+
622
+ If nResult <> 0
623
+ Conout("Erro ao parsear JSON na posicao: " + cValToChar(nResult))
624
+ FreeObj(oJson)
625
+ Return .F.
626
+ EndIf
627
+
628
+ // Accessing values
629
+ cNome := oJson["nome"] // "Teste"
630
+ nValor := oJson["valor"] // 100
631
+
632
+ // Accessing array
633
+ aItens := oJson["itens"]
634
+ For nI := 1 To Len(aItens)
635
+ Conout("Item: " + aItens[nI]["cod"])
636
+ Next nI
637
+
638
+ // Check if key exists (returns Nil if not found)
639
+ If oJson["campo_opcional"] <> Nil
640
+ // Campo existe
641
+ EndIf
642
+
643
+ FreeObj(oJson)
644
+ ```
645
+
646
+ ### 3.3 JSON Array Response
647
+
648
+ ```advpl
649
+ Local oJsonArr := JsonObject():New()
650
+ Local oObj1 := Nil
651
+ Local oObj2 := Nil
652
+ Local aResult := {}
653
+ Local cResponse := ""
654
+
655
+ // Build array of objects
656
+ oObj1 := JsonObject():New()
657
+ oObj1["id"] := 1
658
+ oObj1["nome"] := "Item 1"
659
+ aAdd(aResult, oObj1)
660
+
661
+ oObj2 := JsonObject():New()
662
+ oObj2["id"] := 2
663
+ oObj2["nome"] := "Item 2"
664
+ aAdd(aResult, oObj2)
665
+
666
+ oJsonArr["data"] := aResult
667
+ oJsonArr["total"] := Len(aResult)
668
+
669
+ cResponse := oJsonArr:ToJson()
670
+ // {"data":[{"id":1,"nome":"Item 1"},{"id":2,"nome":"Item 2"}],"total":2}
671
+
672
+ FreeObj(oJsonArr)
673
+ ```
674
+
675
+ ---
676
+
677
+ ## 4. Authentication Middleware
678
+
679
+ ### 4.1 Bearer Token Authentication
680
+
681
+ ```advpl
682
+ Static Function ValidToken(oSelf)
683
+ Local lValid := .F.
684
+ Local cAuthHeader := ""
685
+ Local cToken := ""
686
+
687
+ cAuthHeader := oSelf:GetHeader("Authorization")
688
+
689
+ If Empty(cAuthHeader)
690
+ Return .F.
691
+ EndIf
692
+
693
+ If Left(cAuthHeader, 7) <> "Bearer "
694
+ Return .F.
695
+ EndIf
696
+
697
+ cToken := SubStr(cAuthHeader, 8)
698
+
699
+ // Opcao 1: Validar contra tabela de tokens
700
+ DbSelectArea("SZ9") // Tabela customizada de tokens
701
+ DbSetOrder(1)
702
+ If DbSeek(cToken)
703
+ If SZ9->Z9_EXPIRA >= Date()
704
+ lValid := .T.
705
+ EndIf
706
+ EndIf
707
+
708
+ Return lValid
709
+ ```
710
+
711
+ ### 4.2 Basic Authentication
712
+
713
+ ```advpl
714
+ Static Function ValidBasicAuth(oSelf)
715
+ Local lValid := .F.
716
+ Local cAuthHeader := ""
717
+ Local cBase64 := ""
718
+ Local cDecoded := ""
719
+ Local nPos := 0
720
+ Local cUser := ""
721
+ Local cPass := ""
722
+
723
+ cAuthHeader := oSelf:GetHeader("Authorization")
724
+
725
+ If Empty(cAuthHeader) .Or. Left(cAuthHeader, 6) <> "Basic "
726
+ Return .F.
727
+ EndIf
728
+
729
+ cBase64 := SubStr(cAuthHeader, 7)
730
+ cDecoded := Decode64(cBase64)
731
+ nPos := At(":", cDecoded)
732
+
733
+ If nPos > 0
734
+ cUser := Left(cDecoded, nPos - 1)
735
+ cPass := SubStr(cDecoded, nPos + 1)
736
+
737
+ // Validar usuario e senha contra base
738
+ lValid := FWCheckPass(cUser, cPass) // Funcao customizada
739
+ EndIf
740
+
741
+ Return lValid
742
+ ```
743
+
744
+ ### 4.3 Applying Authentication in WsRestFul
745
+
746
+ ```advpl
747
+ WsMethod GET WsService CUSTOMERS
748
+ // Sempre verificar autenticacao no inicio de cada metodo
749
+ If !ValidToken(Self)
750
+ SetRestFault(401, '{"error":"Unauthorized","message":"Token invalido ou ausente"}')
751
+ Return .F.
752
+ EndIf
753
+
754
+ // ... resto da logica
755
+ Return .T.
756
+ ```
757
+
758
+ ---
759
+
760
+ ## 5. CORS Configuration
761
+
762
+ ### 5.1 CORS Headers in WsRestFul
763
+
764
+ ```advpl
765
+ // Adicionar headers CORS no response
766
+ Static Function SetCorsHeaders(oSelf)
767
+ oSelf:SetHeader("Access-Control-Allow-Origin", "*")
768
+ oSelf:SetHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
769
+ oSelf:SetHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
770
+ oSelf:SetHeader("Access-Control-Max-Age", "3600")
771
+ Return Nil
772
+ ```
773
+
774
+ ### 5.2 Appserver.ini REST Configuration
775
+
776
+ CORS and REST settings can also be configured in `appserver.ini`:
777
+
778
+ ```ini
779
+ [HTTPREST]
780
+ Port=8080
781
+ URIs=HTTPURI
782
+
783
+ [HTTPURI]
784
+ URL=/rest
785
+ PrepareIn=All
786
+ Instances=2,5
787
+ CORSOrigins=*
788
+ CORSHeaders=Content-Type,Authorization
789
+ CORSMethods=GET,POST,PUT,DELETE,OPTIONS
790
+ ```
791
+
792
+ ---
793
+
794
+ ## 6. Common Patterns
795
+
796
+ ### 6.1 Pagination
797
+
798
+ ```advpl
799
+ Static Function GetPaginated(cAlias, nPage, nPageSize, oFilter)
800
+ Local aResult := {}
801
+ Local oResp := Nil
802
+ Local nSkip := (nPage - 1) * nPageSize
803
+ Local nCount := 0
804
+ Local nTotal := 0
805
+
806
+ DbSelectArea(cAlias)
807
+ DbSetOrder(1)
808
+ DbGoTop()
809
+
810
+ // Count total (optional, can be expensive)
811
+ Count To nTotal For xFilial(cAlias) == &(cAlias)->(FieldGet(1))
812
+ DbGoTop()
813
+
814
+ // Skip to page
815
+ While !Eof() .And. nSkip > 0
816
+ If xFilial(cAlias) == &(cAlias)->(FieldGet(1))
817
+ nSkip--
818
+ EndIf
819
+ DbSkip()
820
+ EndWh
821
+
822
+ // Collect results
823
+ While !Eof() .And. nCount < nPageSize
824
+ If xFilial(cAlias) == &(cAlias)->(FieldGet(1))
825
+ // Add record to aResult
826
+ nCount++
827
+ EndIf
828
+ DbSkip()
829
+ EndWh
830
+
831
+ oResp := JsonObject():New()
832
+ oResp["items"] := aResult
833
+ oResp["page"] := nPage
834
+ oResp["pageSize"] := nPageSize
835
+ oResp["totalItems"] := nTotal
836
+ oResp["totalPages"] := Ceiling(nTotal / nPageSize)
837
+ oResp["hasNext"] := (nPage * nPageSize) < nTotal
838
+
839
+ Return oResp
840
+ ```
841
+
842
+ ### 6.2 Filtering with Query Parameters
843
+
844
+ ```advpl
845
+ // WsRestFul approach
846
+ WsMethod GET WsReceive cNome, cCidade, cEstado WsService CUSTOMERS
847
+
848
+ // TLPP approach
849
+ Local cNome := oRest:getQueryString():getValue("nome")
850
+ Local cCidade := oRest:getQueryString():getValue("cidade")
851
+ Local lInclude := .F.
852
+
853
+ // Build filter
854
+ DbSelectArea("SA1")
855
+ DbSetOrder(1)
856
+ DbGoTop()
857
+
858
+ While !Eof()
859
+ lInclude := .T.
860
+
861
+ If !Empty(cNome) .And. !(AllTrim(cNome) $ Upper(SA1->A1_NOME))
862
+ lInclude := .F.
863
+ EndIf
864
+
865
+ If !Empty(cCidade) .And. AllTrim(Upper(SA1->A1_MUN)) <> AllTrim(Upper(cCidade))
866
+ lInclude := .F.
867
+ EndIf
868
+
869
+ If lInclude
870
+ // Add to results
871
+ EndIf
872
+
873
+ DbSkip()
874
+ EndWh
875
+ ```
876
+
877
+ ### 6.3 Standard Error Response
878
+
879
+ ```advpl
880
+ Static Function SendError(oSelf, nStatus, cCode, cMessage, cDetails)
881
+ Local oErr := JsonObject():New()
882
+
883
+ oErr["error"] := cCode
884
+ oErr["message"] := cMessage
885
+ oErr["status"] := nStatus
886
+
887
+ If !Empty(cDetails)
888
+ oErr["details"] := cDetails
889
+ EndIf
890
+
891
+ oSelf:SetContentType("application/json")
892
+ SetRestFault(nStatus, oErr:ToJson())
893
+
894
+ FreeObj(oErr)
895
+
896
+ Return Nil
897
+
898
+ // Usage:
899
+ // SendError(Self, 404, "NOT_FOUND", "Cliente nao encontrado", "Codigo: " + cCodigo)
900
+ // SendError(Self, 422, "VALIDATION_ERROR", "Campos invalidos", "Campo 'nome' e obrigatorio")
901
+ // SendError(Self, 500, "INTERNAL_ERROR", "Erro interno do servidor", "")
902
+ ```
903
+
904
+ ### 6.4 Standard Success Response
905
+
906
+ ```advpl
907
+ Static Function SendSuccess(oSelf, nStatus, xData, cMessage)
908
+ Local oResp := JsonObject():New()
909
+
910
+ Default nStatus := 200
911
+
912
+ If ValType(xData) == "O" // JsonObject
913
+ oResp["data"] := xData
914
+ ElseIf ValType(xData) == "A" // Array
915
+ oResp["data"] := xData
916
+ EndIf
917
+
918
+ If !Empty(cMessage)
919
+ oResp["message"] := cMessage
920
+ EndIf
921
+
922
+ oSelf:SetContentType("application/json")
923
+ oSelf:SetStatus(nStatus)
924
+ oSelf:SetResponse(oResp:ToJson())
925
+
926
+ FreeObj(oResp)
927
+
928
+ Return Nil
929
+ ```
930
+
931
+ ### 6.5 Query with TCQuery (SQL)
932
+
933
+ For more efficient queries, use `TCQuery` instead of navigating records:
934
+
935
+ ```advpl
936
+ Static Function QueryCustomers(cWhere, nPage, nPageSize)
937
+ Local cQuery := ""
938
+ Local cAlias := GetNextAlias()
939
+ Local aResult := {}
940
+ Local oItem := Nil
941
+
942
+ cQuery := "SELECT A1_COD, A1_LOJA, A1_NOME, A1_CGC, A1_EMAIL "
943
+ cQuery += "FROM " + RetSqlName("SA1") + " SA1 "
944
+ cQuery += "WHERE SA1.D_E_L_E_T_ = ' ' "
945
+ cQuery += "AND A1_FILIAL = '" + xFilial("SA1") + "' "
946
+
947
+ If !Empty(cWhere)
948
+ cQuery += "AND " + cWhere + " "
949
+ EndIf
950
+
951
+ cQuery += "ORDER BY A1_COD, A1_LOJA "
952
+
953
+ // Paginacao via SQL (depende do SGBD)
954
+ // SQL Server:
955
+ cQuery += "OFFSET " + cValToChar((nPage - 1) * nPageSize) + " ROWS "
956
+ cQuery += "FETCH NEXT " + cValToChar(nPageSize) + " ROWS ONLY"
957
+
958
+ TCQuery cQuery New Alias (cAlias)
959
+
960
+ While !(cAlias)->(Eof())
961
+ oItem := JsonObject():New()
962
+ oItem["codigo"] := AllTrim((cAlias)->A1_COD)
963
+ oItem["loja"] := AllTrim((cAlias)->A1_LOJA)
964
+ oItem["nome"] := AllTrim((cAlias)->A1_NOME)
965
+ oItem["cnpj"] := AllTrim((cAlias)->A1_CGC)
966
+ oItem["email"] := AllTrim((cAlias)->A1_EMAIL)
967
+ aAdd(aResult, oItem)
968
+ (cAlias)->(DbSkip())
969
+ EndWh
970
+
971
+ (cAlias)->(DbCloseArea())
972
+
973
+ Return aResult
974
+ ```