267 lines
10 KiB
TypeScript
267 lines
10 KiB
TypeScript
import { defineTable } from 'convex/server';
|
|
import { v } from 'convex/values';
|
|
|
|
export const pontoTables = {
|
|
// Sistema de Controle de Ponto
|
|
registrosPonto: defineTable({
|
|
funcionarioId: v.id('funcionarios'),
|
|
tipo: v.union(
|
|
v.literal('entrada'),
|
|
v.literal('saida_almoco'),
|
|
v.literal('retorno_almoco'),
|
|
v.literal('saida')
|
|
),
|
|
data: v.string(), // YYYY-MM-DD
|
|
hora: v.number(),
|
|
minuto: v.number(),
|
|
segundo: v.number(),
|
|
timestamp: v.number(), // Timestamp completo para ordenação
|
|
imagemId: v.optional(v.id('_storage')),
|
|
sincronizadoComServidor: v.boolean(),
|
|
toleranciaMinutos: v.number(),
|
|
dentroDoPrazo: v.boolean(),
|
|
|
|
// Informações de Rede
|
|
ipAddress: v.optional(v.string()),
|
|
ipPublico: v.optional(v.string()),
|
|
ipLocal: v.optional(v.string()),
|
|
|
|
// Informações do Navegador
|
|
userAgent: v.optional(v.string()),
|
|
browser: v.optional(v.string()),
|
|
browserVersion: v.optional(v.string()),
|
|
engine: v.optional(v.string()),
|
|
|
|
// Informações do Sistema
|
|
sistemaOperacional: v.optional(v.string()),
|
|
osVersion: v.optional(v.string()),
|
|
arquitetura: v.optional(v.string()),
|
|
plataforma: v.optional(v.string()),
|
|
|
|
// Informações de Localização
|
|
latitude: v.optional(v.number()),
|
|
longitude: v.optional(v.number()),
|
|
precisao: v.optional(v.number()),
|
|
altitude: v.optional(v.union(v.number(), v.null())),
|
|
altitudeAccuracy: v.optional(v.union(v.number(), v.null())),
|
|
heading: v.optional(v.union(v.number(), v.null())),
|
|
speed: v.optional(v.union(v.number(), v.null())),
|
|
confiabilidadeGPS: v.optional(v.number()), // 0-1 (frontend)
|
|
scoreConfiancaBackend: v.optional(v.number()), // 0-1 (backend)
|
|
suspeitaSpoofing: v.optional(v.boolean()),
|
|
motivoSuspeita: v.optional(v.string()),
|
|
avisosValidacao: v.optional(v.array(v.string())), // Array de avisos detalhados da validação
|
|
distanciaIPvsGPS: v.optional(v.number()), // Distância em metros entre IP geolocation e GPS
|
|
velocidadeUltimoRegistro: v.optional(v.number()), // Velocidade em km/h do último registro
|
|
distanciaUltimoRegistro: v.optional(v.number()), // Distância em metros do último registro
|
|
tempoDecorridoHoras: v.optional(v.number()), // Tempo em horas desde o último registro
|
|
// Informações de Geofencing
|
|
enderecoMarcacaoEsperado: v.optional(v.id('enderecosMarcacao')), // Endereço mais próximo esperado
|
|
distanciaEnderecoEsperado: v.optional(v.number()), // Distância em metros do endereço esperado
|
|
dentroRaioPermitido: v.optional(v.boolean()), // Se está dentro do raio permitido
|
|
enderecoMarcacaoUsado: v.optional(v.id('enderecosMarcacao')), // Qual endereço foi usado na validação
|
|
raioToleranciaUsado: v.optional(v.number()), // Raio usado na validação em metros
|
|
endereco: v.optional(v.string()),
|
|
cidade: v.optional(v.string()),
|
|
estado: v.optional(v.string()),
|
|
pais: v.optional(v.string()),
|
|
timezone: v.optional(v.string()),
|
|
|
|
// Informações do Dispositivo
|
|
deviceType: v.optional(v.string()),
|
|
deviceModel: v.optional(v.string()),
|
|
screenResolution: v.optional(v.string()),
|
|
coresTela: v.optional(v.number()),
|
|
idioma: v.optional(v.string()),
|
|
|
|
// Informações Adicionais
|
|
isMobile: v.optional(v.boolean()),
|
|
isTablet: v.optional(v.boolean()),
|
|
isDesktop: v.optional(v.boolean()),
|
|
connectionType: v.optional(v.string()),
|
|
memoryInfo: v.optional(v.string()),
|
|
|
|
// Informações de Sensores (Acelerômetro e Giroscópio)
|
|
acelerometroX: v.optional(v.number()),
|
|
acelerometroY: v.optional(v.number()),
|
|
acelerometroZ: v.optional(v.number()),
|
|
movimentoDetectado: v.optional(v.boolean()),
|
|
magnitudeMovimento: v.optional(v.number()),
|
|
variacaoAcelerometro: v.optional(v.number()),
|
|
giroscopioAlpha: v.optional(v.number()),
|
|
giroscopioBeta: v.optional(v.number()),
|
|
giroscopioGamma: v.optional(v.number()),
|
|
sensorDisponivel: v.optional(v.boolean()),
|
|
permissaoSensorNegada: v.optional(v.boolean()),
|
|
|
|
// Justificativa opcional para o registro
|
|
justificativa: v.optional(v.string()),
|
|
|
|
// Campos para homologação
|
|
editadoPorGestor: v.optional(v.boolean()),
|
|
homologacaoId: v.optional(v.id('homologacoesPonto')),
|
|
|
|
criadoEm: v.number()
|
|
})
|
|
.index('by_funcionario_data', ['funcionarioId', 'data'])
|
|
.index('by_data', ['data'])
|
|
.index('by_dentro_prazo', ['dentroDoPrazo', 'data'])
|
|
.index('by_funcionario_timestamp', ['funcionarioId', 'timestamp']),
|
|
|
|
// Endereços de Marcação - Locais permitidos para registro de ponto
|
|
enderecosMarcacao: defineTable({
|
|
nome: v.string(), // Ex: "Sede Principal", "Home Office João Silva", "Cliente ABC"
|
|
descricao: v.optional(v.string()), // Descrição opcional
|
|
// Coordenadas (obrigatórias)
|
|
latitude: v.number(),
|
|
longitude: v.number(),
|
|
// Endereço físico (para exibição)
|
|
endereco: v.string(), // Ex: "Rua Exemplo, 123"
|
|
bairro: v.optional(v.string()), // Bairro do endereço
|
|
cep: v.optional(v.string()),
|
|
cidade: v.string(),
|
|
estado: v.string(),
|
|
pais: v.optional(v.string()), // Padrão: "Brasil"
|
|
// Configurações
|
|
raioMetros: v.number(), // Raio de tolerância em metros (ex: 100m, 500m, 1000m)
|
|
ativo: v.boolean(),
|
|
// Tipos de uso
|
|
tipo: v.union(
|
|
v.literal('sede'), // Sede principal (para todos)
|
|
v.literal('home_office'), // Home office específico
|
|
v.literal('deslocamento'), // Deslocamento temporário
|
|
v.literal('cliente') // Local de cliente
|
|
),
|
|
// Metadados
|
|
criadoPor: v.id('usuarios'),
|
|
criadoEm: v.number(),
|
|
atualizadoPor: v.optional(v.id('usuarios')),
|
|
atualizadoEm: v.optional(v.number())
|
|
})
|
|
.index('by_ativo', ['ativo'])
|
|
.index('by_tipo', ['tipo'])
|
|
.index('by_cidade', ['cidade']),
|
|
|
|
// Associação Funcionário ↔ Endereço de Marcação
|
|
funcionarioEnderecosMarcacao: defineTable({
|
|
funcionarioId: v.id('funcionarios'),
|
|
enderecoMarcacaoId: v.id('enderecosMarcacao'),
|
|
// Configurações específicas do funcionário
|
|
raioMetrosPersonalizado: v.optional(v.number()), // Pode ter raio diferente do padrão
|
|
// Período de validade (para deslocamentos temporários)
|
|
dataInicio: v.optional(v.string()), // YYYY-MM-DD
|
|
dataFim: v.optional(v.string()), // YYYY-MM-DD
|
|
// Status
|
|
ativo: v.boolean(),
|
|
// Metadados
|
|
criadoPor: v.id('usuarios'),
|
|
criadoEm: v.number()
|
|
})
|
|
.index('by_funcionario', ['funcionarioId'])
|
|
.index('by_endereco', ['enderecoMarcacaoId'])
|
|
.index('by_funcionario_ativo', ['funcionarioId', 'ativo'])
|
|
.index('by_endereco_ativo', ['enderecoMarcacaoId', 'ativo']),
|
|
|
|
configuracaoPonto: defineTable({
|
|
horarioEntrada: v.string(), // HH:mm
|
|
horarioSaidaAlmoco: v.string(), // HH:mm
|
|
horarioRetornoAlmoco: v.string(), // HH:mm
|
|
horarioSaida: v.string(), // HH:mm
|
|
toleranciaMinutos: v.number(),
|
|
// Nomes personalizados dos tipos de registro
|
|
nomeEntrada: v.optional(v.string()), // Padrão: "Entrada 1"
|
|
nomeSaidaAlmoco: v.optional(v.string()), // Padrão: "Saída 1"
|
|
nomeRetornoAlmoco: v.optional(v.string()), // Padrão: "Entrada 2"
|
|
nomeSaida: v.optional(v.string()), // Padrão: "Saída 2"
|
|
// Ajuste de fuso horário (GMT offset em horas)
|
|
gmtOffset: v.optional(v.number()), // Padrão: 0 (UTC)
|
|
// Configurações de geofencing
|
|
validarLocalizacao: v.optional(v.boolean()), // Habilitar/desabilitar validação de localização
|
|
toleranciaDistanciaMetros: v.optional(v.number()), // Raio padrão global em metros
|
|
ativo: v.boolean(),
|
|
atualizadoPor: v.id('usuarios'),
|
|
atualizadoEm: v.number()
|
|
}).index('by_ativo', ['ativo']),
|
|
|
|
configuracaoRelogio: defineTable({
|
|
servidorNTP: v.optional(v.string()),
|
|
portaNTP: v.optional(v.number()),
|
|
usarServidorExterno: v.boolean(),
|
|
fallbackParaPC: v.boolean(),
|
|
ultimaSincronizacao: v.optional(v.number()),
|
|
offsetSegundos: v.optional(v.number()),
|
|
// Ajuste de fuso horário (GMT offset em horas)
|
|
gmtOffset: v.optional(v.number()), // Padrão: 0 (UTC)
|
|
atualizadoPor: v.id('usuarios'),
|
|
atualizadoEm: v.number()
|
|
}).index('by_ativo', ['usarServidorExterno']),
|
|
|
|
// Banco de Horas - Saldo diário de horas trabalhadas
|
|
bancoHoras: defineTable({
|
|
funcionarioId: v.id('funcionarios'),
|
|
data: v.string(), // YYYY-MM-DD
|
|
cargaHorariaDiaria: v.number(), // Horas esperadas do dia (em minutos)
|
|
horasTrabalhadas: v.number(), // Horas realmente trabalhadas (em minutos)
|
|
saldoMinutos: v.number(), // Saldo do dia (positivo = horas extras, negativo = déficit)
|
|
registrosPontoIds: v.array(v.id('registrosPonto')), // IDs dos registros do dia
|
|
calculadoEm: v.number()
|
|
})
|
|
.index('by_funcionario_data', ['funcionarioId', 'data'])
|
|
.index('by_funcionario', ['funcionarioId'])
|
|
.index('by_data', ['data']),
|
|
|
|
// Homologações de Ponto - Edições e ajustes realizados pelo gestor
|
|
homologacoesPonto: defineTable({
|
|
registroId: v.optional(v.id('registrosPonto')), // ID do registro editado (se for edição)
|
|
funcionarioId: v.id('funcionarios'),
|
|
gestorId: v.id('usuarios'),
|
|
// Dados do registro original (se for edição)
|
|
horaAnterior: v.optional(v.number()),
|
|
minutoAnterior: v.optional(v.number()),
|
|
// Dados do registro novo (se for edição)
|
|
horaNova: v.optional(v.number()),
|
|
minutoNova: v.optional(v.number()),
|
|
// Motivo e observações
|
|
motivoId: v.optional(v.string()), // ID do motivo (referência a atestados/declarações)
|
|
motivoTipo: v.optional(v.string()), // Tipo do motivo (atestado, declaracao, etc)
|
|
motivoDescricao: v.optional(v.string()), // Descrição do motivo
|
|
observacoes: v.optional(v.string()),
|
|
// Tipo de ajuste (se for ajuste de banco de horas)
|
|
tipoAjuste: v.optional(
|
|
v.union(v.literal('compensar'), v.literal('abonar'), v.literal('descontar'))
|
|
),
|
|
// Período do ajuste (se for ajuste de banco de horas)
|
|
periodoDias: v.optional(v.number()),
|
|
periodoHoras: v.optional(v.number()),
|
|
periodoMinutos: v.optional(v.number()),
|
|
// Ajuste em minutos (calculado)
|
|
ajusteMinutos: v.optional(v.number()),
|
|
criadoEm: v.number()
|
|
})
|
|
.index('by_funcionario', ['funcionarioId'])
|
|
.index('by_gestor', ['gestorId'])
|
|
.index('by_registro', ['registroId'])
|
|
.index('by_data', ['criadoEm']),
|
|
|
|
// Dispensas de Registro - Períodos onde funcionário está dispensado de registrar ponto
|
|
dispensasRegistro: defineTable({
|
|
funcionarioId: v.id('funcionarios'),
|
|
gestorId: v.id('usuarios'),
|
|
dataInicio: v.string(), // YYYY-MM-DD
|
|
horaInicio: v.number(),
|
|
minutoInicio: v.number(),
|
|
dataFim: v.string(), // YYYY-MM-DD
|
|
horaFim: v.number(),
|
|
minutoFim: v.number(),
|
|
motivo: v.string(),
|
|
isento: v.boolean(), // Se true, não expira (casos excepcionais)
|
|
ativo: v.boolean(),
|
|
criadoEm: v.number()
|
|
})
|
|
.index('by_funcionario', ['funcionarioId'])
|
|
.index('by_gestor', ['gestorId'])
|
|
.index('by_ativo', ['ativo'])
|
|
.index('by_data_inicio', ['dataInicio'])
|
|
.index('by_data_fim', ['dataFim'])
|
|
};
|