import { CreateUtilizationCategorySchema, UpdateUtilizationCategorySchema, } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { createAuditEntry } from "../lib/audit.js"; import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc.js"; export const utilizationCategoryRouter = createTRPCRouter({ list: protectedProcedure .input(z.object({ isActive: z.boolean().optional() }).optional()) .query(async ({ ctx, input }) => { return ctx.db.utilizationCategory.findMany({ where: { ...(input?.isActive !== undefined ? { isActive: input.isActive } : {}), }, orderBy: { sortOrder: "asc" }, }); }), getById: protectedProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const cat = await findUniqueOrThrow( ctx.db.utilizationCategory.findUnique({ where: { id: input.id }, include: { _count: { select: { projects: true } } }, }), "Utilization category", ); return cat; }), create: adminProcedure .input(CreateUtilizationCategorySchema) .mutation(async ({ ctx, input }) => { const existing = await ctx.db.utilizationCategory.findUnique({ where: { code: input.code } }); if (existing) { throw new TRPCError({ code: "CONFLICT", message: `Code "${input.code}" already exists` }); } // If setting as default, unset the current default first if (input.isDefault) { await ctx.db.utilizationCategory.updateMany({ where: { isDefault: true }, data: { isDefault: false }, }); } const created = await ctx.db.utilizationCategory.create({ data: { code: input.code, name: input.name, ...(input.description !== undefined ? { description: input.description } : {}), sortOrder: input.sortOrder, isDefault: input.isDefault, }, }); void createAuditEntry({ db: ctx.db, entityType: "UtilizationCategory", entityId: created.id, entityName: created.name, action: "CREATE", userId: ctx.dbUser?.id, after: created as unknown as Record, source: "ui", }); return created; }), update: adminProcedure .input(z.object({ id: z.string(), data: UpdateUtilizationCategorySchema })) .mutation(async ({ ctx, input }) => { const existing = await findUniqueOrThrow( ctx.db.utilizationCategory.findUnique({ where: { id: input.id } }), "Utilization category", ); if (input.data.code && input.data.code !== existing.code) { const conflict = await ctx.db.utilizationCategory.findUnique({ where: { code: input.data.code } }); if (conflict) { throw new TRPCError({ code: "CONFLICT", message: `Code "${input.data.code}" already exists` }); } } // If setting as default, unset others if (input.data.isDefault) { await ctx.db.utilizationCategory.updateMany({ where: { isDefault: true, id: { not: input.id } }, data: { isDefault: false }, }); } const before = existing as unknown as Record; const updated = await ctx.db.utilizationCategory.update({ where: { id: input.id }, data: { ...(input.data.code !== undefined ? { code: input.data.code } : {}), ...(input.data.name !== undefined ? { name: input.data.name } : {}), ...(input.data.description !== undefined ? { description: input.data.description } : {}), ...(input.data.sortOrder !== undefined ? { sortOrder: input.data.sortOrder } : {}), ...(input.data.isActive !== undefined ? { isActive: input.data.isActive } : {}), ...(input.data.isDefault !== undefined ? { isDefault: input.data.isDefault } : {}), }, }); void createAuditEntry({ db: ctx.db, entityType: "UtilizationCategory", entityId: updated.id, entityName: updated.name, action: "UPDATE", userId: ctx.dbUser?.id, before, after: updated as unknown as Record, source: "ui", }); return updated; }), });