import { CreateOrgUnitSchema, UpdateOrgUnitSchema } from "@planarchy/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc.js"; import type { OrgUnitTree } from "@planarchy/shared"; interface FlatOrgUnit { id: string; name: string; shortName: string | null; level: number; parentId: string | null; sortOrder: number; isActive: boolean; createdAt: Date; updatedAt: Date; } function buildTree(flatItems: FlatOrgUnit[], parentId: string | null = null): OrgUnitTree[] { return flatItems .filter((item) => item.parentId === parentId) .sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name)) .map((item) => ({ ...item, children: buildTree(flatItems, item.id), })); } export const orgUnitRouter = createTRPCRouter({ list: protectedProcedure .input( z.object({ level: z.number().int().min(5).max(7).optional(), parentId: z.string().optional(), isActive: z.boolean().optional(), }).optional(), ) .query(async ({ ctx, input }) => { return ctx.db.orgUnit.findMany({ where: { ...(input?.level !== undefined ? { level: input.level } : {}), ...(input?.parentId !== undefined ? { parentId: input.parentId } : {}), ...(input?.isActive !== undefined ? { isActive: input.isActive } : {}), }, orderBy: [{ level: "asc" }, { sortOrder: "asc" }, { name: "asc" }], }); }), getTree: protectedProcedure .input(z.object({ isActive: z.boolean().optional() }).optional()) .query(async ({ ctx, input }) => { const all = await ctx.db.orgUnit.findMany({ where: { ...(input?.isActive !== undefined ? { isActive: input.isActive } : {}), }, orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }); return buildTree(all); }), getById: protectedProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const unit = await findUniqueOrThrow( ctx.db.orgUnit.findUnique({ where: { id: input.id }, include: { parent: true, children: { orderBy: { sortOrder: "asc" } }, _count: { select: { resources: true } }, }, }), "Org unit", ); return unit; }), create: adminProcedure .input(CreateOrgUnitSchema) .mutation(async ({ ctx, input }) => { if (input.parentId) { const parent = await findUniqueOrThrow( ctx.db.orgUnit.findUnique({ where: { id: input.parentId } }), "Parent org unit", ); if (parent.level >= input.level) { throw new TRPCError({ code: "BAD_REQUEST", message: `Child level (${input.level}) must be greater than parent level (${parent.level})`, }); } } return ctx.db.orgUnit.create({ data: { name: input.name, ...(input.shortName !== undefined ? { shortName: input.shortName } : {}), level: input.level, ...(input.parentId ? { parentId: input.parentId } : {}), sortOrder: input.sortOrder, }, }); }), update: adminProcedure .input(z.object({ id: z.string(), data: UpdateOrgUnitSchema })) .mutation(async ({ ctx, input }) => { await findUniqueOrThrow( ctx.db.orgUnit.findUnique({ where: { id: input.id } }), "Org unit", ); return ctx.db.orgUnit.update({ where: { id: input.id }, data: { ...(input.data.name !== undefined ? { name: input.data.name } : {}), ...(input.data.shortName !== undefined ? { shortName: input.data.shortName } : {}), ...(input.data.sortOrder !== undefined ? { sortOrder: input.data.sortOrder } : {}), ...(input.data.isActive !== undefined ? { isActive: input.data.isActive } : {}), ...(input.data.parentId !== undefined ? { parentId: input.data.parentId } : {}), }, }); }), deactivate: adminProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { return ctx.db.orgUnit.update({ where: { id: input.id }, data: { isActive: false }, }); }), });