Estoque

Movimentação e consulta de saldo. O estoque tem três visões: o que existe fisicamente, o que está reservado em carrinhos/pedidos abertos, e o que sobra disponível para nova venda.

Pré-requisitos: leia Produtos: visão geral. O produto precisa existir antes de receber estoque; a rota é por productId.


Conceitos

CampoO que é
quantityEstoque físico total na sua loja.
reserved_quantityJá comprometido em carrinhos/pedidos em aberto.
available_quantityO que sobra pra venda: quantity - reserved_quantity.

Regra: o catálogo respeita o available_quantity. Se ele zera, o produto deixa de aparecer pra novas compras, mesmo que ainda exista fisicamente.

O objeto que GET /products/{id}/stock devolve — total consolidado + saldo por localização:

{
  "total": 150,                // soma das quantidades em todos os locais
  "stocks": [                  // ← saldo por localização
    {
      "id": 321,               // ID do registro de saldo
      "product_sku": {},      // resumo do produto dono do saldo
      "location_stock": {},   // a localização deste saldo
      "quantity": 100,         // físico
      "reserved_quantity": 5,  // reservado em pedidos abertos
      "available_quantity": 95 // o que sobra pra venda
    }
  ],
  "locations": [{}]           // metadados dos locais (name, status, location_type…)
}

Tipos de movimentação

A rota de movimentação aceita 3 modos, controlados pelas flags booleanas increment e decrement:

Flags enviadasEfeito sobre quantity
nenhumaSobrescreve com o valor enviado. Útil pra acerto de inventário.
increment: trueSoma. Útil pra entrada de fornecedor.
decrement: trueSubtrai. Útil pra baixa manual (perda, avaria, venda fora do canal).

Mandar as duas flags juntas é recusado com 400.

Os 3 modos só mexem em quantity — e você pode estar se perguntando como ajustar o reserved_quantity. A resposta é: você não precisa. Ele é cuidado pela plataforma, que reserva e libera saldo sozinha conforme os pedidos abrem e fecham. Isso evita que duas vendas simultâneas briguem pela mesma unidade, então deixe essa conta com a gente e foque só no estoque físico.


Consultar saldo

GET/api/products/{id}/stock

{
  "data": {
    "total": 150,
    "stocks": [
      {
        "id": 321,
        "product_sku": {
          "id": "01H81AV32307PVBSV4RXF15EK9",
          "name": "Caneta Esferográfica Azul",
          "sku": "CAN-AZ-01"
        },
        "location_stock": {
          "location_id": "01H81AV32307PVBSV4RXF15LOC",
          "store_id": 42,
          "name": "Matriz - São Paulo",
          "status": "available",
          "location_type": "seller_location",
          "allows_pickup": false,
          "allows_production": false
        },
        "quantity": 100,
        "reserved_quantity": 5,
        "available_quantity": 95
      }
    ],
    "locations": [
      {
        "location_id": "01H81AV32307PVBSV4RXF15LOC",
        "store_id": 42,
        "name": "Matriz - São Paulo",
        "status": "available",
        "location_type": "seller_location",
        "allows_pickup": false,
        "allows_production": false
      }
    ]
  }
}

No exemplo, o product_sku aparece resumido — na prática ele vem com o cadastro completo do produto (preços, thumbnail, dimensões…).


Movimentar

POST/api/products/{id}/stock

O location_id é obrigatório — diz em qual local o saldo se move. Sem flag nenhuma, o valor enviado sobrescreve a quantidade:

{
  "location_id": "01H81AV32307PVBSV4RXF15LOC",
  "quantity": 150
}

Exemplos por cenário

Recebi 40 unidades do fornecedor:

{
  "location_id": "01H81AV32307PVBSV4RXF15LOC",
  "quantity": 40,
  "increment": true
}

Inventário deu 95 (era 100):

{
  "location_id": "01H81AV32307PVBSV4RXF15LOC",
  "quantity": 95
}

Avaria, baixar 3:

{
  "location_id": "01H81AV32307PVBSV4RXF15LOC",
  "quantity": 3,
  "decrement": true
}