Files
sgse-app/packages/backend/convex/configuracaoPonto.ts

162 lines
5.3 KiB
TypeScript

import { v } from 'convex/values';
import { mutation, query } from './_generated/server';
import { getCurrentUserFunction } from './auth';
import type { Id } from './_generated/dataModel';
/**
* Valida formato de horário HH:mm
*/
function validarHorario(horario: string): boolean {
const regex = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/;
return regex.test(horario);
}
/**
* Obtém a configuração ativa de ponto
*/
export const obterConfiguracao = query({
args: {},
handler: async (ctx) => {
const config = await ctx.db
.query('configuracaoPonto')
.withIndex('by_ativo', (q) => q.eq('ativo', true))
.first();
if (!config) {
// Retornar configuração padrão se não houver
return {
horarioEntrada: '08:00',
horarioSaidaAlmoco: '12:00',
horarioRetornoAlmoco: '13:00',
horarioSaida: '17:00',
toleranciaMinutos: 15,
nomeEntrada: 'Entrada 1',
nomeSaidaAlmoco: 'Saída 1',
nomeRetornoAlmoco: 'Entrada 2',
nomeSaida: 'Saída 2',
validarLocalizacao: true,
toleranciaDistanciaMetros: 100,
ativo: false
};
}
// Garantir que os nomes padrão e valores padrão estejam definidos
return {
...config,
nomeEntrada: config.nomeEntrada || 'Entrada 1',
nomeSaidaAlmoco: config.nomeSaidaAlmoco || 'Saída 1',
nomeRetornoAlmoco: config.nomeRetornoAlmoco || 'Entrada 2',
nomeSaida: config.nomeSaida || 'Saída 2',
validarLocalizacao: config.validarLocalizacao ?? true,
toleranciaDistanciaMetros: config.toleranciaDistanciaMetros ?? 100
};
}
});
/**
* Salva configuração de ponto (apenas TI)
*/
export const salvarConfiguracao = mutation({
args: {
horarioEntrada: v.string(),
horarioSaidaAlmoco: v.string(),
horarioRetornoAlmoco: v.string(),
horarioSaida: v.string(),
toleranciaMinutos: v.number(),
nomeEntrada: v.optional(v.string()),
nomeSaidaAlmoco: v.optional(v.string()),
nomeRetornoAlmoco: v.optional(v.string()),
nomeSaida: v.optional(v.string()),
validarLocalizacao: v.optional(v.boolean()),
toleranciaDistanciaMetros: v.optional(v.number())
},
handler: async (ctx, args) => {
const usuario = await getCurrentUserFunction(ctx);
if (!usuario) {
throw new Error('Usuário não autenticado');
}
// TODO: Verificar se usuário tem permissão de TI
// Por enquanto, permitir se tiver roleId
// Validar horários
if (!validarHorario(args.horarioEntrada)) {
throw new Error('Horário de entrada inválido (formato: HH:mm)');
}
if (!validarHorario(args.horarioSaidaAlmoco)) {
throw new Error('Horário de saída para almoço inválido (formato: HH:mm)');
}
if (!validarHorario(args.horarioRetornoAlmoco)) {
throw new Error('Horário de retorno do almoço inválido (formato: HH:mm)');
}
if (!validarHorario(args.horarioSaida)) {
throw new Error('Horário de saída inválido (formato: HH:mm)');
}
// Validar tolerância
if (args.toleranciaMinutos < 0 || args.toleranciaMinutos > 60) {
throw new Error('Tolerância deve estar entre 0 e 60 minutos');
}
// Validar sequência lógica de horários
const [horaEntrada, minutoEntrada] = args.horarioEntrada.split(':').map(Number);
const [horaSaidaAlmoco, minutoSaidaAlmoco] = args.horarioSaidaAlmoco.split(':').map(Number);
const [horaRetornoAlmoco, minutoRetornoAlmoco] = args.horarioRetornoAlmoco
.split(':')
.map(Number);
const [horaSaida, minutoSaida] = args.horarioSaida.split(':').map(Number);
const minutosEntrada = horaEntrada * 60 + minutoEntrada;
const minutosSaidaAlmoco = horaSaidaAlmoco * 60 + minutoSaidaAlmoco;
const minutosRetornoAlmoco = horaRetornoAlmoco * 60 + minutoRetornoAlmoco;
const minutosSaida = horaSaida * 60 + minutoSaida;
if (minutosEntrada >= minutosSaidaAlmoco) {
throw new Error('Horário de entrada deve ser anterior à saída para almoço');
}
if (minutosSaidaAlmoco >= minutosRetornoAlmoco) {
throw new Error('Horário de saída para almoço deve ser anterior ao retorno');
}
if (minutosRetornoAlmoco >= minutosSaida) {
throw new Error('Horário de retorno do almoço deve ser anterior à saída');
}
// Validar tolerância de distância se fornecida
if (args.toleranciaDistanciaMetros !== undefined) {
if (args.toleranciaDistanciaMetros < 0 || args.toleranciaDistanciaMetros > 50000) {
throw new Error('Tolerância de distância deve estar entre 0 e 50000 metros');
}
}
// Desativar configurações antigas
const configsAntigas = await ctx.db
.query('configuracaoPonto')
.withIndex('by_ativo', (q) => q.eq('ativo', true))
.collect();
for (const configAntiga of configsAntigas) {
await ctx.db.patch(configAntiga._id, { ativo: false });
}
// Criar nova configuração
const configId = await ctx.db.insert('configuracaoPonto', {
horarioEntrada: args.horarioEntrada,
horarioSaidaAlmoco: args.horarioSaidaAlmoco,
horarioRetornoAlmoco: args.horarioRetornoAlmoco,
horarioSaida: args.horarioSaida,
toleranciaMinutos: args.toleranciaMinutos,
nomeEntrada: args.nomeEntrada || 'Entrada 1',
nomeSaidaAlmoco: args.nomeSaidaAlmoco || 'Saída 1',
nomeRetornoAlmoco: args.nomeRetornoAlmoco || 'Entrada 2',
nomeSaida: args.nomeSaida || 'Saída 2',
validarLocalizacao: args.validarLocalizacao ?? true,
toleranciaDistanciaMetros: args.toleranciaDistanciaMetros ?? 100,
ativo: true,
atualizadoPor: usuario._id as Id<'usuarios'>,
atualizadoEm: Date.now()
});
return { configId };
}
});