Skip to content

API - Estoque

Endpoints

GET /t/:slug/inventory

Lista todos os itens do estoque.

Response:

json
[
  {
    "_id": "...",
    "name": "Filé Mignon",
    "sku": "CAR-001",
    "currentStock": 15,
    "minStock": 5,
    "unit": "kg",
    "cost": 4500,
    "status": "active"
  }
]

POST /t/:slug/inventory

Cria um novo item.

Body:

json
{
  "name": "Picanha",
  "sku": "CAR-002",
  "currentStock": 10,
  "minStock": 3,
  "unit": "kg",
  "cost": 5500
}

PUT /t/:slug/inventory/:id

Atualiza um item.

DELETE /t/:slug/inventory/:id

Remove um item.


Movimentações

POST /t/:slug/inventory/:id/movements

Registra uma movimentação.

Body:

json
{
  "type": "entry",
  "quantity": 5,
  "reason": "Compra fornecedor"
}

Tipos de Movimentação

TipoDescrição
entryEntrada (compra, importação NFS)
exitSaída (venda, perda)
adjustmentAjuste de inventário
transferTransferência entre filiais
reservationReserva interna

Motivos de Movimentação

MotivoDescrição
purchaseCompra de fornecedor
saleVenda (dedução automática)
transferTransferência
adjustmentAjuste manual
productionProdução interna
lossPerda
theftFurto
expirationVencimento
returnDevolução

Ficha Técnica (Ingredientes por Prato)

Gerencia o vínculo bill-of-materials entre itens de cardápio e itens de estoque. Requer role admin.


GET /t/:slug/inventory/ingredients

Lista todos os ingredientes de um item de cardápio.

Query params:

ParâmetroObrigatórioDescrição
menuItemIdsimID do item de cardápio

Response 200:

json
[
  {
    "_id": "66a1b2c3d4e5f6a7b8c9d0e1",
    "menuItem": "66a1b2c3d4e5f6a7b8c9d0e2",
    "inventoryItem": {
      "_id": "66a1b2c3d4e5f6a7b8c9d0e3",
      "name": "Carne Bovina",
      "sku": "CAR-001",
      "unit": "kg"
    },
    "quantityPerUnit": 0.2,
    "unit": "kg",
    "tenant": "66a1b2c3d4e5f6a7b8c9d0e4"
  }
]

POST /t/:slug/inventory/ingredients

Cria um novo vínculo ingrediente ↔ prato.

Body:

json
{
  "menuItemId": "66a1b2c3d4e5f6a7b8c9d0e2",
  "inventoryItemId": "66a1b2c3d4e5f6a7b8c9d0e3",
  "quantityPerUnit": 0.2,
  "unit": "kg"
}
CampoTipoRegras
menuItemIdMongoIdobrigatório
inventoryItemIdMongoIdobrigatório
quantityPerUnitnumberobrigatório, mín. 0.001
unitstringopcional — apenas informativo

Response 201: documento ProductIngredient criado.

Response 409: par (menuItem, inventoryItem) já existe para este tenant.


DELETE /t/:slug/inventory/ingredients/:id

Remove um vínculo de ingrediente.

Response 200:

json
{ "deleted": true }

Response 404: ingrediente não encontrado.


POST /t/:slug/inventory/import-nfs

Importa uma NF-e XML de fornecedor e cria/atualiza itens de estoque.

Content-Type: multipart/form-data

CampoTipoDescrição
fileFileArquivo XML da NF-e (.xml)

Response 201:

json
{
  "success": true,
  "nfeNumber": "42",
  "nfeKey": "35260312345678000195550010000000421234567890",
  "emitter": "FORNECEDOR LTDA",
  "totalItems": 12,
  "created": 3,
  "updated": 9,
  "movements": 12,
  "skipped": 0,
  "errors": []
}

Requer: role admin ou superadmin


GET /t/:slug/inventory/expiration-alerts

Retorna itens com validade vencida ou próxima.

Response 200:

json
{
  "expiredItems": [
    {
      "_id": "...",
      "name": "Leite Integral",
      "sku": "LAT-001",
      "expirationDate": "2026-02-28T00:00:00.000Z",
      "availableQuantity": 5,
      "unit": "l"
    }
  ],
  "expiringIn7": [...],
  "expiringIn30": [...]
}

Requer: role admin, staff ou superadmin


GET /t/:slug/inventory/stats

Retorna estatísticas agregadas do estoque do tenant.

Permissão: ManageInventory

Response:

json
{
  "totalItems": 142,
  "totalValue": 18450.75,
  "lowStock": 8,
  "expired": 2,
  "blocked": 1
}
CampoDescrição
totalItemsTotal de itens ativos
totalValueSoma de physicalQuantity × costPrice
lowStockItens com availableQuantity ≤ minQuantity
expiredItens com status: expired
blockedItens com status: blocked

Alertas de Estoque

Base: /t/:slug/inventory/alerts

Permissão: ManageInventory

GET /t/:slug/inventory/alerts

Retorna alertas de estoque baixo não lidos pelo usuário atual (não presente em readBy[]). Máximo 50 alertas, ordenados por createdAt desc.

Response:

json
[
  {
    "_id": "64a1b2c3d4e5f6aaa",
    "item": "64a1b2c3d4e5f6789",
    "itemName": "Pão de hambúrguer",
    "availableQuantity": 8,
    "minQuantity": 10,
    "pdv": null,
    "pdvName": null,
    "expiresAt": "2026-04-17T14:30:00.000Z",
    "createdAt": "2026-04-10T14:30:00.000Z"
  }
]

GET /t/:slug/inventory/alerts/count

Retorna contagem de alertas não lidos. Usado para o badge no header.

Response:

json
{ "count": 3 }

PUT /t/:slug/inventory/alerts/:id/read

Marca o alerta como lido pelo usuário atual (adiciona userId ao array readBy[]).

Response: 200 OK com o documento StockAlert atualizado.


Label Tokens (QR Code)

Sistema de rastreamento de perdas, furtos e vencimentos via QR code físico.

POST /t/:slug/inventory/labels

Gera um token QR para registro de baixa física.

Permissão: ManageSettings

Body:

json
{
  "itemId": "64a1b2c3d4e5f6789",
  "quantity": 5,
  "reason": "loss",
  "notes": "Embalagem danificada"
}
CampoTipoValores aceitos
itemIdstringMongoId do InventoryItem
quantitynumber (min 0.001)Quantidade a baixar
reasonstringloss | theft | expiration | adjustment
notesstring?Observação opcional

Response: Documento LabelToken com o campo token (UUID).

json
{
  "_id": "64a1b2c3d4e5f6bbb",
  "token": "550e8400-e29b-41d4-a716-446655440000",
  "item": "64a1b2c3d4e5f6789",
  "quantity": 5,
  "reason": "loss",
  "notes": "Embalagem danificada",
  "status": "pending",
  "createdAt": "2026-04-10T10:00:00.000Z"
}

POST /t/:slug/inventory/scan/:token (público)

Consome o token QR e registra a baixa de estoque. Endpoint público — sem autenticação.

Parâmetro: :token — UUID do LabelToken

Processo:

  1. findOneAndUpdate({ token, status: 'pending' }) — garante uso único (race-condition safe)
  2. Cria InventoryMovement (type: exit, reason: conforme o reason do token)
  3. Decrementa physicalQuantity e availableQuantity
  4. Marca LabelToken.status = 'used'

Response (sucesso): Documento InventoryMovement criado.

Errors:

  • 404 — token não encontrado ou já utilizado

Fornecedores

Base: /t/:slug/inventory/suppliers

Guards: JwtAuthGuard, TenantGuard, RolesGuard

Roles: Admin, Superadmin

Plan feature: supplierManagement

GET /t/:slug/inventory/suppliers

Lista fornecedores do tenant.

Query: ?active=true (opcional) — filtra por status ativo/inativo.

Response: Supplier[]

json
[
  {
    "_id": "64a1b2c3d4e5f6ccc",
    "name": "Distribuidora ABC",
    "cnpj": "12345678000195",
    "email": "contato@abc.com",
    "phone": "11999999999",
    "contactName": "João Silva",
    "categories": ["bebidas", "laticínios"],
    "active": true
  }
]

GET /t/:slug/inventory/suppliers/:id

Retorna um fornecedor pelo ID.

POST /t/:slug/inventory/suppliers

Cria um fornecedor.

Body:

json
{
  "name": "Distribuidora ABC",
  "cnpj": "12345678000195",
  "email": "contato@abc.com",
  "phone": "11999999999",
  "contactName": "João Silva",
  "address": "Rua das Flores, 123, São Paulo-SP",
  "categories": ["bebidas"],
  "notes": "Entrega às terças e quintas"
}

Único obrigatório: name. Todos os demais campos são opcionais.

PUT /t/:slug/inventory/suppliers/:id

Atualiza campos do fornecedor (todos opcionais).

DELETE /t/:slug/inventory/suppliers/:id

Remove o fornecedor.


Pedidos de Compra

Base: /t/:slug/inventory/purchase-orders

Guards: JwtAuthGuard, TenantGuard, RolesGuard

Roles: Admin, Superadmin

Plan feature: supplierManagement

GET /t/:slug/inventory/purchase-orders

Lista pedidos de compra do tenant.

Query: ?status=draft (opcional) — filtra por status (draft|sent|partial|received|cancelled).

Response: PurchaseOrder[] com supplier populado (name, cnpj).

GET /t/:slug/inventory/purchase-orders/:id

Retorna PO com supplier populado.

POST /t/:slug/inventory/purchase-orders

Cria um Pedido de Compra.

Body:

json
{
  "supplierId": "64a1b2c3d4e5f6ccc",
  "items": [
    {
      "inventoryItemId": "64a1b2c3d4e5f6789",
      "itemName": "Pão de hambúrguer",
      "quantity": 100,
      "unitCost": 2.00
    }
  ],
  "expectedDelivery": "2026-04-15",
  "notes": "Urgente"
}

Campos calculados pelo servidor: orderNumber (auto PO-YYYYMMDD-NNNN), totalCost, subtotal por item. Status inicial: draft.

POST /t/:slug/inventory/purchase-orders/generate-from-low-stock

Gera automaticamente um draft PO com base nos itens abaixo do estoque mínimo.

Body (opcional):

json
{ "supplierId": "64a1b2c3d4e5f6ccc" }

Lógica: Para cada item com availableQuantity ≤ minQuantity > 0:

  • Quantidade sugerida = max(1, minQuantity × 2 − availableQuantity)
  • Custo estimado = quantidade × costPrice

Response: Documento PurchaseOrder criado com status: draft e nota "Gerado automaticamente — N itens com estoque baixo".

PUT /t/:slug/inventory/purchase-orders/:id

Atualiza itens, status, supplier, expectedDelivery, notes. Recalcula totalCost se items forem alterados.

DELETE /t/:slug/inventory/purchase-orders/:id

Remove o PO.

POST /t/:slug/inventory/purchase-orders/:id/receive

Registra o recebimento físico dos itens e atualiza o estoque.

Body:

json
{
  "items": [
    { "inventoryItemId": "64a1b2c3d4e5f6789", "receivedQuantity": 95 }
  ]
}

Side effects por item:

  1. Cria InventoryMovement (type: entry, reason: purchase, unitCost do PO)
  2. Incrementa physicalQuantity e availableQuantity do InventoryItem

Status resultante:

  • Todos os itens recebidos na quantidade total → received
  • Quantidade parcial em algum item → partial

Response: Documento PurchaseOrder atualizado.


Previsões de Demanda

Base: /t/:slug/inventory/predictions

Guards: JwtAuthGuard, TenantGuard, RolesGuard

Roles: Admin, Superadmin

Plan feature: inventoryPrediction

GET /t/:slug/inventory/predictions/demand

Retorna previsão de demanda por item do cardápio usando Weighted Moving Average.

Query: ?horizon=7 (padrão: 7) — horizonte em dias.

Response: DemandForecast[]

json
[
  {
    "menuItemId": "64a1b2c3d4e5f6001",
    "menuItemName": "X-Burguer",
    "avgDaily": 12.5,
    "predicted": 87.5,
    "confidence": 0.95,
    "trend": "up"
  }
]
CampoDescrição
avgDailyMédia diária ponderada calculada
predictedavgDaily × horizonDays
confidencemin(1.0, dataPoints / 21) — 0 a 1
trendup / down / stable — comparação últimos 7d vs 7d anteriores

GET /t/:slug/inventory/predictions/suggestions

Retorna demanda de ingredientes com daysUntilStockout. Ordenado crescente por daysUntilStockout (itens mais críticos primeiro).

Query: ?horizon=7

Response: IngredientDemand[]

json
[
  {
    "inventoryItemId": "64a1b2c3d4e5f6002",
    "itemName": "Pão de hambúrguer",
    "unit": "un",
    "currentStock": 50,
    "predictedDemand": 96.25,
    "daysUntilStockout": 3.6,
    "suggestedOrder": 46.25,
    "estimatedCost": 92.50
  }
]

GET /t/:slug/inventory/predictions/suggested-orders

Lista SuggestedPurchaseOrder do tenant. Máximo 20, ordenado por createdAt desc.

Response: SuggestedPurchaseOrder[]

POST /t/:slug/inventory/predictions/generate

Executa generateSuggestions() e persiste novo SuggestedPurchaseOrder. Retorna null se nenhum ingrediente precisar de reposição.

Query: ?horizon=7

Response: Documento SuggestedPurchaseOrder com status: pending.

PUT /t/:slug/inventory/predictions/suggested-orders/:id/:action

Atualiza o status do SuggestedPurchaseOrder.

Path param :action: approve ou dismiss

Response: Documento atualizado com novo status.

POST /t/:slug/inventory/predictions/snapshots/build

Trigger manual do cron de snapshots diários. Processa a data de ontem.

Normalmente executado pelo cron às 02:00. Use este endpoint para seed inicial ou reconstrução.

Response:

json
{ "message": "Snapshots built for tenant <id>" }

Estoque por Filial (BranchStock)

Base: /t/:slug/branch-stock

Guards: JwtAuthGuard, TenantGuard, PermissionsGuard, BranchScopeGuard

BranchScopeGuard: Usuários staff visualizam apenas os PDVs aos quais estão atribuídos. admin e superadmin veem todos.

GET /t/:slug/branch-stock

Lista estoque por filial.

Query params:

  • ?pdv=<id> — estoque de um PDV específico
  • ?item=<id> — um item em todos os PDVs
  • Sem params — todo o estoque de todos os PDVs (BranchScopeGuard filtra por acesso)

Permissão: ManageInventory

Response: BranchStock[] com item e pdv opcionalmente populados.

GET /t/:slug/branch-stock/alerts

Retorna todos os BranchStock onde availableQuantity ≤ minQuantity e minQuantity está definido.

Permissão: ManageSettings

PUT /t/:slug/branch-stock/:pdvId

Upsert — define quantidade absoluta para um item em um PDV. Cria o row se não existir.

Permissão: ManageInventory + BranchScope

Body:

json
{
  "itemId": "64a1b2c3d4e5f6789",
  "physicalQuantity": 50,
  "minQuantity": 10
}

Response: Documento BranchStock criado ou atualizado.

POST /t/:slug/branch-stock/:pdvId/adjust

Ajuste por delta (positivo ou negativo). O row deve existir previamente.

Permissão: ManageInventory + BranchScope

Body:

json
{
  "itemId": "64a1b2c3d4e5f6789",
  "delta": -5
}

Errors:

  • 404 — row não encontrado para este item/PDV
  • 400physicalQuantity + delta seria negativo

POST /t/:slug/branch-stock/transfer

Transferência atômica entre dois PDVs.

Permissão: ManageSettings

Body:

json
{
  "fromPdvId": "64a1b2c3d4e5f6001",
  "toPdvId":   "64a1b2c3d4e5f6002",
  "itemId":    "64a1b2c3d4e5f6789",
  "quantity":  10
}

Response:

json
{
  "from": { "_id": "...", "pdv": "64a1b2c3d4e5f6001", "physicalQuantity": 40, "availableQuantity": 40 },
  "to":   { "_id": "...", "pdv": "64a1b2c3d4e5f6002", "physicalQuantity": 10, "availableQuantity": 10 }
}

Errors:

  • 404 — item não vinculado ao PDV de origem
  • 400 — quantidade insuficiente na origem

DELETE /t/:slug/branch-stock/:pdvId/items/:itemId

Desvincula o item do PDV (remove o row BranchStock).

Permissão: ManageInventory + BranchScope


WebSocket — namespace /inventory

Gateway: backend/src/inventory/inventory.gateway.ts

Conexão: io(VITE_API_URL, { auth: { token: jwt } }) — namespace /inventory

Auth: JWT obrigatório. Roles aceitas: staff, admin, superadmin.

Eventos

DireçãoEventoPayload
Cliente → ServidorjoinInventorytenantId: string
Servidor → ClientestockUpdateVer abaixo
Servidor → ClientelowStockAlertVer abaixo

joinInventory: Entra na sala inventory-<tenantId>. Superadmin pode passar qualquer tenantId. Usuários regulares devem passar seu próprio tenantId.

stockUpdate — catálogo master:

json
{ "itemId": "...", "itemName": "...", "physicalQuantity": 45, "availableQuantity": 40 }

stockUpdate — estoque de filial:

json
{ "type": "branchStock", "pdvId": "...", "itemId": "...", "physicalQuantity": 20, "availableQuantity": 18, "minQuantity": 10 }

lowStockAlert — catálogo master:

json
{ "_id": "...", "itemId": "...", "itemName": "...", "availableQuantity": 8, "minQuantity": 10, "createdAt": "..." }

lowStockAlert — estoque de filial: adiciona type: "branchStock", pdvId, pdvName.

Lançado sob a licença MIT.