O problema
As encomendas fazem-se por telefone e por WhatsApp, e o preço é o que o fornecedor disser na factura. Quando a carne chega, ninguém confere a guia contra o que foi pedido: assina-se e arruma-se. Faltam dois quilos, vem uma caixa danificada, e descobre-se na semana seguinte, se se descobrir. E quando o frango sobe seis por cento, o prato continua ao mesmo preço, porque ninguém deu por ela.
Modelo de dados
Fornecedores e preços são dados de referência, partilhados com o stock e o food cost. Uma encomenda pertence a uma sala e tem linhas: ingrediente, quantidade pedida, preço fixado no momento, e depois quantidade recebida, danificada e o lote que gerou. O preço fica gravado na linha para uma subida futura não reescrever encomendas antigas.
| Tabela | Para que serve |
|---|---|
| demo_suppliers | Quem fornece, com prazo de entrega e encomenda mínima. Partilhado com o stock. |
| demo_supplier_prices | Um preço por fornecedor, ingrediente e data. O actual é o mais recente. Partilhado com o food cost. |
| demo_purchase_orders | Uma encomenda por sala: fornecedor, estado, quando foi enviada e recebida e por quem. |
| demo_purchase_lines | As linhas: pedido, preço fixado no momento, recebido, danificado e o lote que gerou no stock. |
O preço nasce no servidor
O browser diz o que quer e quanto. A função vai buscar o preço mais recente daquele fornecedor para aquele ingrediente e grava-o na linha. Um fornecedor que não vende o ingrediente é recusado. A encomenda mínima do fornecedor é verificada na função, não num aviso no ecrã, e uma encomenda abaixo dela não é criada.
-- O browser diz o que quer e quanto. O preço vem daqui.
v_price := demo_current_price(p_supplier, (v_line->>'ingredient_id')::integer);
if v_price is null then
raise exception 'fornecedor nao vende este ingrediente';
end if;
insert into demo_purchase_lines (order_id, ingredient_id, qty_ordered, unit_price_cents)
values (v_row.id, ..., (v_line->>'qty')::numeric, v_price);
-- Encomenda mínima do fornecedor: na função, não num aviso no ecrã.
if v_total < v_sup.min_order_cents then
raise exception 'abaixo da encomenda minima';
end if;A guia confere-se linha a linha
Receber é comparar encomendado com recebido, linha a linha, e dizer quanto veio danificado. Se tudo bater, a encomenda fica recebida; se não, fica parcial, e a diferença fica escrita. E é aqui que as compras tocam no stock: o que chegou em condições entra como lote, ao preço da encomenda, com a validade típica do ingrediente, e o movimento de entrada fica referenciado à encomenda. Ninguém copia números de um sistema para o outro.
-- Encomendado contra recebido, linha a linha.
v_good := v_recv - v_dmg;
if v_recv <> v_line.qty_ordered or v_dmg > 0 then v_all_ok := false; end if;
-- É aqui que as compras tocam no stock: o que chegou em condições
-- entra como lote, ao preço da encomenda, com a validade do ingrediente.
if v_good > 0 then
v_batch := demo_stock_receive(
p_room, v_line.ingredient_id, v_good, v_line.unit_price_cents,
v_today + v_ing.shelf_days, <local pela categoria>, v_po.supplier_id, p_by);
update demo_purchase_lines set batch_id = v_batch.id where id = v_line.id;
end if;
update demo_purchase_orders
set status = case when v_all_ok then 'recebida' else 'parcial' end;Preços com história
Cada preço tem uma data. O preço actual é o mais recente, os anteriores ficam. É isso que permite ver que o frango subiu de 5,80 para 6,20, e comparar fornecedores lado a lado, com o prazo de entrega e a encomenda mínima ao lado do preço, porque o mais barato ao quilo com trinta quilos de mínimo e dois dias de espera nem sempre é a melhor compra.
-- Cada preço tem uma data. O actual é o mais recente; os outros ficam.
create table demo_supplier_prices (
supplier_id integer references demo_suppliers(id),
ingredient_id integer references demo_ingredients(id),
price_cents integer not null check (price_cents > 0),
since date not null default current_date,
unique (supplier_id, ingredient_id, since)
);
create function demo_current_price(p_supplier int, p_ingredient int)
returns integer language sql stable as $$
select price_cents from demo_supplier_prices
where supplier_id = p_supplier and ingredient_id = p_ingredient
order by since desc limit 1;
$$;O que faria diferente num cliente real
Envio da encomenda por email ao fornecedor a partir do sistema, com PDF. Importação da factura do fornecedor para conferir preço além de quantidade. Sugestão de encomenda a partir do stock baixo e do consumo previsto. E aprovação: encomendas acima de um valor passam pelo dono antes de sair.