Saltar para o conteúdo
PMPaulo Mota
Voltar à demo

Como foi feito

Escalas e horas, por dentro

O turno planeado e o tempo real vivem em tabelas diferentes, e o custo sai da diferença, calculado no servidor.

01

O problema

A escala está num papel na parede, o ponto num caderno, e o custo de pessoal aparece no fim do mês na contabilidade, quando já não há nada a fazer. Ninguém sabe que a Marta fez catorze minutos a mais todos os dias até serem sete horas no fim do mês. E quando alguém se esquece de picar a saída, o gestor mete uma hora à vista, sem registo de que meteu.

02

Modelo de dados

Três tabelas. Empregados, com custo-hora e horas contratadas. Turnos, que são o planeado: quem, que dia, de que horas a que horas, com que pausa. Registos de ponto, que são o real: entrada, saída, e se o gestor corrigiu, a correcção ao lado do valor picado, nunca por cima. Separar planeado de real é a decisão que faz o resto funcionar.

TabelaPara que serve
demo_employeesQuem trabalha na casa: secção, custo-hora e horas contratadas.
demo_shiftsO planeado: quem, que dia, de que horas a que horas, com que pausa e em que posto.
demo_time_entriesO real: entrada e saída picadas, e ao lado, se houver, a correcção do gestor com motivo, e a aprovação.
03

Dois turnos não se sobrepõem

Um empregado não pode estar escalado em dois turnos ao mesmo tempo. A regra não é um aviso no ecrã: é uma restrição de exclusão no Postgres, sobre o intervalo de tempo de cada turno. Um turno das 20h às 23h30 para quem já está das 17h às 23h é recusado pela base de dados. Um das 23h às 23h30 entra, porque não se sobrepõe.

sql
-- Um empregado não está em dois turnos ao mesmo tempo.
-- Restrição de exclusão sobre o intervalo de cada turno, na base de
-- dados, não num aviso no ecrã.
create extension if not exists btree_gist;

alter table demo_shifts add constraint demo_shifts_no_overlap
  exclude using gist (
    room_code   with =,
    employee_id with =,
    tsrange(day + starts, day + ends) with &&
  );
04

As horas calculam-se no servidor

Uma função devolve a folha de ponto já calculada: minutos planeados a partir do turno, minutos reais a partir do ponto, ambos descontada a pausa. O atraso é a entrada contra a hora do turno. As horas extra são o real menos o planeado, nunca negativo. O custo é o real ao custo-hora. Uma só fonte de verdade, e o ecrã só mostra.

sql
-- Planeado vem do turno, real vem do ponto. Uma só fonte de verdade.
scheduled_min := (extract(epoch from (s.ends - s.starts)) / 60) - s.break_min;
actual_min    := (extract(epoch from (coalesce(e.adjusted_out, e.clock_out) - e.clock_in)) / 60)
                 - s.break_min;
late_min      := greatest(0, extract(epoch from (e.clock_in - (s.day + s.starts))) / 60);
overtime_min  := greatest(0, actual_min - scheduled_min);
cost_cents    := round(actual_min / 60.0 * emp.hourly_cents);

-- Repare no coalesce: se o gestor corrigiu a saída, é a corrigida
-- que conta. A picada fica na tabela, ao lado, como estava.
05

Corrigir não é apagar

Quando falta a saída, o gestor mete uma: com motivo obrigatório, assinada e datada. A saída picada fica como estava; a corrigida fica ao lado, e é a corrigida que entra no cálculo. Aprovar exige uma saída, picada ou corrigida, e depois de aprovado não se corrige mais. A restrição está na tabela, não só na função.

sql
-- Corrigir não é apagar. A correcção fica ao lado do valor picado,
-- com motivo obrigatório, assinada e datada.
check (adjusted_out is null or adjust_reason is not null)

-- Não se aprova sem saída, picada ou corrigida:
check (approved_at is null or clock_out is not null or adjusted_out is not null)

-- E depois de aprovado não se corrige mais:
update demo_time_entries set adjusted_out = p_out, ...
 where id = p_entry and room_code = p_room and approved_at is null;
06

O que faria diferente num cliente real

Picagem com PIN ou cartão num tablet fixo, para a entrada ser de quem diz que é. Regras de horas extra da convenção colectiva, com majorações. Exportação para o processamento de salários. Escala semanal com arrastar e largar, e aviso quando alguém passa das horas contratadas. E o custo de pessoal contra as vendas do dia, que é o número que o dono quer ver.