import { mutation, query } from "./_generated/server" import type { MutationCtx, QueryCtx } from "./_generated/server" import { ConvexError, v } from "convex/values" import type { Id } from "./_generated/dataModel" import { requireAdmin } from "./rbac" import { getTemplateByKey, normalizeFormTemplateKey } from "./ticketFormTemplates" import { TICKET_FORM_CONFIG } from "./ticketForms.config" const VALID_SCOPES = new Set(["tenant", "company", "user"]) function normalizeScope(input: string) { const normalized = input.trim().toLowerCase() if (!VALID_SCOPES.has(normalized)) { throw new ConvexError("Escopo inválido") } return normalized } async function ensureTemplateExists(ctx: MutationCtx | QueryCtx, tenantId: string, template: string) { const normalized = normalizeFormTemplateKey(template) if (!normalized) { throw new ConvexError("Template desconhecido") } const existing = await getTemplateByKey(ctx, tenantId, normalized) if (existing && existing.isArchived !== true) { return normalized } const fallback = TICKET_FORM_CONFIG.find((tpl) => tpl.key === normalized) if (fallback) { return normalized } throw new ConvexError("Template desconhecido") } export const list = query({ args: { tenantId: v.string(), viewerId: v.id("users"), template: v.optional(v.string()), }, handler: async (ctx, { tenantId, viewerId, template }) => { await requireAdmin(ctx, viewerId, tenantId) const normalizedTemplate = template ? normalizeFormTemplateKey(template) : null const settings = await ctx.db .query("ticketFormSettings") .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)) .take(100) return settings .filter((setting) => !normalizedTemplate || setting.template === normalizedTemplate) .map((setting) => ({ id: setting._id, template: setting.template, scope: setting.scope, companyId: setting.companyId ?? null, userId: setting.userId ?? null, enabled: setting.enabled, createdAt: setting.createdAt, updatedAt: setting.updatedAt, actorId: setting.actorId ?? null, })) }, }) export const upsert = mutation({ args: { tenantId: v.string(), actorId: v.id("users"), template: v.string(), scope: v.string(), companyId: v.optional(v.id("companies")), userId: v.optional(v.id("users")), enabled: v.boolean(), }, handler: async (ctx, { tenantId, actorId, template, scope, companyId, userId, enabled }) => { await requireAdmin(ctx, actorId, tenantId) const normalizedTemplate = await ensureTemplateExists(ctx, tenantId, template) const normalizedScope = normalizeScope(scope) if (normalizedScope === "company" && !companyId) { throw new ConvexError("Informe a empresa para configurar o template") } if (normalizedScope === "user" && !userId) { throw new ConvexError("Informe o usuário para configurar o template") } if (normalizedScope === "tenant") { if (companyId || userId) { throw new ConvexError("Escopo global não aceita empresa ou usuário") } } const existing = await findExisting(ctx, tenantId, normalizedTemplate, normalizedScope, companyId, userId) const now = Date.now() if (existing) { await ctx.db.patch(existing._id, { enabled, updatedAt: now, actorId, }) return existing._id } const id = await ctx.db.insert("ticketFormSettings", { tenantId, template: normalizedTemplate, scope: normalizedScope, companyId: normalizedScope === "company" ? (companyId as Id<"companies">) : undefined, userId: normalizedScope === "user" ? (userId as Id<"users">) : undefined, enabled, createdAt: now, updatedAt: now, actorId, }) return id }, }) export const remove = mutation({ args: { tenantId: v.string(), actorId: v.id("users"), settingId: v.id("ticketFormSettings"), }, handler: async (ctx, { tenantId, actorId, settingId }) => { await requireAdmin(ctx, actorId, tenantId) const setting = await ctx.db.get(settingId) if (!setting || setting.tenantId !== tenantId) { throw new ConvexError("Configuração não encontrada") } await ctx.db.delete(settingId) return { ok: true } }, }) async function findExisting( ctx: MutationCtx | QueryCtx, tenantId: string, template: string, scope: string, companyId?: Id<"companies">, userId?: Id<"users">, ) { const candidates = await ctx.db .query("ticketFormSettings") .withIndex("by_tenant_template_scope", (q) => q.eq("tenantId", tenantId).eq("template", template).eq("scope", scope)) .take(100) return candidates.find((setting) => { if (scope === "tenant") return true if (scope === "company") { return setting.companyId && companyId && String(setting.companyId) === String(companyId) } if (scope === "user") { return setting.userId && userId && String(setting.userId) === String(userId) } return false }) ?? null }