Transferência de Estoque entre Filiais
Visão Geral
O sistema de estoque usa um modelo de dois níveis:
InventoryItem— catálogo central de produtos (compartilhado entre filiais)BranchStock— ledger por PDV/filial que registra a quantidade física e disponível de cada item em cada terminal
Uma transferência decrementa atomicamente o physicalQuantity e availableQuantity da filial de origem e incrementa a filial de destino. A operação é síncrona — não existe status intermediário: ou completa ou lança erro.
Modelo de Dados: BranchStock
Schema: backend/src/branch-stock/schemas/branch-stock.schema.ts
| Campo | Tipo | Descrição |
|---|---|---|
item | ObjectId → InventoryItem | Item do catálogo central |
pdv | ObjectId → Pdv | Terminal/filial proprietário |
tenant | ObjectId → Tenant | Isolamento multi-tenant |
physicalQuantity | number (default 0) | Quantidade física presente no PDV |
availableQuantity | number (default 0) | Quantidade disponível para venda |
minQuantity | number? | Ponto de alerta de estoque baixo para esta filial |
Índices:
{ item, pdv }— unique (uma entrada por item por PDV){ pdv, tenant }
Nota: Um row
BranchStocké criado na primeira vez que um item é atribuído a um PDV viaPUT /branch-stock/:pdvId. Antes disso, o PDV não "conhece" o item.
Pré-requisitos para Transferência
Antes de executar uma transferência, verifique:
- O item deve ter um row
BranchStockno PDV de origem — caso contrário, retorna404 Not Found - O PDV de destino será upsertado automaticamente (criado se ainda não existir)
quantitydeve ser≥ 0.001physicalQuantityda origem deve ser≥ quantity— caso contrário,400 Insufficient stock in source branch- Requer permissão
ManageSettings(mais restrita queManageInventory)
Fluxo da Operação de Transferência
Arquivo: backend/src/branch-stock/branch-stock.service.ts → transfer()
Passos executados em sequência:
- Busca row de origem por
{ item, pdv: fromPdvId, tenant }→404se não encontrado - Verifica
physicalQuantity >= quantity→400se insuficiente - Decrementa origem:
physicalQuantity -= quantity,availableQuantity -= quantity - Upsert destino:
$inc { physicalQuantity: +quantity, availableQuantity: +quantity }(cria row se ausente) - Emite evento WebSocket
stockUpdatepara a filial de origem - Emite evento WebSocket
stockUpdatepara a filial de destino - Verifica
availableQuantity ≤ minQuantityem cada filial → emitelowStockAlertse necessário - Retorna
{ from: BranchStockDocument, to: BranchStockDocument }
Aviso — Sem transação MongoDB: Os passos 3 e 4 não são executados dentro de uma transação MongoDB (deployment standalone). Se o passo 4 falhar após o 3, a origem já terá sido decrementada — o rollback deve ser feito manualmente via
POST /branch-stock/:pdvId/adjustcom delta positivo.
Formulário de Transferência (UI)
Arquivo: frontend-react/src/views/admin/StockTransferView.tsx
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| PDV de Origem | Select (/pdvs) | Sim | fromPdvId |
| PDV de Destino | Select (/pdvs) | Sim | toPdvId |
| Item | Select (/inventory) | Sim | itemId |
| Quantidade | number (min 0.001) | Sim | quantity |
Validações client-side:
- Todos os campos obrigatórios
fromPdvId ≠ toPdvId— erro inline se iguaisquantity > 0
API — Todos os Endpoints Branch-Stock
Rota base: /t/:slug/branch-stock
Guards: JwtAuthGuard, TenantGuard, PermissionsGuard
BranchScopeGuard: Usuários com role
staffvisualizam apenas os PDVs aos quais estão atribuídos. Usuáriosadminesuperadminveem todos os PDVs.
| Método | Rota | Permissão | Descrição |
|---|---|---|---|
GET | /t/:slug/branch-stock?pdv=<id> | ManageInventory + BranchScope | Estoque de um PDV específico |
GET | /t/:slug/branch-stock?item=<id> | ManageInventory | Item em todos os PDVs do tenant |
GET | /t/:slug/branch-stock | ManageInventory | Todo o estoque por filial |
GET | /t/:slug/branch-stock/alerts | ManageSettings | Itens abaixo de minQuantity por filial |
PUT | /t/:slug/branch-stock/:pdvId | ManageInventory + BranchScope | Upsert — define quantidade absoluta |
POST | /t/:slug/branch-stock/:pdvId/adjust | ManageInventory + BranchScope | Ajuste por delta (+/-) |
POST | /t/:slug/branch-stock/transfer | ManageSettings | Transferência atômica entre filiais |
DELETE | /t/:slug/branch-stock/:pdvId/items/:itemId | ManageInventory + BranchScope | Desvincula item do PDV |
PUT /t/:slug/branch-stock/:pdvId — Upsert
Define a quantidade absoluta de um item em um PDV. Cria o row se não existir.
Body:
{
"itemId": "64a1b2c3d4e5f6789",
"physicalQuantity": 50,
"minQuantity": 10
}Response: BranchStock document.
POST /t/:slug/branch-stock/:pdvId/adjust — Ajuste por Delta
Incrementa ou decrementa a quantidade pelo valor de delta. Requer que o row exista.
Body:
{
"itemId": "64a1b2c3d4e5f6789",
"delta": -5
}Errors:
404— row não encontrado para este item/PDV400— resultado seria negativo (physicalQuantity + delta < 0)
POST /t/:slug/branch-stock/transfer — Transferência
Body:
{
"fromPdvId": "64a1b2c3d4e5f6001",
"toPdvId": "64a1b2c3d4e5f6002",
"itemId": "64a1b2c3d4e5f6789",
"quantity": 10
}Response:
{
"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 origem400— quantidade insuficiente na origem
Eventos WebSocket
A transferência emite dois eventos stockUpdate (um por PDV) e, se aplicável, lowStockAlert para cada PDV afetado.
Ver detalhes completos do namespace /inventory em Módulo de Inventário — WebSocket.