@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,1096 @@
1
+ # TLPP Class Templates
2
+
3
+ Templates for creating classes in TLPP (TOTVS Language Plus Plus) for TOTVS Protheus.
4
+
5
+ ---
6
+
7
+ ## 1. Basic Class Syntax
8
+
9
+ A TLPP class declaration includes the class name, properties (`data`), and methods.
10
+
11
+ ```tlpp
12
+ #Include "tlpp-core.th"
13
+
14
+ /*/{Protheus.doc} MinhaClasse
15
+ Descricao da classe
16
+ @type Class
17
+ @author Autor
18
+ @since 01/01/2026
19
+ @version 1.0
20
+ /*/
21
+ Class MinhaClasse
22
+
23
+ // Properties
24
+ Data cNome As Character
25
+ Data nValor As Numeric
26
+ Data dData As Date
27
+ Data lAtivo As Logical
28
+ Data aItens As Array
29
+ Data oRelated As Object
30
+
31
+ // Methods
32
+ Method New() Constructor
33
+ Method Destroy()
34
+ Method GetNome()
35
+ Method SetNome(cNome)
36
+ Method Process()
37
+
38
+ EndClass
39
+
40
+ // Constructor
41
+ Method New() Class MinhaClasse
42
+ ::cNome := ""
43
+ ::nValor := 0
44
+ ::dData := Date()
45
+ ::lAtivo := .T.
46
+ ::aItens := {}
47
+ ::oRelated := Nil
48
+ Return Self
49
+
50
+ // Destructor
51
+ Method Destroy() Class MinhaClasse
52
+ ::aItens := {}
53
+ ::oRelated := Nil
54
+ Return Nil
55
+
56
+ Method GetNome() Class MinhaClasse
57
+ Return ::cNome
58
+
59
+ Method SetNome(cNome) Class MinhaClasse
60
+ ::cNome := cNome
61
+ Return Nil
62
+
63
+ Method Process() Class MinhaClasse
64
+ // Business logic
65
+ Conout("Processing: " + ::cNome)
66
+ Return .T.
67
+ ```
68
+
69
+ **Usage:**
70
+ ```tlpp
71
+ Local oObj := MinhaClasse():New()
72
+ oObj:SetNome("Teste")
73
+ oObj:Process()
74
+ oObj:Destroy()
75
+ FreeObj(oObj)
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 2. Class with Constructor and Destructor
81
+
82
+ The constructor (`New`) initializes all properties and returns `Self`. The destructor (`Destroy`) cleans up resources.
83
+
84
+ ```tlpp
85
+ Class PedidoVenda
86
+
87
+ Data cNumero As Character
88
+ Data cCliente As Character
89
+ Data cLoja As Character
90
+ Data dEmissao As Date
91
+ Data nTotal As Numeric
92
+ Data aItens As Array
93
+ Data lFinalizado As Logical
94
+
95
+ Method New(cCliente, cLoja) Constructor
96
+ Method Destroy()
97
+ Method AddItem(cProduto, nQuantidade, nPreco)
98
+ Method Finalizar()
99
+ Method GetTotal()
100
+
101
+ EndClass
102
+
103
+ Method New(cCliente, cLoja) Class PedidoVenda
104
+ Default cCliente := ""
105
+ Default cLoja := "01"
106
+
107
+ ::cNumero := GetSXENum("SC5", "C5_NUM")
108
+ ::cCliente := cCliente
109
+ ::cLoja := cLoja
110
+ ::dEmissao := Date()
111
+ ::nTotal := 0
112
+ ::aItens := {}
113
+ ::lFinalizado := .F.
114
+
115
+ Return Self
116
+
117
+ Method Destroy() Class PedidoVenda
118
+ If !::lFinalizado
119
+ RollBackSX8()
120
+ EndIf
121
+ ::aItens := {}
122
+ Return Nil
123
+
124
+ Method AddItem(cProduto, nQuantidade, nPreco) Class PedidoVenda
125
+ Local aItem := {}
126
+
127
+ If ::lFinalizado
128
+ Conout("Pedido ja finalizado. Nao e possivel adicionar itens.")
129
+ Return .F.
130
+ EndIf
131
+
132
+ aAdd(aItem, cProduto)
133
+ aAdd(aItem, nQuantidade)
134
+ aAdd(aItem, nPreco)
135
+ aAdd(aItem, nQuantidade * nPreco) // Subtotal
136
+
137
+ aAdd(::aItens, aItem)
138
+ ::nTotal += nQuantidade * nPreco
139
+
140
+ Return .T.
141
+
142
+ Method Finalizar() Class PedidoVenda
143
+ If Len(::aItens) == 0
144
+ Conout("Pedido sem itens.")
145
+ Return .F.
146
+ EndIf
147
+
148
+ ConfirmSX8()
149
+ ::lFinalizado := .T.
150
+
151
+ Return .T.
152
+
153
+ Method GetTotal() Class PedidoVenda
154
+ Return ::nTotal
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 3. Properties (Data Declarations)
160
+
161
+ Properties are declared with `Data` inside the class block. TLPP supports typed properties.
162
+
163
+ ```tlpp
164
+ Class Exemplo
165
+
166
+ // Typed properties
167
+ Data cNome As Character // String
168
+ Data nValor As Numeric // Number (integer or float)
169
+ Data dData As Date // Date
170
+ Data lAtivo As Logical // Boolean
171
+ Data aLista As Array // Array
172
+ Data oObjeto As Object // Object reference
173
+ Data bBloco As Block // Code block
174
+ Data xVariante As Variant // Any type
175
+
176
+ // Properties with initial values (set in constructor)
177
+ Method New() Constructor
178
+
179
+ EndClass
180
+
181
+ Method New() Class Exemplo
182
+ ::cNome := ""
183
+ ::nValor := 0
184
+ ::dData := CToD("")
185
+ ::lAtivo := .F.
186
+ ::aLista := {}
187
+ ::oObjeto := Nil
188
+ ::bBloco := {|| .T.}
189
+ ::xVariante := Nil
190
+ Return Self
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 4. Method Visibility
196
+
197
+ TLPP supports `public`, `private`, and `protected` visibility for methods.
198
+
199
+ ```tlpp
200
+ Class ContaBancaria
201
+
202
+ Data cConta As Character
203
+ Data cAgencia As Character
204
+ Data nSaldo As Numeric
205
+
206
+ // Public methods - accessible from anywhere
207
+ Public Method New(cConta, cAgencia) Constructor
208
+ Public Method Depositar(nValor)
209
+ Public Method Sacar(nValor)
210
+ Public Method GetSaldo()
211
+ Public Method GetExtrato()
212
+
213
+ // Private methods - accessible only within this class
214
+ Private Method ValidarValor(nValor)
215
+ Private Method RegistrarMovimento(cTipo, nValor)
216
+
217
+ // Protected methods - accessible in this class and subclasses
218
+ Protected Method CalcularTarifa(nValor)
219
+
220
+ EndClass
221
+
222
+ Public Method New(cConta, cAgencia) Class ContaBancaria
223
+ ::cConta := cConta
224
+ ::cAgencia := cAgencia
225
+ ::nSaldo := 0
226
+ Return Self
227
+
228
+ Public Method Depositar(nValor) Class ContaBancaria
229
+ If !::ValidarValor(nValor)
230
+ Return .F.
231
+ EndIf
232
+
233
+ ::nSaldo += nValor
234
+ ::RegistrarMovimento("C", nValor) // Credito
235
+
236
+ Return .T.
237
+
238
+ Public Method Sacar(nValor) Class ContaBancaria
239
+ Local nTarifa := ::CalcularTarifa(nValor)
240
+
241
+ If !::ValidarValor(nValor)
242
+ Return .F.
243
+ EndIf
244
+
245
+ If (nValor + nTarifa) > ::nSaldo
246
+ Conout("Saldo insuficiente")
247
+ Return .F.
248
+ EndIf
249
+
250
+ ::nSaldo -= (nValor + nTarifa)
251
+ ::RegistrarMovimento("D", nValor) // Debito
252
+
253
+ Return .T.
254
+
255
+ Public Method GetSaldo() Class ContaBancaria
256
+ Return ::nSaldo
257
+
258
+ Public Method GetExtrato() Class ContaBancaria
259
+ // Retorna movimentacoes
260
+ Return {}
261
+
262
+ Private Method ValidarValor(nValor) Class ContaBancaria
263
+ If nValor <= 0
264
+ Conout("Valor deve ser positivo")
265
+ Return .F.
266
+ EndIf
267
+ Return .T.
268
+
269
+ Private Method RegistrarMovimento(cTipo, nValor) Class ContaBancaria
270
+ // Gravar movimentacao no banco de dados
271
+ Conout("Movimento: " + cTipo + " Valor: " + cValToChar(nValor))
272
+ Return Nil
273
+
274
+ Protected Method CalcularTarifa(nValor) Class ContaBancaria
275
+ // Tarifa padrao: 0.1% do valor
276
+ Return nValor * 0.001
277
+ ```
278
+
279
+ ---
280
+
281
+ ## 5. Inheritance
282
+
283
+ Use the `FROM` keyword to inherit from a parent class. Override methods by redeclaring them.
284
+
285
+ ```tlpp
286
+ // Base class
287
+ Class Animal
288
+
289
+ Data cNome As Character
290
+ Data cEspecie As Character
291
+ Data nIdade As Numeric
292
+
293
+ Method New(cNome, cEspecie) Constructor
294
+ Method EmitirSom()
295
+ Method GetInfo()
296
+
297
+ EndClass
298
+
299
+ Method New(cNome, cEspecie) Class Animal
300
+ ::cNome := cNome
301
+ ::cEspecie := cEspecie
302
+ ::nIdade := 0
303
+ Return Self
304
+
305
+ Method EmitirSom() Class Animal
306
+ Conout("...")
307
+ Return Nil
308
+
309
+ Method GetInfo() Class Animal
310
+ Return ::cNome + " (" + ::cEspecie + ") - Idade: " + cValToChar(::nIdade)
311
+
312
+ // Derived class
313
+ Class Cachorro FROM Animal
314
+
315
+ Data cRaca As Character
316
+
317
+ Method New(cNome, cRaca) Constructor
318
+ Method EmitirSom() // Override
319
+
320
+ EndClass
321
+
322
+ Method New(cNome, cRaca) Class Cachorro
323
+ // Call parent constructor
324
+ _Super:New(cNome, "Canino")
325
+ ::cRaca := cRaca
326
+ Return Self
327
+
328
+ Method EmitirSom() Class Cachorro
329
+ Conout(::cNome + " diz: Au Au!")
330
+ Return Nil
331
+ ```
332
+
333
+ **Usage:**
334
+ ```tlpp
335
+ Local oCachorro := Cachorro():New("Rex", "Labrador")
336
+ oCachorro:EmitirSom() // "Rex diz: Au Au!"
337
+ Conout(oCachorro:GetInfo()) // "Rex (Canino) - Idade: 0" (inherited method)
338
+ ```
339
+
340
+ ---
341
+
342
+ ## 6. Static Methods
343
+
344
+ Static methods belong to the class itself, not to instances. They can be called without instantiation.
345
+
346
+ ```tlpp
347
+ Class MathHelper
348
+
349
+ Static Method Max(nA, nB)
350
+ Static Method Min(nA, nB)
351
+ Static Method Clamp(nValue, nMin, nMax)
352
+ Static Method Round(nValue, nDecimals)
353
+
354
+ EndClass
355
+
356
+ Static Method Max(nA, nB) Class MathHelper
357
+ Return IIf(nA > nB, nA, nB)
358
+
359
+ Static Method Min(nA, nB) Class MathHelper
360
+ Return IIf(nA < nB, nA, nB)
361
+
362
+ Static Method Clamp(nValue, nMin, nMax) Class MathHelper
363
+ If nValue < nMin
364
+ Return nMin
365
+ ElseIf nValue > nMax
366
+ Return nMax
367
+ EndIf
368
+ Return nValue
369
+
370
+ Static Method Round(nValue, nDecimals) Class MathHelper
371
+ Default nDecimals := 2
372
+ Return Round(nValue, nDecimals)
373
+ ```
374
+
375
+ **Usage:**
376
+ ```tlpp
377
+ Local nMax := MathHelper():Max(10, 20) // 20
378
+ Local nClamped := MathHelper():Clamp(150, 0, 100) // 100
379
+ ```
380
+
381
+ ---
382
+
383
+ ## 7. Namespace Usage
384
+
385
+ Namespaces organize classes and avoid name conflicts.
386
+
387
+ ```tlpp
388
+ #Include "tlpp-core.th"
389
+
390
+ // Declaring a namespace
391
+ namespace custom.vendas
392
+
393
+ // For REST annotations, add: #Include "tlpp-rest.th"
394
+ // Do NOT use "using namespace tlpp.*" -- use .th includes instead
395
+ // "using namespace" is only for custom/project namespaces (see example below)
396
+
397
+ /*/{Protheus.doc} PedidoService
398
+ Servico de pedidos de venda
399
+ @type Class
400
+ @author Autor
401
+ @since 01/01/2026
402
+ @version 1.0
403
+ /*/
404
+ Class PedidoService
405
+
406
+ Method New() Constructor
407
+ Method CriarPedido(oData)
408
+ Method BuscarPedido(cNumero)
409
+
410
+ EndClass
411
+
412
+ Method New() Class PedidoService
413
+ Return Self
414
+
415
+ Method CriarPedido(oData) Class PedidoService
416
+ // Implementation
417
+ Return .T.
418
+
419
+ Method BuscarPedido(cNumero) Class PedidoService
420
+ // Implementation
421
+ Return Nil
422
+ ```
423
+
424
+ **Using a namespaced class:**
425
+ ```tlpp
426
+ using namespace custom.vendas
427
+
428
+ Local oService := PedidoService():New()
429
+ oService:CriarPedido(oData)
430
+ ```
431
+
432
+ ---
433
+
434
+ ## 8. Template: Service Class
435
+
436
+ A Service class encapsulates business logic, keeping it separate from controllers and data access.
437
+
438
+ ```tlpp
439
+ #Include "tlpp-core.th"
440
+
441
+ /*/{Protheus.doc} ClienteService
442
+ Servico de regras de negocio para clientes
443
+ @type Class
444
+ @author Autor
445
+ @since 01/01/2026
446
+ @version 1.0
447
+ /*/
448
+ Class ClienteService
449
+
450
+ Data oRepository As Object
451
+ Data aErrors As Array
452
+ Data lHasErrors As Logical
453
+
454
+ Method New(oRepository) Constructor
455
+ Method Destroy()
456
+
457
+ // Business logic
458
+ Method CriarCliente(cNome, cCNPJ, cEmail, cTelefone)
459
+ Method AtualizarCliente(cCodigo, cLoja, oData)
460
+ Method InativarCliente(cCodigo, cLoja)
461
+ Method BuscarPorCNPJ(cCNPJ)
462
+ Method ValidarCNPJ(cCNPJ)
463
+
464
+ // Error handling
465
+ Private Method AddError(cMessage)
466
+ Method GetErrors()
467
+ Method HasErrors()
468
+ Method ClearErrors()
469
+
470
+ EndClass
471
+
472
+ Method New(oRepository) Class ClienteService
473
+ ::oRepository := oRepository
474
+ ::aErrors := {}
475
+ ::lHasErrors := .F.
476
+ Return Self
477
+
478
+ Method Destroy() Class ClienteService
479
+ ::oRepository := Nil
480
+ ::aErrors := {}
481
+ Return Nil
482
+
483
+ Method CriarCliente(cNome, cCNPJ, cEmail, cTelefone) Class ClienteService
484
+ Local lRet := .T.
485
+ Local oCliente := Nil
486
+
487
+ ::ClearErrors()
488
+
489
+ // Validacoes
490
+ If Empty(cNome)
491
+ ::AddError("Nome e obrigatorio")
492
+ lRet := .F.
493
+ EndIf
494
+
495
+ If Empty(cCNPJ) .Or. !::ValidarCNPJ(cCNPJ)
496
+ ::AddError("CNPJ invalido")
497
+ lRet := .F.
498
+ EndIf
499
+
500
+ // Verifica duplicidade
501
+ If lRet
502
+ oCliente := ::BuscarPorCNPJ(cCNPJ)
503
+ If oCliente <> Nil
504
+ ::AddError("Ja existe um cliente com este CNPJ")
505
+ lRet := .F.
506
+ EndIf
507
+ EndIf
508
+
509
+ // Persiste
510
+ If lRet
511
+ lRet := ::oRepository:Create(cNome, cCNPJ, cEmail, cTelefone)
512
+ If !lRet
513
+ ::AddError("Erro ao gravar cliente no banco de dados")
514
+ EndIf
515
+ EndIf
516
+
517
+ Return lRet
518
+
519
+ Method AtualizarCliente(cCodigo, cLoja, oData) Class ClienteService
520
+ Local lRet := .T.
521
+
522
+ ::ClearErrors()
523
+
524
+ // Verifica se cliente existe
525
+ Local oCliente := ::oRepository:FindById(cCodigo, cLoja)
526
+ If oCliente == Nil
527
+ ::AddError("Cliente nao encontrado: " + cCodigo + "/" + cLoja)
528
+ Return .F.
529
+ EndIf
530
+
531
+ // Validacoes de negocio
532
+ If oData["cnpj"] <> Nil .And. !::ValidarCNPJ(oData["cnpj"])
533
+ ::AddError("CNPJ invalido")
534
+ lRet := .F.
535
+ EndIf
536
+
537
+ If lRet
538
+ lRet := ::oRepository:Update(cCodigo, cLoja, oData)
539
+ EndIf
540
+
541
+ Return lRet
542
+
543
+ Method InativarCliente(cCodigo, cLoja) Class ClienteService
544
+ Local lRet := .T.
545
+
546
+ ::ClearErrors()
547
+
548
+ // Verifica se tem pedidos em aberto
549
+ DbSelectArea("SC5")
550
+ DbSetOrder(3) // Cliente + Loja
551
+ If DbSeek(xFilial("SC5") + cCodigo + cLoja)
552
+ While !Eof() .And. SC5->C5_CLIENTE == cCodigo .And. SC5->C5_LOJACLI == cLoja
553
+ If Empty(SC5->C5_NOTA)
554
+ ::AddError("Cliente possui pedidos em aberto. Nao e possivel inativar.")
555
+ Return .F.
556
+ EndIf
557
+ DbSkip()
558
+ EndWh
559
+ EndIf
560
+
561
+ If lRet
562
+ lRet := ::oRepository:Inactivate(cCodigo, cLoja)
563
+ EndIf
564
+
565
+ Return lRet
566
+
567
+ Method BuscarPorCNPJ(cCNPJ) Class ClienteService
568
+ Return ::oRepository:FindByCNPJ(cCNPJ)
569
+
570
+ Method ValidarCNPJ(cCNPJ) Class ClienteService
571
+ Local lValid := .T.
572
+ Local cCNPJClean := ""
573
+ Local nI := 0
574
+
575
+ // Remove formatacao
576
+ For nI := 1 To Len(cCNPJ)
577
+ If SubStr(cCNPJ, nI, 1) $ "0123456789"
578
+ cCNPJClean += SubStr(cCNPJ, nI, 1)
579
+ EndIf
580
+ Next nI
581
+
582
+ If Len(cCNPJClean) <> 14
583
+ lValid := .F.
584
+ EndIf
585
+
586
+ // Use CGC(cCNPJClean) for full validation if available
587
+ Return lValid
588
+
589
+ Private Method AddError(cMessage) Class ClienteService
590
+ aAdd(::aErrors, cMessage)
591
+ ::lHasErrors := .T.
592
+ Return Nil
593
+
594
+ Method GetErrors() Class ClienteService
595
+ Return ::aErrors
596
+
597
+ Method HasErrors() Class ClienteService
598
+ Return ::lHasErrors
599
+
600
+ Method ClearErrors() Class ClienteService
601
+ ::aErrors := {}
602
+ ::lHasErrors := .F.
603
+ Return Nil
604
+ ```
605
+
606
+ **Usage:**
607
+ ```tlpp
608
+ Local oRepo := ClienteRepository():New()
609
+ Local oService := ClienteService():New(oRepo)
610
+
611
+ If oService:CriarCliente("Empresa Teste", "12345678000199", "teste@email.com", "11999999999")
612
+ Conout("Cliente criado com sucesso")
613
+ Else
614
+ Local aErros := oService:GetErrors()
615
+ Local nI := 0
616
+ For nI := 1 To Len(aErros)
617
+ Conout("Erro: " + aErros[nI])
618
+ Next nI
619
+ EndIf
620
+
621
+ oService:Destroy()
622
+ FreeObj(oService)
623
+ oRepo:Destroy()
624
+ FreeObj(oRepo)
625
+ ```
626
+
627
+ ---
628
+
629
+ ## 9. Template: Repository Class
630
+
631
+ A Repository class encapsulates data access, providing CRUD operations and query methods.
632
+
633
+ ```tlpp
634
+ #Include "tlpp-core.th"
635
+
636
+ /*/{Protheus.doc} ClienteRepository
637
+ Repositorio de acesso a dados de clientes (SA1)
638
+ @type Class
639
+ @author Autor
640
+ @since 01/01/2026
641
+ @version 1.0
642
+ /*/
643
+ Class ClienteRepository
644
+
645
+ Data cAlias As Character
646
+
647
+ Method New() Constructor
648
+ Method Destroy()
649
+
650
+ // CRUD methods
651
+ Method FindById(cCodigo, cLoja)
652
+ Method FindByCNPJ(cCNPJ)
653
+ Method FindAll(nPage, nPageSize, cFiltro)
654
+ Method Create(cNome, cCNPJ, cEmail, cTelefone)
655
+ Method Update(cCodigo, cLoja, oData)
656
+ Method Delete(cCodigo, cLoja)
657
+ Method Inactivate(cCodigo, cLoja)
658
+
659
+ // Query helpers
660
+ Private Method BuildQuery(cWhere, cOrderBy, nLimit, nOffset)
661
+ Private Method RecordToJson()
662
+
663
+ EndClass
664
+
665
+ Method New() Class ClienteRepository
666
+ ::cAlias := "SA1"
667
+ Return Self
668
+
669
+ Method Destroy() Class ClienteRepository
670
+ Return Nil
671
+
672
+ // Find a single customer by code and store
673
+ Method FindById(cCodigo, cLoja) Class ClienteRepository
674
+ Local oResult := Nil
675
+ Local aArea := GetArea()
676
+
677
+ DbSelectArea(::cAlias)
678
+ DbSetOrder(1) // Filial + Codigo + Loja
679
+ If DbSeek(xFilial(::cAlias) + PadR(cCodigo, TamSX3("A1_COD")[1]) + PadR(cLoja, TamSX3("A1_LOJA")[1]))
680
+ oResult := ::RecordToJson()
681
+ EndIf
682
+
683
+ RestArea(aArea)
684
+ Return oResult
685
+
686
+ // Find customer by CNPJ
687
+ Method FindByCNPJ(cCNPJ) Class ClienteRepository
688
+ Local oResult := Nil
689
+ Local aArea := GetArea()
690
+ Local cQuery := ""
691
+ Local cTmpAlias := GetNextAlias()
692
+
693
+ cQuery := "SELECT A1_COD, A1_LOJA, A1_NOME, A1_CGC, A1_EMAIL, A1_TEL "
694
+ cQuery += "FROM " + RetSqlName("SA1") + " "
695
+ cQuery += "WHERE D_E_L_E_T_ = ' ' "
696
+ cQuery += "AND A1_FILIAL = '" + xFilial("SA1") + "' "
697
+ cQuery += "AND A1_CGC = '" + cCNPJ + "' "
698
+
699
+ TCQuery cQuery New Alias (cTmpAlias)
700
+
701
+ If !(cTmpAlias)->(Eof())
702
+ oResult := JsonObject():New()
703
+ oResult["codigo"] := AllTrim((cTmpAlias)->A1_COD)
704
+ oResult["loja"] := AllTrim((cTmpAlias)->A1_LOJA)
705
+ oResult["nome"] := AllTrim((cTmpAlias)->A1_NOME)
706
+ oResult["cnpj"] := AllTrim((cTmpAlias)->A1_CGC)
707
+ oResult["email"] := AllTrim((cTmpAlias)->A1_EMAIL)
708
+ oResult["telefone"] := AllTrim((cTmpAlias)->A1_TEL)
709
+ EndIf
710
+
711
+ (cTmpAlias)->(DbCloseArea())
712
+ RestArea(aArea)
713
+
714
+ Return oResult
715
+
716
+ // Find all with pagination and optional filter
717
+ Method FindAll(nPage, nPageSize, cFiltro) Class ClienteRepository
718
+ Local aResult := {}
719
+ Local oItem := Nil
720
+ Local cQuery := ""
721
+ Local cTmpAlias := GetNextAlias()
722
+
723
+ Default nPage := 1
724
+ Default nPageSize := 20
725
+ Default cFiltro := ""
726
+
727
+ cQuery := "SELECT A1_COD, A1_LOJA, A1_NOME, A1_CGC, A1_EMAIL, A1_TEL, A1_MSBLQL "
728
+ cQuery += "FROM " + RetSqlName("SA1") + " "
729
+ cQuery += "WHERE D_E_L_E_T_ = ' ' "
730
+ cQuery += "AND A1_FILIAL = '" + xFilial("SA1") + "' "
731
+
732
+ If !Empty(cFiltro)
733
+ cQuery += "AND (A1_NOME LIKE '%" + cFiltro + "%' OR A1_CGC LIKE '%" + cFiltro + "%') "
734
+ EndIf
735
+
736
+ cQuery += "ORDER BY A1_COD, A1_LOJA "
737
+ cQuery += "OFFSET " + cValToChar((nPage - 1) * nPageSize) + " ROWS "
738
+ cQuery += "FETCH NEXT " + cValToChar(nPageSize) + " ROWS ONLY"
739
+
740
+ TCQuery cQuery New Alias (cTmpAlias)
741
+
742
+ While !(cTmpAlias)->(Eof())
743
+ oItem := JsonObject():New()
744
+ oItem["codigo"] := AllTrim((cTmpAlias)->A1_COD)
745
+ oItem["loja"] := AllTrim((cTmpAlias)->A1_LOJA)
746
+ oItem["nome"] := AllTrim((cTmpAlias)->A1_NOME)
747
+ oItem["cnpj"] := AllTrim((cTmpAlias)->A1_CGC)
748
+ oItem["email"] := AllTrim((cTmpAlias)->A1_EMAIL)
749
+ oItem["telefone"] := AllTrim((cTmpAlias)->A1_TEL)
750
+ oItem["bloqueado"] := AllTrim((cTmpAlias)->A1_MSBLQL) == "1"
751
+ aAdd(aResult, oItem)
752
+ (cTmpAlias)->(DbSkip())
753
+ EndWh
754
+
755
+ (cTmpAlias)->(DbCloseArea())
756
+
757
+ Return aResult
758
+
759
+ // Create a new customer
760
+ Method Create(cNome, cCNPJ, cEmail, cTelefone) Class ClienteRepository
761
+ Local lRet := .T.
762
+ Local aArea := GetArea()
763
+
764
+ Begin Transaction
765
+ DbSelectArea(::cAlias)
766
+ RecLock(::cAlias, .T.)
767
+ SA1->A1_FILIAL := xFilial(::cAlias)
768
+ SA1->A1_COD := GetSXENum(::cAlias, "A1_COD")
769
+ SA1->A1_LOJA := "01"
770
+ SA1->A1_NOME := cNome
771
+ SA1->A1_CGC := cCNPJ
772
+ SA1->A1_EMAIL := IIf(!Empty(cEmail), cEmail, "")
773
+ SA1->A1_TEL := IIf(!Empty(cTelefone), cTelefone, "")
774
+ MsUnlock()
775
+ ConfirmSX8()
776
+ End Transaction
777
+
778
+ RestArea(aArea)
779
+
780
+ Return lRet
781
+
782
+ // Update an existing customer
783
+ Method Update(cCodigo, cLoja, oData) Class ClienteRepository
784
+ Local lRet := .T.
785
+ Local aArea := GetArea()
786
+
787
+ DbSelectArea(::cAlias)
788
+ DbSetOrder(1)
789
+
790
+ If !DbSeek(xFilial(::cAlias) + PadR(cCodigo, TamSX3("A1_COD")[1]) + PadR(cLoja, TamSX3("A1_LOJA")[1]))
791
+ RestArea(aArea)
792
+ Return .F.
793
+ EndIf
794
+
795
+ Begin Transaction
796
+ RecLock(::cAlias, .F.)
797
+ If oData["nome"] <> Nil
798
+ SA1->A1_NOME := oData["nome"]
799
+ EndIf
800
+ If oData["cnpj"] <> Nil
801
+ SA1->A1_CGC := oData["cnpj"]
802
+ EndIf
803
+ If oData["email"] <> Nil
804
+ SA1->A1_EMAIL := oData["email"]
805
+ EndIf
806
+ If oData["telefone"] <> Nil
807
+ SA1->A1_TEL := oData["telefone"]
808
+ EndIf
809
+ MsUnlock()
810
+ End Transaction
811
+
812
+ RestArea(aArea)
813
+
814
+ Return lRet
815
+
816
+ // Soft delete (logical deletion)
817
+ Method Delete(cCodigo, cLoja) Class ClienteRepository
818
+ Local lRet := .T.
819
+ Local aArea := GetArea()
820
+
821
+ DbSelectArea(::cAlias)
822
+ DbSetOrder(1)
823
+
824
+ If !DbSeek(xFilial(::cAlias) + PadR(cCodigo, TamSX3("A1_COD")[1]) + PadR(cLoja, TamSX3("A1_LOJA")[1]))
825
+ RestArea(aArea)
826
+ Return .F.
827
+ EndIf
828
+
829
+ Begin Transaction
830
+ RecLock(::cAlias, .F.)
831
+ SA1->D_E_L_E_T_ := "*"
832
+ MsUnlock()
833
+ End Transaction
834
+
835
+ RestArea(aArea)
836
+
837
+ Return lRet
838
+
839
+ // Inactivate customer (set block flag)
840
+ Method Inactivate(cCodigo, cLoja) Class ClienteRepository
841
+ Local lRet := .T.
842
+ Local aArea := GetArea()
843
+
844
+ DbSelectArea(::cAlias)
845
+ DbSetOrder(1)
846
+
847
+ If !DbSeek(xFilial(::cAlias) + PadR(cCodigo, TamSX3("A1_COD")[1]) + PadR(cLoja, TamSX3("A1_LOJA")[1]))
848
+ RestArea(aArea)
849
+ Return .F.
850
+ EndIf
851
+
852
+ Begin Transaction
853
+ RecLock(::cAlias, .F.)
854
+ SA1->A1_MSBLQL := "1"
855
+ MsUnlock()
856
+ End Transaction
857
+
858
+ RestArea(aArea)
859
+
860
+ Return lRet
861
+
862
+ // Build query string helper
863
+ Private Method BuildQuery(cWhere, cOrderBy, nLimit, nOffset) Class ClienteRepository
864
+ Local cQuery := ""
865
+
866
+ Default cWhere := ""
867
+ Default cOrderBy := "A1_COD, A1_LOJA"
868
+ Default nLimit := 0
869
+ Default nOffset := 0
870
+
871
+ cQuery := "SELECT * FROM " + RetSqlName(::cAlias) + " "
872
+ cQuery += "WHERE D_E_L_E_T_ = ' ' "
873
+ cQuery += "AND A1_FILIAL = '" + xFilial(::cAlias) + "' "
874
+
875
+ If !Empty(cWhere)
876
+ cQuery += "AND " + cWhere + " "
877
+ EndIf
878
+
879
+ cQuery += "ORDER BY " + cOrderBy + " "
880
+
881
+ If nLimit > 0
882
+ cQuery += "OFFSET " + cValToChar(nOffset) + " ROWS "
883
+ cQuery += "FETCH NEXT " + cValToChar(nLimit) + " ROWS ONLY"
884
+ EndIf
885
+
886
+ Return cQuery
887
+
888
+ // Convert current SA1 record to JsonObject
889
+ Private Method RecordToJson() Class ClienteRepository
890
+ Local oJson := JsonObject():New()
891
+
892
+ oJson["codigo"] := AllTrim(SA1->A1_COD)
893
+ oJson["loja"] := AllTrim(SA1->A1_LOJA)
894
+ oJson["nome"] := AllTrim(SA1->A1_NOME)
895
+ oJson["cnpj"] := AllTrim(SA1->A1_CGC)
896
+ oJson["email"] := AllTrim(SA1->A1_EMAIL)
897
+ oJson["telefone"] := AllTrim(SA1->A1_TEL)
898
+ oJson["bloqueado"] := AllTrim(SA1->A1_MSBLQL) == "1"
899
+
900
+ Return oJson
901
+ ```
902
+
903
+ ---
904
+
905
+ ## 10. Template: DTO Class (Data Transfer Object)
906
+
907
+ A DTO class carries data between layers. It includes serialization (toJson/fromJson) and validation.
908
+
909
+ ```tlpp
910
+ #Include "tlpp-core.th"
911
+
912
+ /*/{Protheus.doc} ClienteDTO
913
+ Data Transfer Object para dados de cliente
914
+ @type Class
915
+ @author Autor
916
+ @since 01/01/2026
917
+ @version 1.0
918
+ /*/
919
+ Class ClienteDTO
920
+
921
+ Data cCodigo As Character
922
+ Data cLoja As Character
923
+ Data cNome As Character
924
+ Data cCNPJ As Character
925
+ Data cEmail As Character
926
+ Data cTelefone As Character
927
+ Data cEndereco As Character
928
+ Data cMunicipio As Character
929
+ Data cEstado As Character
930
+ Data cCEP As Character
931
+ Data lAtivo As Logical
932
+ Data aErrors As Array
933
+
934
+ Method New() Constructor
935
+ Method FromJson(oJson)
936
+ Method ToJson()
937
+ Method Validate()
938
+ Method IsValid()
939
+ Method GetErrors()
940
+
941
+ Static Method FromRecord(cAlias)
942
+
943
+ EndClass
944
+
945
+ Method New() Class ClienteDTO
946
+ ::cCodigo := ""
947
+ ::cLoja := ""
948
+ ::cNome := ""
949
+ ::cCNPJ := ""
950
+ ::cEmail := ""
951
+ ::cTelefone := ""
952
+ ::cEndereco := ""
953
+ ::cMunicipio := ""
954
+ ::cEstado := ""
955
+ ::cCEP := ""
956
+ ::lAtivo := .T.
957
+ ::aErrors := {}
958
+ Return Self
959
+
960
+ // Populate DTO from a JsonObject
961
+ Method FromJson(oJson) Class ClienteDTO
962
+ If oJson == Nil
963
+ Return Self
964
+ EndIf
965
+
966
+ If oJson["codigo"] <> Nil
967
+ ::cCodigo := oJson["codigo"]
968
+ EndIf
969
+ If oJson["loja"] <> Nil
970
+ ::cLoja := oJson["loja"]
971
+ EndIf
972
+ If oJson["nome"] <> Nil
973
+ ::cNome := oJson["nome"]
974
+ EndIf
975
+ If oJson["cnpj"] <> Nil
976
+ ::cCNPJ := oJson["cnpj"]
977
+ EndIf
978
+ If oJson["email"] <> Nil
979
+ ::cEmail := oJson["email"]
980
+ EndIf
981
+ If oJson["telefone"] <> Nil
982
+ ::cTelefone := oJson["telefone"]
983
+ EndIf
984
+ If oJson["endereco"] <> Nil
985
+ ::cEndereco := oJson["endereco"]
986
+ EndIf
987
+ If oJson["municipio"] <> Nil
988
+ ::cMunicipio := oJson["municipio"]
989
+ EndIf
990
+ If oJson["estado"] <> Nil
991
+ ::cEstado := oJson["estado"]
992
+ EndIf
993
+ If oJson["cep"] <> Nil
994
+ ::cCEP := oJson["cep"]
995
+ EndIf
996
+ If oJson["ativo"] <> Nil
997
+ ::lAtivo := oJson["ativo"]
998
+ EndIf
999
+
1000
+ Return Self
1001
+
1002
+ // Serialize DTO to JsonObject
1003
+ Method ToJson() Class ClienteDTO
1004
+ Local oJson := JsonObject():New()
1005
+
1006
+ oJson["codigo"] := ::cCodigo
1007
+ oJson["loja"] := ::cLoja
1008
+ oJson["nome"] := ::cNome
1009
+ oJson["cnpj"] := ::cCNPJ
1010
+ oJson["email"] := ::cEmail
1011
+ oJson["telefone"] := ::cTelefone
1012
+ oJson["endereco"] := ::cEndereco
1013
+ oJson["municipio"] := ::cMunicipio
1014
+ oJson["estado"] := ::cEstado
1015
+ oJson["cep"] := ::cCEP
1016
+ oJson["ativo"] := ::lAtivo
1017
+
1018
+ Return oJson
1019
+
1020
+ // Validate all required fields
1021
+ Method Validate() Class ClienteDTO
1022
+ ::aErrors := {}
1023
+
1024
+ If Empty(::cNome)
1025
+ aAdd(::aErrors, "Campo 'nome' e obrigatorio")
1026
+ EndIf
1027
+
1028
+ If Empty(::cCNPJ)
1029
+ aAdd(::aErrors, "Campo 'cnpj' e obrigatorio")
1030
+ ElseIf Len(StrTran(StrTran(StrTran(::cCNPJ, ".", ""), "-", ""), "/", "")) <> 14
1031
+ aAdd(::aErrors, "CNPJ deve conter 14 digitos")
1032
+ EndIf
1033
+
1034
+ If !Empty(::cEmail) .And. At("@", ::cEmail) == 0
1035
+ aAdd(::aErrors, "Email invalido")
1036
+ EndIf
1037
+
1038
+ If !Empty(::cEstado) .And. Len(::cEstado) <> 2
1039
+ aAdd(::aErrors, "Estado deve conter 2 caracteres (UF)")
1040
+ EndIf
1041
+
1042
+ Return Len(::aErrors) == 0
1043
+
1044
+ Method IsValid() Class ClienteDTO
1045
+ Return Len(::aErrors) == 0
1046
+
1047
+ Method GetErrors() Class ClienteDTO
1048
+ Return ::aErrors
1049
+
1050
+ // Static factory: create DTO from a positioned SA1 record
1051
+ Static Method FromRecord(cAlias) Class ClienteDTO
1052
+ Local oDTO := ClienteDTO():New()
1053
+
1054
+ Default cAlias := "SA1"
1055
+
1056
+ oDTO:cCodigo := AllTrim(SA1->A1_COD)
1057
+ oDTO:cLoja := AllTrim(SA1->A1_LOJA)
1058
+ oDTO:cNome := AllTrim(SA1->A1_NOME)
1059
+ oDTO:cCNPJ := AllTrim(SA1->A1_CGC)
1060
+ oDTO:cEmail := AllTrim(SA1->A1_EMAIL)
1061
+ oDTO:cTelefone := AllTrim(SA1->A1_TEL)
1062
+ oDTO:cEndereco := AllTrim(SA1->A1_END)
1063
+ oDTO:cMunicipio := AllTrim(SA1->A1_MUN)
1064
+ oDTO:cEstado := AllTrim(SA1->A1_EST)
1065
+ oDTO:cCEP := AllTrim(SA1->A1_CEP)
1066
+ oDTO:lAtivo := SA1->A1_MSBLQL <> "1"
1067
+
1068
+ Return oDTO
1069
+ ```
1070
+
1071
+ **Usage:**
1072
+ ```tlpp
1073
+ // From JSON (API request)
1074
+ Local oJson := JsonObject():New()
1075
+ oJson:FromJson(cBody)
1076
+
1077
+ Local oDTO := ClienteDTO():New()
1078
+ oDTO:FromJson(oJson)
1079
+
1080
+ If oDTO:Validate()
1081
+ // Use oDTO data
1082
+ oService:CriarCliente(oDTO:cNome, oDTO:cCNPJ, oDTO:cEmail, oDTO:cTelefone)
1083
+ Else
1084
+ Local aErros := oDTO:GetErrors()
1085
+ // Return validation errors
1086
+ EndIf
1087
+
1088
+ // From database record
1089
+ DbSelectArea("SA1")
1090
+ DbSetOrder(1)
1091
+ If DbSeek(xFilial("SA1") + cCodigo + cLoja)
1092
+ Local oDTOFromDB := ClienteDTO():FromRecord("SA1")
1093
+ Local oRespJson := oDTOFromDB:ToJson()
1094
+ cResponse := oRespJson:ToJson()
1095
+ EndIf
1096
+ ```