feat: Introduce structured table definitions in convex/tables for various entities and remove the todos example table.
This commit is contained in:
266
packages/backend/convex/tables/ponto.ts
Normal file
266
packages/backend/convex/tables/ponto.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
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'])
|
||||
};
|
||||
Reference in New Issue
Block a user