O problema
Numa oficina, o caderno de marcações falha sempre no mesmo sítio: marca-se um carro para uma hora em que a rampa está livre mas o mecânico não, ou o contrário. O sistema trata a marcação como duas reservas ao mesmo tempo, uma de rampa e outra de pessoa.
O modelo
Uma marcação tem dia, início, duração (vinda do serviço), rampa e mecânico. Nada mais é guardado: a ocupação e a capacidade calculam-se a partir daqui.
| Tabela | Para que serve |
|---|---|
| bookings | Dia, início, serviço, rampa, mecânico, matrícula e estado. A duração vem do serviço. |
| services | Catálogo de serviços com a duração em minutos. |
| bays / mechanics | As rampas e as pessoas. Uma marcação ocupa uma de cada. |
A regra da sobreposição
Duas marcações chocam quando partilham rampa ou mecânico e os intervalos se cruzam. A mesma verificação serve para validar o formulário e para procurar a primeira vaga.
// Duas marcações chocam quando partilham rampa ou mecânico
// e os intervalos se cruzam. A mesma função valida o formulário
// e procura a primeira vaga.
export function clashFor(bookings, candidate, ignoreId) {
const end = candidate.start + SERVICES[candidate.service];
if (end > CLOSE || candidate.start < OPEN) return "closing";
const sameDay = bookings.filter(b => b.day === candidate.day && b.id !== ignoreId);
const overlaps = b => b.start < end && candidate.start < endOf(b);
if (sameDay.some(b => b.ramp === candidate.ramp && overlaps(b))) return "ramp";
if (sameDay.some(b => b.mechanic === candidate.mechanic && overlaps(b))) return "mechanic";
return null;
}O que faria diferente em produção
Nesta demo os dados vivem no browser. Numa oficina real ficavam numa base de dados com uma restrição de exclusão por intervalo, para dois telemóveis ao mesmo tempo não conseguirem marcar a mesma rampa.
-- Em produção a regra vive na base de dados: dois telemóveis
-- ao mesmo tempo não conseguem marcar a mesma rampa.
alter table bookings add constraint no_bay_overlap
exclude using gist (bay with =, slot with &&);
alter table bookings add constraint no_mechanic_overlap
exclude using gist (mechanic with =, slot with &&);