import { countPlanningEntries } from "@capakraken/application"; import type { Prisma, PrismaClient } from "@capakraken/db"; import { CreateRoleSchema, UpdateRoleSchema } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; type RolePlanningCountsDb = Pick; type RoleIdentifierDb = Pick; type RoleCountRecord = { id: string; _count: { resourceRoles: number }; }; type RoleListInput = { isActive?: boolean | undefined; search?: string | undefined; }; type CreateRoleInput = z.infer; type UpdateRoleInput = z.infer; export function buildRoleListWhere(input: RoleListInput): Prisma.RoleWhereInput { return { ...(input.isActive !== undefined ? { isActive: input.isActive } : {}), ...(input.search ? { name: { contains: input.search, mode: "insensitive" } } : {}), }; } export async function loadRolePlanningEntryCounts( db: RolePlanningCountsDb, roleIds: string[], ) { const { countsByRoleId } = await countPlanningEntries(db, { roleIds, }); return countsByRoleId; } export async function attachRolePlanningEntryCounts< TRole extends RoleCountRecord, >( db: RolePlanningCountsDb, roles: TRole[], ): Promise> { const countsByRoleId = await loadRolePlanningEntryCounts( db, roles.map((role) => role.id), ); return roles.map((role) => ({ ...role, _count: { ...role._count, allocations: countsByRoleId.get(role.id) ?? 0, }, })); } export async function attachSingleRolePlanningEntryCount< TRole extends RoleCountRecord, >( db: RolePlanningCountsDb, role: TRole, ): Promise { return (await attachRolePlanningEntryCounts(db, [role]))[0]!; } export async function findRoleByIdentifier( db: RoleIdentifierDb, identifier: string, select: Record, ): Promise { const normalizedIdentifier = identifier.trim(); let role = await db.role.findUnique({ where: { id: normalizedIdentifier }, select, }) as TRole | null; if (!role) { role = await db.role.findUnique({ where: { name: normalizedIdentifier }, select, }) as TRole | null; } if (!role) { role = await db.role.findFirst({ where: { name: { equals: normalizedIdentifier, mode: "insensitive" } }, select, }) as TRole | null; } if (!role) { role = await db.role.findFirst({ where: { name: { contains: normalizedIdentifier, mode: "insensitive" } }, select, }) as TRole | null; } if (!role) { throw new TRPCError({ code: "NOT_FOUND", message: "Role not found" }); } return role; } export function buildRoleCreateData( input: CreateRoleInput, ): Prisma.RoleCreateInput { return { name: input.name, description: input.description ?? null, color: input.color ?? null, }; } export function buildRoleUpdateData( input: UpdateRoleInput, ): Prisma.RoleUpdateInput { return { ...(input.name !== undefined ? { name: input.name } : {}), ...(input.description !== undefined ? { description: input.description } : {}), ...(input.color !== undefined ? { color: input.color } : {}), ...(input.isActive !== undefined ? { isActive: input.isActive } : {}), }; } export function appendZeroAllocationCount< TRole extends RoleCountRecord, >( role: TRole, ): TRole & { _count: { resourceRoles: number; allocations: number } } { return { ...role, _count: { ...role._count, allocations: 0, }, }; } export async function assertRoleNameAvailable( db: RoleIdentifierDb, name: string, ignoreId?: string, ): Promise { const existing = await db.role.findUnique({ where: { name } }); if (existing && existing.id !== ignoreId) { throw new TRPCError({ code: "CONFLICT", message: `Role "${name}" already exists` }); } }