Ponto de partida
Oito decisões
Empresa é entidade dentro do tenant, não tenant filho. Company nasce obrigatória e sempre existe, mesmo quando é uma só; o CNPJ solto do tenant morre — e TenantSettings, que esta spec tratava como entidade da plataforma, não existe no Gryd.IO. A plataforma já sabia agrupar organizações — o que ela não sabia era que uma organização tem mais de um CNPJ.
Os dois modos de isolamento são o mesmo modelo. Tenant com uma Company é o modo isolado; tenant com N é o consolidado. O domínio é escrito uma vez, para o consolidado — no isolado o segundo filtro simplesmente não recorta nada. Sem if, sem duas trilhas, sem dois jeitos de emitir um pedido.
O tenant Group é eixo de acesso, nunca de dados. Nenhuma linha de nenhuma tabela pertence a um tenant do tipo grupo. No minuto em que ele guardar dado, todo filtro do sistema vira "tenant ou pai do tenant" — e um filtro que aceita o pai é um filtro que vaza.
Dois níveis de CNPJ, espelhando o fornecedor. Company é a pessoa jurídica, chaveada pela raiz de 8; Establishment é o CNPJ de 14, matriz ou filial. É o mesmo corte de Supplier e SupplierEstablishment: um CNPJ tem um modelo mental só no produto, de dentro e de fora.
O acesso empresarial é do Nexio: UserCompanyScope concede visibilidade e UserCompanyRole limita as operações por empresa. Permissões da plataforma são o teto. Nenhuma linha vigente significa acesso negado; allCompanies exige concessão explícita. Alçada financeira é separada.
O documento aponta para o estabelecimento; a empresa é derivada. Quem emite pedido e recebe nota é um CNPJ de 14, mas alçada, orçamento e conta contábil raciocinam em empresa. Um campo na interface, dois congelados no documento.
Plano de contas no tenant, habilitação e código externo por empresa. Grupo econômico brasileiro usa plano unificado; o que varia é o código no ERP de cada CNPJ. Um plano por empresa multiplicaria manutenção por N e faria o consolidado depender de casar códigos por convenção.
NumberSequence configurável, com empresa como padrão. Grupo brasileiro quase sempre quer pedido numerado por empresa; parte quer por filial; quase ninguém quer número global. O escopo é dado, não código.
Fronteira
O que a plataforma já resolve
A camada gryd nunca entrou no mapa de domínio, e foi por isso que empresa quase foi modelada duas vezes. Validado contra Design/Tenants Handoff, Usuarios Handoff e Roles Handoff.
| Já existe | O que é | O que o Nexio faz com isso |
|---|---|---|
Tenant | A fronteira de isolamento. Type = Company | Group, ParentTenantId, hierarquia de um nível. | Um tenant = um cliente contratante. O Nexio não usa ParentTenantId no modo consolidado. |
User | Global — não pertence a tenant nenhum. | Intacto. Nenhum campo novo. |
UserTenant | A matriz de acesso: uma linha por organização, com vigência, isDefault e isActive. | intacto A preferência de estabelecimento padrão vive do lado do Nexio. |
UserRole | O papel dentro daquele tenant. Não existe papel global. | intacto O recorte por empresa é UserCompanyScope, do Nexio. |
Role · Permission | Catálogo por tenant. | Intactos. Os papéis são do tenant, não da empresa — o que varia é onde o papel vale. |
| Configuração de tenant | A plataforma não tem TenantSettings. O CNPJ único por tenant que o Nexio lia era do próprio Nexio. Mas tem Tenant.Settings, e a recusa precisa dizer isso (09/09/2026): um string? mapeado como documento semiestruturado, com UpdateSettings e leitura por JsonNode na mão — a única leitura tipada hoje é a política de MFA. O JSON genérico que a decisão recusa já está lá, a uma linha de distância, e alguém vai encontrá-lo. Recusá-lo é escolha: parâmetro do Nexio ali seria configuração sem esquema, sem validação de domínio e sem migration, num campo de outro módulo — e o primeiro a ler valida, ou não valida ninguém. | removido — o CNPJ vive em Company.cnpjRoot e Establishment.cnpj. Os parâmetros por tenant do Nexio (teto de profundidade das árvores, materialidade do rateio, expediente e lista de dias não úteis) ganham entidade própria — pendência registrada no mapa de domínio. Desde 08/09/2026 o calendário não pede entidade: virou campo do tenant no fluxo de aprovação, porque o SLA é o único consumidor de dia útil no produto. |
Duas palavras, dois sentidos — e um deles muda. A tela de Tenants rotula o tenant filho como "Empresa". A partir desta spec, "empresa" significa Company e nada mais: o tenant passa a ser chamado de organização em tela, spec e conversa. É troca de rótulo, não de modelo — a tela já se chama "Tenants · Organizações" e só precisa parar de se contradizer. O valor de enum TenantType.Company permanece: é membro de enum, sempre qualificado, e não colide com a classe Company do domínio.
Tenancy
Os dois modos
Modo isolado · N organizações, uma empresa cada
Cada CNPJ vira um tenant, opcionalmente sob um tenant Group. O isolamento é estrutural: o dado da empresa A não é filtrado do dado da empresa B, ele está em outra fronteira. É o modo para quem tem restrição de verdade — empresas concorrentes dentro do mesmo grupo, sociedade com terceiros, exigência de auditoria independente.
O que custa: catálogo, fornecedores homologados, estrutura de centro de custo, papéis e alçadas são recadastrados por empresa, e não sincronizam. Aprovação consolidada não existe. Relatório do grupo exige leitura cross-tenant.
Modo consolidado · uma organização, N empresas
O tenant é o grupo econômico. Cadastro mestre é único e compartilhado; documento, orçamento, conta contábil e alçada são por empresa. O isolamento entre empresas passa a ser escopo — companyId vindo do token, nunca do cliente.
O que ganha: matriz aprova pedido de filial com uma política, não com integração. Fornecedor é homologado uma vez. Consolidado do grupo é consulta comum.
O critério de qual vender
Não é tamanho do grupo nem número de CNPJs. É uma pergunta só: as empresas do grupo podem ver o cadastro mestre umas das outras? Catálogo, fornecedores, estrutura de centro de custo, tabela de preço. Sim — que é o caso da maioria dos grupos brasileiros, porque a estrutura é espelhada mesmo — consolidado. Não — tenants separados, e o cliente aceita duplicar cadastro.
A pergunta é sobre mestre, não sobre documento. Esconder as requisições da empresa B do usuário da empresa A o modo consolidado faz bem; o que ele não faz é esconder que a empresa B existe.
Na dúvida, consolidado
Um tenant consolidado consegue se comportar como isolado: escopa todo usuário a uma empresa, nunca cria registro corporativo, e na prática ninguém vê nada da outra. O inverso não existe sem cross-tenant. Migração entre os dois é dolorosa nos dois sentidos — juntar catálogos com códigos colidindo, ou partir documentos — então erra-se para o lado reversível por configuração.
O que não pode acontecer. O modo isolado não pode pular a Company e continuar lendo um CNPJ solto do tenant. Duas formas de descobrir de que CNPJ um documento é significa dois modelos, dois caminhos de código e dois conjuntos de bug. Company é obrigatória nos dois modos; no isolado ela simplesmente tem uma linha.
Modelo
Mapa de entidades
Convenção de nomes: o documento é escrito em português e todo identificador é em inglês — entidade, atributo, valor de enum, rota, permissão e código de erro. A exceção são os dados de negócio brasileiros, que ficam como são no mundo real: cnpj, stateRegistration, suframaCode, primaryCnae e os nomes de regime tributário (SimplesNacional, LucroReal) — esses são conteúdo, não código.
Group agrega organizações e nunca guarda dado.
userId do gryd sem FK. Nulo — e a ausência da linha — = todas.
legalEntityId vira companyId. A regra do nó corporativo já estava escrita e não muda.
Domínio · Company
Campos · Company
Legenda: obrig sempre · cond conforme a operação · opc opcional · gerado calculado pelo sistema ou pelo banco.
Identificação
| Campo | Tipo | Regra | |
|---|---|---|---|
| id | Guid | gerado | Atribuído pelo agregado, não pelo banco. |
| tenantId | Guid | gerado | Injetado a partir do token. Nunca aceito do cliente. |
| code | string(20) | obrig | Trim e maiúsculas, apenas [A-Za-z0-9_-], único por tenant, imutável. É o que a importação, a política de aprovação e a integração referenciam. |
| cnpjRoot | varchar(8) | obrig | A raiz do CNPJ. Nunca numérico — aceita A–Z e dígitos. Único por tenant, imutável. Duas empresas do mesmo tenant não compartilham raiz: se compartilhassem, seriam a mesma pessoa jurídica. |
| legalName | string(200) | obrig | Razão social. Trim; vazio é 400. |
| tradeName | string(160) | opc | Nome fantasia. É o que aparece no seletor de documento — quando ausente, cai para legalName. |
| externalCode | string(40) | opc | O código da mesma empresa no ERP do cliente. Único por tenant quando preenchido. |
Natureza e regime
| Campo | Tipo | Regra | |
|---|---|---|---|
| taxRegime | enum | obrig | SimplesNacional | MEI | LucroPresumido | LucroReal | TaxExempt | NotApplicable — o mesmo enum de Supplier.taxRegime, e não um paralelo: regime tributário é o mesmo conceito visto dos dois lados. Do lado comprador, NotApplicable é recusado. Vive na empresa, não na filial — o Simples é opção da pessoa jurídica inteira. É o campo que decide se a compra gera crédito. |
| legalNature | varchar(4) | opc | Código de natureza jurídica da tabela do IBGE/Receita (2062, 2054…). Metadado; nenhuma regra depende dele hoje. |
| shareCapital | decimal(18,2) | opc | Capital social. Informativo, e usado por alguns clientes como referência de porte na alçada. |
| functionalCurrency | char(3) | opc | ISO 4217, padrão BRL, e qualquer código ISO 4217 vale já na v1 — a spec de Moeda e Câmbio restringia a BRL e a restrição saiu em 09/09/2026, com o erro que a sustentava. A moeda em que a empresa fecha; compra em moeda estrangeira converte para ela. Duas consequências escritas na mesma data: o limite de alçada tem moeda própria (approvalLimitCurrency no Centro de Custo, obrigatória com o limite preenchido, nó corporativo incluído), e submeter documento cuja moeda não tenha par de conversão vigente contra a moeda do limite é recusa declarada — nunca comparação silenciosa entre moedas no ponto em que a alçada decide. O que continua fora é a conversão cruzada: par que não passe pelo real precisa de fonte própria cadastrada. Deixou de ser informativo: a spec de Orçamento copia este valor para Budget.currency na criação do exercício e o congela, e todo movimento do razão orçamentário guarda a taxa usada na conversão. |
| foundedOn | date | opc | Data de abertura. |
| closedOn | date | opc | Data de baixa. Revisto em 09/09/2026: é fato de calendário e bloqueia documento novo com data posterior, mas não mexe em isActive. Quem desativa é PATCH /deactivate, e só ele. Até 08/09 esta linha dizia “força isActive = false” — era a única exceção à convenção “estado só por rota própria”, e existia só porque era a única saída de um ciclo entre duas guardas que a rota de desativação passou a resolver. Preenchido em empresa ainda ativa, o PUT grava e a resposta avisa que falta desativar. |
Estado e derivados
| Campo | Tipo | Regra | |
|---|---|---|---|
| isActive | bool | gerado | Alterado só por /activate e /deactivate, nunca por PUT. Desde 09/09/2026 a desativação cascateia — os estabelecimentos ativos caem na mesma transação, e desativar com estabelecimento ativo deixou de ser 409. E isActive = false restringe escrita: o que a empresa emitiu continua legível para quem a alcança. Ver Escopo e segurança. |
| headquartersId | Guid | gerado | O estabelecimento matriz. Derivado, não digitado — é o único com type = Headquarters. Nunca nulo: a matriz nasce na mesma transação que a empresa (v1.4), então não existe empresa sem matriz nem por uma chamada. |
| establishmentCount | int | gerado | Exibido na lista. Ativos e inativos separados no detalhe. |
O que não está aqui, de propósito. Certidões, quadro societário, situação cadastral consultada na Receita, CNAE secundário e regime de retenção são do fornecedor, não da empresa compradora: o Nexio faz diligência de quem vende, não de si mesmo. Se um dia o produto virar portal e a empresa compradora precisar se apresentar a alguém, esses campos entram — e entram no mesmo formato que já existe em Supplier.
Domínio · Establishment
Campos · Establishment
A empresa é quem assina; o estabelecimento é quem recebe a nota. Tudo o que muda de filial para filial vive aqui.
Identificação
| Campo | Tipo | Regra | |
|---|---|---|---|
| id | Guid | gerado | — |
| tenantId | Guid | gerado | Do token. Denormalizado para caber no filtro de linha sem join. |
| companyId | Guid | obrig | Dono. Imutável — um estabelecimento não muda de pessoa jurídica; isso seria baixa e abertura. |
| code | string(20) | obrig | Único por empresa, imutável. Cliente vindo de Protheus digita 0001, 0002; cliente novo digita MATRIZ, CD-REC. As duas convenções cabem. |
| cnpj | varchar(14) | obrig | CNPJ completo, sem máscara. Único por tenant. Os 8 primeiros caracteres têm que ser o cnpjRoot da empresa — divergência é 422, não 400: o dado é bem formado e semanticamente impossível. |
type | enum | obrigatório | Headquarters ou Branch, coerente com indicador cadastral e evidência oficial; não inferir apenas da ordem 0001. Exatamente uma matriz cadastrada por Company. Principal operacional de fornecedor é outro conceito. |
| name | string(160) | obrig | Como a filial é chamada internamente. É o que aparece no seletor, sob o nome da empresa. |
| externalCode | string(40) | opc | O código da filial no ERP. Único por (tenant, empresa) quando preenchido. |
Fiscal
| Campo | Tipo | Regra | |
|---|---|---|---|
| icmsTaxpayerStatus | enum | obrig | Taxpayer · Exempt · NonTaxpayer. É o indIEDest da NF-e (1 · 2 · 9) visto do lado do destinatário. Decide se stateRegistration é exigida e como o fornecedor tributa a operação. |
| stateRegistration | varchar(20) | cond | Inscrição estadual. Obrigatória quando icmsTaxpayerStatus = Taxpayer; recusada nos outros dois. Formato varia por UF — validação de formato é por estado e fica na camada fiscal, não aqui. |
| municipalRegistration | varchar(20) | opc | Inscrição municipal. Necessária para tomar serviço com retenção de ISS. |
| suframaCode | varchar(9) | opc | Inscrição Suframa. Presente, muda a tributação de entrada — e é justamente o campo que some quando alguém modela filial como endereço. |
| primaryCnae | varchar(7) | opc | CNAE principal do estabelecimento. Pode divergir entre filiais da mesma empresa. |
| registrationStatus | enum | opc | RegistrationStatus = Null · Active · Suspended · Unfit · ClosedDown — os códigos 01 · 02 · 03 · 04 · 08 da Receita, o mesmo enum do SupplierEstablishment, alinhado em 04/09/2026 (o comprador e o vendedor são consultados na mesma fonte). Situação cadastral do estabelecimento: uma filial pode estar baixada com a matriz ativa, e o sistema precisa saber disso antes de emitir um pedido em nome dela. |
Endereço fiscal
| Campo | Tipo | Regra | |
|---|---|---|---|
| zipCode | varchar(8) | obrig | Sem máscara. |
| street · number · complement · district | string | cond | Logradouro e bairro obrigatórios; número aceita S/N. |
| cityIbgeCode | varchar(7) | obrig | Código do município, não o nome. É o que a NF-e carrega e o que resolve homônimo entre estados. O nome é derivado dele, nunca digitado livre. |
| stateCode | char(2) | gerado | Derivado do código IBGE. É a dimensão que muda o ICMS interestadual — por isso não pode ser digitada em separado e divergir do município. |
| countryCode | char(2) | opc | ISO 3166-1, padrão BR. Reservado; estabelecimento fora do Brasil está fora de escopo. |
Operação
| Campo | Tipo | Regra | |
|---|---|---|---|
| allowsPurchasing | bool | opc | Padrão true. Falso, o estabelecimento não aparece no seletor de documento — mas continua podendo ser destino de entrega. Escritório administrativo que não compra é caso real. |
| allowsReceiving | bool | opc | Padrão true. Falso, não pode ser destinatário de nota. |
| isActive | bool | gerado | Só por /activate e /deactivate. Desativar o último estabelecimento ativo de uma empresa ativa é 409: empresa sem estabelecimento não consegue emitir nada e vira um registro morto que ninguém percebe. |
Contrato vigente · revisão consolidada
Acesso e permissões por empresa
A permissão efetiva é a interseção de três condições: teto de permissões vigentes da plataforma, empresa visível por UserCompanyScope e operação concedida por UserCompanyRole. Ausência de concessão significa acesso negado. Um papel atribuído no tenant não concede automaticamente todas as suas operações em todas as empresas.
| Entidade | Campos mínimos | Regra |
|---|---|---|
| UserCompanyScope | id, tenantId, userId, companyId?, allCompanies, validFrom, validTo, isActive | Visibilidade. companyId nulo exige allCompanies=true escolhido explicitamente. Sem vínculo vigente, conjunto vazio. FK (tenantId, companyId) para Company; userId validado no diretório da plataforma. |
| UserCompanyRole | id, tenantId, userId, companyId?, allCompanies, roleId, validFrom, validTo, isActive | Concessão local da operação. roleId referencia definição de papel da plataforma; não duplica o catálogo de Permission. companyId nulo exige allCompanies=true explícito. Um papel local jamais excede as permissões vigentes da plataforma. |
| Autorização efetiva | tenantId, userId, companyId, operation, authzVersion | Conjunto resolvido no servidor por requisição. Consulta, download, aprovação, delegação, relatórios e workers aplicam o mesmo contrato. Revogação invalida uso futuro, preservando autoria histórica. |
Unicidade de vínculos vigentes por usuário/empresa e por usuário/empresa/papel, respectivamente, com índices próprios para concessão global e prevenção de sobreposição de vigência. Não há coluna roleId reservada em UserCompanyScope: a concessão é entidade própria e necessária desde a v1.
| Situação | Resultado |
|---|---|
| Nenhum vínculo vigente | Nenhuma empresa ou operação permitida. |
| Visibilidade global explícita, sem papel local | Pode alcançar a empresa apenas nas operações locais que também forem concedidas; visibilidade isolada não autoriza ler documentos. |
| Ana: Aprovar na Fábrica; Solicitar na Distribuidora | Pode aprovar na Fábrica se também tiver alçada; na Distribuidora só solicita. O token conter approve não amplia a concessão local. |
| Empresa inativa | Histórico continua legível com autorização. Novos documentos são impedidos; fatos ocorridos e encerramento de obrigações vigentes seguem operação específica autorizada. |
| Vínculo vencido ou revogado | Sai imediatamente da próxima avaliação no servidor. Não vira acesso global. |
O cliente só restringe o conjunto resolvido: filtros de empresa nunca o substituem. IDs fora de escopo retornam 404; falta de permissão sobre operação de objeto já acessível retorna 403. Recorte de valores financeiros aplica ainda read:budget-amounts. Preferência nexio.default-establishment-id na UserPreference da plataforma é validada contra concessões atuais antes de preencher o formulário.
Serviços públicos de consulta e escrita recebem contexto empresarial explícito. Identificadores externos, cache e chaves de idempotência incluem tenant e empresa quando aplicável. FK composta impede referências de outra organização; relatórios autorizados filtram todas as fontes, inclusive anexos e projeções.
RLS é defesa adicional a especificar/testar onde adotada; os pacotes atuais não demonstram isolamento empresarial completo. Com conexão compartilhada, SET LOCAL deve ser instalado e limpo por transação, inclusive em worker. Credencial de serviço declara empresas e operações; execução em background não implica acesso irrestrito.
Na criação da primeira empresa, provisionar explicitamente visibilidade e papéis locais do administrador responsável, limitados pelo teto da plataforma e auditados. Criar a segunda empresa não altera concessões existentes. Administração global exige concessão global explícita e não confere alçada financeira. Desativação de empresa e estabelecimentos é atômica; reativar empresa não reativa filiais automaticamente.
Domínio · GLAccount
Plano de contas
O eixo que apareceu como pendência explícita na spec de Item ("mapeamento categoria → conta contábil … provavelmente deveria estar na próxima") e como fatia do rateio na de Centro de Custo. Fecha aqui.
A terceira árvore — e a mesma mecânica
Centro de custo e categoria já são árvores com caminho materializado, código imutável e ancestrais derivados sem recursão. Plano de contas é a terceira, e reusa a mecânica inteira: parentId, path com separador nas duas pontas, depth, duas portas de status com cascata assimétrica, path como guarda de move e version int + expectedVersion como token de concorrência de toda escrita — o mesmo token de Item, Fornecedor e, desde 03/09/2026, do Centro de Custo. Inventar uma quarta forma de fazer árvore neste produto seria erro por escolha, não por descuido.
| Campo | Tipo | Regra | |
|---|---|---|---|
| code | string(30) | obrig | Único por tenant, imutável. Contas contábeis brasileiras já são hierárquicas no próprio código (3.1.01.001) — o produto não deriva o pai do código: quem manda é parentId, e o código é rótulo. |
| name | string(160) | obrig | Único entre irmãos. |
| parentId | Guid? | opc | Nulo = raiz. Ciclo é recusado. Movimentação só por PATCH /{id}/parent. |
| path · depth | varchar · int | gerado | Idênticos aos do centro de custo, inclusive o escape do separador. |
| accountType | enum | obrig | Asset · Liability · Equity · Revenue · Expense. Herdado do pai por padrão; divergir do pai é 422. |
| version | int | gerado | Novo em v1.4. Token de concorrência de toda escrita, como no Centro de Custo: expectedVersion no PUT, no PATCH /parent e na habilitação por empresa; divergência é 409 GL_ACCOUNT_CONCURRENT_CHANGE_CONFLICT. path fica como guarda de move. |
| allowsPosting | bool | opc | Verdadeiro em folha, falso em nó com filhos — a mesma regra do centro de custo. Nó intermediário existe para consolidar, não para receber lançamento. |
| validFrom · validTo | date? | opc | Plano de contas muda de exercício para exercício. Conta fora de vigência não é oferecida em documento novo e continua legível em documento antigo. |
| isActive | bool | gerado | Duas portas, cascata assimétrica. |
| Campo | Tipo | Regra | |
|---|---|---|---|
| glAccountId · companyId | Guid | obrig | Chave composta única. |
| isEnabled | bool | obrig | Conta habilitada naquela empresa. Ausência de linha = habilitada — o padrão é o plano inteiro valer para todas, e a linha existe para restringir ou para carregar o código externo. O ponto de aplicação está escrito desde 09/09/2026 e é um só: a escolha da conta na fatia de rateio, nos três degraus da cascata, no lookup e de novo na submissão. Até 08/09 nenhuma das superfícies onde a conta é escolhida conhecia a empresa, e metade da razão de existir desta tabela — restringir — não era executável. |
| externalCode | string(40) | opc | O código da mesma conta no ERP daquela empresa. É a razão de existir desta tabela: plano unificado no Nexio, códigos diferentes lá fora. |
De onde vem a conta da linha
Sugerida em cascata, escolhida por gente
A conta da fatia de rateio é sugerida por, nesta ordem: 1 Item.defaultGlAccountId, 2 CategoryNode.defaultGlAccountId do nó do item, subindo a árvore de categoria até achar, 3 vazio — e vazio significa o usuário escolhe: a fatia aceita conta nula só em rascunho, e a submissão do documento a exige (422 ALLOCATION_GL_ACCOUNT_REQUIRED_UNPROCESSABLE, RN-CC-29 do Centro de Custo). Sempre editável por quem tem permissão, e sempre com a origem visível na interface — "sugerida pela categoria Materiais de escritório" é a diferença entre um campo que o usuário confere e um campo que ele ignora.
E a cascata é filtrada pela empresa, desde 09/09/2026. Nos três degraus, conta com GLAccountCompany.isEnabled = false para aquela empresa não é sugerida; escolhida à mão, é recusada com 422 GL_ACCOUNT_NOT_ENABLED_FOR_COMPANY_UNPROCESSABLE — na fatia e outra vez na submissão, pela mesma razão de a conta obrigatória ser conferida duas vezes. O GET /gl-accounts/lookup passou a exigir companyId na query: lookup que não sabe a empresa devolve conta que o documento vai recusar, e devolver opção inválida é pior do que não devolver nada. A empresa lida é a da fatia, não a do documento — mesma regra do centro de custo, ver Compra centralizada.
Nenhuma das duas árvores manda na outra
Centro de custo responde quem paga; conta contábil responde que natureza é o gasto. A linha carrega as duas, e derivar uma da outra é o erro que transforma relatório contábil em relatório gerencial errado. Os dois eixos se cruzam na fatia de rateio e em nenhum outro lugar.
O Nexio classifica, não escritura
A conta existe para que o documento saia classificado e a integração com o ERP não precise adivinhar. Partida dobrada, encerramento, razão e balancete continuam do outro lado. Essa fronteira precisa estar escrita porque é a primeira coisa que um controller pede na demo.
Transversal
Numeração
| Campo | Tipo | Regra | |
|---|---|---|---|
| documentType | enum | obrig | Requisition · QuotationRequest · PurchaseOrder · Receipt · BudgetChangeRequest. Cresce com a espinha transacional. O BudgetChangeRequest entrou em 08/09/2026: o Orçamento já numerava o pedido de verba por aqui, com escopo Company, e o enum não o listava — sequência que uma spec usa e a outra não declara é numeração que ninguém consegue configurar na tela. Contract continua fora, e de propósito: nenhuma página o numera. |
| scope | enum | obrig | Tenant · Company · Establishment. Padrão Company. Entra na chave de unicidade junto com os dois campos abaixo, que são materializados. |
| companyId · establishmentId | Guid? | cond | Exigidos conforme o scope; preenchidos fora dele é 422. Materializados, nunca nulos (09/09/2026): fora do escopo eles recebem valor sentinela — o Guid zero —, não nulo. O requisito é de unicidade: não pode existir mais de uma sequência para a mesma combinação (tenant, documentType, scope, companyId, establishmentId), e duas sequências de mesmo escopo têm de colidir de fato. Com nulo elas não colidem em nenhum banco que trate nulo como valor desconhecido — esta mesma página já ensina isso ao explicar por que o UserCompanyScope precisa de dois índices parciais. Materializar resolve com um índice único só e sem depender de recurso do banco; onde houver equivalente (no Postgres, UNIQUE NULLS NOT DISTINCT, da versão 15 em diante), ele serve igual. Duas sequências para o mesmo documento é número repetido — e número emitido não se renumera. |
| prefix | string(10) | opc | Texto fixo antes do número. Cliente que quer PC- ou a sigla da filial resolve aqui. |
| padding | int | opc | Zeros à esquerda. Padrão 6. |
| resetPolicy | enum | opc | Never · Yearly · Monthly. Padrão Yearly, que é o hábito do mercado. Não vale sozinha: quem zera precisa saber em que período está — ver NumberSequenceCounter.periodKey, abaixo. |
NumberSequenceCounter.periodKey | varchar(7) | gerado | Contador separado por sequência e período: chave única (tenantId, sequenceId, periodKey). Incrementar atomicamente; emitir retroativo não zera contador de período já usado. Never usa sentinela explícita. |
| nextValue | bigint | gerado | Incrementado por UPDATE … RETURNING atômico no banco, nunca por leitura seguida de escrita na aplicação. Dois POST simultâneos com o mesmo número é o defeito clássico deste componente. |
O número composto carrega o período
Novo em 09/09/2026. Com resetPolicy diferente de Never, o período entra no número, entre o prefixo e o contador: PC-2026-000001, PC-2026-03-000001. Não é enfeite — é o que faz o número ser único. Zerar o contador em janeiro sem carregar o ano faz o PC-000001 de 2027 ser idêntico ao de 2026; o requisito de unicidade da sequência garante que existe uma sequência, e quem garante que ela não repete um número já emitido é o par período + contador. Com Never o número não ganha o segmento de período, e não precisa. Número emitido é imutável: trocar resetPolicy depois vale do próximo número em diante e não reescreve nada.
O número pode ter buracos, e isso não é defeito. Transação abortada consome o número e não o devolve. Prometer sequência sem furo obrigaria a serializar a emissão do documento inteiro, e o cliente que precisa de sequência contínua está pensando em NF-e — que o Nexio não emite: quem emite é o fornecedor. Está escrito aqui para não virar chamado.
Contrato vigente · revisão consolidada
Compra centralizada com destinatário fiscal definido
A central pode consolidar demandas e negociar uma cotação comum. A adjudicação gera pedidos separados por destinatário fiscal (establishmentId), cada um com empresa, moeda, numeração, aprovação e orçamento próprios. O usuário comprador precisa de concessão para a operação em cada empresa envolvida.
| Campo | Contrato |
|---|---|
| establishmentId | Destinatário fiscal do pedido; define a Company e sua política. |
| requestingEstablishmentId | Origem da demanda, quando diferente. Referenciar outra empresa exige concessões nas duas; não transfere o custo. |
| companyId | Derivada do destinatário fiscal e congelada. Toda CostAllocation pertence a esta mesma empresa. |
| sourcingGroupId / sourceLineRefs | Liga cotação e adjudicação comuns aos pedidos separados, preservando quantidades e parcelas por demandante. |
Se A compra e recebe para posterior transferência a B, o pedido e seu consumo pertencem a A; transferência física, fiscal e intercompany é processo do ERP, com referências vinculadas. Rateio dentro do Nexio não substitui esse processo nem lança automaticamente na verba de B. Matriz e filial da mesma pessoa jurídica podem compartilhar Company, mas mantêm destinatários fiscais distintos.
A política de aprovação segue a empresa do pedido; a lotação do comprador não muda a autorização necessária. O Nexio emite o pedido oficial e alterações; o ERP registra seu recebimento e aplicação. Ver Contratos transacionais.
Contexto
Gancho fiscal
Duas mudanças de 2026 que tornam o estabelecimento obrigatório, e não uma conveniência de modelagem.
CNPJ alfanumérico — em produção desde 31/07/2026
Coluna varchar, nunca numérica, dos dois lados do produto. Os 12 primeiros caracteres aceitam A–Z; os 2 dígitos verificadores continuam numéricos e o cálculo usa ASCII − 48. Já era regra em Supplier; entra idêntica em Company e Establishment.
IBS e CBS obrigatórios na NF-e desde 03/08/2026
Os campos de IBS e CBS entraram no leiaute do documento fiscal eletrônico, com a alíquota de teste de 1% (0,1% IBS e 0,9% CBS) durante 2026. O Nexio não emite NF-e — mas recebe, e o módulo de nota e o three-way match nascem tendo que saber lê-los. Corrigido em 08/09/2026: até esta versão a frase dizia que nota sem esses campos não é autorizada, e codificada assim ela faria o Nexio recusar nota válida — durante 2026 as notas chegam com IBS/CBS incompletos ou ausentes e ainda assim autorizadas, porque o Ato Técnico Conjunto RFB/CGIBS nº 1/2026 adiou as validações, não a obrigação. A regra do produto é a que o Cadastro de Item já escrevia, e é ela que vale nas três páginas que tocam no assunto: os campos existem no leiaute, o preenchimento só é exigido a partir da data que a norma fixar, e o parser não trata ausência como erro.
Para esta spec a consequência é uma só, e é a razão de o estabelecimento existir: os campos que decidem tributação e crédito estão espalhados nos dois níveis — regime na empresa, contribuinte de ICMS, Suframa e município IBGE no estabelecimento. Modelar filial como endereço perde metade deles.
O que continua fora
Parametrização fiscal completa — CFOP, CST, substituição tributária, benefícios estaduais, regra de crédito por operação — é spec própria, junto com a nota fiscal. Aqui nascem apenas os portadores de identidade fiscal, e eles nascem certos.
Integração
Relação com os demais domínios
| Domínio | Mudança | Detalhe |
|---|---|---|
| Centro de Custo | feito | legalEntityId → companyId no nó e em legalEntityOf(nodeId) → companyOf(nodeId). A regra — nó corporativo, nó de empresa, filho não muda de empresa, linha da empresa A só aponta para corporativo ou A — já estava escrita e não muda uma vírgula. Rename de coluna sem dado para migrar, aplicado em 01/09/2026. A pendência que o Centro de Custo deixou registrada — apontar para a empresa ou para o estabelecimento — foi resolvida no mesmo dia: aponta para a empresa; apontar para o estabelecimento multiplicaria a árvore por CNPJ, que é o que o nó corporativo existe para evitar. |
| Fluxo de Aprovação | feito | O critério "Empresa em lista" lê companyId do documento, derivado do estabelecimento. A política não ganha coluna de empresa: o critério já faz o recorte, e uma Base do grupo convive com uma Supplemental de uma empresa só. Novo critério irmão por establishmentId, e um nível que resolve para aprovador sem escopo na empresa do documento passa a cair no fallback. |
| Cadastro de Item | feito | Item.defaultGlAccountId e CategoryNode.defaultGlAccountId, ambos opcionais, aplicados em 01/09/2026. Encerra as duas pendências que a spec carregava: o eixo contábil ausente e o multi-empresa. Nenhuma mudança de dimensionalidade: item continua sem empresa. |
| Fornecedor | nenhuma | Fornecedor é do tenant. Fica declarado o gancho SupplierQualification.companyId anulável — homologar um fornecedor só para uma empresa do grupo é pedido plausível, e o campo tem o lugar reservado sem ser especificado agora. |
| Plataforma Gryd | Identidade e teto de permissões | Nexio consome diretório e definições Role → Permission para resolver concessão empresarial, além das permissões vigentes do usuário. Essa integração é necessária na v1, não campo reservado. Contrato exige versão, revogação e validação de disponibilidade no pacote; Company permanece entidade do Nexio. |
| Unidade de Medida | nenhuma | Escopo global ou de tenant, como está. |
| Poda do webapp | acrescenta | Empresa e estabelecimento entram na lista do que o admin precisa ter na primeira versão — sem eles, o cadastro de centro de custo por empresa não tem o que oferecer no seletor. |
Camada API
Endpoints
Prefixo api/v{version}/nexio. Todas exigem autenticação e declaram exatamente uma permissão. Toda listagem já vem recortada pelo escopo do usuário — não há parâmetro para pedir fora dele.
| Rota | Permissão | Retorno | Erros |
|---|---|---|---|
| GET /companies | read:companies | PagedResult<CompanyDto> | 400 |
| GET /companies/lookup | read:companies | array puro | 400 |
| GET /companies/{id} | read:companies | CompanyDetailDto | 404 |
| POST /companies | create:companies | CompanyDetailDto. O corpo traz a matriz inline — headquarters com o CNPJ completo e os campos do estabelecimento — e empresa e Establishment Headquarters nascem na mesma transação. Sem headquarters é 400. É também a tela do onboarding de um tenant novo | 400 · 409 · 422 |
| PUT /companies/{id} | update:companies | CompanyDetailDto | 400 · 404 · 409 |
| PATCH /companies/{id}/activate | update:companies | CompanyDetailDto | 404 · 409 |
| PATCH /companies/{id}/deactivate | update:companies | CompanyDetailDto. Cascateia desde 09/09/2026: desativa os estabelecimentos ativos da empresa na mesma transação, um evento por estabelecimento, e devolve a lista do que caiu junto | 404 |
| Rota | Permissão | Retorno | Erros |
|---|---|---|---|
| GET /companies/{id}/establishments | read:companies | array puro | 404 |
| GET /establishments/lookup | read:companies | agrupado por empresa | 400 |
| GET /establishments/{id} | read:companies | EstablishmentDetailDto | 404 |
| POST /companies/{id}/establishments | create:companies | EstablishmentDetailDto | 400 · 404 · 409 · 422 |
| PUT /establishments/{id} | update:companies | EstablishmentDetailDto. Desde 09/09/2026, alterar município, UF ou allowsReceiving revalida os pontos de entrega que herdam o endereço deste estabelecimento, na mesma transação: a edição que deixaria algum deles interestadual sem motivo é recusada com a lista nomeada. Ver Local de Entrega | 400 · 404 · 409 · 422 |
| PATCH /establishments/{id}/activate | update:companies | EstablishmentDetailDto | 404 · 409 |
| PATCH /establishments/{id}/deactivate | update:companies | EstablishmentDetailDto | 404 · 409 |
GET /establishments/lookup é o endpoint que a interface inteira consome. Devolve só o que aquele usuário pode usar, já agrupado por empresa, já filtrado por isActive e allowsPurchasing, com defaultEstablishmentId marcado. Um item na resposta significa que o formulário não mostra o campo — a decisão de exibir o seletor é derivada do tamanho desta lista, nunca de uma configuração que alguém precisa lembrar de ligar.
| Rota | Permissão | Retorno | Erros |
|---|---|---|---|
| GET /gl-accounts | read:gl-accounts | PagedResult<GLAccountDto> | 400 |
| GET /gl-accounts/lookup | read:gl-accounts | companyId obrigatório na query desde 09/09/2026. Devolve só as contas com allowsPosting, vigentes e habilitadas naquela empresa — linha ausente em GLAccountCompany ou isEnabled = true | 400 · 404 |
| GET /gl-accounts/{id}/children | read:gl-accounts | PagedResult<GLAccountDto> | 400 · 404 |
| POST /gl-accounts | create:gl-accounts | GLAccountDetailDto | 400 · 409 · 422 |
| PUT /gl-accounts/{id} | update:gl-accounts | GLAccountDetailDto | 400 · 404 · 409 |
| PATCH /gl-accounts/{id}/parent | update:gl-accounts | GLAccountDetailDto | 404 · 409 · 422 |
| PUT /gl-accounts/{id}/companies/{companyId} | update:gl-accounts | habilitação e código externo | 404 · 422 |
| GET /number-sequences | manage:number-sequences | array puro | 400 |
| POST /number-sequences | manage:number-sequences | NumberSequenceDto. Novo em 09/09/2026. Sem ele, trocar o scope de Company para Establishment — que esta página declara permitido — exigia N linhas que nenhuma rota criava. Recusa a combinação já existente com 409 | 400 · 409 · 422 |
| PUT /number-sequences/{id} | manage:number-sequences | NumberSequenceDto. documentType, scope, companyId e establishmentId são a identidade da sequência e não mudam aqui — troca de escopo é sequência nova | 404 · 409 · 422 |
| Rota | Permissão | Retorno | Erros |
|---|---|---|---|
| GET /users/{userId}/company-scopes | read:company-scopes | array puro | 400 · 404 |
| PUT /users/{userId}/company-scopes | manage:company-scopes | substitui o conjunto | 400 · 404 · 409 · 422 |
| GET /me/company-scope | autenticado | o conjunto resolvido do usuário atual | — |
PUT substitui o conjunto inteiro, não faz merge. É a mesma semântica que a tela de Papéis do gryd já usa — "salvar substitui permissões" — e é o que torna a tela de escopo uma lista de checkboxes honesta em vez de um diff que ninguém consegue conferir. GET /me/company-scope devolve o conjunto já resolvido, com a razão (Explicito ou SemRestricao), porque a interface precisa saber a diferença entre "você tem uma empresa" e "você tem uma empresa por enquanto".
Contrato
Contrato de erros
O status HTTP é derivado do sufixo do código, nunca da mensagem: o mapeador do Core reconhece nove sufixos — _NOT_FOUND → 404 · _ALREADY_EXISTS, _ALREADY_ASSIGNED e _CONFLICT → 409 · _FORBIDDEN, _ACCESS_DENIED e _BLOCKED → 403 · _UNAUTHORIZED → 401 · _UNPROCESSABLE → 422 · sem sufixo → 400. _UNAVAILABLE → 503 seria o décimo: é extensão pedida ao Core pelo épico do GrydFiles, ainda não vigente — conferido no código da plataforma em 08/09/2026.
| Código | HTTP | Quando |
|---|---|---|
| COMPANY_NOT_FOUND | 404 | Não existe neste tenant — ou não está no escopo do usuário. Os dois casos respondem igual, de propósito |
| COMPANY_CODE_ALREADY_EXISTS | 409 | Código já usado no tenant. A mensagem carrega o código |
| COMPANY_CNPJ_ROOT_ALREADY_EXISTS | 409 | Já existe empresa com essa raiz. Duas empresas com a mesma raiz seriam a mesma pessoa jurídica |
| COMPANY_INACTIVE_UNPROCESSABLE | 422 | Novo em 09/09/2026. Criação ou escrita de documento em estabelecimento de empresa inativa. Leitura nunca devolve este código. Entra no lugar de COMPANY_HAS_ACTIVE_ESTABLISHMENTS_CONFLICT, que exigia zerar os estabelecimentos antes de desativar — passo que a cascata da rota de desativação tornou desnecessário e que, contra a guarda do último estabelecimento ativo, era impossível de cumprir |
| COMPANY_HAS_DOCUMENTS_CONFLICT | 409 | Exclusão com documento emitido. Empresa com histórico se desativa, não se apaga |
| ESTABLISHMENT_NOT_FOUND | 404 | Idem — fora de escopo responde como inexistente |
| ESTABLISHMENT_CNPJ_ALREADY_EXISTS | 409 | CNPJ de 14 repetido no tenant |
| ESTABLISHMENT_CODE_ALREADY_EXISTS | 409 | Código repetido dentro da mesma empresa |
| ESTABLISHMENT_CNPJ_ROOT_MISMATCH_UNPROCESSABLE | 422 | O CNPJ não pertence à raiz da empresa. O dado é bem formado e semanticamente impossível — por isso 422 e não 400 |
| ESTABLISHMENT_HEADQUARTERS_ALREADY_EXISTS | 409 | Segunda matriz na mesma empresa |
| ESTABLISHMENT_HEADQUARTERS_REQUIRED_UNPROCESSABLE | 422 | Tentativa de deixar a empresa sem matriz |
| ESTABLISHMENT_LAST_ACTIVE_CONFLICT | 409 | Desativação do último estabelecimento ativo de uma empresa ativa, pela rota do estabelecimento. Não se aplica à cascata de /companies/{id}/deactivate, que desativa as duas coisas na mesma transação |
| ESTABLISHMENT_STATE_REGISTRATION_REQUIRED_UNPROCESSABLE | 422 | icmsTaxpayerStatus = Taxpayer sem inscrição estadual |
| GL_ACCOUNT_NOT_FOUND | 404 | — |
| GL_ACCOUNT_CODE_ALREADY_EXISTS | 409 | — |
| GL_ACCOUNT_POSTING_NOT_ALLOWED_UNPROCESSABLE | 422 | Fatia de rateio apontando para conta que não aceita lançamento |
| GL_ACCOUNT_NOT_ENABLED_FOR_COMPANY_UNPROCESSABLE | 422 | Novo em 09/09/2026. Fatia de rateio apontando para conta desabilitada em GLAccountCompany na empresa da fatia. Verificado na fatia e na submissão; a mensagem carrega o código da conta e o da empresa |
| GL_ACCOUNT_TYPE_MISMATCH_UNPROCESSABLE | 422 | accountType divergente do pai |
| GL_ACCOUNT_CONCURRENT_CHANGE_CONFLICT | 409 | expectedVersion divergente — ou, num move, o path mudou desde a leitura |
| USER_COMPANY_SCOPE_NOT_FOUND | 404 | Linha inexistente, ou de um tenant que não é o do token |
| USER_COMPANY_SCOPE_DUPLICATE_CONFLICT | 409 | Duas linhas para a mesma pessoa e empresa, ou dois "todas as empresas" |
USER_COMPANY_ROLE_INVALID_UNPROCESSABLE | 422 | Papel/concessão inexistente, fora do tenant ou superior ao teto delegável. UserCompanyRole é necessário desde a v1. |
| USER_NOT_IN_TENANT_UNPROCESSABLE | 422 | O userId não tem vínculo ativo com esta organização no GrydAuth. Verificado só na escrita |
| NUMBER_SEQUENCE_SCOPE_UNPROCESSABLE | 422 | companyId ou establishmentId incoerente com o scope declarado |
| NUMBER_SEQUENCE_ALREADY_EXISTS | 409 | Novo em 09/09/2026. Já existe sequência para a mesma combinação (documentType, scope, companyId, establishmentId). É a guarda de aplicação sobre o mesmo requisito de unicidade que o índice garante — as duas existem, e a de banco é a que decide |
Autorização
Permissões
| Permissão | Alcance | Observação |
|---|---|---|
| read:companies | Ler empresas e estabelecimentos | Recortada pelo escopo do usuário. Quase todo papel operacional precisa dela para o seletor funcionar |
| create:companies | Criar empresa e estabelecimento | Administrativa. Criar empresa muda a forma da interface para todo mundo do tenant |
| update:companies | Editar, ativar e desativar | Administrativa |
| read:gl-accounts | Ler o plano de contas | Necessária para preencher a fatia de rateio |
| create:gl-accounts | Criar conta | Controladoria |
| update:gl-accounts | Editar, mover, habilitar por empresa | Controladoria |
| read:company-scopes | Ler o escopo por empresa dos usuários | Administrativa. Não é necessária para operar — GET /me/company-scope não exige permissão |
| manage:company-scopes | Definir quais empresas cada pessoa alcança | Administrativa e sensível: é o que separa as empresas do grupo umas das outras |
| manage:number-sequences | Criar e configurar numeração | Administrativa. Mudar o escopo de numeração com documento emitido é permitido — cria-se a sequência nova, e nada é renumerado |
Escopo não é permissão — é o alcance dela. A permissão, que vem do gryd, diz o que a pessoa pode fazer; o UserCompanyScope, que é do Nexio, diz onde. Confundir os dois leva a criar read:companies:alfa no catálogo do tenant, que é a forma mais rápida de tornar um catálogo de papéis impossível de manter — e a razão de o recorte por empresa não ter virado permissão.
Invariantes
Regras de negócio
| Regra | Onde é garantida |
|---|---|
| Tenant sem empresa ativa está em onboarding | Estado explícito, não seed: a plataforma não conhece Company e não chama o Nexio ao criar tenant. Nesse estado nenhum documento é emitido e a única tela disponível é o cadastro da primeira empresa, com o CNPJ pedido ali — mesmo precedente da união vazia de escopos: o usuário não opera, e a interface diz por quê |
| Toda empresa tem exatamente uma matriz | No máximo uma: índice único parcial em (companyId) filtrado por type = Headquarters. Pelo menos uma: POST /companies exige a matriz inline e grava as duas na mesma transação; ESTABLISHMENT_HEADQUARTERS_REQUIRED_UNPROCESSABLE protege a saída |
| Todo estabelecimento carrega a raiz da sua empresa | Validação no agregado, 422. Coluna gerada left(cnpj, 8) com check contra cnpjRoot |
| Empresa ativa não fica sem estabelecimento ativo | Na criação, pela matriz inline; depois, verificação na desativação do último estabelecimento ativo, 409. A desativação da empresa não passa por essa guarda (09/09/2026): ela cascateia, e empresa inativa sem estabelecimento ativo é o estado correto |
| Empresa inativa restringe escrita, nunca leitura | Duas metades: a escrita verifica isActive e recusa com 422 COMPANY_INACTIVE_UNPROCESSABLE; a resolução do UserCompanyScope não lê isActive. Nenhuma leitura de histórico depende do estado da empresa |
| A fatia de rateio carrega a empresa | Coluna companyId na fatia, gravada na criação e congelada com a linha. É ela que a invariante do centro de custo e a resolução do Budget leem; na v1 recebe sempre a empresa do documento |
cnpjRoot, code e companyId são imutáveis | Ignorados no PUT. Não é erro pedir para mudar — é campo que não existe na atualização |
companyId nunca vem do cliente | Injetado do claim em toda escrita, como tenantId |
| Documento aponta para estabelecimento e congela a empresa | Regra da linha do documento. Alteração de cadastro não reescreve documento emitido |
| A política de aprovação segue o estabelecimento do documento | Critério Empresa em lista do Fluxo de Aprovação, avaliado sobre o companyId do documento. Quem opera a compra é usuário; não existe eixo de política pelo lado do comprador |
| Linha da empresa A só usa centro de custo corporativo ou de A | Já especificado no Centro de Custo, validado na linha com o companyId efetivo do nó |
Conta sem allowsPosting não recebe rateio | Validação na fatia, 422 |
| Fatia sem conta não submete | Validação na submissão do documento, 422 ALLOCATION_GL_ACCOUNT_REQUIRED_UNPROCESSABLE; nulo só em rascunho (RN-CC-29) |
UserCompanyScope só estreita, nunca concede | Desenho. A tabela não dá organização, papel nem permissão — o token do GrydAuth continua decidindo isso. Linha órfã é inerte |
| Ausência de concessão vigente = acesso negado | Resolvedor por empresa/operação, com allCompanies explícito para concessão global. |
| Nenhuma tabela do gryd é alterada | Fronteira de camada. A plataforma não conhece Company, e não deve conhecer |
| Mudar município ou UF do estabelecimento revalida quem herda | Ponto de entrega sem endereço próprio copia o stateCode daqui. O PUT revalida a regra interestadual e o allowsReceiving nesses pontos na mesma transação, recusa a edição que os deixaria interestaduais sem motivo e emite um evento por ponto recalculado. Escrita no Local de Entrega, aplicada aqui |
CNPJ é varchar nos dois lados do produto | Esquema. Validação de DV com ASCII − 48 |
Implantação
Migração
Sem cliente em produção e com o único módulo desenvolvido — centro de custo — autorizado a ser recriado. Esta é a janela em que a mudança custa quase nada.
1 · Onboarding no lugar do seed
Até a v1.3 esta seção mandava semear uma Company por tenant a partir de TenantSettings.cnpj. Não há como: a plataforma não conhece Company, nada no Gryd.IO chama o Nexio ao criar um tenant, e TenantSettings não existe nela. O tenant novo nasce sem empresa e isso é um estado explícito de onboarding — não emite documento, e a única tela disponível é o cadastro da primeira empresa, que pede o CNPJ ali e cria Company e Establishment Headquarters numa chamada só (POST /companies com a matriz inline). Não há tenant em produção: para os que existirem antes deste release, o mesmo onboarding serve, sem migrar dado.
2 · O CNPJ solto do tenant sai no mesmo release
O que o Nexio tinha de CNPJ único por tenant — do lado do Nexio, nunca da plataforma — sai junto com a entrada de Company. Manter os dois por um ciclo cria a segunda fonte de verdade que a Decisão 2 existe para impedir. Sai junto ou não sai.
3 · Renomes sem dado
cost_centers.legal_entity_id → company_id. Nenhuma tabela do gryd é tocada. Nasce uma tabela no esquema do Nexio, user_company_scopes, vazia — e vazia significa "todo mundo enxerga todas as empresas", que é exatamente o comportamento de hoje. ApprovalPolicy também não ganha coluna: o critério resolve. Todos anuláveis e todos vazios: nenhum backfill, nenhuma janela.
4 · Tenants existentes não mudam de comportamento
Com uma empresa por tenant, o seletor não aparece, a numeração continua única e o escopo não recorta nada. A migração é invisível para quem tem um CNPJ só — que é o teste de que o modelo está certo.
Validação
Referência de mercado
| Nexio | Oracle Fusion | SAP | Coupa | Protheus |
|---|---|---|---|---|
| Tenant | Enterprise | Client | Instance | Ambiente |
Company | Legal Entity | Company Code | Legal Entity | Empresa |
Establishment | Não como site — o site não tem identificação fiscal própria. O que existe é a Legal Reporting Unit, subdivisão da entidade legal que carrega registros fiscais; usá-la por filial resolve o CNPJ e cobra o preço de modelar cada filial na estrutura legal | Business Place · Local de Negócio, construto criado para o Brasil | não existe | Filial |
GLAccount | Natural Account | GL Account | Account | Plano de contas |
NumberSequence | Document Sequence | Number Range | — | Numeração por filial |
A linha que importa é a do estabelecimento. Oracle e Coupa não têm o conceito: para eles o site é uma unidade operacional sem CNPJ, e o identificador fiscal mora na entidade legal. Quem implanta esses produtos no Brasil resolve com campo livre, código de site colado no CNPJ ou uma entidade legal por filial — as três saídas ruins. A SAP precisou inventar o Business Place especificamente para a legislação brasileira. O Nexio nasce com o construto certo porque nasce aqui — e é exatamente a mesma vantagem que a spec de Fornecedor identificou ao derivar grupo econômico da raiz de 8.
Limites
Fora de escopo
| O que | Por quê |
|---|---|
| Escrituração contábil | O Nexio classifica o gasto; partida dobrada, razão e balancete continuam no ERP |
| Transferência intercompany | O documento registra quem comprou e quem pediu; o acerto entre as empresas é lançamento no ERP |
| Consolidação cross-tenant | Se um dia existir, é serviço de leitura com permissão própria — nunca um filtro de tenant relaxado |
Herança de cadastro do tenant Group para os filhos | Contraria a Decisão 3. Quem quer cadastro compartilhado usa o modo consolidado |
| Hierarquia entre empresas do mesmo tenant | Holding e controlada dentro do tenant: lista plana até que consolidação societária exista, o que não está no roteiro. parentCompanyId não é reservado |
| Estabelecimento fora do Brasil | countryCode reservado, sem regra. Fornecedor estrangeiro já é suportado; comprar como empresa estrangeira, não |
| Parametrização fiscal por operação | CFOP, CST, ST, benefícios e regra de crédito nascem com a nota fiscal |
DeliveryLocation e Budget | Specs próprias. As duas dependem desta e nenhuma das duas depende da outra |
Contrato vigente · revisão consolidada
Administração das concessões
Contratos mínimos propostos: listar e manter UserCompanyScope e UserCompanyRole por usuário; simular permissão efetiva por empresa/operação; revogar vínculos com motivo, versão esperada e trilha. Exigir manage:company-access no teto da plataforma e no escopo administrado. Não permitir que o administrador conceda operações acima de seu teto delegável, amplie seu próprio escopo ou use gestão de acesso para se atribuir alçada financeira.
Auditoria guarda antes/depois, autor, empresas atingidas, validade e revisão. Delegações de aprovação verificam as concessões e limites na criação, ativação e decisão. Restauração do banco precisa invalidar sessões e caches para não ressuscitar permissões revogadas. Estes contratos ainda exigem implementação no Nexio; não são capacidade comprovada do pacote GrydAuth consumido.