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
  • 400 — physicalQuantity + 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.