Especificação · Unidade de Medida

Unidade de Medida

O módulo mais curto do Nexio e o que mais decide se o comparativo de cotações vale alguma coisa. Aqui está o catálogo de unidades, a matemática da conversão e a cadeia que liga item, fornecedor, cotação, pedido e recebimento sem inventar um número no meio.

complementa Cadastro de Item e Cadastro de Fornecedor 2 agregados · 2 tabelas de conversão v1.4 · 09/09/2026

Ponto de partida

Cinco decisões

Decisão 1 · Código

Sigla local é a chave; UN/CEFACT é metadado. A sigla tem no máximo 6 caracteres porque é ela que vai para o campo uCom da NF-e e para o registro 0190 do SPED. O código UN/CEFACT Rec 20 fica ao lado, opcional e não único, para interoperabilidade. Escolher só um dos dois é o erro.

Decisão 2 · Fator

Numerador e denominador inteiros, nunca fator decimal. Um fator de conversão é uma razão, não uma medida. 1/3 gravado como 0,333333 destrói informação que era exata na origem, e o erro acumula. Isso coloca o Nexio junto da SAP e à frente de Oracle, NetSuite, D365 e Coupa, que usam decimal.

Decisão 3 · Hierarquia

Item × fornecedor vence item, que vence global — e a falta de conversão é erro, não 1:1. O nível de fallback implícito é o bug que produz um pedido de 84 caixas quando o comprador queria 84 unidades.

Decisão 4 · Snapshot

Todo documento copia o fator para a linha. Se o fornecedor mudar a caixa de 12 para 10 amanhã, pedidos em aberto não podem mudar de significado retroativamente. É o mesmo princípio que a legislação do SPED já impõe.

Decisão 5 · Serviço

A classe Serviço não converte, nem internamente. Não existe "quantas horas tem uma verba". Duas propostas sem denominador comum são exibidas lado a lado como não comparáveis, em vez de receberem um preço unitário sem significado.

Fundamentos

Seis princípios

1. Nenhum produto sério tem "a UoM do item"

Coupa carrega seis papéis de unidade; D365 e NetSuite, três; SAP separa unidade base, unidade de pedido e unidade de preço. "Unidade de medida padrão" como campo único é um erro de modelagem que nenhum produto real comete.

2. A unidade base é a única constante da cadeia

A requisição pode vir em uma unidade, a cotação em outra, o pedido em outra e o recebimento em outra. Tudo é comparado, somado e saldado na unidade base do item. Ela é imutável depois da primeira transação, porque trocá-la reescreve o significado de todo o histórico.

3. Seja rígido com quantidade, tolerante com valor

Preservar quantidade original e fatores evita mudar o fato declarado. A quantidade base armazenada tem seis casas e é uma projeção; valor monetário é calculado sem usar essa aproximação intermediária, com arredondamento no total da linha nas casas da moeda.

4. Preço mora na unidade em que foi acordado

Não normalize preço; normalize quantidade. Derivar preço por divisão e depois multiplicar é como nasce a divergência de centavos contra a nota do fornecedor — e, no Brasil, a rejeição 629 ou 630 na autorização da nota.

5. Campos que mudam o significado de dado gravado são imutáveis

Código, classe e fator canônico de uma unidade não se editam depois do primeiro uso. Nome, rótulo e código de interoperabilidade sim. Toda a matriz de governança decorre desta frase.

6. O sistema nunca inventa um fator

Quando não há conversão cadastrada, a resposta é erro explícito e uma pergunta ao usuário — nunca um 1:1 silencioso, nunca uma estimativa por classe.

Modelo

Mapa de entidades

UomClass lista fechada Contagem, Peso, Volume, Comprimento, Área, Tempo, Serviço. O tenant não cria classe.
StandardConversion global · intraclasse Conversões inequívocas dentro da classe: KG↔G, M↔CM, L↔ML. Mantidas pelo Nexio.
RefUneceUom referência · leitura Os 1.755 códigos ativos da Rec 20, para mapeamento e integração. Nunca exposta como lista de escolha.
ItemUomConversion nível 2 · por item "1 CX deste parafuso = 12 UN". Append-only com vigência.
UnitOfMeasure aggregate root · global ou do tenant Sigla de até 6 caracteres, classe, fator canônico, casas decimais, indivisibilidade, código Rec 20/21.
ItemSupplierOffer nível 1 · prioridade máxima Unidade de compra do fornecedor, com fator próprio, preço e quantidade de precificação.
Linhas de documento snapshot Requisição, cotação, pedido, recebimento e nota copiam o fator vigente. A referência é default; o snapshot é a verdade.
Dual UoM / catch weight fora de escopo Compra por caixa, paga por peso real. Gancho previsto, não implementado.
Unidade de estocagem fora de escopo O storage-uom da Coupa. Complexidade que só faz sentido com estoque.

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.

Estrutura

Classes

Lista fechada. O tenant escolhe a classe ao criar uma unidade própria, mas não cria classe nova — porque a classe é o que autoriza ou proíbe uma conversão.

ClasseBaseConverte?Observação
CountUNsó por itemCX, PT, FD, DZ, CT, MI. Não existe conversão global de caixa — depende do item.
WeightKGglobal + itemG, MG, TN. Conversões padrão inequívocas.
VolumeLTglobal + itemML, M3, GL.
LengthMTglobal + itemMM, CM, KM, metro linear.
AreaM2global + itemCM2, HA.
TimeHRglobal parcialMIN, DIA, SEM. MES e ANO não têm conversão global — "1 mês = 30 dias" é convenção contratual, não física. Ver Casos difíceis.
Service—nuncaVB (verba), SERV, OS, ATV, HH, HM. Sem unidade base, sem conversão, sem preço normalizado.

Carga inicial

Catálogo-semente

Cerca de 40 unidades globais, mapeadas manualmente para códigos verificados como ativos na Rec 20 (revisão 17) e na Rec 21 para embalagens. Os 1.755 códigos ativos entram numa tabela de referência de leitura — não se expõem 1.755 opções ao comprador.

ClasseSiglaNomeRec 20/21Fator canônico
CountUNUnidadeEA1
CountPCPeçaH871
CountPARParPR2
CountDZDúziaDZN12
CountCTCentoCEN100
CountMIMilheiroMIL1 000
CountRSResmaRM500 FL (convenção)
CountFLFolhaLEF1
CountKITKitKTpor item
CountJGJogo / conjuntoSETpor item
CountCXCaixaXBX (Rec 21)por item
CountPTPacoteXPK (Rec 21)por item
CountFDFardoXBE (Rec 21)por item
CountSCSacoXSA (Rec 21)por item
CountRLRoloXRO (Rec 21)por item
WeightKGQuilogramaKGMbase
WeightGGramaGRM10⁻³ KG
WeightMGMiligramaMGM10⁻⁶ KG
WeightTNToneladaTNE10³ KG
VolumeLTLitroLTRbase
VolumeMLMililitroMLT10⁻³ LT
VolumeM3Metro cúbicoMTQ10³ LT
LengthMTMetroMTRbase
LengthCM / MM / KMCentímetro / milímetro / quilômetroCMT / MMT / KMT10⁻² / 10⁻³ / 10³ MT
AreaM2Metro quadradoMTKbase
TimeHR / MIN / DIAHora / minuto / diaHUR / MIN / DAYbase / 1/60 / 24 HR
TimeMES / ANOMês / anoMON / ANNsem conversão global
ServiceVBVerbaLS (lump sum)—
ServiceSERVServiçoE48—
ServiceOSOrdem de serviçoE51—
ServiceATVAtividadeACT—
ServiceHH / HMHomem-hora / homem-mêsLH / 3C—

As dez unidades com "por item" na última coluna são as de embalagem — KIT, JG, CX, PT, FD, SC, RL — mais as que dependem de convenção (RS) ou de contrato (MES, ANO). As de embalagem nascem com isPackaging verdadeiro e sem fator canônico: a semente não inventa um, e o cadastro não aceita um (RN-UOM-19). Os mapeamentos menos óbvios e mais úteis: LS (lump sum) cobre exatamente "verba", E48 cobre "serviço", e ACT, E51, LH e 3C cobrem a família de serviço sem inventar código. Marcar unece20Code como nulo é legítimo e precisa ser suportado.

O coração

Conversão

Hierarquia com fallback explícito

1. ItemSupplierOffer.numerator / denominator      item × fornecedor   ← vence
2. ItemUomConversion.numerator / denominator item
3. StandardConversion (só intraclasse, e só  global
   entre unidades com fator canônico)
4. → erro explícito. Nunca fator implícito 1:1.

O nível 4 é o mais importante do desenho inteiro. Nenhum sistema deve assumir fator 1 quando não encontra conversão — é assim que nasce o pedido de 84 caixas para quem queria 84 unidades.

A porta pela qual o 1:1 voltaria — fechada em 09/09/2026. As unidades de embalagem (CX, PT, FD, SC, RL, KIT, JG) são da classe Count, e enquanto o fator canônico era obrigatório fora da classe Serviço não havia valor correto para elas: ou a semente não rodava, ou alguém gravava 1/1 — e aí o nível 3 resolvia CX → UN como um para um, em silêncio, que é literalmente o bug que a Decisão 3 e a RN-UOM-06 existem para impedir. Agora a unidade de embalagem é marcada (isPackaging), não tem fator canônico, e a regra está escrita: unidade sem fator canônico nunca resolve no nível 3 — cai direto no 422. O nível 3 exige canônico nos dois lados da conversão. Quem sabe quantas unidades cabem numa caixa é o item, no nível 2, ou o fornecedor, no nível 1.

Por que inteiros e não decimal

Um fator de conversão é uma razão, não uma medida, e razões de embalagem quase sempre têm denominador pequeno: 6, 10, 12, 24, 100, 1000.

Luva · base UN · caixa de 3
Fator decimal (6 casas)Num/den inteiros
ArmazenadoUN→CX = 0,333333num=1, den=3
300 UN → CX99,9999 CX100 CX exato
Volta 100 CX → UN300,000300 UN300 UN exato
Erro por ida e volta+0,0003 UN0
Após 1.200 recebimentos+0,36 UN de estoque fantasma0

O caso que dói de verdade é o de fatores irracionais aproximados. A documentação da Oracle traz pound → gram = 454; o valor exato é 453,59237, um erro relativo de 0,09%.

20 toneladas compradas em libras          = 44.092 lb
convertido com fator 454                  = 20.017.768 g = 20.017,77 kg
erro                                      = +17,77 kg
a R$ 8,50/kg                              = R$ 151,05 de divergência pedido × nota

com num/den = 45359237 / 100000           → erro zero

Note que esse par não caberia nos campos de conversão da SAP, limitados a 5 dígitos. Dimensione os inteiros como bigint, não int.

Regras de implementação

  • numerator e denominator são bigint, ambos maiores que zero, normalizados por MDC na gravação.
  • Aritmética em decimal exato, nunca em ponto flutuante.
  • Ordem obrigatória: qty × num ÷ den — multiplicar antes de dividir. Inverter para qty × (num ÷ den) reintroduz exatamente o erro que se acabou de eliminar.
  • Conversão entre classes diferentes existe apenas por item e nunca é herdada da conversão padrão.
  • ItemUomConversion é append-only com vigência. Editar o fator de uma conversão em uso é proibido — é literalmente a regra do registro 0220 do SPED, que exige criar um novo código de unidade quando a embalagem muda de conteúdo, em vez de alterar o fator do código existente.

Contrato vigente · revisão consolidada

Precisão e arredondamento

GrandezaPersistênciaCálculo
Quantidade original e normalizadanumeric(19,6)Preservar original, unidade e fatores inteiros. Normalizada é projeção com seis casas HALF_UP; declarar precisão e resíduo. Não é representação exata de toda fração.
Preço unitário acordadonumeric(19,10)Preservar unidade e quantidade de precificação; não derivar e armazenar preço arredondado por unidade base.
Total monetárionumeric(21,4)Arredondar HALF_UP nas casas da moeda (0–4). Limitar magnitude ao tipo; overflow é erro declarado.
Fatoresbigint / bigintPositivos, reduzidos por MDC; multiplicar antes de dividir. Usar precisão decimal ampliada ou racional para evitar overflow intermediário.

Validar a precisão da quantidade informada contra a unidade; qualquer ajuste deve ser apresentado antes da confirmação. Para planejamento de unidade indivisível, arredondar a sugestão para cima e mostrar o acréscimo, mínimo e múltiplo comercial. Nunca alterar silenciosamente quantidade recebida ou evidência da nota.

Dinheiro é calculado da quantidade original e fatores, sem usar a quantidade base já arredondada. Total do documento soma linhas; rateio fecha na moeda original e o total funcional soma fatias convertidas, conforme Moeda. Exportação fiscal pode exigir precisão distinta: o adaptador conserva valores originais e registra a transformação exigida pelo leiaute.

Aritmética

Preço e a unidade dele

O erro que produz divergência contra a nota é derivar preço unitário por divisão e depois multiplicar.

Luva nitrílica · base UN · 1 CX = 12 UN · R$ 37,00/CX · pedido de 7 CX · nota do fornecedor: R$ 259,00
EstratégiaCálculoTotalDivergência
Preço guardado por CX, cálculo na unidade de compra7 × 37,00259,00R$ 0,00
Preço derivado por UN com 10 casas84 × 3,0833333333259,00R$ 0,00
Preço derivado por UN com 4 casas84 × 3,0833 = 258,9972259,00R$ 0,00
Preço derivado por UN com 2 casas84 × 3,08258,72−R$ 0,28

No exemplo, preço unitário reduzido a quatro casas gera R$ 259,00 após arredondar 84 × 3,0833 = 258,9972; com duas casas, gera R$ 258,72. A divergência depende da quantidade e do ponto de arredondamento. O contrato evita acumular aproximações preservando o preço acordado.

As três regras

  1. Guarde o preço na unidade em que foi acordado, com a unidade junto: priceAmount + priceUomId + priceQty. Não normalize preço para a unidade base do item.
  2. Calcule o total na unidade de precificação: lineTotal = ROUND(orderedQty × convNum × priceConvNum × priceAmount ÷ (convDen × priceConvDen × priceQty), Currency.minorUnits). Converta a quantidade, nunca o preço.
  3. priceQty resolve item barato. Uma lâmpada a R$ 0,36263 cadastra-se como priceQty = 1000 e priceAmount = 362,63. Um parafuso a R$ 0,0037 vira priceQty = 1000, priceAmount = 3,70. É o PEINH da SAP e o price quantity do D365, e elimina inteiramente a questão de casas decimais de preço.

No Brasil isso deixa de ser questão contábil e vira rejeição fiscal. A SEFAZ valida vProd = qCom × vUnCom (rejeição 629) e vProd = qTrib × vUnTrib (rejeição 630), com tolerância de exatamente R$ 0,01 para mais ou para menos. No exemplo acima, com uTrib = UN: vUnTrib = 259,00 ÷ 84 = 3,0833333333 e a diferença fica em R$ 0,0000000028 — passa. Truncando a 2 casas, 84 × 3,08 = 258,72, diferença de R$ 0,28 — nota rejeitada. Um erro de modelagem de unidade de medida aqui não gera divergência contábil: gera nota que não autoriza.

Vínculo

Unidade e item

Campo no itemRegra
baseUomIdObrigatório. A classe precisa constar em ItemType.allowedUomClasses. Imutável após a primeira transação — requisição, cotação, pedido ou recebimento congelam a base. Alterar responde 409 e sugere criar item sucessor.
defaultPurchaseUomIdOpcional; default é a base. Precisa ter conversão cadastrada para a base.
conversions[]Uma linha por unidade de destino. A unidade de destino pode ser de outra classe, e é assim que o aço comprado em SC e cobrado em TN, e o cabo comprado em MT e cobrado em KG, entram no cadastro — corrigido em 09/09/2026, quando ficou claro que a regra da coleção proibia os três casos que a própria especificação usa como prova. A equivalência interclasse é deste item: 1 MT daquele cabo pesa o que aquele cabo pesa, e nada disso é herdado nem vira conversão global. A unidade base não pode figurar como destino. Máximo 20.
qtyDecimalsOverrideOpcional. Um item específico pode ser mais restrito que a unidade — parafuso vendido só em caixa fechada.

Item de tipo com classe Service aceita apenas unidades dessa classe e não admite nenhuma linha de conversão. É validação de domínio, não de tela.

Contrato vigente · revisão consolidada

Oferta comercial e conversões

O nível mais específico de conversão é a versão de ItemSupplierOffer selecionada; depois a conversão do item e, por último, o fator global intraclasse. Embalagem sem fator canônico não usa conversão global implícita. ItemSupplier continua sendo a relação; preço referencia a oferta.

A oferta contém purchaseUomId, numerator/denominator, código comercial, supplierUomLabel, MOQ, múltiplo, prazo e tolerâncias. Reajustar preço não altera fator; mudar embalagem ou condição relevante cria revisão de oferta. A caixa de 12 e a caixa de 24 podem coexistir para o mesmo fornecedor e item, com identidades distintas.

Ao receber 70 UN contra 7 CX de 12, registrar a contagem real e divergência de −14 UN; não reescrever o pedido para 7 CX de 10. Confirmar mudança de embalagem permite cadastrar a apresentação CX10 e revisar pedidos ainda abertos pelo processo de alteração. O recebimento ocorrido permanece registrado com pendência.

Fluxo

A cadeia ponta a ponta

EtapaUnidadeRegra
RequisiçãoA que o requisitante escolher, entre as conversões do itemSempre grava também a quantidade na base.
CotaçãoA que o fornecedor escolherNenhum produto impede o fornecedor de cotar em outra unidade — e é a norma. Grava preço, quantidade, unidade cotada e fator declarado.
PedidoUnidade de compra do fornecedorO documento que sai da empresa precisa ser executável por quem vai recebê-lo. Um pedido de "84 unidades" para quem só vende caixa fechada de 12 vira 7 caixas por interpretação do vendedor — ou 84 caixas por erro de digitação dele.
RecebimentoA que chegar fisicamentePrecisa aceitar unidade diferente da do pedido: o fornecedor quebrou a caixa e mandou 80 UN soltas. Converte para a base e a linha fica com 4 UN em aberto — sem arredondar.
NotaA que o fornecedor emitirRegistrar a unidade original. Se só guardar a convertida, perde-se a capacidade de auditar a divergência depois.

O que toda linha de documento carrega

orderedQty       numeric(19,6)   na unidade escolhida      →  7
orderedUomId                                               →  CX
orderedQtyBase   numeric(19,6)   na unidade base do item   →  84
baseUomId                                                  →  UN
convNum, convDen bigint          SNAPSHOT do fator          →  12, 1
priceAmount      numeric(19,10)                             →  37,0000000000
priceUomId                                                  →  CX
priceQty         numeric(19,6)                              →  1
priceConvNum,                    SNAPSHOT do fator          →  1, 12
priceConvDen     bigint          base → unidade de preço

convNum/convDen converte pedido → base; priceConvNum/priceConvDen converte base → preço. No exemplo, 7 CX × 12/1 × 1/12 × R$ 37 = R$ 259. Se o preço for por UN, só o segundo fator será 1/1. Ambos os fatores são congelados na linha.

O saldo em aberto da linha é calculado em unidade base, nunca na unidade do pedido — só assim é possível representar "sobraram 4 UN de uma linha de 7 CX".

Conferência a três (pedido × recebimento × nota)

Os três lados comparam na unidade base, usando o snapshot da linha e não o cadastro atual. A tolerância precisa ser percentual e absoluta ao mesmo tempo, passando se qualquer uma acomodar — o inverso do default de alguns ERPs, e é deliberado: divergências de arredondamento são absolutas e pequenas, divergências de preço são percentuais e grandes. Um piso absoluto de poucos centavos por linha elimina quase todo o ruído sem afrouxar controle real. E quando a nota vem em unidade que a versão de ItemSupplierOffer não conhece, não converta por classe genérica: levante exceção e peça o fator ao comprador — é exatamente o momento em que a embalagem mudou e alguém precisa saber.

Onde tudo isso paga

Comparativo de cotações

O erro clássico é comparar preço por unidade cotada.

Mesmo item · base UN
SupplierCota emPreço1 unidade cotada =Preço por UN
ACXR$ 37,0012 UNR$ 3,083333
BCXR$ 68,0024 UNR$ 2,833333
CMIR$ 2.900,001 000 UNR$ 2,900000
DUNR$ 3,051 UNR$ 3,050000

Sem normalizar, A parece o mais barato — R$ 37 é menos que R$ 68. Normalizado, B ganha e C é o segundo. Nenhum dos dois seria escolhido por leitura ingênua.

Normalize para a unidade base, não para a da requisição

A base é a única constante da cadeia. normalizedPrice = quotedPrice × den ÷ (num × priceQty), calculado com 10 casas e arredondado só na exibição. Duas cotações que diferem em R$ 0,0001 por unidade diferem em R$ 100 num pedido de um milhão.

Exiba as duas colunas

Preço na unidade cotada, para o comprador conferir contra a proposta em PDF, e preço normalizado, para decidir. Um comparativo que só mostra o normalizado gera desconfiança e o comprador refaz a conta na calculadora.

Mostre também o preço efetivo pela quantidade requisitada

Com múltiplo indivisível, o ranking muda. Para 100 UN: o fornecedor B, em caixa de 24, exige 5 caixas — 120 UN por R$ 340,00, ou R$ 3,40 por unidade efetiva. O A, em caixa de 12, exige 9 caixas — 108 UN por R$ 333,00, ou R$ 3,33. O "mais barato por unidade" perde para o "mais barato nesta compra".

Sem conversão, sem número inventado

Se o item é de classe Peso e o fornecedor cotou em UN sem fator declarado, o comparativo exibe as propostas lado a lado marcadas como não comparáveis e pede o fator ao comprador. Estimar seria pior que não comparar. A classe Serviço nunca normaliza — a comparação ali é por escopo, com campo qualitativo.

Recebimento

Tolerância

Avaliar tolerância na unidade de compra ou na unidade base dá o mesmo resultado quando a conversão é linear — o que não é o caso quando há arredondamento no meio.

Pedido de 7 CX (1 CX = 12 UN → 84 UN) · tolerância de +10%
Avaliada emLimiteEntrega de 8 CX (96 UN)
Unidade de compra (CX)7,7 CXrejeita
Unidade base (UN)92,4 UNrejeita
CX arredondado para caixa inteira8 CXaceita — a tolerância virou +14,3% sem ninguém decidir isso
  • Avalie sempre na unidade base, com quantidades não arredondadas. É o único ponto de comparação estável entre requisição, pedido e recebimento.
  • Não arredonde o limite. Compare receivedQtyBase ≤ orderedQtyBase × (1 + tol) diretamente em decimal.
  • Exponha o limite também na unidade de compra, marcando quando ele cai entre dois múltiplos indivisíveis: "+10% de 7 CX = 7,7 CX — a próxima caixa inteira excede a tolerância".
  • Tolerância de entrega a menor e "recebimento concluído" são coisas diferentes: a primeira aceita fechar a linha com menos; a segunda encerra o saldo em aberto e ainda assim admite recebimento dentro da tolerância a maior.

Operação

Governança do catálogo

Camada global

As ~40 unidades da semente, mantidas pelo Nexio. Read-only para o tenant, que não pode editar código, classe ou fator canônico. Pode ocultar o que não usa e pode renomear o rótulo de exibição — "PT" aparece como Pacote ou Sachê conforme o negócio, mas continua sendo PT no XML e na integração. Correções propagam automaticamente, o que só é seguro porque documentos guardam snapshot.

Camada do tenant

Unidades próprias — CARRETA, M3COMP, PTFUNC, TONKM. Não permitir isso é irreal: cada vertical tem a sua. Obrigatório escolher uma classe da lista fechada e informar o fator canônico, exceto na classe Serviço. Código de tenant não pode colidir com código global — se o global tem CT como cento e o tenant quer CT como cartão, isso é ambiguidade fatal em documento fiscal, e é bloqueado na criação com mensagem explícita.

Desativar unidade em uso

SituaçãoComportamento
Nunca usadaExclusão permitida
Usada em documento histórico fechadoExclusão bloqueada; desativação permitida. Documentos antigos continuam legíveis, com marca visual de unidade inativa
Usada em documento em aberto (RFQ, pedido não recebido)Desativação bloqueada, com a lista dos documentos que impedem
É unidade base de item ativoDesativação bloqueada, sempre

A mensagem de erro precisa nomear os documentos, não dizer "unidade em uso". A diferença entre um sistema usável e um irritante costuma ser essa lista.

Matriz de imutabilidade

CampoDepois do primeiro uso
codeimutável É a chave que foi para o XML da NF-e, para o e-mail do fornecedor e para o PDF do pedido
uomClassimutável Mudar a classe reinterpreta toda conversão existente
canonicalNum / Den / Expimutável Corrigir um fator errado exige criar nova unidade e migrar item por item. Doloroso de propósito
isPackagingimutável Liga e desliga o nível 3 da hierarquia de conversão — mudá-lo reinterpreta toda resolução já feita
qtyDecimalsassimétrico Aumentar é livre; diminuir é bloqueado se houver quantidade gravada com mais casas
isIntegerOnlyassimétrico Ligar é bloqueado se houver quantidade fracionária gravada; desligar é livre
name, unece20Code, unece21Codeeditável São metadados de exibição e integração

O princípio geral, que vale registrar como decisão de arquitetura: campos que alteram o significado numérico de dados já gravados são imutáveis; campos que alteram apresentação são editáveis. Toda a matriz acima decorre disso.

Validação do modelo

Casos que quebram modelos ingênuos

CasoO que quebraComo o modelo resolve
Compra por milheiroFator 1000 com preço por milheiro; dividir dá dízimaMI com num=1000, den=1 e preço guardado por MI. Nunca derivar preço por UN.
Preço por tonelada, compra por sacoUnidade de compra e unidade de preço são diferentespurchaseUom = SC, priceUom = TN, com conversão SC→KG por item. É o motivo de priceUomId existir separado.
Service por hora vs. por verbaNão há denominador comumClasse Serviço não converte. Comparativo marca como não comparável e compara por escopo.
Aço e cabo: vende por metro, cobra por pesoConversão entre classes (Comprimento → Peso) que só vale para aquele itemConversão interclasse por item, declarada na coleção conversions[] daquele item e jamais herdada da conversão padrão (RN-UOM-07).
Embalagem muda de conteúdo entre lotesEditar o fator reescreve o históricoNovo código de unidade (CX10) — decidido em 08/09/2026, não é mais alternativa —, nova revisão de ItemSupplierOffer apontando para ela, mais o snapshot na linha do documento. Ver § Unidade, item e fornecedor e RN-ITM-25.
Resma, fardo, pacoteSiglas de embalagem sem equivalente na Rec 20Rec 21 (XBX, XPK, XBE) para embalagem; RM existe na Rec 20 para resma.
Locação por mês"1 mês = 30 dias" é convenção contratual, não físicaMES não tem conversão global para DIA. Se o contrato define, é conversão por item.
Compra por caixa, pagamento por peso realDuas quantidades verdadeiras para a mesma linhaDual UoM — fora do MVP. Enquanto isso, o recebimento aceita a unidade de peso e a divergência sai na tolerância.

Rastreabilidade

Regras de negócio

RN-UOM-01

Código tem no máximo 6 caracteres, caixa alta, único no escopo

Limite herdado da NF-e e do registro 0190 do SPED. Código de tenant não pode colidir com código global.

RN-UOM-02

Toda unidade pertence a exatamente uma classe da lista fechada

O tenant escolhe a classe; não cria classe. A classe é o que autoriza ou proíbe uma conversão.

RN-UOM-03

Fator canônico é obrigatório, exceto em Serviço e em embalagem

Revista em 09/09/2026. Unidade sem fator canônico fora de Serviço e fora de embalagem responde 400 UOM_CANONICAL_FACTOR_REQUIRED; unidade com isPackaging e com fator canônico preenchido responde 400 UOM_CANONICAL_FACTOR_NOT_ALLOWED. As duas metades importam: sem a primeira, unidade física fica fora de toda agregação; sem a segunda, alguém grava 1/1 na caixa e o nível 3 devolve conversão silenciosa. Ver RN-UOM-19.

RN-UOM-04

Conversão usa numerador e denominador inteiros positivos

bigint, normalizados por MDC na gravação. Zero ou negativo responde 400.

RN-UOM-05

Multiplicar antes de dividir, em decimal exato

qty × num ÷ den, nunca qty × (num ÷ den), nunca em ponto flutuante. É regra de implementação com teste automatizado dedicado.

RN-UOM-06Oferta selecionada tem precedência

Resolver pela revisão de ItemSupplierOffer selecionada, depois ItemUomConversion e depois conversão global intraclasse com fatores canônicos. Ausência resulta UOM_CONVERSION_NOT_CONFIGURED_UNPROCESSABLE, sem estimativa implícita.

RN-UOM-07

Conversão entre classes só existe por item

Nunca global, nunca herdada. Comprimento para Peso vale para aquele cabo, não para "cabo". E é permitida: a coleção conversions[] do item aceita unidade de destino de outra classe quando o item declara a equivalência — corrigido em 09/09/2026, quando ficou claro que a regra da coleção dizia "mesma classe da base" e proibia os casos SC → TN e MT → KG que a especificação usa como prova. A classe Service continua sem conversão alguma (RN-UOM-08).

RN-UOM-08

Classe Serviço não admite conversão alguma

Nem entre unidades da própria classe. Tentativa responde 400 UOM_SERVICE_CLASS_NOT_CONVERTIBLE.

RN-UOM-09Conversão usada é versionada

Não editar fatores usados em documento. Publicar nova revisão com validade, preservando a anterior. Mudança física de embalagem ganha apresentação própria na oferta; nome CX10 é convenção do produto.

RN-UOM-10

Todo documento copia o fator para a linha

convNum, convDen e — desde 09/09/2026 — priceConvNum e priceConvDen são snapshot, não referência. Documento emitido nunca muda de significado, nem na quantidade nem no total. Ver RN-ITM-29.

RN-UOM-11Normalização tem precisão declarada

Preservar quantidade original, unidade e fatores. Quantidade base armazenada em numeric(19,6) é projeção arredondada HALF_UP; cálculo monetário usa os fatores originais sem depender desse arredondamento. Comparação e liquidação conservam também as parcelas originais e seus resíduos.

RN-UOM-12Sugestão indivisível mostra o acréscimo

Planejamento arredonda para cima conforme unidade, MOQ e múltiplo, com confirmação. Fato recebido e quantidade fiscal original não são aumentados automaticamente.

RN-UOM-13Total usa as casas da moeda

Calcular em precisão ampliada, arredondar uma vez por linha em Currency.minorUnits e persistir numeric(21,4). Total do documento é soma das linhas. HALF_UP é decisão do produto; adaptador fiscal valida o leiaute aplicável.

RN-UOM-14

Preço é guardado na unidade acordada, com quantidade de precificação

priceAmount, priceUomId e priceQty gravam juntos. Preço nunca é derivado por divisão para armazenamento.

RN-UOM-15

Unidade base do item é imutável após a primeira transação

Requisição, cotação, pedido ou recebimento congelam a base. Alteração responde 409 com a sugestão de criar item sucessor.

RN-UOM-16

Tolerância é avaliada em unidade base, sem arredondar o limite

Arredondar o limite para o múltiplo indivisível seguinte altera silenciosamente a tolerância efetiva.

RN-UOM-17

Comparação sem conversão conhecida é recusada, não estimada

O comparativo exibe as propostas sem preço normalizado, marcadas, e pede o fator ao comprador.

RN-UOM-18

Campos que mudam o significado de dado gravado são imutáveis

Código, classe e fator canônico após o primeiro uso. Nome e códigos de interoperabilidade permanecem editáveis. isPackaging entrou na lista em 09/09/2026.

RN-UOM-19

Unidade de embalagem não tem fator canônico, e sem canônico não há nível 3

Nova em 09/09/2026. isPackaging marca a unidade cujo conteúdo depende do item — CX, PT, FD, SC, RL, KIT, JG. Ela não tem fator canônico, e a hierarquia de resolução passa a dizer isto de frente: unidade sem fator canônico nunca resolve no nível 3, cai direto no 422 UOM_CONVERSION_NOT_CONFIGURED_UNPROCESSABLE. O que se fecha aqui é a porta de entrada do 1:1 implícito: com o canônico obrigatório e nenhum valor correto para a caixa, a saída de menor esforço era gravar 1/1 — e uma caixa que vale uma unidade é o pedido de 84 caixas para quem queria 84 unidades. Quem sabe o conteúdo da embalagem é o item (nível 2) ou o fornecedor (nível 1).

Camada API

Endpoints

RotaPermissãoObservação
GET /uomsread:uomsPaged. Filtros: search, uomClass, scope, isActive. Consumido pelo autocomplete dos formulários.
GET /uoms/{id}read:uomsCom as conversões padrão da classe.
POST /uomsmanage:uomsCria unidade do tenant. Recusa colisão de código com a camada global.
PUT /uoms/{id}manage:uomsRespeita a matriz de imutabilidade. Campos bloqueados respondem 409 nomeando o campo.
PATCH /uoms/{id}/deactivatemanage:uomsDevolve a lista de documentos e itens que impedem, quando impedem.
PUT /uoms/{id}/aliasmanage:uomsRótulo de exibição do tenant sobre unidade global. Não altera o código.
PUT /uoms/visibilitymanage:uomsOculta unidades globais que o tenant não usa.
GET /uom-classesread:uomsLista fechada, com a unidade base de cada classe.
GET /uoms/standard-conversionsread:uomsConversões globais intraclasse.
POST /uoms/convertread:uomsUtilitário: quantidade, unidade de origem, unidade de destino e contexto opcional (item, fornecedor). Devolve o resultado, o fator usado e de qual nível da hierarquia ele veio. Sem efeito colateral — é a ferramenta de diagnóstico quando o número na tela não bate.
GET /ref/unece-uomsread:uomsTabela de referência da Rec 20 e Rec 21, só leitura, para mapeamento e integração.

As conversões por item vivem nos endpoints do catálogo (PUT /catalog/items/{id}/uom-conversions) e as do fornecedor em PUT /catalog/items/{id}/suppliers/{supplierItemId} — estão especificadas nos respectivos documentos.

Contrato

Códigos de erro

CódigoHTTPQuando
UOM_NOT_FOUND404Unidade inexistente no escopo
UOM_CODE_TOO_LONG400Código com mais de 6 caracteres
UOM_CODE_ALREADY_EXISTS409Código já existe no tenant ou colide com a camada global
UOM_CANONICAL_FACTOR_REQUIRED400Fator canônico ausente fora da classe Serviço e fora de unidade de embalagem
UOM_CANONICAL_FACTOR_NOT_ALLOWED400Novo em 09/09/2026. Fator canônico preenchido em unidade com isPackaging — o conteúdo da embalagem é do item, não da unidade (RN-UOM-19)
UOM_CLASS_BASE_ALREADY_EXISTS409Segunda unidade marcada como base da mesma classe
UOM_FIELD_IMMUTABLE_CONFLICT409Alteração de código, classe ou fator canônico após o primeiro uso — mensagem nomeia o campo
UOM_DECIMALS_CANNOT_DECREASE_CONFLICT409Redução de casas com quantidade gravada mais precisa
UOM_IN_USE_CONFLICT409Desativação ou exclusão de unidade em uso — mensagem lista os documentos e itens
UOM_CONVERSION_INVALID400Numerador ou denominador não positivo, ou destino igual à origem
UOM_CONVERSION_NOT_CONFIGURED_UNPROCESSABLE422Nenhum nível da hierarquia resolveu o fator — nunca cair em 1:1
UOM_CONVERSION_CROSS_CLASS_NOT_ALLOWED400Conversão entre classes fora do escopo de item
UOM_SERVICE_CLASS_NOT_CONVERTIBLE400Qualquer conversão envolvendo a classe Serviço
UOM_CONVERSION_LOCKED_CONFLICT409Edição de conversão de item já usada em documento
UOM_QUANTITY_PRECISION_EXCEEDED400Quantidade com mais casas do que a unidade permite
UOM_QUANTITY_NOT_INTEGER400Quantidade fracionária em unidade indivisível

Limites

Fora de escopo

Fora, com gancho previsto
  • Dual UoM / catch weight — comprar por caixa e pagar por peso real, caso de proteína, aço e cabo. Exige duas quantidades verdadeiras na mesma linha e uma segunda unidade no item.
  • Unidade de estocagem separada — o storage-uom da Coupa. Só faz sentido quando houver estoque.
  • Unidade de consumo — distinta da de compra e da de estoque. Mesma condição.
  • Conversão dependente de temperatura ou densidade — combustíveis e químicos. O D365 tem "quantidade adicional" para isso.
Fora, por decisão
  • Expor os 1.755 códigos da Rec 20 como opção de escolha. Ficam em tabela de referência, para mapeamento e integração.
  • Criar classe de unidade pelo tenant. A lista é fechada porque a classe é o que autoriza uma conversão.
  • Editar fator canônico para corrigir erro. A correção é criar unidade nova e migrar — doloroso de propósito.
  • Normalizar preço de serviço. Não existe denominador comum entre hora e verba.

Mudança de embalagem gera nova versão de ItemSupplierOffer; apresentações simultâneas têm ofertas distintas. Documentos preservam seus fatores e códigos originais. O adaptador fiscal mapeia unidade comercial e conversões exigidas pelo ERP/SPED conforme a operação; essa obrigação não determina criar códigos globais CX10 ou alterar o histórico do item.