import { PermissionKey, SkillEntrySchema } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { adminProcedure, managerProcedure, protectedProcedure, requirePermission } from "../trpc.js"; const employeeInfoSchema = z .object({ roleId: z.string().optional(), yearsOfExperience: z.number().optional(), portfolioUrl: z.string().url().optional().or(z.literal("")), }) .optional(); export const resourceSkillImportProcedures = { importSkillMatrix: protectedProcedure .input( z.object({ skills: z.array(SkillEntrySchema), employeeInfo: employeeInfoSchema, }), ) .mutation(async ({ ctx, input }) => { const user = await findUniqueOrThrow( ctx.db.user.findUnique({ where: { id: ctx.dbUser!.id }, include: { resource: true }, }), "User", ); if (!user.resource) { throw new TRPCError({ code: "NOT_FOUND", message: "No resource linked to your account" }); } await ctx.db.resource.update({ where: { id: user.resource.id }, data: { skills: input.skills as unknown as import("@capakraken/db").Prisma.InputJsonValue, skillMatrixUpdatedAt: new Date(), ...(input.employeeInfo?.portfolioUrl !== undefined ? { portfolioUrl: input.employeeInfo.portfolioUrl || null } : {}), ...(input.employeeInfo?.roleId !== undefined ? { roleId: input.employeeInfo.roleId } : {}), }, }); return { count: input.skills.length }; }), importSkillMatrixForResource: managerProcedure .input( z.object({ resourceId: z.string(), skills: z.array(SkillEntrySchema), employeeInfo: employeeInfoSchema, }), ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_RESOURCES); await findUniqueOrThrow( ctx.db.resource.findUnique({ where: { id: input.resourceId } }), "Resource", ); await ctx.db.resource.update({ where: { id: input.resourceId }, data: { skills: input.skills as unknown as import("@capakraken/db").Prisma.InputJsonValue, skillMatrixUpdatedAt: new Date(), ...(input.employeeInfo?.portfolioUrl !== undefined ? { portfolioUrl: input.employeeInfo.portfolioUrl || null } : {}), ...(input.employeeInfo?.roleId !== undefined ? { roleId: input.employeeInfo.roleId } : {}), }, }); return { count: input.skills.length }; }), batchImportSkillMatrices: adminProcedure .input( z.object({ entries: z.array( z.object({ eid: z.string(), skills: z.array(SkillEntrySchema), employeeInfo: employeeInfoSchema, }), ), }), ) .mutation(async ({ ctx, input }) => { const eids = input.entries.map((entry) => entry.eid); const existing = await ctx.db.resource.findMany({ where: { eid: { in: eids } }, select: { id: true, eid: true }, }); const eidToId = new Map(existing.map((resource) => [resource.eid, resource.id])); const notFound = input.entries.length - existing.length; const now = new Date(); const updates = input.entries .filter((entry) => eidToId.has(entry.eid)) .map((entry) => ctx.db.resource.update({ where: { id: eidToId.get(entry.eid)! }, data: { skills: entry.skills as unknown as import("@capakraken/db").Prisma.InputJsonValue, skillMatrixUpdatedAt: now, ...(entry.employeeInfo?.portfolioUrl !== undefined ? { portfolioUrl: entry.employeeInfo.portfolioUrl || null } : {}), ...(entry.employeeInfo?.roleId !== undefined ? { roleId: entry.employeeInfo.roleId } : {}), }, }), ); await ctx.db.$transaction(updates); return { updated: updates.length, notFound }; }), };