import { v } from "convex/values" import { mutation, query, type QueryCtx, type MutationCtx } from "./_generated/server" import type { Id } from "./_generated/dataModel" const EMPRESTIMO_STATUS = ["ATIVO", "DEVOLVIDO", "ATRASADO", "CANCELADO"] as const type EmprestimoStatus = (typeof EMPRESTIMO_STATUS)[number] const EQUIPAMENTO_TIPOS = [ "NOTEBOOK", "DESKTOP", "MONITOR", "TECLADO", "MOUSE", "HEADSET", "WEBCAM", "IMPRESSORA", "SCANNER", "PROJETOR", "TABLET", "CELULAR", "ROTEADOR", "SWITCH", "OUTRO", ] as const async function getNextReference(ctx: MutationCtx, tenantId: string): Promise { const last = await ctx.db .query("emprestimos") .withIndex("by_tenant_reference", (q) => q.eq("tenantId", tenantId)) .order("desc") .first() return (last?.reference ?? 0) + 1 } export const list = query({ args: { tenantId: v.string(), viewerId: v.id("users"), status: v.optional(v.string()), clienteId: v.optional(v.id("companies")), tecnicoId: v.optional(v.id("users")), limit: v.optional(v.number()), }, handler: async (ctx, args) => { const { tenantId, status, clienteId, tecnicoId, limit = 100 } = args let baseQuery = ctx.db .query("emprestimos") .withIndex("by_tenant_created", (q) => q.eq("tenantId", tenantId)) .order("desc") const all = await baseQuery.take(limit * 2) let filtered = all if (status) { filtered = filtered.filter((e) => e.status === status) } if (clienteId) { filtered = filtered.filter((e) => e.clienteId === clienteId) } if (tecnicoId) { filtered = filtered.filter((e) => e.tecnicoId === tecnicoId) } return filtered.slice(0, limit).map((emprestimo) => ({ id: emprestimo._id, reference: emprestimo.reference, clienteId: emprestimo.clienteId, clienteNome: emprestimo.clienteSnapshot.name, responsavelNome: emprestimo.responsavelNome, responsavelContato: emprestimo.responsavelContato, tecnicoId: emprestimo.tecnicoId, tecnicoNome: emprestimo.tecnicoSnapshot.name, tecnicoEmail: emprestimo.tecnicoSnapshot.email, equipamentos: emprestimo.equipamentos, quantidade: emprestimo.quantidade, valor: emprestimo.valor, dataEmprestimo: emprestimo.dataEmprestimo, dataFimPrevisto: emprestimo.dataFimPrevisto, dataDevolucao: emprestimo.dataDevolucao, status: emprestimo.status, observacoes: emprestimo.observacoes, observacoesDevolucao: emprestimo.observacoesDevolucao, multaDiaria: emprestimo.multaDiaria, multaCalculada: emprestimo.multaCalculada, createdAt: emprestimo.createdAt, updatedAt: emprestimo.updatedAt, })) }, }) export const getById = query({ args: { id: v.id("emprestimos"), viewerId: v.id("users"), }, handler: async (ctx, args) => { const emprestimo = await ctx.db.get(args.id) if (!emprestimo) return null return { id: emprestimo._id, reference: emprestimo.reference, clienteId: emprestimo.clienteId, clienteSnapshot: emprestimo.clienteSnapshot, responsavelNome: emprestimo.responsavelNome, responsavelContato: emprestimo.responsavelContato, tecnicoId: emprestimo.tecnicoId, tecnicoSnapshot: emprestimo.tecnicoSnapshot, equipamentos: emprestimo.equipamentos, quantidade: emprestimo.quantidade, valor: emprestimo.valor, dataEmprestimo: emprestimo.dataEmprestimo, dataFimPrevisto: emprestimo.dataFimPrevisto, dataDevolucao: emprestimo.dataDevolucao, status: emprestimo.status, observacoes: emprestimo.observacoes, multaDiaria: emprestimo.multaDiaria, multaCalculada: emprestimo.multaCalculada, createdBy: emprestimo.createdBy, createdAt: emprestimo.createdAt, updatedAt: emprestimo.updatedAt, } }, }) export const create = mutation({ args: { tenantId: v.string(), createdBy: v.id("users"), clienteId: v.id("companies"), responsavelNome: v.string(), responsavelContato: v.optional(v.string()), tecnicoId: v.id("users"), equipamentos: v.array(v.object({ id: v.string(), tipo: v.string(), marca: v.string(), modelo: v.string(), serialNumber: v.optional(v.string()), patrimonio: v.optional(v.string()), })), valor: v.optional(v.number()), dataEmprestimo: v.number(), dataFimPrevisto: v.number(), observacoes: v.optional(v.string()), multaDiaria: v.optional(v.number()), }, handler: async (ctx, args) => { const now = Date.now() const reference = await getNextReference(ctx, args.tenantId) const cliente = await ctx.db.get(args.clienteId) if (!cliente) { throw new Error("Cliente nao encontrado") } const tecnico = await ctx.db.get(args.tecnicoId) if (!tecnico) { throw new Error("Tecnico nao encontrado") } const emprestimoId = await ctx.db.insert("emprestimos", { tenantId: args.tenantId, reference, clienteId: args.clienteId, clienteSnapshot: { name: cliente.name, slug: cliente.slug, }, responsavelNome: args.responsavelNome, responsavelContato: args.responsavelContato, tecnicoId: args.tecnicoId, tecnicoSnapshot: { name: tecnico.name, email: tecnico.email, }, equipamentos: args.equipamentos, quantidade: args.equipamentos.length, valor: args.valor, dataEmprestimo: args.dataEmprestimo, dataFimPrevisto: args.dataFimPrevisto, status: "ATIVO", observacoes: args.observacoes, multaDiaria: args.multaDiaria, createdBy: args.createdBy, createdAt: now, updatedAt: now, }) const creator = await ctx.db.get(args.createdBy) await ctx.db.insert("emprestimoHistorico", { tenantId: args.tenantId, emprestimoId, tipo: "CRIADO", descricao: `Emprestimo #${reference} criado`, autorId: args.createdBy, autorSnapshot: { name: creator?.name ?? "Sistema", email: creator?.email, }, createdAt: now, }) return { id: emprestimoId, reference } }, }) export const devolver = mutation({ args: { id: v.id("emprestimos"), updatedBy: v.id("users"), observacoes: v.optional(v.string()), }, handler: async (ctx, args) => { const emprestimo = await ctx.db.get(args.id) if (!emprestimo) { throw new Error("Emprestimo nao encontrado") } if (emprestimo.status === "DEVOLVIDO") { throw new Error("Emprestimo ja foi devolvido") } const now = Date.now() let multaCalculada: number | undefined if (emprestimo.multaDiaria && now > emprestimo.dataFimPrevisto) { const diasAtraso = Math.ceil((now - emprestimo.dataFimPrevisto) / (1000 * 60 * 60 * 24)) multaCalculada = diasAtraso * emprestimo.multaDiaria } await ctx.db.patch(args.id, { status: "DEVOLVIDO", dataDevolucao: now, multaCalculada, observacoesDevolucao: args.observacoes, updatedBy: args.updatedBy, updatedAt: now, }) const updater = await ctx.db.get(args.updatedBy) await ctx.db.insert("emprestimoHistorico", { tenantId: emprestimo.tenantId, emprestimoId: args.id, tipo: "DEVOLVIDO", descricao: `Emprestimo #${emprestimo.reference} devolvido${multaCalculada ? ` com multa de R$ ${multaCalculada.toFixed(2)}` : ""}`, alteracoes: { multaCalculada }, autorId: args.updatedBy, autorSnapshot: { name: updater?.name ?? "Sistema", email: updater?.email, }, createdAt: now, }) return { ok: true, multaCalculada } }, }) export const update = mutation({ args: { id: v.id("emprestimos"), updatedBy: v.id("users"), responsavelNome: v.optional(v.string()), responsavelContato: v.optional(v.string()), dataFimPrevisto: v.optional(v.number()), observacoes: v.optional(v.string()), multaDiaria: v.optional(v.number()), status: v.optional(v.string()), }, handler: async (ctx, args) => { const emprestimo = await ctx.db.get(args.id) if (!emprestimo) { throw new Error("Emprestimo nao encontrado") } const now = Date.now() const updates: Record = { updatedBy: args.updatedBy, updatedAt: now, } if (args.responsavelNome !== undefined) updates.responsavelNome = args.responsavelNome if (args.responsavelContato !== undefined) updates.responsavelContato = args.responsavelContato if (args.dataFimPrevisto !== undefined) updates.dataFimPrevisto = args.dataFimPrevisto if (args.observacoes !== undefined) updates.observacoes = args.observacoes if (args.multaDiaria !== undefined) updates.multaDiaria = args.multaDiaria if (args.status !== undefined) updates.status = args.status await ctx.db.patch(args.id, updates) const updater = await ctx.db.get(args.updatedBy) await ctx.db.insert("emprestimoHistorico", { tenantId: emprestimo.tenantId, emprestimoId: args.id, tipo: "MODIFICADO", descricao: `Emprestimo #${emprestimo.reference} atualizado`, alteracoes: updates, autorId: args.updatedBy, autorSnapshot: { name: updater?.name ?? "Sistema", email: updater?.email, }, createdAt: now, }) return { ok: true } }, }) export const getHistorico = query({ args: { emprestimoId: v.id("emprestimos"), limit: v.optional(v.number()), }, handler: async (ctx, args) => { const historico = await ctx.db .query("emprestimoHistorico") .withIndex("by_emprestimo_created", (q) => q.eq("emprestimoId", args.emprestimoId)) .order("desc") .take(args.limit ?? 50) return historico.map((h) => ({ id: h._id, tipo: h.tipo, descricao: h.descricao, alteracoes: h.alteracoes, autorNome: h.autorSnapshot.name, createdAt: h.createdAt, })) }, }) export const getStats = query({ args: { tenantId: v.string(), viewerId: v.id("users"), }, handler: async (ctx, args) => { const all = await ctx.db .query("emprestimos") .withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId)) .take(200) const now = Date.now() const ativos = all.filter((e) => e.status === "ATIVO") const atrasados = ativos.filter((e) => e.dataFimPrevisto < now) const devolvidos = all.filter((e) => e.status === "DEVOLVIDO") return { total: all.length, ativos: ativos.length, atrasados: atrasados.length, devolvidos: devolvidos.length, valorTotalAtivo: ativos.reduce((sum, e) => sum + (e.valor ?? 0), 0), } }, })