Referência · Modelo

Modelo de classes

Tudo o que está especificado, em uma página só. Quatro bandas, 31 classes, com os campos como as specs os definem — não como seria bonito defini-los. Serve para três coisas: conferir uma decisão sem reabrir cinco documentos, achar o campo certo antes de inventar um novo, e enxergar as repetições de mecânica que são a única razão de este produto ser previsível.

deriva de Empresa e Estabelecimento v1.0, Centro de Custo v2.1, Cadastro de Item v3.1, Fornecedor v2.0, UoM v1.0, Fluxo de Aprovação v2.1 31 classes · 4 bandas v1.0 · 01/09/2026

Ponto de partida

Como ler

Notação

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 em 01/09/2026.

O que foi omitido

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.

Onde está a verdade

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.

Banda 1 · gryd e a fronteira

Plataforma e escopo

O usuário é global e não pertence a tenant; tudo o que o liga a uma organização passa por UserTenant. O token do GrydAuth traz userId, tenantId, papéis e permissões — e não traz empresa. O conjunto permitido é resolvido no servidor a cada requisição.

Tenant plataforma type TenantType parentTenantId? Guid → Tenant settings json maxUsers? int isActive bool UserTenant matriz de acesso userId Guid → User tenantId Guid → Tenant isDefault / isActive bool assignmentPeriod período UserRole userTenantId Guid → UserTenant roleId Guid → Role Role por tenant tenantId Guid → Tenant name string(100) isSystemRole bool User global email string firstName / lastName string isActive bool allowCrossTenant bool UserPermission userTenantId Guid → UserTenant permissionId Guid → Permission Permission por tenant tenantId Guid → Tenant code string UserCompanyScope Nexio · escopo userId Guid → User companyId? Guid → Company roleId? Guid · reservado validFrom / validTo? date isActive bool 1 · N 1 · N N · 1 1 · N 1 · N N · 1 1 · N
Tracejado é do gryd e fica intacto. A caixa cheia embaixo é a única coisa nova, e é do Nexio — é onde a fronteira cai.

A multiempresa não encosta no gryd. A plataforma não conhece Company e não deve conhecer: pôr companyId em UserRole faria a camada de baixo depender de uma tabela de um produto que roda sobre ela. Por isso o recorte mora em UserCompanyScope, do Nexio, referenciando o userId do gryd sem chave estrangeira — o mesmo padrão que CostCenterAssignment já usa hoje, inclusive na consulta de elegibilidade ao GrydAuth. A tabela só estreita, nunca concede: quem dá organização, papel e permissão continua sendo o token, então linha órfã é inerte e ausência de linha significa todas as empresas.

Banda 2 · o dinheiro

Organização e dinheiro

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 e path como token de concorrência.

Company aggregate root code string(20) cnpjRoot varchar(8) legalName string(200) tradeName? string(160) taxRegime TaxRegime functionalCurrency char(3) headquartersId? Guid → Est. isActive bool Establishment 1..N por empresa companyId Guid → Company code string(20) cnpj varchar(14) type EstablishmentType icmsTaxpayerStatus enum stateRegistration? varchar(20) suframaCode? varchar(9) cityIbgeCode varchar(7) stateCode char(2) registrationStatus? enum allowsPurchasing bool allowsReceiving bool GLAccount árvore code string(30) name string(160) parentId? Guid → GLAccount path / depth varchar / int accountType AccountType allowsPosting bool validFrom / validTo? date isActive bool GLAccountCompany vínculo N×N glAccountId Guid → GLAccount companyId Guid → Company isEnabled bool externalCode? string(40) CostCenter árvore code string(20) name string(160) parentId? Guid → CostCenter path / depth varchar / int companyId? Guid → Company validFrom / validTo? date allowsPosting bool isActive / isDeleted bool CostCenterAssignment N por nó costCenterId Guid → CostCenter userId Guid → User role AssignmentRole approvalLimit? numeric(19,2) validFrom / validTo? date endedReason? string(200) CostAllocation value object order int costCenterId Guid → CostCenter glAccountId? Guid → GLAccount percent numeric(9,6) amount numeric(19,2) NumberSequence por tipo de doc documentType enum scope SequenceScope companyId? Guid → Company establishmentId? Guid → Est. prefix? / padding string / int resetPolicy enum nextValue bigint 1 · 1..N 1 · N 0..1 · N 1 · N 1 · N parentId parentId
Duas árvores com a mesma mecânica de caminho materializado, e o rateio que cruza as duas.

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.

Banda 4 · quem decide

Motor de aprovação

Sete classes de configuração 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.

ApprovalProcess por tenant Qual documento é aprovável valor avaliado · critérios ações · aprovador de reserva ApprovalPolicy N por processo kind Base | Complementar priority · isDefault · version validFrom / validTo fallbackApproverId? ApprovalLevel 1..N por política order · approverType · target mode · quorum · scopeMode activationMinValue maxApprovalHours · label ApprovalGroup N por tenant Pool nomeado de usuários referenciado por nível ApprovalSettings por processo dedupe · autoaprovação materialidade do rateio SLA padrão · escalonamento ApprovalCriteria value object faixa de valor · nó de CC nó de categoria · tipo de item empresa · estabelecimento fornecedor · flags · orçamento ApprovalDelegation N por usuário Ausência temporária escopo por processo · teto ApprovalRequest aggregate root subjectType + subjectId empresa · requisitante · valor snapshot das políticas estado ApprovalStep 1..N por instância order · política e nível de origem escopo · modo · SLA · estado ApprovalAssignment 1..N por passo A tarefa de uma pessoa origem · estado · decisão comentário · momento ApprovalEvent append-only Toda transição, com autor, ação, alvo e metadados 1 · N 1 · 1..N N · 0..1 1 · 1 1 · 1 1 · 1..N 1 · 1..N
Configuração acima, execução abaixo. As caixas trazem o papel de cada entidade — os campos completos estão na spec do fluxo.

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 Complementar de uma empresa só.

Integridade

Cardinalidades

Toda relação do modelo, com a regra que a governa. É a tabela que responde "posso apagar isso?".

Plataforma e escopo

RelaçãoCardinalidadeRegra
User → UserTenant1 · NUma linha por organização. Remover o vínculo apaga em cascata papéis e permissões diretas naquela organização
UserTenant → UserRole1 · NO papel é sempre dentro de um tenant. Não existe papel global
UserCompanyScope → CompanyN · 0..1Nulo = todas as empresas do tenant; ausência de linha, também. O escopo efetivo é a união das linhas vigentes. FK composta (tenantId, companyId)companies(tenantId, id), com dois índices únicos parciais — um para o caso nulo, outro para o preenchido
UserCompanyScope → UserN · 1userId do gryd sem FK — é outro serviço. Validado contra o diretório na escrita; linha órfã é inerte, porque o token nunca mais traz aquele tenant
Estabelecimento padrão do usuárioNão é tabela. Preferência de interface em localStorage, seguindo o precedente do próprio gryd. "Preferências do usuário" é conceito da plataforma; uma tabela homônima no Nexio criaria duas fontes para a mesma pergunta

Organização e dinheiro

RelaçãoCardinalidadeRegra
Company → Establishment1 · 1..NExatamente um Headquarters. Empresa ativa nunca fica sem estabelecimento ativo; companyId é imutável
Establishment → cnpjOs 8 primeiros caracteres têm que ser o cnpjRoot da empresa. Divergência é 422
GLAccount → GLAccount0..1 · NÁrvore. Ciclo recusado; accountType herdado do pai e divergir dele é 422
GLAccount × CompanyN · NVia GLAccountCompany. Ausência de linha = habilitada — a linha existe para restringir ou carregar o código externo
CostCenter → CostCenter0..1 · NÁrvore. Nó de empresa não aceita filho de outra empresa; nó corporativo aceita qualquer filho
CostCenter → CompanyN · 0..1Nulo = corporativo. Aponta para a empresa, nunca para o estabelecimento
CostCenter → CostCenterAssignment1 · NMesma 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 / GLAccountN · 1 / N · 0..1Só nó e conta com allowsPosting. Soma dos percentuais fecha em 100 com regra de resíduo declarada
NumberSequence → Company / EstablishmentN · 0..1Exigidos conforme o scope; preenchidos fora dele é 422

Catálogo, fornecimento e aprovação

RelaçãoCardinalidadeRegra
CategoryNode → CategoryNode0..1 · NÁrvore de profundidade livre, com teto de política em TenantSettings
CategoryNode → GLAccountN · 0..1Sugestão. A busca sobe a árvore: o nó mais próximo com conta preenchida vence
Item → GLAccountN · 0..1Primeiro degrau da cascata; a categoria é o segundo. Sempre editável na linha
Item → UnitOfMeasureN · 1baseUomId imutável depois do primeiro documento. Conversões vivem no item, não na unidade
Supplier → SupplierEstablishment1 · 1..NEspelha CompanyEstablishment. Filial pode estar baixada com a matriz ativa
ItemSupplier → Item / SupplierN · 1supplierEstablishmentId compõe a chave; nulo vale para todos os estabelecimentos do fornecedor
ApprovalPolicy → ApprovalLevel1 · 1..NPolítica sem nível é recusada na publicação. Editar publica versão nova; a anterior segue respondendo pelas instâncias vivas
ApprovalRequest → ApprovalStep → ApprovalAssignment1 · 1..N · 1..NA instância congela as políticas com versão. ApprovalEvent é append-only e é a fonte da verdade da trilha

O que o diagrama não mostra

Invariantes entre agregados

Regras que atravessam duas ou mais classes e por isso não cabem em nenhuma caixa.

A empresa da linha tem que bater com a do nó

Uma linha de documento da empresa A só aponta para centro de custo corporativo ou da empresa A. Validado na linha, com o companyId efetivo lido do nó — não no cadastro, porque o cadastro é anterior ao documento.

Nulo significa a mesma coisa em toda parte

CostCenter.companyId nulo é nó corporativo. UserCompanyScope.companyId nulo — e a ausência da linha — é acesso a todas as empresas. ItemSupplier.supplierEstablishmentId nulo vale para todos os estabelecimentos do fornecedor. GLAccountCompany ausente é conta habilitada. Uma convenção só — nulo é "todos", nunca "nenhum" e nunca "não sei".

Três árvores, uma mecânica

CostCenter, CategoryNode e GLAccount compartilham caminho materializado, código imutável, ancestrais sem recursão, cascata assimétrica de status e path como token de concorrência. Uma quarta forma de fazer árvore neste produto seria erro por escolha.

Documento congela; cadastro muda

Preço, fator de conversão, cadeia de aprovação, dados do fornecedor, empresa e conta são copiados para a linha quando ela nasce. Nenhuma seta deste diagrama é lida em tempo de execução por um documento já emitido.

Aprovador precisa de escopo na empresa do documento

Um nível que resolve para alguém sem companyId compatível — nem a empresa do documento, nem nulo — conta como nível vazio: cai no fallbackApproverId e registra o motivo na trilha. Sem isso, o motor produz tarefa para quem não consegue abrir o documento.

O escopo só estreita, e nunca atravessa a organização

UserCompanyScope não concede organização, papel nem permissão — isso continua vindo do token do GrydAuth. Ela responde apenas em quais empresas daquele tenant o que o token já concedeu tem efeito. Daí duas consequências: linha órfã é inerte, e ausência de linha pode ser permissiva sem risco.

O que precisa ser garantido é o outro lado — a empresa tem que ser do mesmo tenant da linha. Aí a chave é composta e local, dentro do esquema do Nexio: (tenantId, companyId)companies(tenantId, id). E a unicidade são dois índices parciais, não um: no Postgres dois nulos não colidem, e "todas as empresas" duplicado passaria batido.

Dois CNPJs com o mesmo nome são coisas diferentes

Establishment é comprador; SupplierEstablishment é vendedor. Foi por isso que ItemSupplier.establishmentId virou supplierEstablishmentId em 01/09/2026 — com as duas classes existindo, o nome curto era ambíguo em toda consulta.

Vocabulário

Enumerações

EnumValoresOnde
TenantTypeCompany | GroupPlataforma. Group é eixo de acesso e nunca guarda dado
TaxRegimeSimplesNacional | MEI | LucroPresumido | LucroReal | TaxExempt | NotApplicableCompany e Supplier — o mesmo enum dos dois lados
EstablishmentTypeHeadquarters | BranchEstablishment e SupplierEstablishment
IcmsTaxpayerStatusTaxpayer | Exempt | NonTaxpayerEstablishment. É o indIEDest da NF-e
RegistrationStatusActive | Suspended | Inactive | ClosedSituação cadastral, dos dois lados
AccountTypeAsset | Liability | Equity | Revenue | ExpenseGLAccount. Herdado do pai
SequenceScopeTenant | Company | EstablishmentNumberSequence. Padrão Company
ResetPolicyNever | Yearly | MonthlyNumberSequence. Padrão Yearly
AssignmentRoleOwner | Deputy | WatcherCostCenterAssignment
PredominantUseProductionInput | Resale | ConsumableUse | FixedAsset | PersonalBenefitItem e ItemType. Critério de aprovação
UomClassCount | Weight | Volume | Length | Area | Time | ServiceUnitOfMeasure. Conversão só dentro da classe
SupplierEntityTypeCompany | MEI | Individual | RuralProducer | ForeignSupplier. Governa a obrigatoriedade de todo o resto
CompanySizeNotInformed | ME | EPP | OtherSupplier. Indicativo, nunca verdade contratual
PolicyKindBase | ComplementarApprovalPolicy. Base concorre, complementar empilha

Governança

Convenções e dívidas

ConvençãoEstadoDetalhe
Identificadores em inglêsvaleEntidade, atributo, valor de enum, rota, permissão e código de erro. Exceção: dados de negócio brasileiros — cnpj, primaryCnae, SimplesNacional, siglas de unidade
Sufixo do erro decide o HTTPvale_NOT_FOUND → 404 · _CONFLICT e _ALREADY_EXISTS → 409 · _UNPROCESSABLE → 422 · sem sufixo → 400
Fora de escopo responde 404valeRegistro que existe mas não está no escopo do usuário se comporta como inexistente. 403 confirmaria a existência
Estado só por rota própriavale/activate e /deactivate; PUT nunca muda isActive
Valores de enum do motor de aprovaçãodívidamode (Qualquer | Todos | Quorum), scopeMode (Documento | Fatia) e os estados de ApprovalRequest, ApprovalStep e ApprovalAssignment estão em português, contra a convenção. Normalizar antes de existir dado
establishmentId dentro do agregado do fornecedordívidaSupplierAddress e SupplierContact ainda usam o nome curto. Sem ambiguidade dentro do agregado, ambíguo na leitura. Renomear quando essas classes forem tocadas

Limites

O que ainda não está aqui

Oito agregados a especificar. A ordem e o custo de adiar estão no mapa de domínio.

ClasseOnde encaixaO que já está reservado
RequisitionEspinha transacional · a primeiraestablishmentId obrigatório, companyId derivado e congelado, linhas com CostAllocation, número por NumberSequence
PurchaseOrderEspinha transacionalMais requestingEstablishmentId, para compra centralizada
Receipt · InvoiceEspinha transacional · three-way matchA nota chega endereçada a um CNPJ de 14 e já traz IBS e CBS desde 03/08/2026
QuotationRequestEspinha transacionalDepende da decisão sobre portal do fornecedor
ContractEstruturalItemSupplier.contractId já existe como gancho
DeliveryLocationEstruturalItemSupplier.shipToLocationId reservado; aponta para o Establishment que recebe fiscalmente
BudgetEstruturalVerba por empresa × centro de custo × conta × período. Os três eixos existem
TransversaisAttachment · AuditLog · Notification · ExternalRef · ReasonCodeNumberSequence já saiu com a spec de empresa