Ponto de partida
Como ler
Caixa cheia é agregado especificado; borda azul é raiz de agregado ou árvore; borda tracejada é classe da plataforma gryd, que o Nexio consome e não possui. ? no fim do nome é anulável. Campo em azul nasceu ou mudou entre 01 e 03/09/2026.
id e tenantId não aparecem: toda classe tem os dois, id atribuído pelo agregado e tenantId injetado do token e nunca aceito do cliente. createdAt, updatedAt e isDeleted também são universais. Classes com muitos campos trazem a contagem do que ficou de fora.
Este documento é derivado. Regra, invariante e contrato de erro vivem na spec de cada módulo; aqui só a forma. Divergência entre os dois é defeito deste arquivo — a spec ganha sempre.
Contrato vigente · revisão consolidada
Identidade e concessões empresariais
Concessão global exige escolha explícita. Visibilidade não concede operação; papel local não supera o teto da plataforma. Empresa e autorização são verificadas em consulta, aprovação, delegação, download e worker. Alçada financeira continua em CostCenterAssignment e não nasce do papel.
Banda 2 · o dinheiro
Organização e dinheiro
Nove classes. Duas árvores — centro de custo e plano de contas — com a mesma mecânica: parentId, caminho materializado, depth, allowsPosting só em folha, duas portas de status, path como guarda de move e version int como token de concorrência. E, desde 02/09/2026, o DeliveryLocation, que pendura no estabelecimento e cruza com a árvore de custo por sugestão.
Establishment porque é ele quem recebe fiscalmente, e só sugere o centro de custo.Os dois eixos se cruzam em um lugar só. CostAllocation é o único ponto do modelo onde centro de custo e conta contábil se encontram, e ele é value object da linha do documento — não vive em nenhum dos dois. Derivar a conta do centro de custo, ou o contrário, é o erro que transforma relatório contábil em relatório gerencial errado.
Contrato vigente · revisão consolidada
Razão e parcelas
Dinheiro numeric(21,4) respeita as casas da moeda. Negative/positive de reclassificação conservam estágio e valor; cada parcela mantém sua origem. Actual ocorrido pode ter pendência de classificação. Nota ajusta Actual correspondente e não cria verba. Ver Orçamento.
Banda 4 · a conversão
Moeda e câmbio
Cinco classes transversais, e a única do modelo com linha sem tenant: ExchangeRate.tenantId nulo é a cotação oficial compartilhada por todos os clientes. A taxa própria do tenant vence a oficial para o mesmo par, tipo e data.
Currency é catálogo do produto e não tem tenantId — o cliente ativa, não cria. CurrencyPair é o que faz a captura automática existir: sem a lista de pares, a rotina diária não sabe o que buscar, e ela se mantém sozinha porque ativar uma moeda garante o par. ExchangeRate não se edita: corrigir é gravar linha nova e apontar supersededById na antiga — daí a auto-relação. E o movimento que já converteu guarda taxa, tipo, data e quoteUnit como valores, não como chave estrangeira, para que substituir a cotação corrija o futuro sem reescrever o passado.Contrato vigente · revisão consolidada
Catálogo, relacionamento e oferta
Snapshot de linha guarda oferta/revisão, preço, fatores pedido→base e base→preço, custo total e condições. Para CX de 12 com preço por CX, os fatores são 12/1 e 1/12. Embalagens simultâneas não duplicam a relação. Demais satélites e campos estão em Item e Fornecedor.
Banda 6 · quem decide
Motor de aprovação
Nove classes de configuração — a oitava é ApprovalGroupMember, entidade-filha do agregado ApprovalGroup, que dá vigência ao membro do comitê, e a nona é o ApprovalSettings do tenant, que nasceu em 08/09/2026 quando o que não varia por processo saiu do ApprovalProcessSettings — e quatro de execução. A separação é a tese do módulo: a instância congela as políticas que a formaram, com versão — editar a regra não reescreve o que já está em curso.
Supplier (cadastro), SupplierQualification, BankAccountChangeProposal, Item, Budget e BudgetChangeRequest guardam um approvalRequestId e aplicam o efeito quando a instância termina — dez processos-semente, quinze critérios, e os tipos de nível PermissionHolder e Panel que as políticas padrão e a homologação por área exigem.Empresa entra aqui como critério, não como coluna. ApprovalCriteria ganhou empresa e estabelecimento na lista de recortes, e ApprovalRequest carrega a empresa do documento. Dar um companyId à própria política criaria um segundo mecanismo concorrente para a mesma pergunta — e o critério já resolve, inclusive o caso de uma política Base do grupo convivendo com uma Supplemental de uma empresa só.
Banda 7 · a prova
Anexo e arquivo
Quatro classes, e duas delas não são nossas. É a única banda do modelo que desenha a fronteira com um módulo da plataforma, porque é a única em que o Nexio depende de uma entidade de outro bounded context em cada operação — e a dependência é sem chave estrangeira, dos dois lados.
Attachment aponta para o StoredFile e para um dono polimórfico, e nenhum dos dois é chave estrangeira: o primeiro é de outro DbContext, o segundo é de outro contexto do próprio Nexio. Quem sustenta a integridade é o registro de descritores por ownerType, o evento de descarte de rascunho e a varredura diária. Cada anexo cria a sua própria FileReference — inclusive a cópia propagada, que compartilha o storedFileId mas não a referência. As setas para Company, Supplier e ReasonCode saem da banda e estão nas cardinalidades.A regra de corte, em uma frase. Arquivo com metadado próprio — número, órgão emissor, validade, status derivado da data — é entidade do domínio: ItemDocument e SupplierDocument, que desde 02/09/2026 guardam storedFileId em vez de URL. Os dois são satélites: não têm caixa aqui, e os campos vivem na spec do agregado dono — ver a seção de satélites. Arquivo que é só evidência de um documento é Attachment. Sem essa linha, ou o anexo ganha campos de validade que quase nunca são preenchidos, ou a certidão perde a vigência que é a razão de ela existir.
Banda 8 · o aviso
Notificação
Quatro classes, e a banda mais magra do modelo — de propósito. Canal, template, fila e entrega são do GrydNotifications; o Nexio guarda a exceção que o usuário configurou, a memória curta que impede o mesmo aviso duas vezes, e a fila que submete o documento quando o antivírus libera. O catálogo de slugs não aparece aqui porque não é tabela: é registro em código.
NotificationPreference só existe quando o usuário difere do padrão do catálogo — ausência de linha é a configuração, e é isso que faz um slug publicado depois funcionar para quem configurou antes. NotificationThrottle e DeferredSubmission não se ligam a nada nesta banda, e não é omissão: a primeira é memória de entrega, sem dono; a segunda aponta para um dono polimórfico, com o mesmo AttachmentOwnerType e o mesmo registro de descritores da banda 7 — que ganhou por causa dela a nona pergunta, SubmitPolicyOf. As setas que importam saem da banda e estão nas cardinalidades.Onde está o resto do módulo — as classes que não existem. O catálogo de avisos é uma NotificationDefinition por slug, em código, validada na inicialização — mesma escolha do AttachmentOwnerRegistry da banda 7 e pela mesma razão: o que é do produto e muda com release não vira linha que o tenant edita. O tenant personaliza o texto pelo override de NotificationTemplate, que a plataforma já oferece, e não o comportamento. Quantos slugs existem é conta da Notificação, não desta página — aqui só se contam classes.
Banda 9 · o porquê
Motivo
Uma classe, e é a mais conectada do modelo — ela é lida por cinco bandas: aprovação, catálogo, fornecimento, moeda e anexo. Nenhuma dessas setas é desenhada aqui, e não por economia: um ReasonCode não é dono nem filho de nada, ele é uma dimensão que os atos congelam. As relações estão nas cardinalidades.
reasonCodeId · reasonCode · reasonLabel · reasonComment?, com os dois do meio congelados no instante da decisão — regra 1 do mapa aplicada a um cadastro de apoio. Não existe contador de uso, não existe coleção de ocorrências e não existe campo de cor, severidade ou peso: cada um desses seria uma porta para o motivo passar a decidir comportamento, que é o que a spec proíbe na primeira regra.Integridade
Cardinalidades
Toda relação do modelo, com a regra que a governa. É a tabela que responde "posso apagar isso?".
Plataforma e escopo
| Relação | Cardinalidade | Regra |
|---|---|---|
| User → UserTenant | 1 · N | Uma linha por organização. Remover o vínculo apaga em cascata papéis e permissões diretas naquela organização |
| UserTenant → UserRole | 1 · N | O papel é sempre dentro de um tenant. Não existe papel global |
| UserCompanyScope → Company | N · 0..1 | Nulo exige allCompanies explícito. Sem linha vigente não há escopo. FK composta por tenant; operação ainda exige UserCompanyRole. |
| UserCompanyScope → User | N · 1 | userId do gryd sem FK — é outro serviço. Validado contra o diretório na escrita; revogação exige revalidação de vínculo e concessões nas próximas operações; um token ainda válido não mantém autorização removida |
| Estabelecimento padrão do usuário | — | Não é tabela do Nexio. Vive na UserPreference do GrydAuth, chave nexio.default-establishment-id, lida e escrita por API — sem caixa neste diagrama e sem entrar na contagem da plataforma, porque o produto a consome como contrato, não a modela. "Preferências do usuário" é conceito da plataforma, e é por isso que o lugar é a tabela dela: uma homônima no Nexio criaria duas fontes para a mesma pergunta. Revisto em 08/09/2026: até esta versão a linha dizia localStorage e a decisão se apoiava em UserPreference não existir — existe, é TenantScopedEntity, e é justamente o escopo por (usuário, tenant) que o localStorage não tem. Sem esquema e sem cascata: quem lê valida |
| UserCompanyRole → Company / Role | N · 0..1 / N · 1 | Concessão local com vigência. Role é definição da plataforma; operação efetiva é interseção com teto e visibilidade. |
| ItemSupplier → ItemSupplierOffer → ItemSupplierPrice | 1 · N · N | Oferta/revisão separa embalagem, código, mínimo e prazo. Preço referencia a oferta, não apenas a relação. |
| ItemSupplierPrice → ItemSupplierPriceComponent | 1 · N | Custo total explícito, com inclusão, base, moeda, origem e recuperabilidade. |
| BudgetPostingGroup → BudgetEntry | 1 · N | Grupo identifica a operação; entryOrdinal cada entrada. Dedupe não descarta liquidações legítimas da mesma fatia. |
Organização e dinheiro
| Relação | Cardinalidade | Regra |
|---|---|---|
| Company → Establishment | 1 · 1..N | Exatamente um Headquarters, que nasce na mesma transação que a empresa (POST /companies com a matriz inline) — por isso headquartersId deixou de ser anulável em 03/09/2026. Empresa ativa nunca fica sem estabelecimento ativo; companyId é imutável |
| Establishment → cnpj | — | Os 8 primeiros caracteres têm que ser o cnpjRoot da empresa. Divergência é 422 |
| Establishment → DeliveryLocation | 1 · N | establishmentId é obrigatório e imutável — é quem recebe fiscalmente. O estabelecimento precisa ter allowsReceiving. O mesmo galpão servindo três empresas do grupo são três linhas, uma por CNPJ destinatário |
| DeliveryLocation → CostCenter | N · 0..1 | defaultCostCenterId sugere o rateio da linha, nunca o impõe. Mão única: o nó de centro de custo não ganha endereço. Obra é nó da árvore; aqui mora só o endereço dela |
| DeliveryLocation → stateCode | — | Derivado do cityIbgeCode quando há endereço próprio, copiado do estabelecimento quando não há. Divergir da UF do estabelecimento exige interstateReason; nunca é nulo e nunca é digitado |
| GLAccount → GLAccount | 0..1 · N | Árvore. Ciclo recusado; accountType herdado do pai e divergir dele é 422 |
| GLAccount × Company | N · N | Via GLAccountCompany. Ausência de linha = habilitada — a linha existe para restringir ou carregar o código externo |
| CostCenter → CostCenter | 0..1 · N | Árvore. Nó de empresa não aceita filho de outra empresa; nó corporativo aceita qualquer filho |
| CostCenter → Company | N · 0..1 | Nulo = corporativo. Aponta para a empresa, nunca para o estabelecimento |
| CostCenter → CostCenterAssignment | 1 · N | Mesma pessoa e papel não podem ter períodos sobrepostos no mesmo nó — garantido por EXCLUDE no banco, não em memória |
| CostAllocation → CostCenter / GLAccount | N · 1 / N · 0..1 em rascunho, N · 1 na submissão | Só nó e conta com allowsPosting. A conta é anulável só enquanto o documento é rascunho — na submissão toda fatia a tem (RN-CC-29, 422 ALLOCATION_GL_ACCOUNT_REQUIRED_UNPROCESSABLE), o que garante a tupla que BudgetEntry exige. Soma dos percentuais fecha em 100 com regra de resíduo declarada |
| NumberSequence → Company / Establishment | N · 0..1 | Exigidos conforme o scope; preenchidos fora dele é 422 |
Orçamento
| Relação | Cardinalidade | Regra |
|---|---|---|
| Company → Budget | 1 · N | Um exercício por empresa e período. Dois Approved com períodos sobrepostos são recusados por EXCLUDE — a mesma mecânica de ApprovalPolicy e CostCenterAssignment |
| Budget → BudgetLine | 1 · N | Chave lógica (budgetId, costCenterId, glAccountId, periodKey) com quatro índices únicos parciais, porque duas colunas anuláveis não colidem sozinhas no Postgres |
| BudgetLine → CostCenter / GLAccount | N · 0..1 | Nulo = qualquer. Preenchido, cobre o nó e a subárvore por prefixo de path. Aceita nó sem allowsPosting — orçar em nó intermediário é o caso normal |
| BudgetLine → BudgetEntry | 0..1 · N | Resolvido na gravação e congelado. Nulo quando não há controle orçamentário — o movimento é gravado assim mesmo, pela tupla de dimensões |
| BudgetEntry → BudgetEntry | 0..1 · N | liquidatesEntryId encadeia os estágios; reversalOfEntryId marca estorno integral. Liquidar é avançar, estornar é desfazer — não são a mesma coisa |
| BudgetLine → BudgetLineBalance | 1 · N | Uma linha por estágio. Cache, não verdade: atualizada na mesma transação e reconstruível a partir do razão |
| BudgetPolicy → CostCenter | N · 0..1 | Nulo = padrão do tenant. Cada campo é herdado subindo o path independentemente dos outros; sobreposição de vigência no mesmo nó recusada por EXCLUDE. Desde 03/09/2026 é aqui que mora a decisão de bloqueio por verba — o motor de aprovação lê a overBudgetPolicy resolvida e obedece, e o budgetBlockBehavior por tenant que ele tinha foi removido |
| Budget → BudgetChangeRequest | 1 · N | Origem e destino têm que ser do mesmo exercício. Aprovado, grava Adjustment; o valor efetivo da linha é amount + Σ Adjustment |
| BudgetEntry → ExchangeRate | N · 0..1 | A taxa é copiada para o movimento, não referenciada — o razão não muda quando a cotação muda. A relação existe só para rastro |
Moeda e câmbio
| Relação | Cardinalidade | Regra |
|---|---|---|
| Currency → TenantCurrency | 1 · N | Uma linha por tenant que ativou. BRL nasce ativa e não é desativável na v1; desativar com documento em aberto é 409 |
| Currency → CurrencyPair | 1 · N | Duas vezes, por fromCurrency e toCurrency. Único por par. O par inverso é outro par — não se deriva por inversão, porque compra e venda não são recíprocas. Global: autoFetch é a união das ativações dos tenants, e a edição exige permissão de plataforma (04/09/2026) |
| CurrencyPair → ExchangeRate | 1 · N | Único parcial em (tenantId, from, to, rateType, rateDate) WHERE supersededById IS NULL. tenantId nulo = cotação oficial compartilhada, e a do tenant vence a oficial |
| ExchangeRate → ExchangeRate | 0..1 · N | supersededById. Cotação não se edita: corrigir é gravar linha nova e marcar a antiga, com motivo obrigatório. A resolução só enxerga a vigente |
| Tenant → CurrencySettings | 1 · 1 | Tipo padrão, janela de idade da cotação, horários da rotina e destinatários do alerta. É a entidade que rascunhos da spec de Orçamento chamavam de BudgetSettings |
Anexo e arquivo
| Relação | Cardinalidade | Regra |
|---|---|---|
| StoredFile → Attachment | 1 · N | Sem chave estrangeira — outro módulo, outro DbContext. N porque a propagação e o dedupe fazem vários anexos compartilharem o mesmo blob. storedFileId é imutável no anexo |
| StoredFile → FileReference | 1 · N | Do GrydFiles. Único por (storedFileId, ownerScope, ownerKey) entre as não liberadas; liberar é append de releasedAt, nunca exclusão |
| Attachment → FileReference | 1 · 0..1 | ownerScope = "nexio.attachment", ownerKey = attachment.id. A referência não nasce na confirmação do upload: ela nasce na liberação do arquivo (09/09/2026), a partir do par pendente do StoredFile — até lá o anexo existe, não é referenciável e não é baixável, e a invariante do GrydFiles ("nada é referenciável antes de ser escaneado") sobrevive intacta. Infected e Purged descartam o anexo, com aviso ao dono. A cópia propagada cria a sua — com uma referência só, descartar a origem liberaria o arquivo enquanto o destino ainda o exibe |
| AttachmentType → Attachment | 1 · N | tenantId nulo no tipo é tipo global do produto; linha do tenant com o mesmo code o sobrepõe — padrão do NotificationTemplate, e o mesmo do ReasonCode desde 04/09/2026. Tipo desativado não invalida anexo existente; trocar de tipo só entre tipos de mesma retenção |
| Attachment → Attachment | 0..1 · N | copiedFromAttachmentId dá o pai, rootAttachmentId a origem da cadeia sem recursão. Revogar a origem não revoga as cópias |
| Attachment → dono | N · 1 | ownerType + ownerId, sem FK e sem navegação de volta: nenhum agregado tem coleção de anexos. ownerType sem descritor registrado derruba a inicialização |
| Company → Attachment | 1 · N | companyId derivado do dono e denormalizado, para o filtro por UserCompanyScope caber na linha sem join entre contextos. Fora do escopo é 404 |
| Supplier → Attachment | 0..1 · N | Obrigatório em anexo de origem portal, resolvido do token da RFQ e congelado. É o que dá o isolamento entre concorrentes por comparação de coluna, e o que permite promover o arquivo a SupplierDocument |
Catálogo, fornecimento e aprovação
| Relação | Cardinalidade | Regra |
|---|---|---|
| CategoryNode → CategoryNode | 0..1 · N | Árvore de profundidade livre, com teto de política por tenant (configuração do Nexio, a especificar — TenantSettings não existe na plataforma). Mesma mecânica das outras duas árvores desde 08/09/2026 |
| CategoryNode → GLAccount | N · 0..1 | Sugestão. A busca sobe a árvore: o nó mais próximo com conta preenchida vence |
| Item → GLAccount | N · 0..1 | Primeiro degrau da cascata; a categoria é o segundo. Sempre editável na linha |
| Item → UnitOfMeasure | N · 1 | baseUomId imutável depois do primeiro documento. Conversões vivem no item, não na unidade |
| Supplier → SupplierEstablishment | 1 · 1..N | Espelha Company → Establishment, para todo entityType: quem não tem CNPJ tem um estabelecimento sem cnpj, e a identidade (cpf, ou taxIdCountry + foreignTaxId) fica na raiz. Filial pode estar baixada com a matriz ativa |
| ItemSupplier → Item / Supplier | N · 1 | A chave da relação tem três partes — itemId, supplierId, supplierEstablishmentId?; o nulo vale para todos os estabelecimentos do fornecedor. A linha referencia a oferta e sua revisão, além da relação e do preço selecionado; congela condições e fatores efetivamente usados |
| ItemSupplierOffer → ItemSupplierPrice | 1 · 0..N | Versões de preço por oferta, empresa, UF e condição comercial, com vigência sem cruzamento. Incoterm condicional. Zero preços é possível para oferta ainda em negociação. |
| Tenant → ApprovalSettings | 1 · 1 | Nova em 08/09/2026. Limiar de materialidade do rateio, teto de duração da delegação, expediente e lista de dias não úteis — o que vale para todos os processos. A de processo chama-se ApprovalProcessSettings desde a mesma data, para que cada nome declare a própria chave. Mesmo padrão de Tenant → CurrencySettings: entidade por assunto, nunca JSON genérico |
| ApprovalPolicy → ApprovalLevel | 1 · 1..N | Política sem nível é recusada na publicação. Editar publica versão nova; a anterior segue respondendo pelas instâncias vivas |
| ApprovalRequest → ApprovalStep → ApprovalAssignment | 1 · 1..N · 1..N | A instância congela as políticas com versão. ApprovalEvent é append-only e é a fonte da verdade da trilha |
| Assunto → ApprovalRequest | 1 · 0..1 por ciclo | Supplier, SupplierQualification, BankAccountChangeProposal, Item, Budget e BudgetChangeRequest apontam para a instância viva por approvalRequestId, sem FK (contexto do motor). O assunto submete e aplica o efeito; nenhum deles tem rota própria de aprovar — princípio 7 do Fluxo, 04/09/2026 |
Contrato vigente · revisão consolidada
Invariantes entre contextos
- Referência empresarial sempre no tenant correto. CostAllocation.companyId é a empresa do documento; pedidos centralizados separam destinatários fiscais.
- Teto de permissão, escopo e concessão empresarial precisam autorizar a ação; ausência de vínculo nega acesso. Responsabilidade e alçada são verificadas separadamente.
- Decisão vincula revisão, conteúdo, valor autorizado, política/versão e evidência imutável. Mudança material exige revisão.
- Pedido mantém effectiveRevisionId e obrigação durante aprovação da candidata; alteração efetiva aplica delta.
- Razão é append-only, com grupo, entrada e parcela identificados. Backfill, reorçamento e virada são balanceados. Actual ocorrido não desaparece por falta de verba.
- Transações obrigatórias usam chamadas explícitas. Evento em memória espera o commit produtor; reação essencial tem outbox/inbox duráveis.
- FK estratégica pode ser registrada na persistência compartilhada. Relatório controlado pode fazer join, com filtros de todas as fontes. Separar bancos exige nova análise de atomicidade.
- Arquivo referencia versão selada; hash, scan e download correspondem aos mesmos bytes. Desfecho GrydFiles/Nexio exige conexão/transação compartilhadas.
- Portal vê a própria proposta/anexos com vínculo válido, sem acessar concorrentes. Identidade convidada não prova pessoa física autora.
Vocabulário
Enumerações
| Enum | Valores | Onde |
|---|---|---|
| AttachmentOwnerType | Requisition | RequisitionItem | QuotationRequest | QuotationResponse | PurchaseOrder | PurchaseOrderItem | Receipt | Invoice | Contract | QuotationRequestItem | ApprovalRequest | Supplier | Item | Budget | BankAccountChangeProposal | Attachment. Fechado: entrar na lista custa escrever o descritor com as onze respostas do registro. Nove valores até 08/09/2026; quinze desde 09/09 — a migração de enum fechado foi feita agora, com o catálogo ainda sem código além do centro de custo, e não depois. Os seis novos estão declarados na tabela de descritores da página de Anexo, e a RN-ATT-03 confere no startup que todo valor do enum tem descritor registrado |
| AttachmentVisibility | Internal | Supplier | Attachment e AttachmentType. Desde a v1, porque decidir depois obrigaria a reclassificar acervo e o modo de falha é vazamento |
| UploadedByKind | User | PortalUser | System | Attachment. Autoria de negócio; o uploadedBy do StoredFile continua sendo identidade do tenant no GrydAuth |
| RevocationSource | User | OwnerDiscarded | OrphanSweep | FileInfected | Attachment. Separa o que uma pessoa decidiu do que o sistema concluiu — é o que a tela usa para escrever a frase certa |
| RetentionMode | UntilReleased | RetainUntil | Permanent | AttachmentType declara, StoredFile executa. O Nexio decide quanto tempo guardar; o GrydFiles cobra a declaração |
| AttachmentOwnerState | Draft | InProgress | Closed | Attachment.lastKnownOwnerState. É o mesmo vocabulário que o StateOf do descritor devolve, menos o Missing — que é resposta do descritor, nunca estado gravado. Quem grava é o módulo dono, na transição, no mesmo método que muda o estado do documento |
| FileOutcome | Released | Infected | Rejected | O que o contrato de veredito (IFileOutcomeHandler) entrega a quem espera pelo arquivo. Não é o FileStatus: é o subconjunto que interessa ao consumidor |
| FileStatus | Pending | Scanning | Available | Infected | Failed | Purged | StoredFile, da plataforma. Não é espelhado no anexo: muda, e por isso é consultado em lote |
| ApprovalRequestStatus | InApproval | Approved | Rejected | Returned | Cancelled | ApprovalRequest. Returned volta ao requisitante e a instância continua viva; os outros três são terminais. Draft e Expired saíram em 08/09/2026: nada os produzia — a instância nasce em POST /approvals e nada vence por decurso |
| ApprovalStepStatus | Pending | Active | Approved | Rejected | Skipped | Obsolete | ApprovalStep. Skipped só por activationMinValue ou dedupe — nunca por falta de aprovador |
| ApprovalAssignmentStatus | Pending | Approved | Rejected | Returned | Obsolete | Reassigned | ApprovalAssignment. Obsolete quando outra tarefa do nível já fechou o passo em modo Any. A linha permanece |
| ApprovalMode | Any | All | Quorum(n) | ApprovalLevel. É aqui que vive o paralelo — entre níveis é sempre sequencial. Rejeição encerra a instância em todos os modos; Panel é tipo de nível, sempre All. Vote(n) — votação com dissidência tolerada — é fase 2, restrito a três processos, e não entra no enum até lá |
| ApprovalScopeMode | Document | Line | Slice | ApprovalLevel e ApprovalStep. Line entrou no enum em 09/09/2026: o valor já era usado em três lugares e não existia aqui, o que deixava o padrão "interestadual passa pelo fiscal" inconfigurável. Um passo por linha que casa o critério, scopeAmount = valor da linha, materialidade não se aplica e a dedupe é por (pessoa, linha) |
| AssignmentOrigin | Direct | ByRole | ByGroup | ByDelegation | ByEscalation | ByReassignment | ApprovalAssignment, com o id da delegação, do grupo ou do papel em originRef |
| EscalationBehavior | None | Notify | Escalate | ApprovalProcessSettings, por processo — dono único desde 08/09/2026: política e nível herdam o prazo, não o que fazer com ele; o para quem é escalationTarget, no nível, e allowEscalation = false proíbe escalar no processo. Não existe AutoApprove — a ausência é a decisão. NotifyManager saiu no mesmo dia: dependia da árvore de subordinação do GrydAuth, que é fase 2 |
| ApproverType | SpecificUser | Role | PermissionHolder | Panel | CostCenterOwner | CostCenterOwnerUpToAmount | CostCenterOwnerAtDistance | ApprovalGroup | CategoryOwner | RequesterManager | ExternalEmail | Auto | ApprovalLevel. PermissionHolder e Panel entraram em 04/09/2026 — o primeiro é o alvo das políticas padrão semeadas, o segundo o paralelo por área da homologação; os quatro últimos são fase 2 |
| ApprovalProcessCode | REQUISITION | QUOTATION | PURCHASE_ORDER | CONTRACT | SUPPLIER_REGISTRATION | SUPPLIER | BANK_ACCOUNT_CHANGE | ITEM | BUDGET | BUDGET_CHANGE | Os dez processos-semente desde 04/09/2026. SUPPLIER_REGISTRATION, ITEM e BUDGET entraram com o motor universal; ORCAMENTO e ALTERACAO_ORCAMENTARIA, que o Orçamento citava, nunca existiram |
| TenantType | Company | Group | Plataforma. Group é eixo de acesso e nunca guarda dado |
| TaxRegime | SimplesNacional | MEI | LucroPresumido | LucroReal | TaxExempt | NotApplicable | Company e Supplier — o mesmo enum dos dois lados |
| EstablishmentType | Headquarters | Branch | Establishment e SupplierEstablishment |
| IcmsTaxpayerStatus | Taxpayer | Exempt | NonTaxpayer | Establishment. É o indIEDest da NF-e |
| DeliveryLocationType | OwnSite | Warehouse | ConstructionSite | ThirdParty | DeliveryLocation. Só ThirdParty libera recipientCnpj; só ConstructionSite pré-seleciona o motivo interestadual |
| InterstateReason | ConstructionSite | GeneralWarehouse | ThirdPartyProcessing | DirectToCustomer | IntraGroupTransfer | Other | DeliveryLocation e o snapshot da linha. Fechado de propósito: motivo em texto livre vira "conforme combinado" no terceiro mês. IntraGroupTransfer é de 09/09/2026 — entregar na filial de PE com a matriz de SP como destinatária fiscal é o arranjo que a compra centralizada produz o tempo todo, e até então só cabia em Other + texto livre |
| RegistrationStatus | Null | Active | Suspended | Unfit | ClosedDown | Situação cadastral — Establishment e SupplierEstablishment, o mesmo enum dos dois lados, com os códigos 01 · 02 · 03 · 04 · 08 da Receita. Alinhado em 04/09/2026; nulo para fornecedor sem CNPJ |
| AccountType | Asset | Liability | Equity | Revenue | Expense | GLAccount. Herdado do pai |
| SequenceScope | Tenant | Company | Establishment | NumberSequence. Padrão Company |
| ResetPolicy | Never | Yearly | Monthly | NumberSequence. Padrão Yearly |
| AssignmentRole | Owner | Deputy | Watcher | CostCenterAssignment |
| PredominantUse | ProductionInput | Resale | ConsumableUse | FixedAsset | PersonalBenefit | Item e ItemType. Critério de aprovação |
| UomClass | Count | Weight | Volume | Length | Area | Time | Service | UnitOfMeasure. Conversão só dentro da classe |
| BankAccountKind | Domestic | International | SupplierBankAccount.accountKind. Discriminador do bloco bancário: iban, swiftBic, bankCountryCode e os campos de banco intermediário são obrigatórios em International |
| RoutingCodeType | ABA | TransitNumber | SortCode | Other | SupplierBankAccount, no bloco internacional |
| ProtectedChangeKind | BankAccount | NoticeContact | BankAccountChangeProposal.changeKind. Mudar o e-mail ou o telefone do contato financeiro é proposta pela mesma entidade e pela mesma instância de BANK_ACCOUNT_CHANGE — porque trocar o canal do aviso é o primeiro passo da fraude que a proposta bancária existe para deter |
| SupplierEntityType | Company | MEI | Individual | RuralProducer | Foreign | Supplier. Governa a obrigatoriedade de todo o resto |
| CompanySize | NotInformed | ME | EPP | Other | Supplier. Indicativo, nunca verdade contratual |
| PolicyKind | Base | Supplemental | ApprovalPolicy. Base concorre, Supplemental empilha |
| BudgetStatus | Draft | InReview | Approved | Closed | Archived | Budget. Só Approved e Closed participam da resolução |
| BudgetOrigin | Imported | Planned | Budget. Muda por onde as linhas entraram, não o ciclo de vida |
| PeriodGranularity | Monthly | Quarterly | Yearly | Budget. Imutável depois de aprovado |
| BudgetEntryReason | Post | Liquidate | Reverse | DocumentCancelled | Return | ReceiptReversal | QuantityChange | DateChange | ResidueRelease | YearEndRollover | BudgetBackfill | BudgetReclassification | 12 valores, mapeamento para operation em Motivo. Idempotência é por postingGroupId e entryOrdinal, não pelo motivo. |
| BudgetStage | Commitment | Obligation | Actual | Adjustment | BudgetEntry. Seis valores até 08/09/2026; quatro desde 09/09. Os três primeiros consomem; Adjustment altera a verba. CarryOut e CarryIn saíram porque não consumiam, não tinham campo em amounts e não entravam no somatório: a virada de exercício grava nos próprios estágios, com entryReason = YearEndRollover |
| BudgetSignal | NotControlled | Ok | Warning | Exceeded | Uncovered | Resposta de evaluate:budget-states. É o que a tela pinta e o que o motor lê como critério |
| ResolutionReason | NotControlled | Covered | Uncovered | BudgetEntry. Congelado na gravação |
| CarryForwardMode | PeriodOnly | Cumulative | BudgetPolicy. É modo de leitura do saldo, não movimento de transporte. Padrão PeriodOnly |
| OverBudgetPolicy | Warn | Block | RequireApproval | BudgetPolicy. Padrão Warn; não existe permissão que fure Block |
| YearEndCommitmentPolicy | CarryCommitmentOnly | CarryCommitmentAndBudget | CancelOpen | BudgetPolicy. Padrão CarryCommitmentOnly — o compromisso segue o documento, a verba não vai junto |
| ChangeRequestType | Supplement | Transfer | Reduction | BudgetChangeRequest. O sinal vem do tipo, não do número digitado |
| BudgetSourceType | Requisition | PurchaseOrder | Receipt | Invoice | Contract | BudgetChangeRequest | Rollover | Import | Manual | BudgetEntry. É o rastro que responde "quem comeu minha verba" |
| RateType | PtaxVenda | PtaxCompra | Commercial | Contracted | Manual | ExchangeRate. Padrão PtaxVenda de fechamento D−1 |
| NotificationChannel | Email | InApp | Push | Sms | NotificationPreference e o catálogo. Canal fora de allowedChannels do slug não aparece na tela e é recusado na API. Sms é dependência declarada do GrydNotifications, que hoje entrega três canais — um único slug o usa, o do aviso bancário, onde a redundância de canal é o controle; enquanto não existir, o aviso sai por e-mail e a ausência do segundo canal é registrada |
| NotificationCategory | Approval | Budget | Catalog | Supplier | Attachment | System | Catálogo. Só agrupa a tela — a preferência é por slug, e não existe interruptor de categoria |
| AudienceType | User | TenantRole | CostCenterOwners | PendingApprovers | SupplierFinanceContact | Catálogo. Cinco resolvedores, cada um implementado pelo módulo dono do conceito; tipo sem resolvedor derruba a inicialização |
| DeferredSubmissionMode | NotifyOnly | Submit | DeferredSubmission.mode. As duas escolhas da tela criam a fila — "só avisar" deixou de ser "não cria nada", que era o desenho em que ninguém sabia dizer o que tinha ficado para trás |
| DeferredSubmissionState | Waiting | Submitting | Completed | Abandoned | Cancelled | DeferredSubmission. Submitting existe para duas instâncias da aplicação não submeterem o mesmo documento |
| ReasonCodeUsage | ApprovalReject | ApprovalReturn | BankAccountChangeReject | BudgetReject | BudgetChangeReject | ItemReject | ItemDuplicateOverride | SupplierReject | SupplierBlock | SupplierUnblock | SupplierManualPromotion | AttachmentRevoke | BudgetChangeJustify | PriceVarianceAccept | ExchangeRateSupersede | DocumentCancel | SupplierAwardOverride | ReasonCode.appliesTo. Doze usos até 08/09/2026; dezessete desde 09/09, com a semente do produto em 45 códigos. Três rejeições que faltavam (BankAccountChangeReject, BudgetReject, BudgetChangeReject), a justificativa da mudança de verba (BudgetChangeJustify, que aposentou o justification em texto livre do BudgetChangeRequest) e o aceite de diferença de preço do three-way match (PriceVarianceAccept, declarado) |
| AbandonReason | FileInfected | FileScanFailed | PermissionLost | OwnerChanged | OwnerDiscarded | Expired | OwnerRefused | DeferredSubmission. Motivo de sistema, por isso continua enum quando o ReasonCode existir. OwnerRefused entrou em 09/09/2026, com a transição Submitting → Waiting: o dono recusar a submissão devolve a fila à espera e não é falha |
Contrato vigente · revisão consolidada
Convenções vigentes
| Convenção | Contrato |
|---|---|
| Fonte canônica | Campos e regras pertencem à sua especificação; este modelo mostra relações. Sem acúmulo de decisões históricas contraditórias. |
| Estado real | Especificado não significa implementado; código em branch não significa pacote publicado. Ver Arquitetura e relato datado do spike. |
| Persistência | PostgreSQL compartilhado e NexioDbContext simplificam a v1. FK estratégica requer registro de exceção; testes atuais ainda proíbem todas e deverão ser adaptados antes do uso. |
| Transações | Efeitos obrigatórios explícitos; memória após commit produtor; outbox durável também para reações locais essenciais. |
| Dinheiro | numeric(21,4), Currency.minorUnits 0–4; unitário numeric(19,10); quantidade numeric(19,6) com original e fatores preservados; taxa numeric(18,8); percentual numeric(9,6). |
| Resíduo | HALF_UP nas casas da moeda, maior fatia e empate pelo menor order. Total funcional soma fatias convertidas. |
| Concorrência | expectedVersion na raiz e operationId estável. Orçamento acrescenta travas de todos os limites/janelas; inbox e efeito são atômicos. |
| Revisões | revisionId identifica conteúdo; roundNumber identifica rodada. Não trocar versão efetiva enquanto candidata aguarda. |
| Permissão | verbo:recurso, com regras de catálogo por módulo; autorização efetiva inclui empresa. Contratos de portal não criam usuário de tenant. |
| Ações e motivos | Ação estável e reasonCode snapshot; entryReason é enum do sistema. Campo Action da plataforma permanece dependência a conferir no pacote. |
| Evolução | Migração coordenada, compatibilidade de payload e restore testado. Particionamento, isolamento físico e infraestrutura de IA aguardam gatilhos observados. |
Inventário
Satélites — as classes que não têm caixa
Catorze classes especificadas que não aparecem nos diagramas, de propósito. A regra de corte é esta: ganha caixa o que se lê ao lado de outra coisa — raiz, configuração, árvore, o que participa de uma invariante entre agregados. Satélite é o que só se lê dentro do seu próprio agregado, e desenhá-lo aqui dobraria o tamanho de nove diagramas para repetir o que a spec do dono já diz melhor. O que esta tabela garante é o que faltava: que ninguém invente um campo que já existe, porque a linha diz onde procurá-lo.
| Classe | Agregado dono | Onde os campos vivem |
|---|---|---|
ItemPhoto | Item | Cadastro de Item · § Coleções filhas. Máx. 5, storedFileId no perfil nexio.item-photo |
ItemDocument | Item | Cadastro de Item · § Coleções filhas. FISPQ, certificado, laudo; tipo, versão, emissão, validade e storedFileId |
ItemCompliance | Item | Cadastro de Item · § Compliance. Vencimento bloqueia a compra quando marcado |
ItemUomConversion | Item | Unidade de Medida · § Conversão, e Cadastro de Item · conversions. Append-only com vigência (RN-UOM-09) |
ItemLifecyclePhase | Item · configuração | Cadastro de Item. É o destino de Item.lifecyclePhaseId, que até 08/09/2026 apontava para lugar nenhum nesta página |
AttributeDefinition | Catálogo · raiz própria | Cadastro de Item · § Atributos. É raiz e mesmo assim não tem caixa: o esquema por categoria é assunto de uma spec só, e desenhá-lo custaria a banda inteira |
SupplierAddress | Supplier | Fornecedor · § Endereços. Um endereço com vários papéis — não confundir com DeliveryLocation, que são vários pontos com um papel só |
SupplierContact | Supplier | Fornecedor · § Contatos. SupplierFinanceContact é o recorte financeiro dele, usado pela Notificação |
SupplierDocument | Supplier | Fornecedor · § Documentos. Certidão com validade e storedFileId; libera a referência em vez de apagar. Ganhou supplierEstablishmentId? em 09/09/2026 — obrigatório quando scope = Establishment, proibido quando scope = Supplier: certidão é por CNPJ, e o fornecedor tem vários |
SupplierBankAccount | Supplier | Fornecedor · § Dados bancários. Números mascarados por padrão, sob read:supplier-banks. Duas colunas de 09/09/2026: accountKind (Domestic | International), que discrimina o bloco de IBAN/SWIFT, e usableFrom, que materializa a carência de 24–72 h — conta com usableFrom no futuro não sai no pacote do ERP. Antes disso a carência existia só como frase |
SupplierFinanceContact | Supplier | Fornecedor e Notificação · é audiência de aviso, não identidade |
SupplierQualification | Supplier | Fornecedor · § Homologação. approvals[] vem da instância do processo SUPPLIER do motor; companyId anulável é gancho declarado. Desde 09/09/2026 é ela que autoriza o gasto, por consulta às qualificações vigentes (supplierId × categoryNode, casando por prefixo de path e respeitando validTo) — e não mais o estado SpendAuthorized sem escopo, que tornava autorizado para ácido sulfúrico quem foi aprovado para limpeza |
BankAccountChangeProposal | Supplier | Fornecedor · § Alteração bancária. Abre instância de BANK_ACCOUNT_CHANGE; o Fornecedor aplica ao registro vivo pelo contrato de desfecho (RN-AP-38). changeKind (09/09/2026) faz a mesma entidade servir à troca do canal de aviso — e-mail e telefone do contato financeiro viraram campos protegidos, porque trocar o canal é o primeiro passo da fraude que a proposta existe para deter. noticeSentAt sustenta a janela mínima entre o aviso e a primeira decisão |
PortalUser | Supplier · condicional | Fornecedor · § Portal. Três campos reservados. A decisão do portal deixou de ser uma folha em branco em 08/09/2026 — há proposta escrita, em validação; ver o gate do mapa de domínio |
Nem tudo o que tem nome é classe. Commitment, Obligation, Actual e Adjustment aparecem em toda a spec de Orçamento e não são entidades: são valores do estágio de um BudgetEntry. Procurar uma tabela Commitment é o erro que esta linha existe para evitar.
Limites
O que ainda não está aqui
Seis agregados e um transversal a especificar — Receipt e Invoice dividem uma linha da tabela e são duas classes; são as mesmas seis caixas a especificar do mapa de domínio. A ordem e o custo de adiar estão no mapa de domínio. Notification e ReasonCode saíram em 03/09/2026, nas bandas 8 e 9 — as duas entraram no fim, que é a inserção mais barata desta página.
| Classe | Onde encaixa | O que já está reservado |
|---|---|---|
Requisition | Espinha transacional · a primeira | establishmentId obrigatório, companyId derivado e congelado, linhas com CostAllocation, número por NumberSequence |
PurchaseOrder | Espinha transacional | Mais requestingEstablishmentId, para compra centralizada |
Receipt · Invoice | Espinha transacional · three-way match | A nota chega endereçada a um CNPJ de 14, e o leiaute já tem IBS e CBS — exigidos a partir da data que a norma fixar, ausentes sem erro até lá (regra do Item) |
QuotationRequest | Espinha transacional | Depende da decisão sobre portal do fornecedor, que tem proposta na mesa e ainda não foi validada. QuotationRequestItem já entrou no AttachmentOwnerType, com as onze respostas declaradas — de propósito: o custo de deixá-lo fora era uma migração de enum fechado depois |
Contract | Contrato transacional mínimo | Oferta pode referenciar contractLineId e revisão; mínimo obrigatório gera obrigação na ativação, com liberações sem dupla contagem. Especificação completa ainda pendente. |
| Transversais | ExternalRef · Outbox | Sobrou um. NumberSequence saiu com a spec de empresa, Currency · ExchangeRate com a de Moeda e Câmbio, Attachment · AttachmentType com a de Anexo e as três de notificação com a de Notificação, que levou junto o Motivo — e o AuditLog saiu do backlog porque o GrydAudit já o entrega inteiro |
Contrato vigente · revisão consolidada
Contratos mínimos entre documentos
Ver Contratos transacionais para responsabilidade, condições de efetivação, aceite de serviço, mínimo contratual e cenários de validação. Os blocos são contratos mínimos; não afirmam entidades já implementadas.