import { CreateManagementLevelGroupSchema, CreateManagementLevelSchema, UpdateManagementLevelGroupSchema, UpdateManagementLevelSchema, } from "@planarchy/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 managementLevelRouter = createTRPCRouter({ // ─── Groups ───────────────────────────────────────────── listGroups: protectedProcedure.query(async ({ ctx }) => { return ctx.db.managementLevelGroup.findMany({ include: { levels: { orderBy: { name: "asc" } } }, orderBy: { sortOrder: "asc" }, }); }), getGroupById: protectedProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const group = await findUniqueOrThrow( ctx.db.managementLevelGroup.findUnique({ where: { id: input.id }, include: { levels: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }), "Management level group", ); return group; }), createGroup: adminProcedure .input(CreateManagementLevelGroupSchema) .mutation(async ({ ctx, input }) => { const existing = await ctx.db.managementLevelGroup.findUnique({ where: { name: input.name } }); if (existing) { throw new TRPCError({ code: "CONFLICT", message: `Group "${input.name}" already exists` }); } const created = await ctx.db.managementLevelGroup.create({ data: { name: input.name, targetPercentage: input.targetPercentage, sortOrder: input.sortOrder, }, include: { levels: true }, }); void createAuditEntry({ db: ctx.db, entityType: "ManagementLevelGroup", entityId: created.id, entityName: created.name, action: "CREATE", userId: ctx.dbUser?.id, after: created as unknown as Record, source: "ui", }); return created; }), updateGroup: adminProcedure .input(z.object({ id: z.string(), data: UpdateManagementLevelGroupSchema })) .mutation(async ({ ctx, input }) => { const existing = await findUniqueOrThrow( ctx.db.managementLevelGroup.findUnique({ where: { id: input.id } }), "Group", ); if (input.data.name && input.data.name !== existing.name) { const conflict = await ctx.db.managementLevelGroup.findUnique({ where: { name: input.data.name } }); if (conflict) { throw new TRPCError({ code: "CONFLICT", message: `Group "${input.data.name}" already exists` }); } } const before = existing as unknown as Record; const updated = await ctx.db.managementLevelGroup.update({ where: { id: input.id }, data: { ...(input.data.name !== undefined ? { name: input.data.name } : {}), ...(input.data.targetPercentage !== undefined ? { targetPercentage: input.data.targetPercentage } : {}), ...(input.data.sortOrder !== undefined ? { sortOrder: input.data.sortOrder } : {}), }, include: { levels: true }, }); void createAuditEntry({ db: ctx.db, entityType: "ManagementLevelGroup", entityId: updated.id, entityName: updated.name, action: "UPDATE", userId: ctx.dbUser?.id, before, after: updated as unknown as Record, source: "ui", }); return updated; }), // ─── Levels ───────────────────────────────────────────── createLevel: adminProcedure .input(CreateManagementLevelSchema) .mutation(async ({ ctx, input }) => { await findUniqueOrThrow( ctx.db.managementLevelGroup.findUnique({ where: { id: input.groupId } }), "Group", ); const existing = await ctx.db.managementLevel.findUnique({ where: { name: input.name } }); if (existing) { throw new TRPCError({ code: "CONFLICT", message: `Level "${input.name}" already exists` }); } const created = await ctx.db.managementLevel.create({ data: { name: input.name, groupId: input.groupId }, }); void createAuditEntry({ db: ctx.db, entityType: "ManagementLevel", entityId: created.id, entityName: created.name, action: "CREATE", userId: ctx.dbUser?.id, after: created as unknown as Record, source: "ui", }); return created; }), updateLevel: adminProcedure .input(z.object({ id: z.string(), data: UpdateManagementLevelSchema })) .mutation(async ({ ctx, input }) => { const existing = await findUniqueOrThrow( ctx.db.managementLevel.findUnique({ where: { id: input.id } }), "Level", ); if (input.data.name && input.data.name !== existing.name) { const conflict = await ctx.db.managementLevel.findUnique({ where: { name: input.data.name } }); if (conflict) { throw new TRPCError({ code: "CONFLICT", message: `Level "${input.data.name}" already exists` }); } } const before = existing as unknown as Record; const updated = await ctx.db.managementLevel.update({ where: { id: input.id }, data: { ...(input.data.name !== undefined ? { name: input.data.name } : {}), ...(input.data.groupId !== undefined ? { groupId: input.data.groupId } : {}), }, }); void createAuditEntry({ db: ctx.db, entityType: "ManagementLevel", entityId: updated.id, entityName: updated.name, action: "UPDATE", userId: ctx.dbUser?.id, before, after: updated as unknown as Record, source: "ui", }); return updated; }), deleteLevel: adminProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const level = await findUniqueOrThrow( ctx.db.managementLevel.findUnique({ where: { id: input.id }, include: { _count: { select: { resources: true } } }, }), "Level", ); if (level._count.resources > 0) { throw new TRPCError({ code: "PRECONDITION_FAILED", message: `Cannot delete level assigned to ${level._count.resources} resource(s)`, }); } await ctx.db.managementLevel.delete({ where: { id: input.id } }); void createAuditEntry({ db: ctx.db, entityType: "ManagementLevel", entityId: level.id, entityName: level.name, action: "DELETE", userId: ctx.dbUser?.id, before: level as unknown as Record, source: "ui", }); return { success: true }; }), });