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 29/08/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

Arredondar quantidade muda o mundo físico e o erro é cumulativo — o saldo carrega para sempre. Arredondar valor é local e limitado a meio centavo por linha. Por isso a quantidade convertida nunca é arredondada, e o valor é arredondado exatamente uma vez, no total da linha.

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.
ItemSupplier 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.
ServicenuncaVB (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

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. ItemSupplier.purchaseNum / purchaseDen     item × fornecedor   ← vence
2. ItemUomConversion.num / den               item
3. StandardConversion (só intraclasse)       global
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.

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

  • num e den 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 ÷ denmultiplicar 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.

Aritmética

Precisão e arredondamento

Casas decimais

GrandezaColunaReferência
Quantidadenumeric(19,6)NF-e usa 4 casas em qCom e qTrib; SPED 0220 usa 6 no fator de conversão. Seis cobre os dois.
Preço unitárionumeric(19,10)NF-e permite até 10 casas em vUnCom e vUnTrib — e o campo existe justamente para absorver conversão de embalagem.
Valores monetários totalizadosnumeric(19,2)vProd tem 2 casas.
Fator de conversãobigint / bigintSem casas decimais, por construção.

Onde arredondar

Arredonde a quantidade uma vez, na entrada. Nunca arredonde a quantidade convertida. Arredonde o valor uma vez, no total da linha. Nunca arredonde o preço unitário.

Ponto da cadeiaArredonda?Por quê
Quantidade digitada pelo usuáriosimPara qtyDecimals da unidade escolhida. É a única quantidade que um humano afirmou.
Quantidade convertida para a basenãoValor derivado. Arredondar aqui é onde nascem o estoque fantasma e o clássico 4,99998.
Preço unitárionãoNão derive por divisão. Armazene na unidade em que foi cotado.
Total da linhasim2 casas, ROUND_HALF_UP. É o único ponto em que dinheiro vira dinheiro.
Total do documentonãoÉ a soma dos totais de linha já arredondados. Arredondar de novo cria a divergência "soma dos itens ≠ total".

Meio para cima, não bancário

ROUND_HALF_UP é a convenção comercial brasileira. Se o Nexio usar arredondamento bancário e o fornecedor usar meio para cima, os dois divergem em metade dos empates — sem que nenhum dos dois esteja errado.

Unidade indivisível arredonda sempre para cima

A documentação da PeopleSoft traz o alerta que vale ouro: com arredondamento ao mais próximo, uma quantidade calculada de 0,25 vira zero, não 1. Uma linha de pedido com quantidade zero é um bug silencioso. Para isIntegerOnly, arredonde para cima, sempre.

Divisível para indivisível é caminho de mão única

Aumentar as casas decimais de uma unidade é sempre seguro; reduzi-las depois de haver quantidade fracionária gravada, não. É a mesma assimetria que a PeopleSoft documenta e que a matriz de governança formaliza.

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,0833258,99−R$ 0,01
Preço derivado por UN com 2 casas84 × 3,08258,72−R$ 0,28

Quatro casas quebram a partir de 200 unidades: o erro máximo de arredondamento é 5×10⁻⁵, e a divergência é qty × 5×10⁻⁵. Com 10 casas o mesmo limite só seria atingido acima de duzentos milhões de unidades — é exatamente por isso que a NF-e dá 10 casas ao vUnCom.

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(qtyInPriceUom ÷ priceQty × priceAmount, 2). 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, mesma classe da base, exceto no caso de dual UoM. 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.

Vínculo

Unidade, item e fornecedor

A unidade de compra é do fornecedor, não do item. O fornecedor A vende em caixa de 12; o B, em caixa de 24; o C, em milheiro. É o mesmo item.

Campo no ItemSupplierRegra
purchaseUomIdObrigatório. É a unidade em que o pedido sai.
purchaseNum / purchaseDenObrigatórios. 1 purchaseUom = num ÷ den × baseUom. Prioridade máxima na hierarquia — sobrepõe a conversão do item.
priceUomIdPode ser diferente da unidade de compra. Compra-se em CX e cobra-se por KG, em cabo e aço. Precisa ter conversão conhecida para a base.
priceQtyDefault 1. Ver Preço.
minOrderQty / orderMultipleNa unidade de compra. O múltiplo é o lote fechado — o comprador não consegue pedir 7 quando o fornecedor só vende de 5 em 5.
supplierUomLabelTexto livre: como o fornecedor chama a embalagem. Serve só para conferência humana contra a proposta em PDF. Nunca participa de cálculo.
overTolerancePct / underTolerancePctTolerância de recebimento. Fica aqui porque é o fornecedor quem tem variabilidade de embalagem, com override na linha do pedido.
validFrom / validToVersionamento. É o que permite que a embalagem mude sem reescrever o passado.

Quando o fornecedor muda a embalagem

Cenário: a caixa de luva da ACME passa de 12 para 10 sem aviso. Chega nota de "7 CX" que na verdade são 70 UN, não 84.

O que não funciona: editar o registro para num = 10. Isso reescreve retroativamente todos os pedidos históricos — um pedido de 7 CX do ano passado passa a valer 70 UN, o estoque histórico fica errado e o custo médio é recalculado sobre uma mentira.

MecanismoO que resolve
1. Snapshot na linha do documentoDocumentos já emitidos continuam com num = 12. Imunes por construção.
2. Nova versão do ItemSuppliervalidFrom na data da mudança, num = 10. Documentos novos usam a nova versão, e fica auditável em que data a caixa mudou.
3. Detecção no recebimentoÉ o que salva na prática: esperado 7 × 12 = 84 UN, contado 70 UN, desvio de −16,7%, além da tolerância. O sistema bloqueia e pergunta: quantidade divergente ou embalagem mudou? Se for embalagem, abre o fluxo de nova versão e sinaliza os pedidos em aberto daquele item e fornecedor.

A alternativa mais conservadora — e a que a legislação do SPED já exige para efeito fiscal — é criar um novo código de unidade (CX10) em vez de mudar o fator de CX. Vale considerar como comportamento padrão, não só como opção.

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

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 o ItemSupplier 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
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, jamais herdada da conversão padrão.
Embalagem muda de conteúdo entre lotesEditar o fator reescreve o históricoNova versão do ItemSupplier ou novo código de unidade (CX10), mais snapshot na linha. Ver § Unidade, item e fornecedor.
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 na classe Serviço

Unidade sem fator canônico fora de Serviço responde 400. Sem ele, a unidade não participa de nenhuma agregação.

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-06

A hierarquia de resolução é fixa e o fallback final é erro

Item × fornecedor, depois item, depois global intraclasse, depois 422 UOM_CONVERSION_NOT_FOUND. Nunca fator implícito.

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".

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-09

Conversão de item é append-only com vigência

Editar o fator de uma conversão em uso é proibido. Cria-se nova versão com validFrom.

RN-UOM-10

Todo documento copia o fator para a linha

convNum e convDen são snapshot, não referência. Documento emitido nunca muda de significado.

RN-UOM-11

Quantidade convertida nunca é arredondada

Arredonda-se a quantidade digitada, para as casas da unidade escolhida, e mais nada. O saldo em aberto vive em unidade base com 6 casas.

RN-UOM-12

Unidade indivisível arredonda para cima, nunca ao mais próximo

Evita que uma quantidade calculada de 0,25 vire zero e produza linha de pedido vazia.

RN-UOM-13

Valor arredonda uma vez, no total da linha

Duas casas, meio para cima. O total do documento é a soma dos totais de linha já arredondados — nunca um segundo arredondamento.

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.

Camada API

Endpoints

RotaPermissãoObservação
GET /uomsuom:readPaged. Filtros: search, uomClass, scope, isActive. Consumido pelo autocomplete dos formulários.
GET /uoms/{id}uom:readCom as conversões padrão da classe.
POST /uomsuom:manageCria unidade do tenant. Recusa colisão de código com a camada global.
PUT /uoms/{id}uom:manageRespeita a matriz de imutabilidade. Campos bloqueados respondem 409 nomeando o campo.
POST /uoms/{id}/deactivateuom:manageDevolve a lista de documentos e itens que impedem, quando impedem.
PUT /uoms/{id}/aliasuom:manageRótulo de exibição do tenant sobre unidade global. Não altera o código.
PUT /uoms/visibilityuom:manageOculta unidades globais que o tenant não usa.
GET /uom-classesuom:readLista fechada, com a unidade base de cada classe.
GET /uoms/standard-conversionsuom:readConversões globais intraclasse.
POST /uoms/convertuom:readUtilitá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-uomsuom:readTabela 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_DUPLICATED409Código já existe no tenant ou colide com a camada global
UOM_CANONICAL_FACTOR_REQUIRED400Fator canônico ausente fora da classe Serviço
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_DECREASE409Reduçã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_FOUND422Nenhum 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.

Pendência para decidir: quando a embalagem do fornecedor muda, o comportamento padrão é nova versão do ItemSupplier ou novo código de unidade (CX10)? A segunda opção é o que a legislação do SPED já exige para efeito fiscal e é mais à prova de erro humano; a primeira é mais limpa no cadastro. A escolha muda a UI do recebimento e vale fechar antes de implementar.