feat: implement vacation management system with request approval, notification handling, and employee training tracking; enhance UI components for improved user experience
This commit is contained in:
8
packages/backend/convex/_generated/api.d.ts
vendored
8
packages/backend/convex/_generated/api.d.ts
vendored
@@ -18,9 +18,11 @@ import type * as betterAuth_auth from "../betterAuth/auth.js";
|
||||
import type * as chat from "../chat.js";
|
||||
import type * as configuracaoEmail from "../configuracaoEmail.js";
|
||||
import type * as crons from "../crons.js";
|
||||
import type * as cursos from "../cursos.js";
|
||||
import type * as dashboard from "../dashboard.js";
|
||||
import type * as documentos from "../documentos.js";
|
||||
import type * as email from "../email.js";
|
||||
import type * as ferias from "../ferias.js";
|
||||
import type * as funcionarios from "../funcionarios.js";
|
||||
import type * as healthCheck from "../healthCheck.js";
|
||||
import type * as http from "../http.js";
|
||||
@@ -29,6 +31,7 @@ import type * as logsAcesso from "../logsAcesso.js";
|
||||
import type * as logsAtividades from "../logsAtividades.js";
|
||||
import type * as logsLogin from "../logsLogin.js";
|
||||
import type * as menuPermissoes from "../menuPermissoes.js";
|
||||
import type * as migrarParaTimes from "../migrarParaTimes.js";
|
||||
import type * as migrarUsuariosAdmin from "../migrarUsuariosAdmin.js";
|
||||
import type * as monitoramento from "../monitoramento.js";
|
||||
import type * as perfisCustomizados from "../perfisCustomizados.js";
|
||||
@@ -37,6 +40,7 @@ import type * as seed from "../seed.js";
|
||||
import type * as simbolos from "../simbolos.js";
|
||||
import type * as solicitacoesAcesso from "../solicitacoesAcesso.js";
|
||||
import type * as templatesMensagens from "../templatesMensagens.js";
|
||||
import type * as times from "../times.js";
|
||||
import type * as todos from "../todos.js";
|
||||
import type * as usuarios from "../usuarios.js";
|
||||
import type * as verificarMatriculas from "../verificarMatriculas.js";
|
||||
@@ -66,9 +70,11 @@ declare const fullApi: ApiFromModules<{
|
||||
chat: typeof chat;
|
||||
configuracaoEmail: typeof configuracaoEmail;
|
||||
crons: typeof crons;
|
||||
cursos: typeof cursos;
|
||||
dashboard: typeof dashboard;
|
||||
documentos: typeof documentos;
|
||||
email: typeof email;
|
||||
ferias: typeof ferias;
|
||||
funcionarios: typeof funcionarios;
|
||||
healthCheck: typeof healthCheck;
|
||||
http: typeof http;
|
||||
@@ -77,6 +83,7 @@ declare const fullApi: ApiFromModules<{
|
||||
logsAtividades: typeof logsAtividades;
|
||||
logsLogin: typeof logsLogin;
|
||||
menuPermissoes: typeof menuPermissoes;
|
||||
migrarParaTimes: typeof migrarParaTimes;
|
||||
migrarUsuariosAdmin: typeof migrarUsuariosAdmin;
|
||||
monitoramento: typeof monitoramento;
|
||||
perfisCustomizados: typeof perfisCustomizados;
|
||||
@@ -85,6 +92,7 @@ declare const fullApi: ApiFromModules<{
|
||||
simbolos: typeof simbolos;
|
||||
solicitacoesAcesso: typeof solicitacoesAcesso;
|
||||
templatesMensagens: typeof templatesMensagens;
|
||||
times: typeof times;
|
||||
todos: typeof todos;
|
||||
usuarios: typeof usuarios;
|
||||
verificarMatriculas: typeof verificarMatriculas;
|
||||
|
||||
@@ -17,5 +17,13 @@ crons.interval(
|
||||
internal.chat.limparIndicadoresDigitacao
|
||||
);
|
||||
|
||||
// Atualizar status de férias dos funcionários diariamente
|
||||
crons.interval(
|
||||
"atualizar-status-ferias",
|
||||
{ hours: 24 },
|
||||
internal.ferias.atualizarStatusTodosFuncionarios,
|
||||
{}
|
||||
);
|
||||
|
||||
export default crons;
|
||||
|
||||
|
||||
67
packages/backend/convex/cursos.ts
Normal file
67
packages/backend/convex/cursos.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { v } from "convex/values";
|
||||
import { query, mutation } from "./_generated/server";
|
||||
|
||||
export const listarPorFuncionario = query({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("cursos"),
|
||||
_creationTime: v.number(),
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
descricao: v.string(),
|
||||
data: v.string(),
|
||||
certificadoId: v.optional(v.id("_storage")),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("cursos")
|
||||
.withIndex("by_funcionario", (q) =>
|
||||
q.eq("funcionarioId", args.funcionarioId)
|
||||
)
|
||||
.collect();
|
||||
},
|
||||
});
|
||||
|
||||
export const criar = mutation({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
descricao: v.string(),
|
||||
data: v.string(),
|
||||
certificadoId: v.optional(v.id("_storage")),
|
||||
},
|
||||
returns: v.id("cursos"),
|
||||
handler: async (ctx, args) => {
|
||||
const cursoId = await ctx.db.insert("cursos", args);
|
||||
return cursoId;
|
||||
},
|
||||
});
|
||||
|
||||
export const atualizar = mutation({
|
||||
args: {
|
||||
id: v.id("cursos"),
|
||||
descricao: v.string(),
|
||||
data: v.string(),
|
||||
certificadoId: v.optional(v.id("_storage")),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const { id, ...updates } = args;
|
||||
await ctx.db.patch(id, updates);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
export const excluir = mutation({
|
||||
args: {
|
||||
id: v.id("cursos"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.delete(args.id);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
475
packages/backend/convex/ferias.ts
Normal file
475
packages/backend/convex/ferias.ts
Normal file
@@ -0,0 +1,475 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query, internalMutation } from "./_generated/server";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
// Validador para períodos
|
||||
const periodoValidator = v.object({
|
||||
dataInicio: v.string(),
|
||||
dataFim: v.string(),
|
||||
diasCorridos: v.number(),
|
||||
});
|
||||
|
||||
// Query: Listar TODAS as solicitações (para RH)
|
||||
export const listarTodas = query({
|
||||
args: {},
|
||||
returns: v.array(v.any()),
|
||||
handler: async (ctx) => {
|
||||
const solicitacoes = await ctx.db.query("solicitacoesFerias").collect();
|
||||
|
||||
const solicitacoesComDetalhes = await Promise.all(
|
||||
solicitacoes.map(async (s) => {
|
||||
const funcionario = await ctx.db.get(s.funcionarioId);
|
||||
|
||||
// Buscar time do funcionário
|
||||
const membroTime = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", s.funcionarioId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
let time = null;
|
||||
if (membroTime) {
|
||||
time = await ctx.db.get(membroTime.timeId);
|
||||
}
|
||||
|
||||
return {
|
||||
...s,
|
||||
funcionario,
|
||||
time,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return solicitacoesComDetalhes.sort((a, b) => b._creationTime - a._creationTime);
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Listar solicitações do funcionário
|
||||
export const listarMinhasSolicitacoes = query({
|
||||
args: { funcionarioId: v.id("funcionarios") },
|
||||
returns: v.array(v.any()),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId))
|
||||
.order("desc")
|
||||
.collect();
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Listar solicitações dos subordinados (para gestores)
|
||||
export const listarSolicitacoesSubordinados = query({
|
||||
args: { gestorId: v.id("usuarios") },
|
||||
returns: v.array(v.any()),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar times onde o usuário é gestor
|
||||
const timesGestor = await ctx.db
|
||||
.query("times")
|
||||
.withIndex("by_gestor", (q) => q.eq("gestorId", args.gestorId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.collect();
|
||||
|
||||
const solicitacoes: Array<any> = [];
|
||||
|
||||
for (const time of timesGestor) {
|
||||
// Buscar membros do time
|
||||
const membros = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true))
|
||||
.collect();
|
||||
|
||||
// Buscar solicitações de cada membro
|
||||
for (const membro of membros) {
|
||||
const solic = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", membro.funcionarioId))
|
||||
.collect();
|
||||
|
||||
// Adicionar info do funcionário
|
||||
for (const s of solic) {
|
||||
const funcionario = await ctx.db.get(s.funcionarioId);
|
||||
solicitacoes.push({
|
||||
...s,
|
||||
funcionario,
|
||||
time,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return solicitacoes.sort((a, b) => b._creationTime - a._creationTime);
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Obter detalhes completos de uma solicitação
|
||||
export const obterDetalhes = query({
|
||||
args: { solicitacaoId: v.id("solicitacoesFerias") },
|
||||
returns: v.union(v.any(), v.null()),
|
||||
handler: async (ctx, args) => {
|
||||
const solicitacao = await ctx.db.get(args.solicitacaoId);
|
||||
if (!solicitacao) return null;
|
||||
|
||||
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
|
||||
let gestor = null;
|
||||
if (solicitacao.gestorId) {
|
||||
gestor = await ctx.db.get(solicitacao.gestorId);
|
||||
}
|
||||
|
||||
return {
|
||||
...solicitacao,
|
||||
funcionario,
|
||||
gestor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Criar solicitação de férias
|
||||
export const criarSolicitacao = mutation({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
anoReferencia: v.number(),
|
||||
periodos: v.array(periodoValidator),
|
||||
observacao: v.optional(v.string()),
|
||||
},
|
||||
returns: v.id("solicitacoesFerias"),
|
||||
handler: async (ctx, args) => {
|
||||
if (args.periodos.length === 0) {
|
||||
throw new Error("É necessário adicionar pelo menos 1 período");
|
||||
}
|
||||
|
||||
if (args.periodos.length > 3) {
|
||||
throw new Error("Máximo de 3 períodos permitidos");
|
||||
}
|
||||
|
||||
const funcionario = await ctx.db.get(args.funcionarioId);
|
||||
if (!funcionario) throw new Error("Funcionário não encontrado");
|
||||
|
||||
// Buscar usuário que está criando (pode não ser o próprio funcionário)
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", args.funcionarioId))
|
||||
.first();
|
||||
|
||||
const solicitacaoId = await ctx.db.insert("solicitacoesFerias", {
|
||||
funcionarioId: args.funcionarioId,
|
||||
anoReferencia: args.anoReferencia,
|
||||
status: "aguardando_aprovacao",
|
||||
periodos: args.periodos,
|
||||
observacao: args.observacao,
|
||||
historicoAlteracoes: [{
|
||||
data: Date.now(),
|
||||
usuarioId: usuario?._id || funcionario.gestorId!,
|
||||
acao: "Solicitação criada",
|
||||
}],
|
||||
});
|
||||
|
||||
// Notificar gestor
|
||||
if (funcionario.gestorId) {
|
||||
await ctx.db.insert("notificacoesFerias", {
|
||||
destinatarioId: funcionario.gestorId,
|
||||
solicitacaoFeriasId: solicitacaoId,
|
||||
tipo: "nova_solicitacao",
|
||||
lida: false,
|
||||
mensagem: `${funcionario.nome} solicitou férias`,
|
||||
});
|
||||
}
|
||||
|
||||
return solicitacaoId;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Aprovar férias
|
||||
export const aprovar = mutation({
|
||||
args: {
|
||||
solicitacaoId: v.id("solicitacoesFerias"),
|
||||
gestorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const solicitacao = await ctx.db.get(args.solicitacaoId);
|
||||
if (!solicitacao) throw new Error("Solicitação não encontrada");
|
||||
|
||||
if (solicitacao.status !== "aguardando_aprovacao") {
|
||||
throw new Error("Esta solicitação já foi processada");
|
||||
}
|
||||
|
||||
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
|
||||
|
||||
await ctx.db.patch(args.solicitacaoId, {
|
||||
status: "aprovado",
|
||||
gestorId: args.gestorId,
|
||||
dataAprovacao: Date.now(),
|
||||
historicoAlteracoes: [
|
||||
...(solicitacao.historicoAlteracoes || []),
|
||||
{
|
||||
data: Date.now(),
|
||||
usuarioId: args.gestorId,
|
||||
acao: "Aprovado",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Notificar funcionário
|
||||
if (funcionario) {
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.first();
|
||||
|
||||
if (usuario) {
|
||||
await ctx.db.insert("notificacoesFerias", {
|
||||
destinatarioId: usuario._id,
|
||||
solicitacaoFeriasId: args.solicitacaoId,
|
||||
tipo: "aprovado",
|
||||
lida: false,
|
||||
mensagem: "Suas férias foram aprovadas!",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Reprovar férias
|
||||
export const reprovar = mutation({
|
||||
args: {
|
||||
solicitacaoId: v.id("solicitacoesFerias"),
|
||||
gestorId: v.id("usuarios"),
|
||||
motivoReprovacao: v.string(),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const solicitacao = await ctx.db.get(args.solicitacaoId);
|
||||
if (!solicitacao) throw new Error("Solicitação não encontrada");
|
||||
|
||||
if (solicitacao.status !== "aguardando_aprovacao") {
|
||||
throw new Error("Esta solicitação já foi processada");
|
||||
}
|
||||
|
||||
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
|
||||
|
||||
await ctx.db.patch(args.solicitacaoId, {
|
||||
status: "reprovado",
|
||||
gestorId: args.gestorId,
|
||||
dataReprovacao: Date.now(),
|
||||
motivoReprovacao: args.motivoReprovacao,
|
||||
historicoAlteracoes: [
|
||||
...(solicitacao.historicoAlteracoes || []),
|
||||
{
|
||||
data: Date.now(),
|
||||
usuarioId: args.gestorId,
|
||||
acao: `Reprovado: ${args.motivoReprovacao}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Notificar funcionário
|
||||
if (funcionario) {
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.first();
|
||||
|
||||
if (usuario) {
|
||||
await ctx.db.insert("notificacoesFerias", {
|
||||
destinatarioId: usuario._id,
|
||||
solicitacaoFeriasId: args.solicitacaoId,
|
||||
tipo: "reprovado",
|
||||
lida: false,
|
||||
mensagem: `Suas férias foram reprovadas: ${args.motivoReprovacao}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Ajustar data e aprovar
|
||||
export const ajustarEAprovar = mutation({
|
||||
args: {
|
||||
solicitacaoId: v.id("solicitacoesFerias"),
|
||||
gestorId: v.id("usuarios"),
|
||||
novosPeriodos: v.array(periodoValidator),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const solicitacao = await ctx.db.get(args.solicitacaoId);
|
||||
if (!solicitacao) throw new Error("Solicitação não encontrada");
|
||||
|
||||
if (solicitacao.status !== "aguardando_aprovacao") {
|
||||
throw new Error("Esta solicitação já foi processada");
|
||||
}
|
||||
|
||||
if (args.novosPeriodos.length === 0) {
|
||||
throw new Error("É necessário adicionar pelo menos 1 período");
|
||||
}
|
||||
|
||||
if (args.novosPeriodos.length > 3) {
|
||||
throw new Error("Máximo de 3 períodos permitidos");
|
||||
}
|
||||
|
||||
const funcionario = await ctx.db.get(solicitacao.funcionarioId);
|
||||
|
||||
await ctx.db.patch(args.solicitacaoId, {
|
||||
status: "data_ajustada_aprovada",
|
||||
periodos: args.novosPeriodos,
|
||||
gestorId: args.gestorId,
|
||||
dataAprovacao: Date.now(),
|
||||
historicoAlteracoes: [
|
||||
...(solicitacao.historicoAlteracoes || []),
|
||||
{
|
||||
data: Date.now(),
|
||||
usuarioId: args.gestorId,
|
||||
acao: "Data ajustada e aprovada",
|
||||
periodosAnteriores: solicitacao.periodos,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Notificar funcionário
|
||||
if (funcionario) {
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.first();
|
||||
|
||||
if (usuario) {
|
||||
await ctx.db.insert("notificacoesFerias", {
|
||||
destinatarioId: usuario._id,
|
||||
solicitacaoFeriasId: args.solicitacaoId,
|
||||
tipo: "data_ajustada",
|
||||
lida: false,
|
||||
mensagem: "Suas férias foram aprovadas com ajuste de datas",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Verificar status de férias automático
|
||||
export const verificarStatusFerias = query({
|
||||
args: { funcionarioId: v.id("funcionarios") },
|
||||
returns: v.union(v.literal("ativo"), v.literal("em_ferias")),
|
||||
handler: async (ctx, args) => {
|
||||
const hoje = new Date();
|
||||
hoje.setHours(0, 0, 0, 0);
|
||||
|
||||
const solicitacoesAprovadas = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario_and_status", (q) =>
|
||||
q.eq("funcionarioId", args.funcionarioId)
|
||||
.eq("status", "aprovado")
|
||||
)
|
||||
.collect();
|
||||
|
||||
const solicitacoesAjustadas = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario_and_status", (q) =>
|
||||
q.eq("funcionarioId", args.funcionarioId)
|
||||
.eq("status", "data_ajustada_aprovada")
|
||||
)
|
||||
.collect();
|
||||
|
||||
const todasSolicitacoes = [...solicitacoesAprovadas, ...solicitacoesAjustadas];
|
||||
|
||||
for (const solicitacao of todasSolicitacoes) {
|
||||
for (const periodo of solicitacao.periodos) {
|
||||
const inicio = new Date(periodo.dataInicio);
|
||||
const fim = new Date(periodo.dataFim);
|
||||
inicio.setHours(0, 0, 0, 0);
|
||||
fim.setHours(23, 59, 59, 999);
|
||||
|
||||
if (hoje >= inicio && hoje <= fim) {
|
||||
return "em_ferias";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "ativo";
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Obter notificações não lidas
|
||||
export const obterNotificacoesNaoLidas = query({
|
||||
args: { usuarioId: v.id("usuarios") },
|
||||
returns: v.array(v.any()),
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("notificacoesFerias")
|
||||
.withIndex("by_destinatario_and_lida", (q) =>
|
||||
q.eq("destinatarioId", args.usuarioId).eq("lida", false)
|
||||
)
|
||||
.collect();
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Marcar notificação como lida
|
||||
export const marcarComoLida = mutation({
|
||||
args: { notificacaoId: v.id("notificacoesFerias") },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.notificacaoId, { lida: true });
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Internal Mutation: Atualizar status de todos os funcionários
|
||||
export const atualizarStatusTodosFuncionarios = internalMutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
const funcionarios = await ctx.db.query("funcionarios").collect();
|
||||
|
||||
for (const func of funcionarios) {
|
||||
const hoje = new Date();
|
||||
hoje.setHours(0, 0, 0, 0);
|
||||
|
||||
const solicitacoesAprovadas = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario_and_status", (q) =>
|
||||
q.eq("funcionarioId", func._id)
|
||||
.eq("status", "aprovado")
|
||||
)
|
||||
.collect();
|
||||
|
||||
const solicitacoesAjustadas = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario_and_status", (q) =>
|
||||
q.eq("funcionarioId", func._id)
|
||||
.eq("status", "data_ajustada_aprovada")
|
||||
)
|
||||
.collect();
|
||||
|
||||
const todasSolicitacoes = [...solicitacoesAprovadas, ...solicitacoesAjustadas];
|
||||
|
||||
let emFerias = false;
|
||||
for (const solicitacao of todasSolicitacoes) {
|
||||
for (const periodo of solicitacao.periodos) {
|
||||
const inicio = new Date(periodo.dataInicio);
|
||||
const fim = new Date(periodo.dataFim);
|
||||
inicio.setHours(0, 0, 0, 0);
|
||||
fim.setHours(23, 59, 59, 999);
|
||||
|
||||
if (hoje >= inicio && hoje <= fim) {
|
||||
emFerias = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (emFerias) break;
|
||||
}
|
||||
|
||||
const novoStatus = emFerias ? "em_ferias" : "ativo";
|
||||
|
||||
if (func.statusFerias !== novoStatus) {
|
||||
await ctx.db.patch(func._id, { statusFerias: novoStatus });
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ export const create = mutation({
|
||||
args: {
|
||||
// Campos obrigatórios
|
||||
nome: v.string(),
|
||||
matricula: v.string(),
|
||||
matricula: v.optional(v.string()),
|
||||
simboloId: v.id("simbolos"),
|
||||
nascimento: v.string(),
|
||||
rg: v.string(),
|
||||
@@ -149,13 +149,15 @@ export const create = mutation({
|
||||
throw new Error("CPF já cadastrado");
|
||||
}
|
||||
|
||||
// Unicidade: Matrícula
|
||||
const matriculaExists = await ctx.db
|
||||
.query("funcionarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.unique();
|
||||
if (matriculaExists) {
|
||||
throw new Error("Matrícula já cadastrada");
|
||||
// Unicidade: Matrícula (apenas se fornecida)
|
||||
if (args.matricula) {
|
||||
const matriculaExists = await ctx.db
|
||||
.query("funcionarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.unique();
|
||||
if (matriculaExists) {
|
||||
throw new Error("Já existe um funcionário com esta matrícula. Por favor, use outra ou deixe em branco.");
|
||||
}
|
||||
}
|
||||
|
||||
const novoFuncionarioId = await ctx.db.insert("funcionarios", args as any);
|
||||
@@ -168,7 +170,7 @@ export const update = mutation({
|
||||
id: v.id("funcionarios"),
|
||||
// Campos obrigatórios
|
||||
nome: v.string(),
|
||||
matricula: v.string(),
|
||||
matricula: v.optional(v.string()),
|
||||
simboloId: v.id("simbolos"),
|
||||
nascimento: v.string(),
|
||||
rg: v.string(),
|
||||
@@ -269,13 +271,15 @@ export const update = mutation({
|
||||
throw new Error("CPF já cadastrado");
|
||||
}
|
||||
|
||||
// Unicidade: Matrícula (excluindo o próprio registro)
|
||||
const matriculaExists = await ctx.db
|
||||
.query("funcionarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.unique();
|
||||
if (matriculaExists && matriculaExists._id !== args.id) {
|
||||
throw new Error("Matrícula já cadastrada");
|
||||
// Unicidade: Matrícula (apenas se fornecida, excluindo o próprio registro)
|
||||
if (args.matricula) {
|
||||
const matriculaExists = await ctx.db
|
||||
.query("funcionarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
||||
.unique();
|
||||
if (matriculaExists && matriculaExists._id !== args.id) {
|
||||
throw new Error("Já existe um funcionário com esta matrícula. Por favor, use outra ou deixe em branco.");
|
||||
}
|
||||
}
|
||||
|
||||
const { id, ...updateData } = args;
|
||||
@@ -306,13 +310,52 @@ export const getFichaCompleta = query({
|
||||
// Buscar informações do símbolo
|
||||
const simbolo = await ctx.db.get(funcionario.simboloId);
|
||||
|
||||
// Buscar cursos do funcionário
|
||||
const cursos = await ctx.db
|
||||
.query("cursos")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.id))
|
||||
.collect();
|
||||
|
||||
// Buscar URLs dos certificados
|
||||
const cursosComUrls = await Promise.all(
|
||||
cursos.map(async (curso) => {
|
||||
let certificadoUrl = null;
|
||||
if (curso.certificadoId) {
|
||||
certificadoUrl = await ctx.storage.getUrl(curso.certificadoId);
|
||||
}
|
||||
return {
|
||||
...curso,
|
||||
certificadoUrl,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...funcionario,
|
||||
simbolo: simbolo ? {
|
||||
nome: simbolo.nome,
|
||||
descricao: simbolo.descricao,
|
||||
tipo: simbolo.tipo,
|
||||
vencValor: simbolo.vencValor,
|
||||
repValor: simbolo.repValor,
|
||||
valor: simbolo.valor,
|
||||
} : null,
|
||||
cursos: cursosComUrls,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Configurar gestor (apenas para TI_MASTER)
|
||||
export const configurarGestor = mutation({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
gestorId: v.optional(v.id("usuarios")),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.funcionarioId, {
|
||||
gestorId: args.gestorId,
|
||||
});
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
171
packages/backend/convex/migrarParaTimes.ts
Normal file
171
packages/backend/convex/migrarParaTimes.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { internalMutation } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
/**
|
||||
* Migração: Converte estrutura antiga de gestores individuais para times
|
||||
*
|
||||
* Esta função cria automaticamente times baseados nos gestores existentes
|
||||
* e adiciona os funcionários subordinados aos respectivos times.
|
||||
*
|
||||
* Execute uma vez via dashboard do Convex:
|
||||
* Settings > Functions > Internal > migrarParaTimes > executar
|
||||
*/
|
||||
export const executar = internalMutation({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
timesCreated: v.number(),
|
||||
funcionariosAtribuidos: v.number(),
|
||||
erros: v.array(v.string()),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const erros: string[] = [];
|
||||
let timesCreated = 0;
|
||||
let funcionariosAtribuidos = 0;
|
||||
|
||||
try {
|
||||
// 1. Buscar todos os funcionários que têm gestor definido
|
||||
const funcionariosComGestor = await ctx.db
|
||||
.query("funcionarios")
|
||||
.filter((q) => q.neq(q.field("gestorId"), undefined))
|
||||
.collect();
|
||||
|
||||
if (funcionariosComGestor.length === 0) {
|
||||
return {
|
||||
timesCreated: 0,
|
||||
funcionariosAtribuidos: 0,
|
||||
erros: ["Nenhum funcionário com gestor configurado encontrado"],
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Agrupar funcionários por gestor
|
||||
const gestoresMap = new Map<string, any[]>();
|
||||
|
||||
for (const funcionario of funcionariosComGestor) {
|
||||
if (!funcionario.gestorId) continue;
|
||||
|
||||
const gestorId = funcionario.gestorId;
|
||||
if (!gestoresMap.has(gestorId)) {
|
||||
gestoresMap.set(gestorId, []);
|
||||
}
|
||||
gestoresMap.get(gestorId)!.push(funcionario);
|
||||
}
|
||||
|
||||
// 3. Para cada gestor, criar um time
|
||||
for (const [gestorId, subordinados] of gestoresMap.entries()) {
|
||||
try {
|
||||
const gestor = await ctx.db.get(gestorId as any);
|
||||
|
||||
if (!gestor) {
|
||||
erros.push(`Gestor ${gestorId} não encontrado`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verificar se já existe time para este gestor
|
||||
const timeExistente = await ctx.db
|
||||
.query("times")
|
||||
.withIndex("by_gestor", (q) => q.eq("gestorId", gestorId as any))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
let timeId;
|
||||
|
||||
if (timeExistente) {
|
||||
timeId = timeExistente._id;
|
||||
} else {
|
||||
// Criar novo time
|
||||
timeId = await ctx.db.insert("times", {
|
||||
nome: `Equipe ${gestor.nome}`,
|
||||
descricao: `Time gerenciado por ${gestor.nome} (migração automática)`,
|
||||
gestorId: gestorId as any,
|
||||
ativo: true,
|
||||
cor: "#3B82F6",
|
||||
});
|
||||
timesCreated++;
|
||||
}
|
||||
|
||||
// Adicionar membros ao time
|
||||
for (const funcionario of subordinados) {
|
||||
try {
|
||||
// Verificar se já está em algum time
|
||||
const membroExistente = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
if (!membroExistente) {
|
||||
await ctx.db.insert("timesMembros", {
|
||||
timeId: timeId,
|
||||
funcionarioId: funcionario._id,
|
||||
dataEntrada: Date.now(),
|
||||
ativo: true,
|
||||
});
|
||||
funcionariosAtribuidos++;
|
||||
}
|
||||
} catch (e: any) {
|
||||
erros.push(`Erro ao adicionar ${funcionario.nome} ao time: ${e.message}`);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
erros.push(`Erro ao processar gestor ${gestorId}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
timesCreated,
|
||||
funcionariosAtribuidos,
|
||||
erros,
|
||||
};
|
||||
} catch (e: any) {
|
||||
erros.push(`Erro geral na migração: ${e.message}`);
|
||||
return {
|
||||
timesCreated,
|
||||
funcionariosAtribuidos,
|
||||
erros,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Função auxiliar para limpar times inativos antigos
|
||||
*/
|
||||
export const limparTimesInativos = internalMutation({
|
||||
args: {
|
||||
diasInativos: v.optional(v.number()),
|
||||
},
|
||||
returns: v.number(),
|
||||
handler: async (ctx, args) => {
|
||||
const diasLimite = args.diasInativos || 30;
|
||||
const dataLimite = Date.now() - (diasLimite * 24 * 60 * 60 * 1000);
|
||||
|
||||
const timesInativos = await ctx.db
|
||||
.query("times")
|
||||
.filter((q) => q.eq(q.field("ativo"), false))
|
||||
.collect();
|
||||
|
||||
let removidos = 0;
|
||||
|
||||
for (const time of timesInativos) {
|
||||
if (time._creationTime < dataLimite) {
|
||||
// Remover membros inativos do time
|
||||
const membrosInativos = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time", (q) => q.eq("timeId", time._id))
|
||||
.filter((q) => q.eq(q.field("ativo"), false))
|
||||
.collect();
|
||||
|
||||
for (const membro of membrosInativos) {
|
||||
await ctx.db.delete(membro._id);
|
||||
}
|
||||
|
||||
// Remover o time
|
||||
await ctx.db.delete(time._id);
|
||||
removidos++;
|
||||
}
|
||||
}
|
||||
|
||||
return removidos;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,11 +26,16 @@ export default defineSchema({
|
||||
uf: v.string(),
|
||||
telefone: v.string(),
|
||||
email: v.string(),
|
||||
matricula: v.string(),
|
||||
matricula: v.optional(v.string()),
|
||||
admissaoData: v.optional(v.string()),
|
||||
desligamentoData: v.optional(v.string()),
|
||||
simboloId: v.id("simbolos"),
|
||||
simboloTipo: simboloTipo,
|
||||
gestorId: v.optional(v.id("usuarios")),
|
||||
statusFerias: v.optional(v.union(
|
||||
v.literal("ativo"),
|
||||
v.literal("em_ferias")
|
||||
)),
|
||||
|
||||
// Dados Pessoais Adicionais (opcionais)
|
||||
nomePai: v.optional(v.string()),
|
||||
@@ -135,7 +140,8 @@ export default defineSchema({
|
||||
.index("by_simboloId", ["simboloId"])
|
||||
.index("by_simboloTipo", ["simboloTipo"])
|
||||
.index("by_cpf", ["cpf"])
|
||||
.index("by_rg", ["rg"]),
|
||||
.index("by_rg", ["rg"])
|
||||
.index("by_gestor", ["gestorId"]),
|
||||
|
||||
atestados: defineTable({
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
@@ -145,11 +151,87 @@ export default defineSchema({
|
||||
descricao: v.string(),
|
||||
}),
|
||||
|
||||
ferias: defineTable({
|
||||
solicitacoesFerias: defineTable({
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
dataInicio: v.string(),
|
||||
dataFim: v.string(),
|
||||
}),
|
||||
anoReferencia: v.number(),
|
||||
status: v.union(
|
||||
v.literal("aguardando_aprovacao"),
|
||||
v.literal("aprovado"),
|
||||
v.literal("reprovado"),
|
||||
v.literal("data_ajustada_aprovada")
|
||||
),
|
||||
periodos: v.array(
|
||||
v.object({
|
||||
dataInicio: v.string(),
|
||||
dataFim: v.string(),
|
||||
diasCorridos: v.number(),
|
||||
})
|
||||
),
|
||||
observacao: v.optional(v.string()),
|
||||
motivoReprovacao: v.optional(v.string()),
|
||||
gestorId: v.optional(v.id("usuarios")),
|
||||
dataAprovacao: v.optional(v.number()),
|
||||
dataReprovacao: v.optional(v.number()),
|
||||
historicoAlteracoes: v.optional(
|
||||
v.array(
|
||||
v.object({
|
||||
data: v.number(),
|
||||
usuarioId: v.id("usuarios"),
|
||||
acao: v.string(),
|
||||
periodosAnteriores: v.optional(v.array(v.object({
|
||||
dataInicio: v.string(),
|
||||
dataFim: v.string(),
|
||||
diasCorridos: v.number(),
|
||||
}))),
|
||||
})
|
||||
)
|
||||
),
|
||||
})
|
||||
.index("by_funcionario", ["funcionarioId"])
|
||||
.index("by_status", ["status"])
|
||||
.index("by_funcionario_and_status", ["funcionarioId", "status"])
|
||||
.index("by_ano", ["anoReferencia"]),
|
||||
|
||||
notificacoesFerias: defineTable({
|
||||
destinatarioId: v.id("usuarios"),
|
||||
solicitacaoFeriasId: v.id("solicitacoesFerias"),
|
||||
tipo: v.union(
|
||||
v.literal("nova_solicitacao"),
|
||||
v.literal("aprovado"),
|
||||
v.literal("reprovado"),
|
||||
v.literal("data_ajustada")
|
||||
),
|
||||
lida: v.boolean(),
|
||||
mensagem: v.string(),
|
||||
})
|
||||
.index("by_destinatario", ["destinatarioId"])
|
||||
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
|
||||
|
||||
times: defineTable({
|
||||
nome: v.string(),
|
||||
descricao: v.optional(v.string()),
|
||||
gestorId: v.id("usuarios"),
|
||||
ativo: v.boolean(),
|
||||
cor: v.optional(v.string()), // Cor para identificação visual
|
||||
}).index("by_gestor", ["gestorId"]),
|
||||
|
||||
timesMembros: defineTable({
|
||||
timeId: v.id("times"),
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
dataEntrada: v.number(),
|
||||
dataSaida: v.optional(v.number()),
|
||||
ativo: v.boolean(),
|
||||
})
|
||||
.index("by_time", ["timeId"])
|
||||
.index("by_funcionario", ["funcionarioId"])
|
||||
.index("by_time_and_ativo", ["timeId", "ativo"]),
|
||||
|
||||
cursos: defineTable({
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
descricao: v.string(),
|
||||
data: v.string(),
|
||||
certificadoId: v.optional(v.id("_storage")),
|
||||
}).index("by_funcionario", ["funcionarioId"]),
|
||||
|
||||
simbolos: defineTable({
|
||||
nome: v.string(),
|
||||
|
||||
270
packages/backend/convex/times.ts
Normal file
270
packages/backend/convex/times.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
// Query: Listar todos os times
|
||||
export const listar = query({
|
||||
args: {},
|
||||
returns: v.array(v.any()),
|
||||
handler: async (ctx) => {
|
||||
const times = await ctx.db.query("times").collect();
|
||||
|
||||
// Buscar gestor e contar membros de cada time
|
||||
const timesComDetalhes = await Promise.all(
|
||||
times.map(async (time) => {
|
||||
const gestor = await ctx.db.get(time.gestorId);
|
||||
const membrosAtivos = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
...time,
|
||||
gestor,
|
||||
totalMembros: membrosAtivos.length,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return timesComDetalhes;
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Obter time por ID com membros
|
||||
export const obterPorId = query({
|
||||
args: { id: v.id("times") },
|
||||
returns: v.union(v.any(), v.null()),
|
||||
handler: async (ctx, args) => {
|
||||
const time = await ctx.db.get(args.id);
|
||||
if (!time) return null;
|
||||
|
||||
const gestor = await ctx.db.get(time.gestorId);
|
||||
const membrosRelacoes = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time_and_ativo", (q) => q.eq("timeId", args.id).eq("ativo", true))
|
||||
.collect();
|
||||
|
||||
// Buscar dados completos dos membros
|
||||
const membros = await Promise.all(
|
||||
membrosRelacoes.map(async (rel) => {
|
||||
const funcionario = await ctx.db.get(rel.funcionarioId);
|
||||
return {
|
||||
...rel,
|
||||
funcionario,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...time,
|
||||
gestor,
|
||||
membros,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Obter time do funcionário
|
||||
export const obterTimeFuncionario = query({
|
||||
args: { funcionarioId: v.id("funcionarios") },
|
||||
returns: v.union(v.any(), v.null()),
|
||||
handler: async (ctx, args) => {
|
||||
const relacao = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
if (!relacao) return null;
|
||||
|
||||
const time = await ctx.db.get(relacao.timeId);
|
||||
if (!time) return null;
|
||||
|
||||
const gestor = await ctx.db.get(time.gestorId);
|
||||
|
||||
return {
|
||||
...time,
|
||||
gestor,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Query: Obter times do gestor
|
||||
export const listarPorGestor = query({
|
||||
args: { gestorId: v.id("usuarios") },
|
||||
returns: v.array(v.any()),
|
||||
handler: async (ctx, args) => {
|
||||
const times = await ctx.db
|
||||
.query("times")
|
||||
.withIndex("by_gestor", (q) => q.eq("gestorId", args.gestorId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.collect();
|
||||
|
||||
const timesComMembros = await Promise.all(
|
||||
times.map(async (time) => {
|
||||
const membrosRelacoes = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true))
|
||||
.collect();
|
||||
|
||||
const membros = await Promise.all(
|
||||
membrosRelacoes.map(async (rel) => {
|
||||
const funcionario = await ctx.db.get(rel.funcionarioId);
|
||||
return {
|
||||
...rel,
|
||||
funcionario,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...time,
|
||||
membros,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return timesComMembros;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Criar time
|
||||
export const criar = mutation({
|
||||
args: {
|
||||
nome: v.string(),
|
||||
descricao: v.optional(v.string()),
|
||||
gestorId: v.id("usuarios"),
|
||||
cor: v.optional(v.string()),
|
||||
},
|
||||
returns: v.id("times"),
|
||||
handler: async (ctx, args) => {
|
||||
const timeId = await ctx.db.insert("times", {
|
||||
nome: args.nome,
|
||||
descricao: args.descricao,
|
||||
gestorId: args.gestorId,
|
||||
ativo: true,
|
||||
cor: args.cor || "#3B82F6",
|
||||
});
|
||||
|
||||
return timeId;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Atualizar time
|
||||
export const atualizar = mutation({
|
||||
args: {
|
||||
id: v.id("times"),
|
||||
nome: v.string(),
|
||||
descricao: v.optional(v.string()),
|
||||
gestorId: v.id("usuarios"),
|
||||
cor: v.optional(v.string()),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const { id, ...dados } = args;
|
||||
await ctx.db.patch(id, dados);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Desativar time
|
||||
export const desativar = mutation({
|
||||
args: { id: v.id("times") },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Desativar o time
|
||||
await ctx.db.patch(args.id, { ativo: false });
|
||||
|
||||
// Desativar todos os membros
|
||||
const membros = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time_and_ativo", (q) => q.eq("timeId", args.id).eq("ativo", true))
|
||||
.collect();
|
||||
|
||||
for (const membro of membros) {
|
||||
await ctx.db.patch(membro._id, {
|
||||
ativo: false,
|
||||
dataSaida: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Adicionar membro ao time
|
||||
export const adicionarMembro = mutation({
|
||||
args: {
|
||||
timeId: v.id("times"),
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
},
|
||||
returns: v.id("timesMembros"),
|
||||
handler: async (ctx, args) => {
|
||||
// Verificar se já não está em outro time ativo
|
||||
const membroExistente = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
if (membroExistente) {
|
||||
throw new Error("Funcionário já está em um time ativo");
|
||||
}
|
||||
|
||||
const membroId = await ctx.db.insert("timesMembros", {
|
||||
timeId: args.timeId,
|
||||
funcionarioId: args.funcionarioId,
|
||||
dataEntrada: Date.now(),
|
||||
ativo: true,
|
||||
});
|
||||
|
||||
return membroId;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Remover membro do time
|
||||
export const removerMembro = mutation({
|
||||
args: { membroId: v.id("timesMembros") },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.membroId, {
|
||||
ativo: false,
|
||||
dataSaida: Date.now(),
|
||||
});
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// Mutation: Transferir membro para outro time
|
||||
export const transferirMembro = mutation({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
novoTimeId: v.id("times"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
// Desativar do time atual
|
||||
const relacaoAtual = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
if (relacaoAtual) {
|
||||
await ctx.db.patch(relacaoAtual._id, {
|
||||
ativo: false,
|
||||
dataSaida: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Adicionar ao novo time
|
||||
await ctx.db.insert("timesMembros", {
|
||||
timeId: args.novoTimeId,
|
||||
funcionarioId: args.funcionarioId,
|
||||
dataEntrada: Date.now(),
|
||||
ativo: true,
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user