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