Especificação · Catálogo

Cadastro de Item

O registro fundacional do Nexio. Um único agregado que precisa descrever matéria-prima, produto acabado, MRO, serviço, assinatura e ativo — em qualquer setor — com precisão suficiente para cotar, comparar, aprovar e conferir.

substitui o cap. 2 de NEXIO_Catalogo_Itens_v2.0 7 agregados · 1 entidade de configuração 29/08/2026

Ponto de partida

Decisões tomadas antes desta especificação

Cinco escolhas foram fechadas com o time e são premissa de tudo abaixo. Cada uma altera o spec v2.0 do Catálogo.

Decisão 1 · Tipo

Entidade única com ItemType. Um Item para material, serviço, ativo e assinatura, com campos condicionais validados no domínio. É o que Coupa e Precoro fazem — os dois produtos da amostra que são P2P puro, como o Nexio. O SAP separa material e serviço porque é ERP e precisa de MRP e valoração; nós não.

Decisão 2 · Taxonomia

CategoryNode auto-relacionado. Substitui Category + Subcategory. Profundidade livre no armazenamento, teto por configuração do tenant (maxDepth, default 2). A UI segue com dois selects; o terceiro nível vira um INSERT em vez de migração multi-tenant.

Decisão 3 · Fiscal

Cobertura BR completa, em regime de "esperado". NCM, CEST, GTIN para mercadorias; LC 116, cTribNac e NBS para serviços; destinação predominante para o crédito de IBS/CBS. Nenhum campo fiscal é obrigatório para criar item, e nenhum é fonte de verdade fiscal — são referências de conferência.

Decisão 4 · Estoque

Fora do escopo, com ganchos previstos. O item não carrega saldo, mínimo, ponto de reposição nem localização. O que fica previsto no modelo: controlsStock no tipo, lote e validade como grupo condicional desligado, e a unidade base já sendo a unidade de estoque futura.

Decisão 5 · Nome

A entidade se chama Item, e as linhas de documento se chamam Line. Catalog.CatalogItem gagueja — o namespace já diz o módulo. O único argumento a favor do prefixo era a ambiguidade com "item do pedido", e ela desaparece ao nomear as linhas como RequisitionLine, QuotationLine, PurchaseOrderLine e ReceiptLine. Essa convenção é parte da decisão, não consequência dela: se uma linha de documento voltar a se chamar "item", a ambiguidade volta junto — e aí o prefixo faria falta.

Fundamentos

Sete princípios que governam o cadastro

1. A descrição é gerada, não digitada

A identidade de um item é nome básico + conjunto de (característica, valor). O cadastrador preenche atributos; o sistema monta a descrição canônica. É o modelo do PDM do CATMAT, das properties do eCl@ss e dos attribute groups da Oracle — quatro instâncias independentes do mesmo padrão. É também a única defesa estrutural contra "PARAF. M8 40MM INOX" e "Parafuso sextavado M8x40" coexistirem como itens diferentes.

2. O que é do fornecedor não fica no item

Preço, prazo de entrega, quantidade mínima, múltiplo de compra, código do item no fornecedor e unidade de compra vivem em ItemSupplier. Coupa, SAP, Oracle e Ariba são unânimes nisso, e o motivo é simples: nada disso é verdade sobre o item — é verdade sobre uma relação. O primeiro segundo fornecedor quebra qualquer modelo que tenha colocado isso no item.

3. O cadastro guarda o que decide a compra, não o que apura tributo

Campos fiscais existem para conferir a nota do fornecedor e para classificar a intenção da compra. CST, CFOP, alíquotas, base reduzida, código de benefício e cClassTrib são responsabilidade do emitente e do ERP fiscal. Guardá-los aqui produz dado que estará errado numa fração relevante das operações.

4. Nenhum campo fiscal é obrigatório para criar um item

O item nasce de uma necessidade de compra, frequentemente antes de existir fornecedor. Exigir NCM no cadastro faz o usuário digitar 0000.00.00 — e dado errado é pior que dado ausente. Na UI, o rótulo é explícito: "NCM esperado — usado para conferir a nota do fornecedor".

5. A taxonomia classifica; ela não carrega atributos nem políticas

"Requer laudo", "exige DPO", "criticidade" e "com dado pessoal" nunca viram nós da árvore. Viram atributo, flag ou documento. O eCl@ss consegue ser fixo em quatro níveis porque escoa granularidade em 23 mil propriedades; sem esse escape, a granularidade vira nome de subcategoria e a árvore apodrece.

6. Uma unidade nunca basta

Nenhum dos seis produtos pesquisados tem "a UoM do item" — Coupa tem seis papéis de unidade, D365 e NetSuite têm três. E a conversão é por item: "1 CX = 12 UN" não é verdade sobre "CX", é verdade sobre este parafuso. Conversão global de classe quebra no primeiro tenant que compra caixa de 12 e caixa de 24 do mesmo produto.

7. Buscar antes de criar

A tela de novo item começa como busca, não como formulário em branco. É o passo mais barato e mais eficaz do fluxo do SAP MDG, e é onde a duplicidade morre — antes de nascer, não numa rotina de limpeza depois.

Modelo

Mapa de entidades

Sete agregados novos, um deles de configuração. O Item é o centro; tudo que varia por fornecedor, por categoria ou por operação fica fora dele.

ItemType configuração · por tenant Dirige quais campos existem, quais são obrigatórios e quais classes de unidade são válidas.
CategoryNode árvore · N níveis Classifica. Fornece o esquema de atributos, o NCM sugerido e as exigências regulatórias herdadas.
AttributeDefinition dicionário · por tenant Atributo tipado, com unidade e lista de valores. Ligado às categorias por CategoryAttribute.
UnitOfMeasure mestre · por classe Unidade dentro de uma classe (peso, volume, contagem, tempo, serviço). Conversão padrão só intraclasse.
Item aggregate root Filhos: ItemAttributeValue, ItemUomConversion, ItemPhoto, ItemKeyword, ItemDocument, ItemCompliance.
ItemSupplier aggregate root · N por item Preço, prazo, MOQ, múltiplo, unidade de compra, código no fornecedor, status de homologação.
ItemLifecyclePhase configuração · por tenant Fase do ciclo de vida e a matriz processo × política que ela impõe.
Estoque fora de escopo Saldo, depósito, lote, ponto de reposição. Ganchos previstos, nada implementado.
Nota recebida outro módulo Dados fiscais como declarados pelo emitente, imutáveis. É contra isso que o "esperado" do item é conferido.

O CostCenter não aparece no cadastro de item — a amarração contábil acontece na requisição, não no catálogo. Ver Impacto na aprovação.

Convenção de nomes. O documento é em português; os identificadores são em inglês — entidades, atributos, valores de enum, rotas, permissões e códigos de erro. A exceção são os dados de negócio brasileiros, que ficam como são no mundo real: identificadores fiscais (cnpj, cpf, primaryCnae), nomes de regime tributário (SimplesNacional, LucroReal), siglas de unidade (UN, CX, KG), tipos de certidão e os exemplos de taxonomia — esses são conteúdo, não código.

Configuração · ItemType

Tipo de item

ItemType não é um enum: é entidade de configuração por tenant, seguindo o padrão do material type do SAP e do item model group do D365. O tipo dirige field selection — quais grupos de campos aparecem, quais são obrigatórios e o que o domínio recusa.

Campos do ItemType

CampoTipoEfeito
code / namestring(30) / string(80)Código único por tenant, imutável. Nome exibido.
isSystemboolTipos-semente não podem ser excluídos, só desativados ou renomeados.
requiresGoodsTaxCodeboolLibera o grupo fiscal de mercadoria (NCM, CEST, GTIN) e o grupo físico (peso, volume, dimensões).
requiresServiceTaxCodeboolLibera o grupo fiscal de serviço (LC 116, cTribNac, NBS). Mutuamente exclusivo com o anterior.
allowsRecurrenceboolLibera periodicidade e unidade de cobrança.
isCapexboolLiga requiresAssetTag por default e habilita vida útil.
controlsStockboolgancho Reservado. Hoje sempre false; libera lote e validade quando estoque entrar.
allowedUomClassesUomClass[]Impede "hora" em matéria-prima e "quilograma" em serviço de consultoria.
defaultPredominantUseenumDestinação sugerida no cadastro (ver Fiscal).
requiredFieldsstring[]Field selection: campos opcionais no domínio que este tipo torna obrigatórios.
isActiveboolTipo inativo não aceita novos itens; itens existentes não são afetados.

Tipos-semente por tenant

CódigoFiscalRecorrênciaCAPEXClasses de unidadeDestinação padrão
RAW_MATERIALMercadoriaCount, Weight, Volume, Length, AreaProductionInput
FINISHED_GOODMercadoriaCount, Weight, VolumeResale
MROMercadoriaCount, Weight, Volume, LengthConsumableUse
PACKAGINGMercadoriaCount, Weight, AreaProductionInput
CAPEX_ASSETMercadoriasimCountFixedAsset
SERVICEServiçoTime, Service, AreaConsumableUse
RECURRING_SERVICEServiçosimTime, ServiceConsumableUse
SOFTWARE_SUBSCRIPTIONServiçosimService, TimeConsumableUse
RENTALServiçosimTime, Service, CountConsumableUse

Por que tipo configurável e não enum. O tipo de um item não é estável: MRO vira contrato de manutenção, software perpétuo vira assinatura. Migrar entre entidades é caro; mudar um ItemTypeId é barato — desde que o domínio revalide os campos condicionais na troca (ver RN-ITM-06).

Domínio · Item

Campos do item

Legenda de obrigatoriedade: obrig sempre · cond conforme ItemType ou categoria · opc opcional · gerado calculado pelo sistema.

Identificação

CampoTipoRegra
internalCodestring(16)geradoFormato CAT-{YYYY}-{NNNNN}, único por tenant, imutável. Gerado por IItemCodeService.
externalCodestring(100)opcCódigo legado ou do ERP. Não é único — chave de upsert na importação.
itemTypeIdGuidobrigDeve referenciar tipo ativo do tenant.
gtinstring(14)opc8, 12, 13 ou 14 dígitos com dígito verificador válido. Único por tenant quando informado. Recusado se requiresServiceTaxCode.
manufacturerNamestring(160)opcFabricante ou marca. Não confundir com fornecedor.
mpnstring(100)opcPart number do fabricante. O par (manufacturerName, mpn) normalizado é único por tenant.

Classificação e descrição

CampoTipoRegra
categoryNodeIdGuidobrigDeve ser nó folha ativo (sem filhos ativos). Ver RN-ITM-11.
basicNamestring(60)obrigNome básico do item, equivalente ao INC do CATMAT. Sem modificadores: PARAFUSO, não PARAFUSO M8 INOX.
standardDescriptionstring(200)geradoMontada a partir de basicName + atributos marcados como usedInDescription, na ordem definida. Recalculada a cada alteração de atributo. Digitável apenas quando a categoria não tem esquema de atributos.
technicalDescriptionstring(1000)opcTexto livre complementar. Nunca canônico, nunca usado em matching.
unspscCodestring(8)opcMapeamento para taxonomia externa. Só para benchmark e exportação — não participa de nenhuma regra.
keywordsstring(50)[]opcMáx. 10. Entram na busca full-text.

Unidades e características físicas

CampoTipoRegra
baseUomIdGuidobrigUnidade canônica do item. Toda comparação de preço normaliza para ela. A classe da unidade deve constar em ItemType.allowedUomClasses. Imutável após o primeiro pedido.
defaultPurchaseUomIdGuidopcDefault = base. Precisa ter conversão cadastrada para a base.
conversionsItemUomConversion[]opcVer Unidades e conversão.
netWeight / netWeightUomIddecimal(18,6)condSó quando requiresGoodsTaxCode. Unidade da classe Peso.
grossWeight / grossWeightUomIddecimal(18,6)condIdem. Usado em frete e carga.
length / width / height / dimensionUomIddecimal(18,6)condIdem. Unidade da classe Comprimento, comum às três medidas.

Comercial

CampoTipoRegra
referencePricedecimal(18,4)opcBenchmark exibido no comparativo de cotações. Exige catalog.price:manage.
referencePriceCurrencystring(3)condISO 4217. Obrigatório quando há preço. Deve estar entre as moedas ativas do tenant.
referencePriceUomIdGuidcondEm qual unidade o preço está. Default = base. Preço sem unidade é número sem significado.
referencePriceUpdatedAtdatetimegeradoExibido junto ao preço. Preço de referência de dois anos atrás é ruído, não benchmark.

Recorrência — quando allowsRecurrence

CampoTipoRegra
defaultBillingPeriodenumcondMonthly | Quarterly | SemiAnnual | Annual | OnDemand.
billingUnitstring(40)opcBase de cobrança: usuário-mês, licença, host, GB. Texto controlado por lista do tenant.

Vigência real, data de renovação, aviso prévio e regra de reajuste não ficam no item — ficam no contrato e são copiados para a linha do pedido. A mesma licença tem vigências diferentes por fornecedor; renewalDate no item master é dado que nasce errado.

Política de compra

CampoTipoRegra
isPurchasableboolobrigSeparado de "existe". Item pode existir no catálogo e não ser requisitável hoje.
requiresInspectionboolopcMarca o recebimento como sujeito a inspeção.
requiresAssetTagboolopcDefault herdado de ItemType.isCapex, sobrescrevível.
requiresContractboolopcItem só pode virar pedido sob contrato vigente.
allowsPartialReceiptboolopcDefault true.
requiresNationalContentboolopcFlag de negócio para política de conteúdo nacional. Não é o código de origem da NF-e.
criticalityenum?opcStrategic | Leverage | Bottleneck | Routine (matriz de Kraljic). Alimenta analytics e pode entrar como critério de aprovação.
accessGroupIdsGuid[]opcVazio = visível a todos. Restringe quem enxerga e requisita o item.

Ciclo de vida e controle

CampoTipoRegra
approvalStatusenumgeradoDraft | PendingApproval | Approved | Rejected. Muda só por comandos de transição.
rejectionReasonstring(500)condObrigatório na rejeição, mínimo 20 caracteres.
lifecyclePhaseIdGuidobrigFase configurável por tenant. Traz a matriz processo × política.
processPolicyOverridesmapopcSobrescreve a política da fase para este item específico.
successorItemIdGuid?opcItem substituto. Exibido ao requisitante quando o item está em descontinuação.
validFrom / validTodate?opcVigência do cadastro. Fora da vigência, o item não aparece na requisição.
predominantUseenumobrigVer Fiscal. Default vem do ItemType.
versionintgeradoNasce 1, incrementa a cada update. Token de concorrência do PUT.
completenessScoreintgeradoPercentual de atributos obrigatórios e recomendados preenchidos. Indicador de governança, exibido na listagem.

Coleções filhas

ColeçãoLimiteRegra
attributeValuesesquema da categoriaUm valor por AttributeDefinition, ou N quando o atributo é multivalorado.
conversions20Uma por unidade de destino. Não pode conter a unidade base.
photos5JPG, PNG, WEBP, 5 MB cada. IObjectStorageService, path tenants/{tid}/catalog/{iid}/photos/{uuid}.{ext}, URL pré-assinada com TTL de 1h.
documents20FISPQ, certificado, laudo, manual. Tipo, versão, data de emissão, validade e arquivo. Documento vencido gera alerta e pode bloquear a compra.
compliances10Registro regulatório: órgão, número, validade. Ver Regulatório.
keywords1050 caracteres cada.

Fornecedores preferidos não são mais uma coleção do item — viraram o agregado ItemSupplier, com preço, prazo e status próprios.

Domínio · CategoryNode

Taxonomia

Um único tipo, auto-relacionado por parentId. Nenhuma taxonomia de referência de compras tem dois níveis — UNSPSC, eCl@ss, GPC e CATMAT têm quatro; as spend taxonomies corporativas convergem em três a quatro. E nenhum produto pesquisado usa profundidade fixa: Ariba, Coupa, Dynamics e Oracle usam todos lista de adjacência.

CampoTipoRegra
parentIdGuid?Nulo = raiz. Ciclo é recusado. Profundidade validada contra TenantSettings.catalogMaxCategoryDepth.
codestring(30)Único por tenant. Estável — é o que a importação e as regras de aprovação referenciam.
name / descriptionstring(120) / string(500)Nome único entre irmãos.
pathltreegerado Caminho materializado, usado para consulta de ancestrais e descendentes em uma query. Recalculado em cascata ao mover um nó.
levelintgerado 1-based, derivado do path.
sortOrderintOrdem de exibição entre irmãos.
suggestedNcmstring(8)?Pré-preenche o NCM esperado dos itens da categoria. Sugestão, nunca imposição.
defaultItemTypeIdGuid?Pré-seleciona o tipo ao criar item nesta categoria.
requiredComplianceKindsenum[]Exigências regulatórias herdadas por todos os itens da subárvore. Ex.: categoria "EPI" exige CA.
isActiveboolDesativação em cascata para a subárvore. Categoria inativa não aceita novos itens; itens existentes não são afetados.

Profundidade livre no armazenamento, teto na política

maxDepth é configuração do tenant, default 2 — a UI segue exibindo dois selects encadeados e nada muda para quem já usa. Regras de aprovação e mapeamentos contábeis operam sobre qualquer nível. É o padrão da Oracle, que permite criar mais de dez níveis mas só amarra política nos dez primeiros: desacopla "quantos níveis o cliente desenha" de "quantos o motor entende".

Herança para baixo, override no filho

Esquema de atributos, NCM sugerido, tipo default e exigências regulatórias são herdados do ancestral mais próximo que os define. O nó filho pode acrescentar, nunca remover o que o pai marcou como obrigatório.

Item só classifica em folha

Nó com filhos ativos não aceita item. Sem essa regra, metade do catálogo fica pendurada em nós intermediários e a agregação por categoria mente. Ao criar um filho sob um nó que já tem itens, o sistema exige a reclassificação desses itens antes de concluir — com sugestão automática por atributos.

Migração a partir de Category / Subcategory

Cada Category vira nó nível 1 com parentId = null; cada Subcategory vira nó nível 2 apontando para ela. Item.subcategoryId vira categoryNodeId; categoryId é descartado, já que o path o reconstrói. Categoria sem subcategorias ganha um filho GERAL para satisfazer a regra da folha. Migração determinística, sem decisão humana.

Migration · substitui o agregado Category e a entidade Subcategory

Domínio · AttributeDefinition

Atributos por categoria

É o mecanismo que faz "parafuso precisa de bitola, rosca e material" e "notebook precisa de processador, RAM e tela" conviverem sem explodir a taxonomia. Quatro implementações independentes chegaram ao mesmo desenho — eCl@ss properties, SAP characteristics, Oracle attribute groups por item class e o PDM do CATMAT.

Definição do atributo

CampoTipoRegra
code / namestring(40) / string(80)Código único por tenant, imutável.
dataTypeenumText | LocalizedText | Integer | Decimal | Measure | Currency | Boolean | Date | List | MultiList | Reference. Imutável após o primeiro valor gravado.
uomClassIdGuid?Obrigatório em Measure. É o que faz 3/8 pol e 9,525 mm serem comparáveis.
minValue / maxValue / decimalsdecimal? / intValidação de faixa para tipos numéricos.
valuesAttributeValue[]Lista de valores codificados, para List e MultiList.
isRestrictiveboolLista fechada (só os valores cadastrados) ou sugerida (aceita valor novo, que entra na lista). "Cor da tinta" é fechada; "acabamento" pode ser aberta.
isMultiValuedboolPermite N valores no mesmo atributo. Ex.: certificações: ISO 9001, ISO 14001.

Vínculo categoria × atributo

CampoTipoRegra
categoryNodeId / attributeDefinitionIdGuidChave composta.
isRequiredboolItem não é aprovado sem os obrigatórios preenchidos.
usedInDescriptionboolParticipa da descrição gerada.
descriptionOrderint?Posição na descrição gerada. Obrigatório quando usedInDescription.
displayOrderintOrdem no formulário.
Exemplo · descrição gerada
categoria      FIXADORES › PARAFUSOS › SEXTAVADOS
basicName      PARAFUSO
atributos      tipo_cabeca   = Sextavado          (ordem 1)
               diametro      = M8                 (ordem 2, Measure)
               comprimento   = 40 mm              (ordem 3, Measure)
               material      = Aço inox 304       (ordem 4, List restritiva)
               rosca         = Parcial            (ordem 5, List restritiva)

standardDescription  →  "PARAFUSO SEXTAVADO M8 X 40MM AÇO INOX 304 ROSCA PARCIAL"
attributeSignature   →  sha256("tipo_cabeca=SEXTAVADO|diametro=8MM|…")

A assinatura é o hash normalizado dos atributos obrigatórios. Duas pessoas preenchendo os mesmos valores produzem a mesma assinatura — e a duplicata é detectada antes de existir.

Categoria sem esquema continua funcionando

Nem todo tenant vai desenhar PDM no primeiro dia. Categoria sem atributos vinculados aceita standardDescription digitada, e o completenessScore reflete isso. O esquema é uma evolução da qualidade do cadastro, não um pré-requisito de adoção.

Atributo não vira coluna

Valores vivem em ItemAttributeValue com colunas tipadas (valueText, valueNumber, valueBool, valueDate, valueListId) mais uomId. Nenhum produto pesquisado modela atributo como coluna do item — com taxonomias distintas por tenant, seria inviável.

Domínio · UnitOfMeasure

Unidades e conversão

Esta é a seção que paga ou cobra no comparativo de cotações. Se o fornecedor A cota R$ 120 / CX(12 UN) e o B cota R$ 11 / UN, o comprador precisa ver R$ 10,00/UN vs R$ 11,00/UN sem calculadora.

Classes de unidade

ClasseBase sugeridaConvertívelObservação
CountUNsimCX, DZ, MIL, PC, PAR — conversão sempre por item.
WeightKGsimG, TON. Conversão padrão global.
VolumeLsimML, M3.
LengthMsimMM, CM, KM, POL.
AreaM2simCM2, HA.
TimeHsimMIN, DIA, MES. Conversão global só onde é inequívoca.
ServiceVBnãoVerba, entrega, marco, ponto de função. Classe não conversível — ver abaixo.

Conversão por item, com numerador e denominador

ItemUomConversion (itemId, uomId, numerator, denominator) sobrepõe a conversão global da classe. O par inteiro evita o erro de arredondamento que um fator decimal introduz — 1 MIL = 1000 UN em inteiros, não 0,001. O NetSuite escolheu conversão global no nível do Units Type e documenta as consequências: não é possível editar o tipo depois de atribuído nem trocar o tipo de um item. SAP (MARM) e Oracle fazem por item, e a Oracle é explícita ao exigir que o item exista antes de criar conversões.

A classe Serviço não converte, nem entre si

Não existe "1 mês = X horas" universal. Duas propostas de consultoria — uma em horas, outra em verba global — não têm denominador comum. O sistema recusa a comparação numérica e sinaliza, em vez de estimar. Forçar preço por unidade base em serviço produz número sem significado.

A cotação guarda o original e o normalizado

A linha de cotação carrega (priceAmount, qty, quotedUomId, declaredFactor) e o sistema deriva pricePerBaseUom. Guardar só o normalizado perde a informação original e impede auditoria; guardar só o original impede comparação. O declaredFactor é o que o fornecedor afirma caber na embalagem — se divergir do cadastro do item, o comparativo sinaliza em vez de calcular errado em silêncio.

Sem conversão cadastrada, sem comparação

Se um fornecedor cota em KG e outro em UN e não há conversão interclasse para aquele item, o comparativo exibe as duas propostas lado a lado sem preço normalizado, com aviso explícito. Estimar seria pior que não comparar.

A unidade base é imutável depois do primeiro pedido

Trocar a base reescreve o significado de todo o histórico de preço e de todo o comparativo já emitido. Se for realmente necessário, o caminho é criar item sucessor e marcar o atual como substituído.

Domínio · ItemSupplier

Item × Fornecedor

Agregado próprio. Coupa (/supplier_items), SAP (purchasing info record), Oracle (Approved Supplier List) e Ariba (o próprio arquivo CIF) são unânimes: essa relação é uma entidade, não uma lista no item.

CampoTipoRegra
itemId / supplierIdGuidobrigFornecedor precisa existir e não estar bloqueado.
establishmentIdGuid?opcCompõe a chave. Nulo = vale para todos os estabelecimentos do fornecedor. É o eixo supplier site da ASL da Oracle, sem obrigar todo cliente a modelar filiais.
shipToLocationIdGuid?ganchoReservado. Preço e prazo variam por destino; o campo entra na chave quando houver cliente com várias filiais compradoras.
supplierPartNumberstring(100)obrigCódigo do item no fornecedor. Vai para o pedido e é a chave do matching da nota.
supplierAuxPartNumberstring(100)opcDiferencia variante ou opção de entrega sob o mesmo part number.
purchaseUomId + numerator/denominatorGuid, int, intobrigA unidade de compra é do fornecedor, não do item. O fornecedor A vende em CX(12); o B, em CX(24).
priceAmount / currencydecimal(19,10) / char(3)opcMoeda ativa no tenant. Dez casas decimais — ver o documento de Unidade de Medida.
priceUomId / priceQtyGuid / decimal(19,6)condPode diferir da unidade de compra — compra-se em SC e cobra-se por TN. priceQty é a quantidade de precificação (preço "por 1000"), obrigatória junto com o preço.
supplierUomLabelstring(40)opcComo o fornecedor chama a embalagem. Só conferência humana contra a proposta — nunca participa de cálculo.
priceTierstier[]opcFaixas por quantidade: (minQty, priceAmount). Máx. 10.
validFrom / validTodate?opcVigência do preço. Preço vencido não entra em comparativo sem aviso.
leadTimeDaysint?opcEntra no comparativo ao lado do preço — prazo é decisão, não detalhe.
moq / orderIncrement / standardQuantitydecimal?opcQuantidade mínima, múltiplo e quantidade padrão de pedido, na unidade de compra.
incoterm / deliveryLocationstring(3) / string(160)opcRelevante para importação e para comparar propostas com frete embutido.
contractIdGuid?opcQuando presente, o contrato sobrepõe preço, prazo e múltiplo.
statusenumobrigNew | UnderQualification | Approved | Blocked. Não é rótulo: cada valor carrega allowsQuotation e allowsOrder.
isPreferred / prioritybool / intopcOrdem de sugestão ao abrir RFQ. Máx. 10 preferidos por item.
overTolerancePct / underTolerancePctdecimal?opcTolerância de recebimento. Precedência: SupplierEstablishment.receiptToleranceItemSupplier → linha do pedido. Avaliada sempre em unidade base.
reviewByDatedate?opcRevisão planejada da relação. Vencida, entra no painel de saúde do catálogo.

Precedência de valores na linha do pedido: contrato → ItemSupplier → Item. O item só fornece o que ninguém mais forneceu (unidade base, descrição, atributos). Preço e prazo vindos do item são sempre referência, nunca compromisso.

Quando o status do fornecedor muda para bloqueado no módulo Fornecedores, o handler de SupplierStatusChangedEvent marca os ItemSupplier como Blockedsem apagar o registro. Desativação destrutiva é o erro documentado da Coupa, onde Active = No destrói os supplier items.

Brasil

Fiscal e regulatório

O Nexio é o lado do comprador: a maioria dos atributos fiscais não é dado que se cadastra, é dado que chega na nota. Cada campo abaixo está em um de três baldes.

cadastrar

Atributo estável do item, necessário antes de comprar — para especificar, comparar, exigir ou bloquear.

esperar

Vem no XML da nota. O Nexio armazena como dado observado e usa para conferência. Não se cadastra.

não carregar

Responsabilidade do ERP fiscal. Guardar aqui produz dado errado e cria duas fontes de verdade.

Mercadorias — quando requiresGoodsTaxCode

CampoBaldeRegra no Nexio
expectedNcmcadastrar8 dígitos, validado contra a tabela NCM espelhada. Rotulado como NCM esperado. Pré-preenchido pelo suggestedNcm da categoria. Divergência contra a nota vira alerta de conferência.
expectedCestcadastrar7 dígitos, opcional, exige NCM preenchido. Relevante só para tenants que revendem. A relação NCM→CEST é 1:N e se resolve pela descrição — o autocomplete sugere, o humano escolhe.
gtincadastrarJustificado por antiduplicidade, matching de linha de nota e leitura por coletor. A obrigação fiscal é do emitente; aqui é valor de negócio.
Origem da mercadoria (0–8)esperarDepende de como o emitente adquiriu — o mesmo produto sai com origem 1 pelo importador e 2 pelo distribuidor. É atributo do par item×fornecedor observado, não do item. Para política de conteúdo nacional existe a flag requiresNationalContent.
cEANTrib, uTrib, qTribnão carregarCampos de emissão. O conceito — unidade de compra × unidade base × fator — está modelado em Unidades; o campo fiscal, não.
cProdANPesperarSetorial (combustíveis). Vem na nota.

Serviços — quando requiresServiceTaxCode

CampoBaldeRegra no Nexio
serviceCodeLc116cadastrarSubitem da lista da LC 116/2003 (ex.: 7.02). Estrutura de dois níveis, ~200 subitens ativos após as alterações até a LC 218/2025.
cTribNaccadastrarCódigo-âncora recomendado. Os 338 códigos do Código de Tributação Nacional da NFS-e desdobram a LC 116 de forma padronizada — é o primeiro código de serviço nacionalmente comparável, o que viabiliza benchmark entre tenants de municípios diferentes.
nbsCodecadastrar9 dígitos, formato 1.XXXX.XX.XX. Opcional hoje, com tendência clara de obrigatoriedade conforme IBS/CBS amadurece. Campo preparado agora, exigido depois.
cTribMunnão carregarVaria por município do prestador. Não é atributo do item.
Alíquotas e regras de retençãonão carregarRetenção depende de quatro variáveis e o item carrega apenas uma. Ver o quadro abaixo.

Por que não existe campo "retenções" no item. IRRF, INSS, CSRF e ISS retido dependem de: (a) tipo de serviço — atributo do item; (b) natureza jurídica e regime do prestador — atributo do fornecedor; (c) forma de execução — atributo do contrato ou do pedido: a mesma "manutenção predial" dispara INSS de 11% com equipe alocada e pode não disparar por empreitada de resultado; (d) município e regra de local da prestação — atributo da operação.

O item guarda o código de classificação. A matriz de retenção vive no motor fiscal. O que o Nexio deve oferecer é a flag cessaoDeMaoDeObra na linha do pedido ou no contrato — porque isso é decisão de compra, negociada, e ninguém a jusante sabe disso melhor que o comprador.

Reforma tributária — o único campo que nasce no comprador

CampoBaldeRegra no Nexio
predominantUsecadastrarProductionInput | Resale | ConsumableUse | FixedAsset | PersonalBenefit. Default do ItemType, sobrescrevível na linha do pedido. O art. 57 da LC 214/2025 pergunta se o bem se destina à atividade econômica ou ao consumo pessoal — o fornecedor não sabe, o ERP fiscal não sabe, o sistema de compras sabe: está no requisitante, no centro de custo e na justificativa.
cClassTribnão carregarA armadilha de 2026. O mesmo NCM tem cClassTrib diferentes conforme a descrição precisa e o tipo de operação — absorvente e fralda descartável compartilham NCM e têm códigos distintos. Não é derivável do cadastro de item. Armazenar apenas o observado na nota.
CST IBS/CBS, alíquotas, reduçõesesperarInsumo de custo real, vindo da nota.
CST/CSOSN, CFOP, alíquotas ICMS/IPI/PIS/COFINS, cBenef, FCInão carregarCFOP é 100% da operação. Alíquota é função de origem, destino, regime, produto, benefício e data — guardar no item é garantir número errado. Custo real vem da nota recebida.

Durante 2026 as notas chegam com dados de IBS/CBS incompletos ou ausentes e ainda assim autorizadas — o Ato Técnico Conjunto RFB/CGIBS nº 1/2026 adiou as validações, não a obrigação. O parser de nota não pode tratar ausência como erro.

Regulatório — ItemCompliance e ItemDocument

Quase nada disso é "um campo no item". A regra de decisão: flag quando a política é universal e binária; atributo de categoria quando é específica de um tipo de produto; objeto com validade quando tem número, órgão emissor e vencimento; documento anexo quando o que importa é o arquivo.

CasoMecanismoOnde mora
EPI — Certificado de AprovaçãoItemComplianceNº do CA, órgão MTE, validade. Bloqueia a compra quando vencido — é vedado ao empregador fornecer EPI sem CA. O caso mais forte de campo estruturado.
Registro ANVISA (medicamento, saneante, correlato)ItemComplianceNº de registro do produto no item. A AFE é do fornecedor, não do item.
Registro MAPA (fertilizante, defensivo, veterinário)ItemComplianceNº de registro no item. Receituário agronômico é documento da requisição — tem validade curta e é por aplicação.
Produto controlado — Exército / PF / Polícia Civilflag + habilitaçãocontrolKind no item (None | FederalPolice | Army | StatePolice). CR, licença e mapas mensais são habilitação do tenant e do fornecedor, com validade. Nunca no item.
Produto perigoso — FISPQItemDocument + camposFISPQ anexada com versão e data. Extraídos como campos: número ONU e classe de risco — porque disparam regra de transporte e armazenagem.
Certificação INMETRO / homologação ANATELItemComplianceNº do certificado, órgão, validade, arquivo anexo.
Exigência de laudo ou certificado na entregarequiredDocumentKindsLista herdada da categoria. O recebimento cobra os documentos declarados.

ItemCompliance: kind, agency, registrationNumber, issuedAt, expiresAt, documentId?, blocksPurchaseWhenExpired. Um job diário varre vencimentos e notifica os responsáveis pelo catálogo via GrydNotifications.

Governança

Ciclo de vida

Três eixos ortogonais, seguindo a Oracle, que separa Status, Lifecycle Phase e Approval Status como atributos distintos. Colapsar os três num único enum é o erro que obriga a inventar estados como "ativo mas em revisão e bloqueado para pedido".

Eixo 1 — aprovação do cadastro

DeParaComandoRegra
DraftPOST /itemsQuem tem catalog:create. Validação mínima: nome básico, tipo, categoria, unidade base.
DraftPendingApprovalPOST /items/{id}/submitExige atributos obrigatórios da categoria preenchidos e re-checagem de duplicidade.
PendingApprovalApprovedPOST /items/{id}/approveExige catalog.item:approve. O aprovador vê os candidatos a duplicata antes de decidir.
PendingApprovalRejectedPOST /items/{id}/rejectMotivo obrigatório, mínimo 20 caracteres. Notifica o criador.
RejectedDraftPUT /items/{id}Editar um item rejeitado o devolve a rascunho.
ApprovedPendingApprovalPUT /items/{id}Somente quando a alteração toca campos materiais (categoria, tipo, unidade base, atributos obrigatórios, identificadores). Alterações menores não reabrem aprovação.

Eixo 2 — fase do ciclo de vida

Configurável por tenant (ItemLifecyclePhase). Fases-semente: New, Active, PhasingOut, Obsolete, Superseded. Fase não é status de aprovação nem bloqueio — é onde o item está na vida dele.

Eixo 3 — matriz processo × política

Cada fase declara, por processo, uma das três políticas do modelo do D365: permitido, aviso ou bloqueado.

FaseRequisiçãoCotaçãoPedidoRecebimento
Newbloqueadopermitidobloqueadobloqueado
Activepermitidopermitidopermitidopermitido
PhasingOutavisoavisopermitidopermitido
Obsoletebloqueadobloqueadobloqueadopermitido
Supersededbloqueadobloqueadobloqueadopermitido

Recebimento permanece permitido em todas as fases finais: bloquear o recebimento de um pedido legítimo já colocado transforma uma decisão de catálogo num problema operacional. E Superseded sempre exibe o successorItemId ao requisitante — bloquear sem oferecer alternativa gera compra fora do processo.

Qualidade de dados

Antiduplicidade

Seis camadas, três pontos de disparo. O modelo de dois limiares vem do SAP MDG: abaixo do limiar baixo nada é sinalizado; entre os dois, apresenta-se como candidato; no ou acima do alto, considera-se idêntico — e ainda assim se apresenta ao usuário, nunca se decide sozinho.

#Regra de matchAção
1Exato em (manufacturerName, mpn) normalizado — maiúsculas, sem hífen, sem espaçobloqueia
2Exato em gtinbloqueia
3Exato em (supplierId, supplierPartNumber)bloqueia no ItemSupplier
4attributeSignature — hash dos atributos obrigatórios da categoriabloqueia
5Similaridade trigram (pg_trgm) sobre standardDescription, dentro da mesma categoria, com dicionário de abreviações pt-BR (PARAF→PARAFUSO, GALV→GALVANIZADO, INOX→AÇO INOXIDÁVEL)alerta com score e candidatos
6Re-execução das camadas 4 e 5 na submissão e na aprovaçãoalerta ao aprovador

A tela de novo item começa como busca

POST /catalog/items/duplicate-check recebe nome básico, categoria e atributos parciais e devolve candidatos antes de qualquer gravação. É o passo mais barato do fluxo e o que mais reduz duplicata — bloquear na submissão já é tarde, o usuário já investiu no cadastro.

Bloqueio tem escape auditado

Quem tem catalog.item:approve pode forçar a criação de um item que casou em camada bloqueante, com justificativa obrigatória registrada em GrydAudit. Existem casos legítimos — mesmo MPN com especificações divergentes entre fabricantes homônimos. Bloqueio sem escape gera cadastro criativo: "PARAFUSO M8 (2)".

Rastreabilidade

Regras de negócio

RN-ITM-01

Código interno gerado e imutável

Formato CAT-{YYYY}-{NNNNN}, único por tenant, sem edição manual. Reservado no momento da criação, mesmo em rascunho, para não haver furo de sequência.

RN-ITM-02

Todo item tem tipo, categoria folha e unidade base

Os três são obrigatórios já no rascunho. É o mínimo que torna o item comparável, classificável e cotável.

RN-ITM-03

A classe da unidade base deve ser permitida pelo tipo

ItemType.allowedUomClasses é validado no domínio. Impede matéria-prima medida em horas e consultoria medida em quilos.

RN-ITM-04

Campos condicionais são recusados fora do seu tipo

NCM, CEST, GTIN, peso e dimensões em item de serviço, ou código LC 116 em matéria-prima, respondem 400 ITEM_FIELD_NOT_APPLICABLE com o nome do campo e do tipo. Ignorar silenciosamente é pior — o usuário acha que gravou.

RN-ITM-05

Nenhum campo fiscal é obrigatório para criar item

Nem para aprovar. O tenant pode elevar campos a obrigatórios via ItemType.requiredFields, mas o default do produto é opcional.

RN-ITM-06

Troca de tipo revalida os campos condicionais

Ao mudar itemTypeId, o domínio verifica os campos preenchidos contra o novo tipo. Campos que deixam de se aplicar são listados na resposta e a operação só prossegue com discardIncompatibleFields: true explícito. Os valores descartados ficam no log de auditoria.

RN-ITM-07

A descrição padronizada é derivada

Recalculada a cada alteração de basicName ou de atributo marcado como usedInDescription. Só é editável quando a categoria não tem esquema de atributos. Truncada em 200 caracteres com aviso quando os atributos geram texto maior.

RN-ITM-08

Atributos obrigatórios da categoria bloqueiam a submissão

Não bloqueiam a criação do rascunho — bloqueiam submit. Cadastrar em duas sessões é um caso real; aprovar item incompleto não é.

RN-ITM-09

Conversão de unidade é por item, com par inteiro

numerator e denominator inteiros positivos. A unidade base não pode figurar como destino. Conversão entre classes distintas só existe por item e nunca é herdada da conversão padrão.

RN-ITM-10

A unidade base é imutável após o primeiro documento

Requisição, cotação ou pedido referenciando o item congelam a base. Alteração responde 409 ITEM_BASE_UOM_LOCKED_CONFLICT e sugere criar item sucessor.

RN-ITM-11

Item só classifica em nó folha ativo

Criar filho sob um nó que tem itens exige reclassificar esses itens na mesma transação. A API devolve 409 CATEGORY_NODE_HAS_ITEMS_CONFLICT com a contagem, e o comando aceita um mapa de reclassificação.

RN-ITM-12

Profundidade da árvore respeita o teto do tenant

Criar nó além de catalogMaxCategoryDepth responde 400 CATEGORY_MAX_DEPTH_EXCEEDED. Aumentar o teto é operação de admin e não afeta nada existente; diminuir é recusado enquanto houver nós mais profundos.

RN-ITM-13

Categoria e tipo inativos não recebem novos itens

Itens existentes não são afetados nem reclassificados. Ao desativar, a resposta informa a contagem de itens ativos atingidos.

RN-ITM-14

Item não é excluído fisicamente

Havendo requisição, cotação, pedido ou nota vinculada, só há mudança de fase. O DELETE existe apenas para itens em Draft nunca submetidos.

RN-ITM-15

Fornecedor bloqueado não apaga o vínculo

SupplierStatusChangedEvent muda o ItemSupplier para Blocked, preservando preço, histórico e prioridade. Reaprovação do fornecedor restaura o vínculo no status anterior.

RN-ITM-16

Compliance vencido bloqueia a compra quando declarado

blocksPurchaseWhenExpired impede requisição e pedido a partir do vencimento. Job diário notifica 30, 15 e 5 dias antes. É o comportamento exigido para EPI sem CA válido.

RN-ITM-17

Destinação predominante é obrigatória e sobrescrevível

Default vem do ItemType. A linha do pedido pode sobrescrever, e a sobrescrita é auditada — é evidência da intenção no momento da compra, defensável em fiscalização.

RN-ITM-18

Preço de referência exige moeda e unidade

Os três campos gravam juntos ou nenhum grava. Preço sem unidade não é comparável e vira ruído no comparativo de cotações.

RN-ITM-19

Concorrência otimista em todo update

expectedVersion obrigatório no corpo do PUT, checado antes de tocar o agregado. Versão defasada responde 409 ITEM_VERSION_CONFLICT com a versão atual na mensagem.

RN-ITM-20

Auditoria completa com diff de campos

Item, ItemSupplier, CategoryNode e AttributeDefinition são IAuditableEntity, categoria Catalog. Registram campo, valor anterior, valor novo, usuário, IP e timestamp.

Camada API

Endpoints

Prefixo api/v{version}/nexio. Todos exigem [Authorize] e uma permissão explícita via [RequirePermission]. Sucesso é 200, inclusive nos POST, seguindo o padrão já estabelecido no módulo Admin.

Item

RotaPermissãoRetornoErros
GET /catalog/itemscatalog:readPaged de ItemSummaryDto400
GET /catalog/items/{id}catalog:readItemDto completo, com atributos, conversões, documentos e compliance404
POST /catalog/itemscatalog:createItemDto em Draft400 · 409 · 422
PUT /catalog/items/{id}catalog:updateItemDto400 · 404 · 409
POST /catalog/items/duplicate-checkcatalog:readLista de candidatos com score e camada de match. Sem efeito colateral.400
POST /catalog/items/{id}/submitcatalog:createItemDto em PendingApproval409 · 422
POST /catalog/items/{id}/approvecatalog.item:approveItemDto em Approved404 · 409
POST /catalog/items/{id}/rejectcatalog.item:approveItemDto em Rejected400 · 404 · 409
PUT /catalog/items/{id}/lifecycle-phasecatalog:updateItemDto400 · 404 · 409
POST /catalog/items/{id}/clonecatalog:cloneNovo item em Draft, sem código interno do original404
DELETE /catalog/items/{id}catalog:deleteSem conteúdo. Só rascunho nunca submetido404 · 409
GET /catalog/items/{id}/auditcatalog.audit:readPaged de eventos com diff de campos404

Coleções do item

RotaPermissãoObservação
PUT /catalog/items/{id}/attributescatalog:updateReescreve o conjunto inteiro. Recalcula descrição e assinatura.
PUT /catalog/items/{id}/uom-conversionscatalog:updateReescreve o conjunto inteiro.
POST /catalog/items/{id}/photoscatalog:updateMultipart. Máx. 5 no total.
DELETE /catalog/items/{id}/photos/{photoId}catalog:updateChama DeleteAsync do storage.
POST /catalog/items/{id}/documentscatalog:updateTipo, versão, emissão e validade obrigatórios.
PUT /catalog/items/{id}/compliancescatalog:updateReescreve o conjunto.
GET /catalog/items/{id}/supplierscatalog:readPaged, filtrável por status.
POST /catalog/items/{id}/supplierscatalog.supplier:managePreço exige catalog.price:manage adicionalmente.
PUT /catalog/items/{id}/suppliers/{supplierItemId}catalog.supplier:manageConcorrência por expectedVersion.
DELETE /catalog/items/{id}/suppliers/{supplierItemId}catalog.supplier:manageSó se nunca usado em cotação ou pedido; caso contrário, bloquear.

Configuração do catálogo

RotaPermissãoObservação
GET /catalog/categoriescatalog:readÁrvore completa ou subárvore via rootId + depth. Não paginada — é árvore, não lista.
POST /catalog/categoriescatalog.category:manageValida ciclo, profundidade e nome único entre irmãos.
PUT /catalog/categories/{id}catalog.category:manageMover nó recalcula path e level em cascata, em transação.
DELETE /catalog/categories/{id}catalog.category:manageDesativa em cascata. Recusa se houver item ativo no nó.
PUT /catalog/categories/{id}/attributescatalog.category:manageVincula atributos e define obrigatoriedade e ordem na descrição.
GET /catalog/attribute-definitionscatalog:readPaged, filtrável por tipo de dado.
POST /catalog/attribute-definitionscatalog.attribute:managedataType imutável após o primeiro valor gravado.
GET /catalog/item-typescatalog:readConsumido pelo formulário para decidir campos visíveis.
POST /catalog/item-typescatalog.itemtype:managerequiresGoodsTaxCode e requiresServiceTaxCode são mutuamente exclusivos.
GET /catalog/uomsuom:readMódulo UoM. Filtro por classe, consumido pelo autocomplete.
GET /catalog/ncmcatalog:readTabela NCM espelhada, busca full-text sobre a descrição concatenada. Global, não por tenant.

Listagem de itens — filtros e ordenação

FiltrosOrdenável porPadrão
search, categoryNodeId (+includeDescendants), itemTypeId, approvalStatus, lifecyclePhaseId, isPurchasable, supplierId, hasExpiredCompliance, completenessBelowinternalCode, standardDescription, updatedAt, completenessScoreupdatedAt desc

Página padrão 20, teto 100, valores fora da faixa ajustados. sortBy inválido responde 400 SORT_FIELD_INVALID com a lista permitida na mensagem. includeDeleted=true é recusado. A busca cobre standardDescription, basicName, internalCode, externalCode, mpn, gtin e keywords. As DTOs resolvem nomes de fornecedor e de categoria pelo diretório — nunca devolvem só Guids.

Contrato

Códigos de erro

Payload de erro: code, traceId e type, com mensagem resolvida em pt-BR, en e es-ES. Onde a tela precisa de dados para agir — lista de duplicatas, campos incompatíveis — ela consulta antes; o erro fica como guarda de corrida.

CódigoHTTPQuando
ITEM_NOT_FOUND404Item inexistente no tenant
ITEM_BASIC_NAME_REQUIRED / _TOO_LONG400Nome básico vazio ou > 60
ITEM_TYPE_REQUIRED / _INACTIVE400Tipo ausente ou desativado
ITEM_CATEGORY_NOT_LEAF400Categoria informada tem filhos ativos
ITEM_BASE_UOM_REQUIRED400Unidade base ausente
ITEM_UOM_CLASS_NOT_ALLOWED400Classe da unidade não permitida pelo tipo
ITEM_FIELD_NOT_APPLICABLE400Campo preenchido que o tipo não usa — mensagem nomeia campo e tipo
ITEM_GTIN_INVALID400Comprimento ou dígito verificador inválido
ITEM_NCM_INVALID400NCM não existe na tabela vigente
ITEM_CEST_REQUIRES_NCM400CEST informado sem NCM
ITEM_PRICE_INCOMPLETE400Preço sem moeda ou sem unidade
ITEM_CONVERSION_INVALID400Numerador ou denominador ≤ 0, ou destino igual à base
ITEM_ATTRIBUTE_NOT_IN_SCHEMA400Atributo não pertence ao esquema da categoria
ITEM_ATTRIBUTE_VALUE_INVALID400Fora da faixa, fora da lista restritiva ou tipo incompatível
ITEM_DUPLICATE_CONFLICT409Match em camada bloqueante — mensagem traz o código interno do item existente
ITEM_VERSION_CONFLICT409Versão defasada no PUT
ITEM_BASE_UOM_LOCKED_CONFLICT409Unidade base já usada em documento
ITEM_INVALID_TRANSITION_CONFLICT409Transição de aprovação inválida a partir do status atual
ITEM_HAS_DOCUMENTS_CONFLICT409Exclusão de item com requisição, cotação, pedido ou nota
ITEM_REQUIRED_ATTRIBUTES_MISSING422Submissão sem atributos obrigatórios — lista os códigos faltantes
ITEM_REJECTION_REASON_REQUIRED422Rejeição sem motivo ou com menos de 20 caracteres
CATEGORY_NODE_NOT_FOUND404Nó inexistente
CATEGORY_MAX_DEPTH_EXCEEDED400Profundidade acima do teto do tenant
CATEGORY_CYCLE_DETECTED400Mover nó para dentro da própria subárvore
CATEGORY_NAME_DUPLICATED_AMONG_SIBLINGS409Nome repetido entre irmãos
CATEGORY_NODE_HAS_ITEMS_CONFLICT409Criar filho sob nó com itens, ou desativar nó com item ativo
ATTRIBUTE_DATA_TYPE_LOCKED_CONFLICT409Alterar tipo de dado com valores já gravados
ITEM_SUPPLIER_DUPLICATED_CONFLICT409Par item × fornecedor já existe
ITEM_SUPPLIER_BLOCKED_CONFLICT409Fornecedor bloqueado no módulo Fornecedores
ITEM_COMPLIANCE_EXPIRED_CONFLICT409Compra de item com compliance vencido e bloqueante

Autorização

Permissões

Padrão {verbo}:{recurso}, verbo derivado do método HTTP por [AutoPermission]. Um endpoint declara exatamente uma permissão; nada é implicado por outra.

PermissãoSituaçãoCobre
catalog:read / create / update / deleteexisteCRUD do item
catalog:import / export / cloneexisteImportação, exportação e clonagem
catalog.category:manageexisteÁrvore de categorias e vínculo de atributos
catalog.supplier:manageexisteItemSupplier
catalog.price:manageexistePreço de referência e preço do fornecedor
catalog.audit:readexisteHistórico por registro
catalog.item:approvenovoAprovar, rejeitar e forçar criação sobre duplicata bloqueante
catalog.attribute:managenovoDicionário de atributos
catalog.itemtype:managenovoTipos de item e fases do ciclo de vida

Grupos sugeridos: Nexio - Catálogo (read/create/update/clone/import/export), Nexio - Curadoria de Catálogo (+ approve, category, attribute, itemtype, price) e Nexio - Consulta de Catálogo (read). Um teste compara o conjunto seedado pelo atributo com o exigido pelo [RequirePermission], como já é feito no módulo Admin.

Onboarding

Importação em massa

É o maior gargalo de adoção: o cliente chega com uma planilha de dezenas de milhares de linhas de texto livre. O desenho abaixo assume isso.

ColunaRegra
external_codeopcChave de upsert. Presente e existente = atualiza; ausente = cria.
item_typeobrigCódigo do tipo.
category_pathobrigCaminho completo separado por >: FIXADORES > PARAFUSOS > SEXTAVADOS. Nós ausentes são criados quando createMissingCategories: true, respeitando maxDepth.
basic_nameobrigNome básico.
descriptioncondUsado apenas quando a categoria não tem esquema de atributos.
base_uomobrigSímbolo da unidade, validado contra as classes permitidas pelo tipo.
attr:{codigo}condColunas dinâmicas por atributo do esquema. O template baixado já vem com as colunas da categoria escolhida.
gtin, mpn, manufactureropcParticipam da antiduplicidade.
ncm, cest, service_code, ctribnac, nbsopcValidados, nunca obrigatórios.
reference_price, currency, price_uomopcOs três juntos ou nenhum.
supplier_code, supplier_part_number, purchase_uom, purchase_factor, price, lead_time, moqopcCria o ItemSupplier na mesma linha. Repetir a linha com fornecedor diferente adiciona vínculo.

Duas passadas: simular, depois aplicar

POST /catalog/items/import?mode=dryRun devolve o relatório completo — linhas válidas, erros por linha e campo, categorias que seriam criadas, duplicatas detectadas — sem gravar nada. O usuário corrige a planilha e reenvia em mode=apply. Importar 5.000 linhas e descobrir os erros depois é o caminho mais rápido para um catálogo sujo.

Erro por linha não interrompe

Linhas válidas entram; inválidas vão para o relatório com linha, campo e motivo. Limite de 10.000 linhas e 20 MB por arquivo, processamento assíncrono via GrydJobs, notificação por e-mail ao concluir com link para o relatório.

Itens importados nascem em rascunho

Salvo quando o usuário tem catalog.item:approve e marca autoApprove: true. Importação que aprova automaticamente por default transforma a planilha do cliente em verdade sem revisão.

Encadeamento

O que o item entrega ao fluxo de aprovação

O fluxo de aprovação é impactado por este cadastro em cinco pontos. Vale fixá-los agora, porque cada um é uma decisão que o motor de aprovação vai herdar pronta ou vai ter que improvisar.

O que o item forneceComo a aprovação usa
categoryNodeId + pathA regra amarra em qualquer nível da árvore e vale para os descendentes por rollup. Guardar o nó, nunca a lista expandida de folhas — assim subcategoria criada depois já nasce coberta pela política.
itemTypeIdDimensão de regra natural e barata: serviço e CAPEX quase sempre têm alçada diferente de material de consumo.
criticality e flags de políticarequiresContract, criticality e controlKind são candidatos a disparar aprovador funcional obrigatório — Jurídico em contrato, SESMT em produto controlado.
referencePriceInsumo de detecção de anomalia de preço: pedido muito acima da referência sinaliza ao aprovador, o que é a correção prescrita para rubber-stamping.
predominantUseSepara despesa de investimento na origem. É o dado que distingue alçada de OPEX de alçada de CAPEX sem depender do centro de custo.

A pergunta que fica aberta e precisa ser resolvida no motor, não aqui: um pedido tem N linhas, cada uma com sua categoria e seu tipo. O modelo atual do ApprovalFlow tem um CategoryId singular e o invariante de resolver para um único fluxo. A resposta do mercado é avaliar as regras contra as linhas e decidir sobre o pedido inteiro, com união deduplicada de aprovadores. Como não existe ainda entidade de requisição, essa decisão continua gratuita — e é o próximo assunto.

Limites

Fora de escopo e decisões pendentes

Fora de escopo · com gancho
  • Estoque — saldo, depósito, ponto de reposição, lote, número de série, validade. ItemType.controlsStock existe e é sempre false.
  • Variantes de item — o par cor/tamanho de varejo. Hoje cada combinação é um item; o esquema de atributos já suporta a modelagem quando fizer sentido.
  • Kit e lista técnica — item composto por outros. Exige decisão sobre explosão em requisição e cotação.
  • Catálogo de fornecedor externo — punchout, CIF, cXML. O ItemSupplier foi desenhado para receber isso depois.
  • Tradução de itens — Ariba e Coupa modelam i18n do item como entidade filha. Fica previsto, não implementado.
Decisões pendentes
  • Compartilhamento de esquema entre tenants. Vale entregar um esquema-semente por setor, ou cada tenant desenha o seu do zero? O primeiro reduz o custo de onboarding; o segundo evita impor taxonomia errada.
  • Extração assistida na importação. Mapear texto livre para atributos é tarefa em que LLM é excelente e verificável. Entra no MVP da importação ou depois?
  • Mapeamento categoria → conta contábil. Todo produto pesquisado carrega o eixo contábil; clientes vindos do Protheus vão cobrar. Não está nesta spec e provavelmente deveria estar na próxima.
  • Multi-empresa. TenantSettings tem um único CNPJ. Grupo econômico com N CNPJs duplica catálogo, categorias e itens. Decisão de tenancy, não de catálogo — mas o catálogo é quem mais sofre.