Models e CRUD

defineModel registra schema Mongoose, metadata, permissões e um router REST completo — listagem paginada, busca, leitura, criação, atualização, exclusão, import e export.

Uma model é a unidade de domínio do backend. Declarada uma vez, ela gera o schema Mongoose, a metadata usada pelo frontend e as rotas REST do CRUD, já com autenticação, RBAC, escopo de dados e auditoria.

const { defineModel, fields } = require("@oondemand/oon-core-back");

module.exports = defineModel({
  name: "Pedido",            // PascalCase — nome no registry e no Mongoose
  singular: "pedido",
  basePath: "/pedidos",      // rota do CRUD no backend (/api/pedidos no navegador)
  scope: "tenant",           // legacy | system | tenant | user
  schema: {
    numero: fields.string({ required: true, label: "Número" }),
    fornecedor: fields.ref("Fornecedor", { required: true, label: "Fornecedor" }),
    status: fields.enum(["Rascunho", "Enviado", "Aprovado"], { default: "Rascunho", label: "Status" }),
    valor: fields.currency({ label: "Valor" }),
    entregaEm: fields.date({ label: "Entrega" }),
  },
  crud: {
    enabled: true,
    permissions: { read: "pedidos.read", write: "pedidos.write" },
  },
});

Opções de defineModel

Opção Descrição
name Obrigatório. Nome PascalCase.
schema Obrigatório. Campos com fields ou definições Mongoose cruas (viram kind: "raw" na metadata).
singular Nome singular (padrão: name em minúsculas).
basePath Caminho do router CRUD, iniciado por /.
scope Escopo de dados: legacy (padrão), system, tenant ou user. Veja Escopo e accessContext.
tenancy { tenantField?, userField? } — nomes dos campos de isolamento (padrão tenantId e usuarioId).
crud.enabled false desliga o CRUD automático (padrão: ligado).
crud.permissions { read, write } — permissões RBAC exigidas. Forma recomendada.
crud.roles { read: [], write: [] } — compatibilidade legada por perfil.
crud.populateRefs true ou lista de campos ref — devolve as referências com rótulo na listagem e na leitura. Veja Rótulos de referências.
options Opções do schema Mongoose (timestamps: true já é aplicado).

Campos unique em models tenant/user viram índices compostos com o campo de escopo, para que a unicidade seja por tenant.

Rotas geradas

Com basePath: "/pedidos":

Método e rota Guard Uso
GET /pedidos read Lista paginada com busca, ordenação e filtros
GET /pedidos/export read Exporta todos os registros do escopo
GET /pedidos/:id read Leitura por ID
POST /pedidos write Criação
POST /pedidos/import write Importação em lote (rows: [])
PUT /pedidos/:id write Atualização
PATCH /pedidos/:id write Atualização parcial
DELETE /pedidos/:id write Exclusão

Todas exigem sessão autenticada. Mutações bem-sucedidas são auditadas em ControleAlteracao com as ações adicionado, alterado e excluido.

Parâmetros da listagem

GET /api/pedidos?pageIndex=0&pageSize=20&searchTerm=acme&sort=valor:desc&status=Aprovado
Parâmetro Padrão Regra
pageIndex 0 Página a partir de zero
pageSize 20 Entre 1 e 200
searchTerm — Busca case-insensitive em todos os campos searchable
sort createdAt:desc campo:asc ou campo:desc
demais — Filtros de igualdade por campo (vazios são ignorados)

Resposta:

{
  "results": [{ "_id": "…", "numero": "PC-001", "valor": 1200 }],
  "pagination": { "currentPage": 0, "itemsPerPage": 20, "totalItems": 1, "totalPages": 1 }
}

O filtro de escopo (tenantId/usuarioId) é sempre mesclado pelo servidor — o cliente não consegue removê-lo.

Rótulos de referências

Por padrão, campos fields.ref(...) voltam do CRUD como ObjectId. Para grids e cards mostrarem o nome do registro relacionado sem consultas extras, ative populateRefs:

defineModel({
  name: "Pedido",
  schema: {
    clienteId: fields.ref("Cliente", { required: true, label: "Cliente" }),
    fornecedorId: fields.ref("Fornecedor", { label: "Fornecedor" }),
  },
  crud: {
    enabled: true,
    populateRefs: ["clienteId"], // ou true para todas as referências
  },
});
  • O CRUD devolve somente _id e campos usuais de identificação da model referenciada (nome, razão social, descrição, código, e-mail e os campos pesquisáveis).
  • O escopo de tenant/usuário da model referenciada é aplicado também na população.
  • Exportações mantêm os identificadores originais.

Pipeline de mutação

Em POST, PUT, PATCH e importação, o Core:

  1. carrega o registro atual quando existem regras declarativas ou defineValidation;
  2. remove ou bloqueia campos readonly e calculados;
  3. consolida os campos atuais com as alterações recebidas;
  4. recalcula fórmulas em ordem de dependência;
  5. executa validações declarativas e de processo (transições, bloqueios por etapa);
  6. executa a validação JavaScript registrada;
  7. persiste somente as alterações permitidas e os valores calculados;
  8. dispara triggers, bindings e auditoria.

Metadata

O backend publica a metadata consumida pelo frontend:

Endpoint Conteúdo
GET /core/metadata Serviço, models, coleções, documentos, esteiras, features e menus
GET /core/models Resumo de todas as models
GET /core/models/:name Campos, tipos, rótulos, obrigatoriedade, searchable, readonly, computed

Campos de escopo são internos: fazem parte do schema e dos índices, mas não aparecem na metadata nem nos formulários.

Duas formas de declarar

defineModel (JS) central.domain.json
Campos e CRUD ✅ ✅
Fórmulas computed e validações declarativas — ✅
scope tenant/user ✅ — (single_tenant dedicado)
Esteiras (central.process.json) — ✅ (models citadas precisam estar no domínio)

Boas práticas

  • Campos explícitos, com tipos, obrigatoriedade e enums.
  • Preserve campos de status para esteiras.
  • Evite regras complexas no schema — use validações e o manifesto.
  • Não use o CRUD genérico para armazenar segredos; veja Segredos.
  • Rota customizada só quando a operação não couber em CRUD, ação declarativa ou processo.