import { CreateOrgUnitSchema, UpdateOrgUnitSchema } from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { createAuditEntry } from "../lib/audit.js"; import type { TRPCContext } from "../trpc.js"; import { assertOrgUnitParentLevel, buildOrgUnitCreateData, buildOrgUnitListWhere, buildOrgUnitTree, buildOrgUnitUpdateData, findOrgUnitByIdentifier, } from "./org-unit-support.js"; type OrgUnitProcedureContext = Pick; type OrgUnitIdentifierReadModel = { id: string; name: string; shortName: string | null; level: number; isActive: boolean; }; type OrgUnitDetailReadModel = OrgUnitIdentifierReadModel & { parentId: string | null; sortOrder: number; createdAt: Date; updatedAt: Date; _count: { resources: number }; }; function withAuditUser(userId: string | undefined) { return userId ? { userId } : {}; } export const orgUnitListInputSchema = z.object({ level: z.number().int().min(5).max(7).optional(), parentId: z.string().optional(), isActive: z.boolean().optional(), }).optional(); export const orgUnitTreeInputSchema = z.object({ isActive: z.boolean().optional(), }).optional(); export const orgUnitIdInputSchema = z.object({ id: z.string(), }); export const orgUnitIdentifierInputSchema = z.object({ identifier: z.string().trim().min(1), }); export const orgUnitUpdateInputSchema = z.object({ id: z.string(), data: UpdateOrgUnitSchema, }); type OrgUnitListInput = z.infer; type OrgUnitTreeInput = z.infer; type OrgUnitIdInput = z.infer; type OrgUnitIdentifierInput = z.infer; type CreateOrgUnitInput = z.infer; type UpdateOrgUnitInput = z.infer; export async function listOrgUnits(ctx: OrgUnitProcedureContext, input: OrgUnitListInput) { return ctx.db.orgUnit.findMany({ where: buildOrgUnitListWhere(input ?? {}), orderBy: [{ level: "asc" }, { sortOrder: "asc" }, { name: "asc" }], }); } export async function getOrgUnitTree(ctx: OrgUnitProcedureContext, input: OrgUnitTreeInput) { const all = await ctx.db.orgUnit.findMany({ where: buildOrgUnitListWhere({ isActive: input?.isActive }), orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }); return buildOrgUnitTree(all); } export async function getOrgUnitById(ctx: OrgUnitProcedureContext, input: OrgUnitIdInput) { return findUniqueOrThrow( ctx.db.orgUnit.findUnique({ where: { id: input.id }, include: { parent: true, children: { orderBy: { sortOrder: "asc" } }, _count: { select: { resources: true } }, }, }), "Org unit", ); } export async function resolveOrgUnitByIdentifier( ctx: OrgUnitProcedureContext, input: OrgUnitIdentifierInput, ) { return findOrgUnitByIdentifier(ctx.db, input.identifier, { select: { id: true, name: true, shortName: true, level: true, isActive: true, }, }); } export async function getOrgUnitByIdentifier( ctx: OrgUnitProcedureContext, input: OrgUnitIdentifierInput, ) { return findOrgUnitByIdentifier(ctx.db, input.identifier, { include: { _count: { select: { resources: true } } }, }); } export async function createOrgUnit(ctx: OrgUnitProcedureContext, input: CreateOrgUnitInput) { if (input.parentId) { const parent = await findUniqueOrThrow( ctx.db.orgUnit.findUnique({ where: { id: input.parentId } }), "Parent org unit", ); assertOrgUnitParentLevel(parent, input.level); } const created = await ctx.db.orgUnit.create({ data: buildOrgUnitCreateData(input), }); void createAuditEntry({ db: ctx.db, entityType: "OrgUnit", entityId: created.id, entityName: created.name, action: "CREATE", ...withAuditUser(ctx.dbUser?.id), after: created as unknown as Record, source: "ui", }); return created; } export async function updateOrgUnit(ctx: OrgUnitProcedureContext, input: UpdateOrgUnitInput) { const existing = await findUniqueOrThrow( ctx.db.orgUnit.findUnique({ where: { id: input.id } }), "Org unit", ); const before = existing as unknown as Record; const updated = await ctx.db.orgUnit.update({ where: { id: input.id }, data: buildOrgUnitUpdateData(input.data), }); void createAuditEntry({ db: ctx.db, entityType: "OrgUnit", entityId: updated.id, entityName: updated.name, action: "UPDATE", ...withAuditUser(ctx.dbUser?.id), before, after: updated as unknown as Record, source: "ui", }); return updated; } export async function deactivateOrgUnit(ctx: OrgUnitProcedureContext, input: OrgUnitIdInput) { const updated = await ctx.db.orgUnit.update({ where: { id: input.id }, data: { isActive: false }, }); void createAuditEntry({ db: ctx.db, entityType: "OrgUnit", entityId: updated.id, entityName: updated.name, action: "UPDATE", ...withAuditUser(ctx.dbUser?.id), before: { isActive: true }, after: { isActive: false }, source: "ui", summary: "Deactivated OrgUnit", }); return updated; }