Skip to content

Estrutura do inventário

Um Inventário representa o estoque disponível de uma variante de produto no sistema. Pode operar em 3 estratégias diferentes: ilimitado (não-rastreado), manualmente controlado (rastreado) ou dinamicamente sincronizado com um grupo (rastreado por referência).

json
{
  "_id": "698b8a805eaf8de7c5af1cb9",
  "community": "test",
  "itemId": "698b8a805eaf8de7c5af1cba",
  "variantId": "SKU-001",
  "alias": null,
  "strategy": "tracked",
  "isVirtual": false,
  "trackedQuantity": 100,
  "invalidatedAt": null,
  "indexedAt": "2026-02-12T20:45:01.549Z",
  "referencedTrackingAllowed": false,
  "variantReference": null,
  "level": {
    "quantity": 100,
    "alocated": 100,
    "tracked": true,
    "reserves": 2,
    "reserved": 5,
    "available": 95,
    "reservedUntil": "2026-02-13T20:45:01.549Z",
    "availableUntil": "2026-02-12T21:45:01.549Z",
    "validUntil": "2026-02-12T21:10:01.549Z",
    "processDuration": 245
  },
  "createdAt": "2026-02-10T19:44:01.198Z",
  "updatedAt": "2026-02-12T20:45:01.697Z"
}

Campos principais

CampoTipoDescrição
_idstringID único do inventário no formato ObjectId
communitystringComunidade (tenant)
itemIdstring | nullID do produto. Obrigatório para inventários não-virtuais. Caso virtual, null
variantIdstring | nullSKU/Identificador da variante. Obrigatório para não-virtuais. Caso virtual, null
aliasstring | nullAlias único do inventário. Obrigatório para virtuais (integrados). Caso não-virtual, null
strategystringEstratégia de rastreamento. Valores possíveis: untracked, tracked, tracked:reference
isVirtualbooleanIndica se é um inventário virtual (integrado/não-associado a item específico)
trackedQuantityintegerQuantidade atual em estoque. Aplicável apenas para strategies tracked e tracked:reference
invalidatedAtstring (ISO 8601) | nullData e hora da última invalidação do cache. Caso não invalidado, null
indexedAtstring (ISO 8601) | nullData e hora do último cálculo do nível de estoque
referencedTrackingAllowedbooleanIndicador virtual: Se rastreamento por referência está permitido (readonly, derivado de variantReference.dynamicInventory)
variantReferenceobject | nullReferência dinâmica para um grupo (layers:group). Caso não vinculado, null
levelobject | nullNível de estoque calculado com disponibilidade. Caso não indexado/cacheado, null
createdAtstring (ISO 8601)Data e hora de criação do inventário
updatedAtstring (ISO 8601)Data e hora da última atualização

Estratégias de Inventário

O inventário suporta 3 estratégias que definem como a quantidade é gerenciada:

EstratégiaComportamentoQuantidadeRastreamentoCaso de Uso
untrackedEstoque ilimitado (padrão)Infinito/IgnoradoNãoProdutos digitais, serviços ilimitados
trackedEstoque manualmente controladotrackedQuantitySim, manualProdutos físicos com estoque definido
tracked:referenceEstoque dinamicamente sincronizado com um grupoVinculado a grupoSim, automáticoCombos/kits com sincronização dinâmica

Comportamento por Estratégia

untracked (Padrão)

  • Estoque é considerado ilimitado
  • Campo trackedQuantity é ignorado
  • Qualquer quantidade pode ser vendida
  • Melhor para: Serviços, produtos digitais, produtos com estoque infinito

tracked (Rastreamento Manual)

  • Estoque é controlado manualmente via trackedQuantity
  • Cada ajuste cria um evento de inventário
  • Reservas são rastreadas e subtraídas da disponibilidade
  • Melhor para: Produtos físicos, estoque limitado

tracked:reference (Rastreamento Dinâmico)

  • Estoque sincronizado dinamicamente com um grupo de produtos
  • Requer variantReference com dynamicInventory = true
  • Transição automática quando referência é vinculada
  • Melhor para: Combos/kits onde um item é parte do grupo

Transições Automáticas de Estratégia

O sistema automaticamente muda a estratégia em certas condições:

  • trackedtracked:reference: Quando variantReference.dynamicInventory é definido como true
  • tracked:referencetracked: Quando variantReference é removido ou dynamicInventory muda para false

level - Nível de Estoque (InventoryLevel)

Representa a disponibilidade calculada do inventário, incluindo quantidade, reservas e cache.

json
{
  "quantity": 100,
  "alocated": 100,
  "tracked": true,
  "reserves": 2,
  "reserved": 5,
  "available": 95,
  "reservedUntil": "2026-02-13T20:45:01.549Z",
  "availableUntil": "2026-02-12T21:45:01.549Z",
  "validUntil": "2026-02-12T21:10:01.549Z",
  "processDuration": 245
}
CampoTipoDescrição
quantityintegerQuantidade total em estoque (quantidade física/rastreada)
alocatedinteger | nullTotal de "assentos" alocados para este produto. Caso não aplicável, null
trackedbooleanIndica se o produto é rastreado (true) ou ilimitado (false)
reservesinteger | nullNúmero de reservas ativas. Caso não aplicável, null
reservedinteger | nullQuantidade total reservada (soma de todas as reservas). Caso não aplicável, null
availableintegerQuantidade disponível para venda: quantity - reserved
reservedUntilstring (ISO 8601)Data e hora de expiração das reservas
availableUntilstring (ISO 8601)Data e hora de validade do cálculo de disponibilidade
validUntilstring (ISO 8601)Data e hora até quando este nível é válido (TTL do cache)
processDurationintegerTempo em milissegundos para calcular este nível

Cálculo de Disponibilidade

A disponibilidade é calculada como:

available = quantity - reserved

Onde:

  • quantity = quantidade atual em estoque (ou ilimitado se não-rastreado)
  • reserved = somatório de todas as reservas pendentes

Se tracked = false (estoque ilimitado), então available é sempre considerado suficiente.

Cache e Validade

O level é cacheado para performance e expira de acordo com validUntil:

  • Validade mínima: 10 segundos
  • Renovação automática: Quando acesso getLevel() e cache expirou
  • Invalidação: Quando adjust() é chamado ou invalidateInventories() é executado

variantReference - Referência Dinâmica (ItemSkuReference)

Representa uma referência dinâmica a um grupo de produtos, permitindo sincronização automática de estoque e formulário.

json
{
  "resourceKind": "layers:group",
  "resourceId": "grupo-camisa-azul-123",
  "dynamicInventory": true,
  "dynamicForm": true
}
CampoTipoDescrição
resourceKindstringTipo de recurso referenciado. Valor: layers:group
resourceIdstringID único do grupo referenciado
dynamicInventorybooleanSe inventário deve ser sincronizado dinamicamente com o grupo. Quando true, muda strategy para tracked:reference
dynamicFormbooleanSe formulário deve ser sincronizado dinamicamente com o grupo

Ciclo de Vida

Criação

  1. Inventário criado com strategy = untracked por padrão
  2. Se tiver variantReference com dynamicInventory = true, automaticamente muda para tracked:reference
  3. Validações:
    • Não-virtuais: Exigem itemId E variantId
    • Virtuais: Exigem alias obrigatoriamente

Rastreamento de Nível

  1. level é calculado quando necessário (getter getLevel())
  2. Resultado é cacheado em level
  3. indexedAt marca quando foi último cálculo
  4. validUntil indica quando cache expira
  5. invalidatedAt marca quando foi invalido (força recalcular)

Movimentações

  1. Ajustes via adjust({ quantity }) apenas afetam estratégias tracked e tracked:reference
  2. Cada ajuste incrementa trackedQuantity
  3. Cria um InventoryEvent com sequence incremental
  4. Invalida cache (level = null)
  5. Mantém histórico completo via eventos

Validações

Para Inventários Não-Virtuais

✓ itemId é obrigatório
✓ variantId é obrigatório
✗ Erro se ambos não estiverem presentes: "Inventory must have an item"

Para Inventários Virtuais

✓ alias é obrigatório
✗ Erro se não tiver: "Virtual Inventory must have an alias"
✗ itemId/variantId devem estar vazios

Quantidade Rastreada

✓ trackedQuantity deve ser número inteiro
✗ Decimais não são permitidos
✓ Padrão: 0

Alias

✓ Deve ser único por comunidade
✓ Sparse index (permite null para não-virtuais)
✗ Colisão de alias resulta em erro de índice

Estratégia

✓ Enum: 'untracked', 'tracked', 'tracked:reference'
✓ Padrão: 'untracked'
✓ Transições automáticas aplicadas na validação

Eventos de Inventário (InventoryEvent)

Cada movimentação de estoque cria um evento rastreável para auditoria e histórico.

json
{
  "_id": "698b8a805eaf8de7c5af1cc0",
  "community": "test",
  "inventoryId": "698b8a805eaf8de7c5af1cb9",
  "kind": "manual",
  "delta": 10,
  "sequence": 5,
  "balance": 110,
  "lineItemId": "698b8a805eaf8de7c5af1cc1",
  "refs": [],
  "createdAt": "2026-02-12T20:45:01.549Z",
  "updatedAt": "2026-02-12T20:45:01.549Z"
}
CampoTipoDescrição
_idstringID único do evento
communitystringComunidade (tenant)
inventoryIdstringID do inventário ajustado
kindstringTipo do evento. Valores: manual, system
deltaintegerMudança na quantidade (pode ser negativo ou positivo)
sequenceintegerNúmero sequencial do evento (índice histórico)
balanceintegerSaldo de trackedQuantity após o ajuste
lineItemIdstring | nullID do item de linha da venda (se aplicável)
refsarrayReferências a objetos relacionados
createdAtstring (ISO 8601)Data e hora de criação
updatedAtstring (ISO 8601)Data e hora de atualização

Sequência e Histórico

  • sequence começa em 0 e incrementa com cada evento
  • Forma um histórico ordenado de movimentações
  • Permite reconstruir quantidade em qualquer ponto no tempo
  • Índice único em (inventoryId, sequence)

Relacionamentos

Inventário ← Item (Produto)

  • Um Item pode ter múltiplos Inventários (um por variante)
  • Cada variante (SKU) tem seu próprio inventário
  • itemId referencia o Item relacionado

Inventário → InventoryLevel

  • InventoryLevel é sub-objeto de Inventário
  • Representa cache da disponibilidade calculada
  • Pode ser null se não indexado

Inventário → InventoryEvent

  • Histórico completo de movimentações
  • Um para cada adjust() chamado
  • Relacionamento 1:N

Inventário → ItemSkuReference (Opcional)

  • Referência dinâmica para um grupo
  • Apenas presente se strategy = tracked:reference
  • Permite sincronização automática

Casos de Uso

1. Produto Físico com Estoque Limitado

json
{
  "itemId": "produto-123",
  "variantId": "camiseta-azul-p",
  "strategy": "tracked",
  "isVirtual": false,
  "trackedQuantity": 50,
  "level": {
    "quantity": 50,
    "available": 48,
    "tracked": true
  }
}

Comportamento:

  • Estoque manualmente controlado via trackedQuantity
  • Cada venda consome quantidade
  • Eventos rastreiam histórico completo

2. Serviço Digital Ilimitado

json
{
  "itemId": "curso-123",
  "variantId": "modulo-basico",
  "strategy": "untracked",
  "isVirtual": false,
  "trackedQuantity": 0,
  "level": {
    "available": 999999,
    "tracked": false
  }
}

Comportamento:

  • Estoque considerado infinito
  • trackedQuantity ignorado
  • Qualquer quantidade pode ser vendida

3. Combo/Kit com Estoque Dinâmico

json
{
  "itemId": "combo-123",
  "variantId": "kit-completo",
  "strategy": "tracked:reference",
  "isVirtual": false,
  "variantReference": {
    "resourceKind": "layers:group",
    "resourceId": "grupo-camiseta-azul",
    "dynamicInventory": true
  },
  "level": {
    "available": 25,
    "tracked": true
  }
}

Comportamento:

  • Estoque sincronizado com grupo
  • Quantidade atualiza automaticamente com grupo
  • Alterações no grupo afetam este inventário

4. Produto Integrado (Virtual)

json
{
  "alias": "produto-externo-sku-789",
  "strategy": "tracked",
  "isVirtual": true,
  "itemId": null,
  "variantId": null,
  "trackedQuantity": 100,
  "level": {
    "available": 100,
    "tracked": true
  }
}

Comportamento:

  • Representa item de sistema externo integrado
  • Sem itemId/variantId (virtual)
  • Identificado por alias único
  • Rastreamento manual

Exemplo: Ciclo Completo

1. Criar Inventário Rastreado

json
POST /inventories
{
  "community": "test",
  "itemId": "camiseta-123",
  "variantId": "azul-p",
  "strategy": "tracked",
  "trackedQuantity": 100
}

Resultado: Inventário criado com strategy = tracked, level = null (não indexado ainda)

2. Obter Nível Atual

json
GET /inventories/{id}/level

Resultado:

json
{
  "quantity": 100,
  "available": 100,
  "tracked": true,
  "validUntil": "2026-02-12T21:10:01.549Z"
}

3. Fazer Venda (reduz disponibilidade)

json
POST /sales
{
  "items": [{ "inventoryId": "...", "quantity": 5 }]
}

Resultado: 5 unidades reservadas, available = 95

4. Ajustar Estoque (chegada de mercadoria)

json
POST /inventories/{id}/adjust
{
  "quantity": 50
}

Resultado:

  • trackedQuantity = 150
  • Novo InventoryEvent com delta = 50, balance = 150, sequence = 1
  • Cache invalidado (level = null)

5. Obter Nível Novamente

json
GET /inventories/{id}/level

Resultado:

json
{
  "quantity": 150,
  "available": 145,
  "tracked": true,
  "validUntil": "2026-02-12T21:15:01.549Z"
}

Comparação: Estratégias de Rastreamento

Aspectountrackedtrackedtracked:reference
EstoqueIlimitadoLimitadoDinâmico (grupo)
Campo trackedQuantityIgnoradoUsadoUsado
ReservasIgnoradasRastreadasRastreadas
EventosNenhumSimSim
Caso de UsoDigital/ServiçoFísicoCombo/Kit
Cache (level)Sempre válidoExpiraExpira

Estatísticas Calculadas

O level fornece várias métricas úteis:

MétricaCálculoExemplo
Disponívelquantity - reserved100 - 5 = 95
Taxa de Ocupaçãoreserved / quantity5 / 100 = 5%
Utilizávelavailable / quantity95 / 100 = 95%
Dias até Expiração(validUntil - now()) / 86400000Cache expira em ~4 minutos