Mirror all SPECs and construction docs from pizza-digital, add hub/sidebar links and bidirectional sync scripts.
262 lines
8.5 KiB
Markdown
262 lines
8.5 KiB
Markdown
# SPEC — Estoque (Insumos & CMV)
|
||
|
||
| Campo | Valor |
|
||
|-------|--------|
|
||
| **Produto** | Pizza Digital |
|
||
| **Capítulo** | SPEC §7.9 RF-EST (novo) |
|
||
| **Versão** | 0.1.0 |
|
||
| **Data** | 2026-10-19 |
|
||
| **Owner** | Roger / iTecnologys |
|
||
| **SPEC irmã (obrigatória)** | [`SPEC-CARDAPIO.md`](SPEC-CARDAPIO.md) — menu + ficha técnica |
|
||
| **Dispara baixa** | Order `confirmado` → `em_producao` (Central / PDV / canais) |
|
||
|
||
---
|
||
|
||
## 1. Objetivo
|
||
|
||
Controlar **o que a cozinha tem** (insumos e embalagens), amarrado ao **o que a loja vende** (cardápio).
|
||
|
||
Sem estoque ligado ao cardápio: pedido vende o que não existe → ruptura, CMV cego, pausa manual.
|
||
|
||
```text
|
||
Compra / ajuste ──► Ingredient.stockQty
|
||
│
|
||
Pedido (OrderItem) ──BOM──┤
|
||
▼
|
||
StockMovement (saída)
|
||
│
|
||
▼
|
||
se qty < min → MenuItem paused
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Escopo
|
||
|
||
### Inclui
|
||
- Cadastro de insumos (`Ingredient`) com unidade e saldo
|
||
- Ficha técnica (`Recipe`) por `MenuItem` (+ tamanho opcional)
|
||
- Movimentos: entrada (compra), saída (pedido), ajuste, perda
|
||
- Alerta mínimo / pausa automática no cardápio
|
||
- Relatório CMV simples (custo teórico por pedido)
|
||
- SKU 1:1 para bebidas prontas (refri, energético)
|
||
|
||
### Não inclui (fases posteriores)
|
||
- NFe XML automática / SPED
|
||
- Multi-depósito / transferência entre lojas
|
||
- WMS / código de barras avançado
|
||
- Produção de massa com OP industrial
|
||
|
||
---
|
||
|
||
## 3. Modelo de dados
|
||
|
||
```text
|
||
Ingredient
|
||
id, storeId, name, sku?
|
||
unit ← g | ml | un | kg | L
|
||
stockQty ← Decimal
|
||
minQty ← alerta
|
||
costCents ← custo médio unitário (na unidade base)
|
||
active
|
||
|
||
Recipe
|
||
id, storeId, menuItemId
|
||
sizeCode? ← broto|media|grande|gigante|null (= preço base)
|
||
yieldNote? ← "1 pizza G"
|
||
lines: RecipeLine[]
|
||
|
||
RecipeLine
|
||
id, recipeId, ingredientId
|
||
qty ← quantidade na unidade do Ingredient
|
||
wasteFactor? ← 1.05 = 5% perda
|
||
|
||
StockMovement
|
||
id, storeId, ingredientId
|
||
kind ← purchase|sale|adjust|waste|cancel_restore
|
||
qty ← sinal: + entrada / − saída
|
||
unitCostCents?
|
||
refType ← order|orderItem|manual|purchase
|
||
refId?
|
||
note?, userId?, createdAt
|
||
|
||
MenuItem.stockMode ← bom | sku | none (campo no Cardápio)
|
||
MenuItem.skuIngredientId? ← quando stockMode=sku
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Ponte com Cardápio
|
||
|
||
> Contrato: todo item vendável com impacto de estoque **declara** `stockMode` na SPEC-CARDAPIO.
|
||
|
||
| `stockMode` | Baixa |
|
||
|-------------|--------|
|
||
| `bom` | Soma `RecipeLine.qty × OrderItem.quantity` (meia-a-meia: 0,5 + 0,5) |
|
||
| `sku` | −1 un do `skuIngredientId` por unidade vendida |
|
||
| `none` | Sem movimento |
|
||
|
||
### Momento da baixa (política piloto)
|
||
|
||
| Momento | Ação |
|
||
|---------|------|
|
||
| `order` → `em_producao` | **Baixa** (sale) — padrão piloto |
|
||
| `order` → `cancelado` e ainda não saiu da cozinha | **Estorno** (cancel_restore) |
|
||
| Após `pronto` | Cancelamento **não** devolve (perda / waste opcional) |
|
||
|
||
Alternativa futura: reserva no `confirmado` + baixa no `pronto`.
|
||
|
||
### Exemplos Joinville
|
||
|
||
**Calabresa G** (`bom`) — pesos em **g** (fonte: [`FICHAS-TECNICAS-PESO.md`](FICHAS-TECNICAS-PESO.md)):
|
||
| Insumo | Qty |
|
||
|--------|-----|
|
||
| Massa (disco) | **480 g** |
|
||
| Molho tomate | **90 g** |
|
||
| Mussarela | **200 g** |
|
||
| Calabresa fatiada | **120 g** |
|
||
| Cebola | **40 g** |
|
||
| Orégano | **2 g** |
|
||
| **Peso montado ≈** | **930 g** |
|
||
|
||
**Coca-Cola 2L** (`sku`): −1 un `ingredient:coca-2l`
|
||
|
||
**Vitamina Morango** (`bom`):
|
||
| Insumo | Qty |
|
||
|--------|-----|
|
||
| Leite integral | 300 ml |
|
||
| Morango | 100 g |
|
||
| Açúcar | 20 g |
|
||
| Gelo | 100 g |
|
||
|
||
### 4.1 Quantização por peso — pizzas (obrigatório)
|
||
|
||
> Catálogo completo (38 tipos): [`data/fichas-pizzas-peso.json`](data/fichas-pizzas-peso.json) · resumo [`FICHAS-TECNICAS-PESO.md`](FICHAS-TECNICAS-PESO.md)
|
||
|
||
| Regra | Valor |
|
||
|-------|--------|
|
||
| Unidade canônica toppings/massa | **gramas (g)** |
|
||
| Base Grande 40 cm | massa 480 · molho 90 · mussarela 200 |
|
||
| Base Broto 25 cm | massa 200 · molho 40 · mussarela 85 (≈ 0,42× G) |
|
||
| Baixa | `Σ (line.qty_g × quantity)` |
|
||
| Meia-a-meia | massa/molho 100% · toppings 50% de cada sabor |
|
||
| Cobertura piloto | **38 pizzas** (20 trad. + 5 broto + 6 especiais SC + 7 doces) |
|
||
|
||
Cada tipo de pizza **tem ficha própria** — não compartilhar um único “peso médio”. Ajustes por loja na UI sem quebrar o JSON seed.
|
||
|
||
---
|
||
|
||
## 5. Requisitos (RF-EST)
|
||
|
||
| ID | Requisito | Track |
|
||
|----|-----------|-------|
|
||
| RF-EST-01 | CRUD insumos (nome, unidade, min, custo) | **E1** |
|
||
| RF-EST-02 | Ficha técnica por MenuItem (+ size) | **E1** |
|
||
| RF-EST-03 | Baixa automática no pedido (política §4) | **E1** |
|
||
| RF-EST-04 | Entrada por compra / ajuste manual | **E1** |
|
||
| RF-EST-05 | Alerta `stockQty ≤ minQty` (WS + UI gestor) | **E1** |
|
||
| RF-EST-06 | Pausa automática MenuItem se insumo crítico zerar | **E2** |
|
||
| RF-EST-07 | Estorno em cancelamento elegível | **E1** |
|
||
| RF-EST-08 | Relatório CMV teórico por dia / item | **E2** |
|
||
| RF-EST-09 | Inventário físico (contagem → ajuste) | **E2** |
|
||
| RF-EST-10 | Perda / desperdício com motivo | **E2** |
|
||
| RF-EST-11 | Bloquear venda se `strictStock=true` e saldo insuficiente | **E2** |
|
||
| RF-EST-12 | Auditoria: todo movimento com user + ref | **E1** |
|
||
| RF-EST-13 | Ficha por tipo de pizza com **qty em gramas** (38+ SKUs menu) | **E1** |
|
||
| RF-EST-14 | Importar/versionar catálogo `fichas-pizzas-peso.json` | **E1** |
|
||
| RF-EST-15 | Escala Broto/Grande/Gigante por fator ou ficha dedicada | **E1** |
|
||
|
||
---
|
||
|
||
## 6. Eventos
|
||
|
||
```text
|
||
stock.low → store:{id} (ingredientId, qty, minQty)
|
||
stock.updated → store:{id}
|
||
menu.item.paused → store:{id} (menuItemId, reason=stock)
|
||
menu.item.resumed → store:{id}
|
||
```
|
||
|
||
PDV / PWA escutam `menu.item.paused|resumed` (SPEC-CARDAPIO RF-MEN-13).
|
||
|
||
---
|
||
|
||
## 7. Regras de negócio
|
||
|
||
1. Unidade canônica por insumo; conversões (kg↔g) só na UI de entrada.
|
||
2. Custo médio móvel em compras: atualiza `Ingredient.costCents`.
|
||
3. Meia-a-meia: duas half-recipes; se faltar um sabor, cardápio bloqueia o par.
|
||
4. Bebidas `sku`: não passam por montagem/forno; só estação `bebidas` + expedição.
|
||
5. Vitaminas/sucos naturais: baixa no mesmo momento do pedido; se fruta acabou → pausa só o sabor.
|
||
6. `strictStock` (config loja): default **false** no piloto (alerta sem bloquear); true em produção madura.
|
||
|
||
---
|
||
|
||
## 8. Track E — implementação
|
||
|
||
| ID | Entrega | Dependência |
|
||
|----|---------|-------------|
|
||
| **E0** | Este SPEC + link no SPEC.md / Cardápio | ✅ agora |
|
||
| **E1** | Prisma Ingredient/Recipe/Movement + baixa no create/transition | **feito 2026-10-20** |
|
||
| **E2** | Pausa auto + CMV + inventário UI | E1 · Central estável |
|
||
| **E3** | Compras com custo médio + multi-loja | Fase 3 |
|
||
|
||
**Ordem sugerida com Cardápio:** M0 (menu Joinville) → **E1** → M1/M2 (admin ficha) → E2.
|
||
|
||
---
|
||
|
||
## 9. Critérios de aceite
|
||
|
||
1. Cadastro de mussarela / calabresa / Coca 2L com saldo e mínimo.
|
||
2. Ficha Calabresa G salva e listável a partir do MenuItem.
|
||
3. Criar pedido com 1× Calabresa G reduz mussarela em 200 g (ou qty da ficha).
|
||
4. Criar pedido com 1× Coca 2L reduz 1 un do SKU.
|
||
5. Cancelar pedido em produção devolve saldo.
|
||
6. Mussarela em 0 → itens BOM que usam mussarela pausam (E2) ou alerta (E1).
|
||
7. Movimentos auditáveis com `refType=order`.
|
||
|
||
---
|
||
|
||
## 10. UI alvo (gestor)
|
||
|
||
| Tela | Rota (proposta) | Função |
|
||
|------|-----------------|--------|
|
||
| Insumos | `/estoque` | Lista + saldo + min |
|
||
| Ficha técnica | `/cardapio/:itemId/ficha` | BOM editor |
|
||
| Movimentos | `/estoque/movimentos` | Extrato |
|
||
| Inventário | `/estoque/inventario` | Contagem |
|
||
|
||
Papel: `gestor` (escrita) · `pdv` (leitura alertas).
|
||
|
||
---
|
||
|
||
## 11. Fora de escopo
|
||
|
||
- Integração contábil completa
|
||
- Validade / lote FEFO (fase futura)
|
||
- App de compras do fornecedor
|
||
|
||
---
|
||
|
||
## 12. Glossário cruzado
|
||
|
||
| Termo | Onde |
|
||
|-------|------|
|
||
| **MenuItem** | SPEC-CARDAPIO — o que vende |
|
||
| **Recipe / BOM** | Esta SPEC — como consome |
|
||
| **Ingredient** | Esta SPEC — o que tem |
|
||
| **stockMode** | Cardápio declara · Estoque executa |
|
||
| **Order** | SPEC principal / Central — dispara baixa |
|
||
|
||
---
|
||
|
||
*SPEC satélite. Versão do produto pai: SPEC.md. Cruzamento obrigatório com SPEC-CARDAPIO.*
|
||
|
||
|
||
---
|
||
|
||
## 13. Implementação E1 (2026-10-20)
|
||
|
||
Entregue: `stockQty` · `StockMovement` · `strictStock` · `/admin/estoque` · baixa em produção · estorno no cancelamento · RF-EST-11.
|
||
|
||
`strictStock` default **false** (piloto). Gestor liga em `/admin/estoque` para bloquear pedido sem saldo.
|