Saltar para o conteúdo
PMPaulo Mota
Voltar à demo

Como foi feito

Gestão de obra, por dentro

Duas coisas: as fases que saem da medição em vez de saírem da cabeça de alguém, e um portal do cliente que é uma projecção diferente feita no servidor, não um ecrã com colunas escondidas.

01

O problema

O cliente liga a perguntar como vai a obra, e quem atende não sabe dizer sem ligar ao encarregado. Depois manda-se uma foto pelo WhatsApp e fica-se sem registo de nada. O que falta não é uma aplicação bonita: é um sítio onde o estado da obra existe, e uma maneira de o mostrar ao cliente sem lhe mostrar as margens.

02

As fases saem da medição

Quando um orçamento é adjudicado, a obra nasce dele: uma fase por especialidade que o orçamento tem, e o peso de cada fase é a fatia do custo directo que essa especialidade vale. Uma obra em que a pintura pesa 14% pesa isso porque a medição diz que pesa. A percentagem executada é a soma dos pesos vezes o que está feito em cada fase, e por isso fechar quatro tarefas de pintura mexe menos o ponteiro do que fechar uma de estrutura.

sql
-- O peso de cada fase e a fatia do custo directo que a
-- especialidade vale na medicao. Ninguem o escreve a mao.
for v_cat in
  select l.category,
         sum(round(l.qty * l.material_cents)
           + round(l.qty * l.labour_hours * l.hourly_cents)
           + round(l.qty * l.equipment_cents)) as cents
    from obra_quote_lines l
   where l.quote_id = p_quote
   group by l.category
  having sum(...) > 0
   order by min(l.position)
loop
  insert into obra_phases (project_id, name, ord, weight_pct, budget_cents)
  values (v_p.id, v_cat.category, v_ord,
          round(100.0 * v_cat.cents / v_total, 2), v_cat.cents);
end loop;
03

O portal não esconde: projecta

obra_tasks e obra_updates têm RLS ligada e nenhuma política de leitura. Não há select que as devolva, venha de onde vier, incluindo da API pública. A única porta é uma função security definer que devolve um JSON com fases, percentagem e as notas marcadas como visíveis. Se alguém abrir as ferramentas do browser no portal, não encontra colunas escondidas: encontra o que a função devolveu, e mais nada existe do lado de lá.

sql
-- Sem politica de leitura, estas tabelas nao respondem a ninguem.
alter table obra_tasks   enable row level security;
alter table obra_updates enable row level security;

-- A unica porta do cliente: uma projeccao, nao um select filtrado.
create function obra_project_public(p_room text, p_code text)
returns jsonb security definer stable as $$
  select jsonb_build_object(
    'code', v_p.code, 'site', v_p.site,
    'progress_pct', (select round(sum(weighted), 1) from obra_progress(p_room)
                      where project_id = v_p.id),
    'phases',  (select jsonb_agg(...) from obra_phases where project_id = v_p.id),
    'updates', (select jsonb_agg(...) from obra_updates
                 where project_id = v_p.id and client_visible));
$$;
04

As regras estão na base de dados

Uma fase não fecha com tarefas por fechar, e a verificação está dentro da função, não no botão. Uma tarefa bloqueada não está feita: marcar como feita apaga o motivo do bloqueio, porque as duas coisas ao mesmo tempo são uma contradição que depois ninguém sabe ler.

sql
-- Uma fase nao fecha com tarefas por fechar.
if p_status = 'concluida' and exists (
     select 1 from obra_tasks where phase_id = p_phase and done_at is null) then
  raise exception 'ha tarefas por fechar';
end if;

-- Feita e bloqueada ao mesmo tempo e uma contradicao: marcar feita
-- apaga o motivo do bloqueio.
update obra_tasks
   set done_at = case when p_done then coalesce(done_at, now()) else null end,
       blocked_reason = case when p_done then null else nullif(btrim(p_blocked), '') end
 where id = p_task and room_code = p_room;
05

As tabelas

Quatro tabelas. Duas respondem a qualquer leitura; as outras duas não respondem a ninguém de fora, e é essa a diferença entre um portal e um ecrã com colunas escondidas.

TabelaPara que serve
obra_projectsA obra, com o orçamento do momento da adjudicação guardado.
obra_phasesUma por especialidade do orçamento, com peso e orçamento próprios.
obra_tasksRLS ligada, sem política de leitura: só as funções respondem.
obra_updatesO diário. Cada registo traz client_visible, e é essa coluna que o portal filtra.
06

O que faria diferente num cliente real

Fotografias a sério, com armazenamento e miniaturas, em vez de um tipo de registo chamado foto. Notificação ao cliente quando há novidade, por email ou SMS, em vez de ele ter de ir ver. Autos de medição para facturação por fases, que é como uma obra se paga. Datas planeadas por fase com um Gantt simples. E autenticação, com o cliente a entrar por um link só dele em vez de um código que se adivinha.