O problema
Quase toda a gente orçamenta em folha de cálculo, e quase toda a gente perde a folha. Pior: a folha mistura material e mão de obra numa célula só, e no fim da obra ninguém consegue dizer se se perdeu dinheiro no material, nas horas ou na máquina. Sem os três separados, orçado contra real é uma conta que não se faz, e é por isso que este sistema começa pela composição e não pelo PDF.
As tabelas
Quatro coisas. A base de custos é partilhada com a folha de obra de propósito: o mesmo artigo que se orçamenta é o que se lança em campo, e por isso as duas pontas falam a mesma língua.
| Tabela | Para que serve |
|---|---|
| obra_cost_items | A base de custos. Material, horas e equipamento por unidade, separados. Partilhada com a folha de obra. |
| obra_quotes | Cabeçalho: cliente, obra, estado, margem, IVA, validade. |
| obra_quote_lines | A medição e uma cópia da composição no momento em que a linha entrou. |
| demo_rooms.obra_hourly_cents | A hora com que a empresa orçamenta. Vive na sala porque é decisão da empresa. |
A linha guarda o preço
Uma linha de orçamento não aponta para o preço de hoje: guarda uma cópia da composição no momento em que entrou. Se o cimento subir amanhã, um orçamento já enviado continua a dizer o que dizia quando o cliente o leu. Enquanto é rascunho há uma função que o actualiza de propósito, e que devolve quantas linhas mexeu, para ninguém ser surpreendido em silêncio.
-- A linha guarda o que o artigo custava quando entrou.
-- Se o cimento subir amanhã, um orçamento já assinado não muda
-- debaixo dos pés de quem o assinou.
insert into obra_quote_lines
(quote_id, cost_item_id, code_snapshot, name_snapshot, unit, category,
qty, material_cents, labour_hours, equipment_cents, hourly_cents)
values
(p_quote, v_i.id, v_i.code, v_i.name_pt, v_i.unit, v_i.category,
round(p_qty, 3), v_i.material_cents, v_i.labour_hours,
v_i.equipment_cents, obra_room_hourly(p_room));As contas nascem no servidor
O browser manda o artigo e a medição. Custo directo, margem, IVA e total saem de uma função, e a mesma função devolve as contas de todos os orçamentos da sala de uma vez, para a lista não fazer uma consulta por linha. Mexer a margem no ecrã mostra o total a andar, mas só grava quando se larga: um slider a escrever a cada pixel é uma escrita por pixel.
-- Uma chamada devolve as contas de todos os orçamentos da sala.
-- O ecrã da lista não faz uma consulta por linha.
select id,
(mat + lab + eqp) as direct_cents,
round((mat + lab + eqp) * margin_pct / 100) as margin_cents,
(mat + lab + eqp)
+ round((mat + lab + eqp) * margin_pct / 100) as net_cents
from (select q.id, q.margin_pct,
sum(round(l.qty * l.material_cents)) as mat,
sum(round(l.qty * l.labour_hours * l.hourly_cents)) as lab,
sum(round(l.qty * l.equipment_cents)) as eqp
from obra_quotes q
left join obra_quote_lines l on l.quote_id = q.id
where q.room_code = p_room
group by q.id, q.margin_pct) base;O estado fecha a porta
Rascunho, enviado, adjudicado ou perdido, e não se anda para trás. A regra de que um orçamento enviado já não muda de linhas está dentro do update, não dentro do botão: um ecrã velho aberto noutro separador não a contorna, porque a condição corre na base de dados e devolve zero linhas se não bater certo.
-- Um orçamento enviado já não muda de linhas. A regra está no
-- update, não no botão: um ecrã velho noutro separador não a contorna.
update obra_quotes set margin_pct = round(p_pct, 2)
where id = p_quote and room_code = p_room and status = 'rascunho'
returning * into v_row;
if not found then raise exception 'orcamento fechado'; end if;O que faria diferente num cliente real
PDF com a folha de medições e as condições de pagamento, gerado no servidor e enviado por email com registo de abertura. Versões do orçamento, para saber o que mudou entre a proposta e a revisão que o cliente assinou. Revisão de preços indexada, que numa obra longa é dinheiro a sério. E autenticação: aqui qualquer visitante adjudica um orçamento; num negócio real, só quem pode.