obsidian-vault/Pizza-Digital/docs/SPEC-ESTOQUE.md
Roger 62a20a2573 Add Pizza-Digital docs to Obsidian vault and sync with VM125.
Mirror all SPECs and construction docs from pizza-digital, add hub/sidebar links and bidirectional sync scripts.
2026-11-09 19:45:11 +00:00

262 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.