@oondemand/create-central-oon 0.3.40 → 0.3.41

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.
@@ -1,14 +1,14 @@
1
1
  # Manifesto declarativo de domínio — `central.domain.json`
2
2
 
3
- O arquivo `central.domain.json`, localizado na raiz do backend da Central, declara models e campos sem exigir um arquivo JavaScript por model.
3
+ O arquivo `central.domain.json`, localizado na raiz do backend da Central, declara models, campos, fórmulas e validações sem exigir um arquivo JavaScript por model.
4
4
 
5
5
  O OonCore carrega o manifesto durante o bootstrap, antes de carregar `src/models`, `src/validations`, `src/triggers` e os demais diretórios de extensão.
6
6
 
7
- > A Central declara o domínio. O OonCore constrói schema Mongoose, metadata, CRUD e recursos derivados.
7
+ > A Central declara o domínio e as regras. O OonCore constrói schema Mongoose, metadata, CRUD, cálculos protegidos e validações.
8
8
 
9
9
  ## Escopo da versão 1
10
10
 
11
- A primeira versão do contrato cobre:
11
+ O contrato cobre:
12
12
 
13
13
  - identidade do manifesto;
14
14
  - models e seus caminhos de API;
@@ -16,21 +16,24 @@ A primeira versão do contrato cobre:
16
16
  - campos primitivos, enumerações, referências e moedas;
17
17
  - obrigatoriedade, valor padrão, busca, unicidade e índice simples;
18
18
  - limites numéricos e de tamanho de texto;
19
- - campos somente leitura, convertidos para `immutable` no schema;
19
+ - campos somente leitura protegidos nas mutações HTTP;
20
+ - campos calculados no servidor;
21
+ - dependências entre campos calculados, ordenadas automaticamente;
22
+ - precisão e tratamento de valores ausentes em fórmulas;
23
+ - validações declarativas entre campos, com condição opcional;
20
24
  - validação estrutural com todos os problemas retornados em uma única exceção.
21
25
 
22
- Ainda não fazem parte da versão 1:
26
+ Ainda não fazem parte desta versão:
23
27
 
24
- - fórmulas e campos calculados;
25
28
  - índices compostos;
26
- - validações entre campos;
27
- - triggers e transições;
29
+ - triggers e transições declarativas;
28
30
  - migrações automáticas de dados;
29
- - mappings de integração.
31
+ - mappings de integração;
32
+ - funções JavaScript embutidas no JSON.
30
33
 
31
- Esses recursos serão adicionados em contratos próprios ou em versões posteriores, sem transformar expressões de negócio em JavaScript arbitrário dentro do JSON.
34
+ As expressões são interpretadas por um avaliador fechado. O Core não usa `eval`, `Function` ou execução de código vindo do manifesto.
32
35
 
33
- ## Exemplo
36
+ ## Exemplo financeiro
34
37
 
35
38
  ```json
36
39
  {
@@ -39,49 +42,103 @@ Esses recursos serão adicionados em contratos próprios ou em versões posterio
39
42
  "schemaVersion": 1,
40
43
  "models": [
41
44
  {
42
- "name": "ClienteFornecedor",
43
- "singular": "cliente/fornecedor",
44
- "basePath": "/clientes-fornecedores",
45
+ "name": "ProjetoItem",
46
+ "singular": "item",
47
+ "basePath": "/itens",
45
48
  "crud": {
46
49
  "enabled": true
47
50
  },
48
51
  "fields": {
49
- "nome": {
50
- "kind": "string",
51
- "label": "Nome",
52
- "required": true,
53
- "searchable": true,
54
- "minLength": 2
52
+ "quantidade": {
53
+ "kind": "number",
54
+ "required": true
55
55
  },
56
- "tipo": {
57
- "kind": "enum",
58
- "label": "Tipo",
59
- "values": ["PF", "PJ", "Est"],
60
- "default": "PJ"
56
+ "diarias": {
57
+ "kind": "number",
58
+ "required": true
61
59
  },
62
- "documento": {
63
- "kind": "string",
64
- "label": "Documento",
65
- "unique": true,
66
- "index": true
60
+ "valorUnitario": {
61
+ "kind": "currency",
62
+ "required": true
67
63
  },
68
- "responsavelId": {
69
- "kind": "ref",
70
- "label": "Responsável",
71
- "ref": "Responsavel"
64
+ "percentualFee": {
65
+ "kind": "number",
66
+ "default": 0
72
67
  },
73
- "ativo": {
74
- "kind": "boolean",
75
- "label": "Ativo",
76
- "default": true
68
+ "valorContratado": {
69
+ "kind": "currency",
70
+ "default": 0
77
71
  },
78
- "limite": {
72
+ "valorPago": {
79
73
  "kind": "currency",
80
- "label": "Limite",
81
- "default": 0,
74
+ "default": 0
75
+ },
76
+ "statusIntegracao": {
77
+ "kind": "string",
82
78
  "readonly": true
79
+ },
80
+ "subtotal": {
81
+ "kind": "currency",
82
+ "computed": {
83
+ "precision": 2,
84
+ "expression": {
85
+ "op": "multiply",
86
+ "args": [
87
+ { "field": "quantidade" },
88
+ { "field": "diarias" },
89
+ { "field": "valorUnitario" }
90
+ ]
91
+ }
92
+ }
93
+ },
94
+ "valorFee": {
95
+ "kind": "currency",
96
+ "computed": {
97
+ "precision": 2,
98
+ "expression": {
99
+ "op": "divide",
100
+ "args": [
101
+ {
102
+ "op": "multiply",
103
+ "args": [
104
+ { "field": "subtotal" },
105
+ { "field": "percentualFee" }
106
+ ]
107
+ },
108
+ { "value": 100 }
109
+ ]
110
+ }
111
+ }
112
+ },
113
+ "total": {
114
+ "kind": "currency",
115
+ "computed": {
116
+ "precision": 2,
117
+ "expression": {
118
+ "op": "add",
119
+ "args": [
120
+ { "field": "subtotal" },
121
+ { "field": "valorFee" }
122
+ ]
123
+ }
124
+ }
125
+ }
126
+ },
127
+ "validations": [
128
+ {
129
+ "name": "pagamento-limitado-ao-contratado",
130
+ "code": "PAGAMENTO_ACIMA_CONTRATADO",
131
+ "field": "valorPago",
132
+ "message": "O valor pago não pode superar o valor contratado.",
133
+ "assert": {
134
+ "op": "lte",
135
+ "args": [
136
+ { "field": "valorPago" },
137
+ { "field": "valorContratado" }
138
+ ]
139
+ }
83
140
  }
84
- }
141
+ ]
85
142
  }
86
143
  ]
87
144
  }
@@ -106,6 +163,7 @@ Esses recursos serão adicionados em contratos próprios ou em versões posterio
106
163
  | `crud` | não | Mesmo contrato aceito por `defineModel`. |
107
164
  | `options` | não | Opções JSON compatíveis com o schema Mongoose. |
108
165
  | `fields` | sim | Objeto com pelo menos um campo. |
166
+ | `validations` | não | Lista de regras declarativas executadas depois dos cálculos. |
109
167
 
110
168
  Não declare a mesma model no manifesto e em `src/models`. O registry interrompe o bootstrap para impedir duas fontes de verdade.
111
169
 
@@ -127,10 +185,11 @@ Não declare a mesma model no manifesto e em `src/models`. O registry interrompe
127
185
  - `description`: explicação funcional;
128
186
  - `required`: campo obrigatório;
129
187
  - `default`: valor padrão JSON;
130
- - `readonly`: gera campo imutável no backend e metadata somente leitura;
188
+ - `readonly`: campo controlado pelo servidor;
131
189
  - `searchable`: inclui texto na busca derivada do Core;
132
190
  - `unique`: índice único simples;
133
- - `index`: índice simples.
191
+ - `index`: índice simples;
192
+ - `computed`: fórmula declarativa para campos numéricos ou monetários.
134
193
 
135
194
  ### Opções por tipo
136
195
 
@@ -140,9 +199,172 @@ Não declare a mesma model no manifesto e em `src/models`. O registry interrompe
140
199
  - `enum`: `values` com textos únicos e não vazios;
141
200
  - `currencyConverted`: `base` com código ISO de três letras.
142
201
 
143
- ## Erros de validação
202
+ ## Campos calculados
203
+
204
+ `computed` é permitido em `number`, `currency` e `currencyConverted`.
205
+
206
+ ```json
207
+ {
208
+ "kind": "currency",
209
+ "computed": {
210
+ "expression": {
211
+ "op": "multiply",
212
+ "args": [
213
+ { "field": "quantidade" },
214
+ { "field": "valorUnitario" }
215
+ ]
216
+ },
217
+ "precision": 2,
218
+ "nullAsZero": true
219
+ }
220
+ }
221
+ ```
222
+
223
+ | Propriedade | Padrão | Descrição |
224
+ |---|---:|---|
225
+ | `expression` | — | Expressão obrigatória. |
226
+ | `precision` | `2` | Casas decimais, entre 0 e 8. |
227
+ | `nullAsZero` | `true` | Trata campos ausentes ou vazios como zero nas operações numéricas. |
228
+
229
+ Campos calculados:
230
+
231
+ - são automaticamente `readonly`;
232
+ - recebem `immutable` no schema Mongoose;
233
+ - são recalculados no backend em criação, edição, patch e importação;
234
+ - são calculados em ordem de dependência;
235
+ - não podem formar ciclos;
236
+ - aparecem na metadata com `readonly: true` e a declaração `computed`.
237
+
238
+ ## Expressões
239
+
240
+ Uma expressão declara exatamente um destes nós:
241
+
242
+ ```json
243
+ { "value": 100 }
244
+ ```
245
+
246
+ ```json
247
+ { "field": "valorUnitario" }
248
+ ```
249
+
250
+ ```json
251
+ {
252
+ "op": "multiply",
253
+ "args": [
254
+ { "field": "quantidade" },
255
+ { "field": "valorUnitario" }
256
+ ]
257
+ }
258
+ ```
259
+
260
+ ### Operadores aritméticos
261
+
262
+ - `add`
263
+ - `subtract`
264
+ - `multiply`
265
+ - `divide`
266
+ - `min`
267
+ - `max`
268
+ - `abs`
269
+ - `negate`
270
+ - `coalesce`
271
+
272
+ ### Operadores de comparação
273
+
274
+ - `eq`
275
+ - `neq`
276
+ - `gt`
277
+ - `gte`
278
+ - `lt`
279
+ - `lte`
280
+
281
+ ### Operadores lógicos e de presença
282
+
283
+ - `and`
284
+ - `or`
285
+ - `not`
286
+ - `present`
287
+ - `in`
288
+
289
+ Divisão por zero e valores não numéricos em fórmulas geram `DomainRuleError` com status 422.
290
+
291
+ ## Validações entre campos
292
+
293
+ As validações rodam depois que o Core consolidou o registro e recalculou todos os campos dependentes.
294
+
295
+ ```json
296
+ {
297
+ "name": "valor-pago-valido",
298
+ "code": "PAGAMENTO_ACIMA_CONTRATADO",
299
+ "field": "valorPago",
300
+ "message": "O valor pago não pode superar o valor contratado.",
301
+ "when": {
302
+ "op": "present",
303
+ "args": [{ "field": "valorPago" }]
304
+ },
305
+ "assert": {
306
+ "op": "lte",
307
+ "args": [
308
+ { "field": "valorPago" },
309
+ { "field": "valorContratado" }
310
+ ]
311
+ }
312
+ }
313
+ ```
314
+
315
+ | Propriedade | Obrigatória | Descrição |
316
+ |---|---:|---|
317
+ | `name` | sim | Identificador único da validação dentro da model. |
318
+ | `message` | sim | Mensagem operacional apresentada ao usuário. |
319
+ | `assert` | sim | Expressão que deve resultar em verdadeiro. |
320
+ | `when` | não | Condição para executar a regra. |
321
+ | `field` | não | Campo associado ao erro. |
322
+ | `code` | não | Código em maiúsculas para tratamento programático. |
323
+
324
+ Falhas geram `DomainRuleError` com `statusCode: 422`, `code`, `field`, `rule` e detalhes compatíveis com o tratamento de erros do Core.
325
+
326
+ ## Proteção de campos somente leitura
327
+
328
+ A proteção não depende apenas do frontend.
329
+
330
+ - valor readonly enviado na criação é rejeitado;
331
+ - alteração de valor readonly é rejeitada;
332
+ - em edição, o mesmo valor pode voltar no payload e é removido antes da persistência;
333
+ - campos calculados podem voltar no payload somente quando coincidem com o resultado calculado pelo servidor;
334
+ - tentativa de adulterar um campo calculado é rejeitada;
335
+ - services recebem somente campos permitidos e os resultados recalculados.
336
+
337
+ Isso permite formulários que enviam o registro completo sem abrir espaço para alterar totais, status técnicos ou identificadores controlados pelo sistema.
338
+
339
+ ## Atualizações parciais
340
+
341
+ Em `PUT` ou `PATCH`, o Core:
342
+
343
+ 1. carrega o registro atual quando existem regras declarativas ou `defineValidation`;
344
+ 2. remove ou bloqueia campos readonly;
345
+ 3. consolida os campos atuais com as alterações recebidas;
346
+ 4. recalcula campos dependentes em ordem;
347
+ 5. executa validações declarativas;
348
+ 6. executa a validação JavaScript registrada, quando existir;
349
+ 7. persiste somente as alterações permitidas e os valores calculados.
350
+
351
+ A validação JavaScript recebe:
352
+
353
+ ```js
354
+ {
355
+ op,
356
+ method,
357
+ id,
358
+ current,
359
+ requestedChanges,
360
+ changes,
361
+ consolidated
362
+ }
363
+ ```
364
+
365
+ ## Erros de validação do manifesto
144
366
 
145
- Um manifesto inválido lança `DomainManifestError`:
367
+ Um manifesto estruturalmente inválido lança `DomainManifestError`:
146
368
 
147
369
  ```js
148
370
  {
@@ -151,8 +373,8 @@ Um manifesto inválido lança `DomainManifestError`:
151
373
  statusCode: 422,
152
374
  issues: [
153
375
  {
154
- path: "models[0].fields.clienteId.ref",
155
- message: "é obrigatório para campos ref."
376
+ path: "models[0].fields.total.computed.expression",
377
+ message: "dependência circular entre campos calculados: total -> fee -> total."
156
378
  }
157
379
  ]
158
380
  }
@@ -168,17 +390,22 @@ const {
168
390
  domainManifestToDefinitions,
169
391
  registerDomainManifest,
170
392
  loadDomainManifest,
393
+ evaluateDomainExpression,
394
+ applyDomainMutation,
395
+ DomainManifestError,
396
+ DomainRuleError,
397
+ DOMAIN_EXPRESSION_OPERATORS
171
398
  } = require("@oondemand/oon-core-back");
172
399
  ```
173
400
 
174
- Na operação normal não é necessário chamar essas funções: `oonCore-back start` descobre automaticamente `central.domain.json`.
401
+ Na operação normal não é necessário chamar essas funções: `oonCore-back start` descobre automaticamente `central.domain.json` e o CRUD aplica as regras.
175
402
 
176
403
  ## Compatibilidade durante a migração
177
404
 
178
- Os diretórios JavaScript continuam disponíveis para validações, regras e recursos ainda não declarativos. A ordem é:
405
+ Os diretórios JavaScript continuam disponíveis para regras ainda não declarativas. A ordem é:
179
406
 
180
407
  1. `central.config.js`;
181
408
  2. `central.domain.json`;
182
409
  3. diretórios em `src/`.
183
410
 
184
- Isso permite migrar model por model, mantendo regras específicas fora do Core até existir um contrato declarativo equivalente.
411
+ Isso permite migrar model por model. `defineValidation` continua disponível e roda depois das fórmulas e validações declarativas.
@@ -0,0 +1,37 @@
1
+ # OonCore 0.3.41 — Manifesto declarativo de domínio
2
+
3
+ Versão destinada à homologação do primeiro contrato declarativo completo de domínio no backend.
4
+
5
+ ## Recursos incluídos
6
+
7
+ - carregamento automático de `central.domain.json`;
8
+ - declaração versionada de models e campos;
9
+ - validação estrutural agregada;
10
+ - fórmulas declarativas sem execução de JavaScript arbitrário;
11
+ - campos calculados no servidor;
12
+ - ordenação de dependências e detecção de ciclos;
13
+ - proteção de campos `readonly` em criação, atualização e importação;
14
+ - validações declarativas entre campos;
15
+ - atualização parcial com consolidação e recálculo;
16
+ - compatibilidade com `defineValidation` durante a migração gradual.
17
+
18
+ ## Objetivo da homologação
19
+
20
+ Validar o contrato em uma Central de teste antes da conversão dos models e regras da SS-Eventos.
21
+
22
+ A homologação deve confirmar:
23
+
24
+ 1. criação e carregamento do manifesto;
25
+ 2. geração de metadata e CRUD;
26
+ 3. recálculo de campos em `POST`, `PUT` e `PATCH`;
27
+ 4. rejeição de adulteração em campos controlados pelo servidor;
28
+ 5. mensagens de validação associadas ao campo correto;
29
+ 6. compatibilidade com models e validações JavaScript ainda não migrados.
30
+
31
+ ## Fora do escopo
32
+
33
+ - fórmulas reativas no frontend;
34
+ - índices compostos;
35
+ - triggers e transições declarativas;
36
+ - engine genérica de integrações;
37
+ - migração da SS-Eventos.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/create-central-oon",
3
- "version": "0.3.40",
3
+ "version": "0.3.41",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -12,6 +12,6 @@
12
12
  "deploy": "oonCore-back deploy"
13
13
  },
14
14
  "dependencies": {
15
- "@oondemand/oon-core-back": "^0.3.40"
15
+ "@oondemand/oon-core-back": "^0.3.41"
16
16
  }
17
17
  }
@@ -10,7 +10,7 @@
10
10
  "sync:metadata": "oonCore-front sync:metadata"
11
11
  },
12
12
  "dependencies": {
13
- "@oondemand/oon-core-front": "^0.3.40",
13
+ "@oondemand/oon-core-front": "^0.3.41",
14
14
  "@chakra-ui/react": "^3.13.0",
15
15
  "@emotion/react": "^11.14.0",
16
16
  "@tanstack/react-query": "^5.65.0",