diff --git a/apps/web/src/components/admin/CountriesClient.tsx b/apps/web/src/components/admin/CountriesClient.tsx index 641592e..6d7b787 100644 --- a/apps/web/src/components/admin/CountriesClient.tsx +++ b/apps/web/src/components/admin/CountriesClient.tsx @@ -71,7 +71,7 @@ export function CountriesClient() { const utils = trpc.useUtils(); const { data: countries, isLoading } = trpc.country.list.useQuery(); - // @ts-expect-error TS2589: tRPC infers union type too deeply for nullable JSONB scheduleRules schema + // @ts-expect-error TS2589: tRPC type instantiation depth — intermittent with country schema flattening const createMut = trpc.country.create.useMutation({ onSuccess: () => { void utils.country.list.invalidate(); diff --git a/apps/web/src/components/admin/SystemRolesClient.tsx b/apps/web/src/components/admin/SystemRolesClient.tsx index ea040c9..8b3d8ee 100644 --- a/apps/web/src/components/admin/SystemRolesClient.tsx +++ b/apps/web/src/components/admin/SystemRolesClient.tsx @@ -102,7 +102,6 @@ export function SystemRolesClient() { staleTime: 10_000, }); - // @ts-expect-error TS2589: tRPC infers union type too deeply for the role config update payload const updateMutation = trpc.systemRoleConfig.update.useMutation({ onSuccess: async () => { await utils.systemRoleConfig.list.invalidate(); diff --git a/apps/web/src/components/admin/UsersClient.tsx b/apps/web/src/components/admin/UsersClient.tsx index b28892d..9885f37 100644 --- a/apps/web/src/components/admin/UsersClient.tsx +++ b/apps/web/src/components/admin/UsersClient.tsx @@ -155,7 +155,6 @@ export function UsersClient() { onError: (err) => setActionError(err.message), }); - // @ts-expect-error TS2589: tRPC infers union type too deeply for nullable overrides schema const setPermissionsMutation = trpc.user.setPermissions.useMutation({ onSuccess: async () => { await utils.user.list.invalidate(); diff --git a/apps/web/src/components/assistant/ChatPanel.tsx b/apps/web/src/components/assistant/ChatPanel.tsx index 8c69bbc..5be0e56 100644 --- a/apps/web/src/components/assistant/ChatPanel.tsx +++ b/apps/web/src/components/assistant/ChatPanel.tsx @@ -263,7 +263,8 @@ export function ChatPanel({ onClose }: { onClose: () => void }) { if (actions) { for (const action of actions) { if (action.type === "navigate" && action.url) { - router.push(action.url as string & {}); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + router.push(action.url as any); } else if (action.type === "invalidate" && action.scope) { // Invalidate relevant tRPC queries so the UI refreshes for (const scope of action.scope) { diff --git a/apps/web/src/components/projects/ProjectModal.tsx b/apps/web/src/components/projects/ProjectModal.tsx index 22f5430..f878a63 100644 --- a/apps/web/src/components/projects/ProjectModal.tsx +++ b/apps/web/src/components/projects/ProjectModal.tsx @@ -109,7 +109,6 @@ export function ProjectModal({ project, onClose, onSuccess }: ProjectModalProps) }); const { data: clientList } = trpc.clientEntity.list.useQuery(undefined, { staleTime: 60_000 }); - // @ts-expect-error TS2589: tRPC infers union type too deeply for CreateProjectSchema with .refine() const createMutation = trpc.project.create.useMutation({ onSuccess: async () => { await utils.project.listWithCosts.invalidate(); diff --git a/apps/web/src/components/vacations/VacationModal.tsx b/apps/web/src/components/vacations/VacationModal.tsx index 2862039..bc8a942 100644 --- a/apps/web/src/components/vacations/VacationModal.tsx +++ b/apps/web/src/components/vacations/VacationModal.tsx @@ -142,7 +142,6 @@ export function VacationModal({ const utils = trpc.useUtils(); - // @ts-expect-error TS2589: tRPC infers union type too deeply for CreateVacationRequestSchema with .superRefine() const createMutation = trpc.vacation.create.useMutation({ onSuccess: async () => { await utils.vacation.list.invalidate(); diff --git a/packages/api/src/router/allocation-conflict-procedures.ts b/packages/api/src/router/allocation-conflict-procedures.ts index 1e330d9..5b37ced 100644 --- a/packages/api/src/router/allocation-conflict-procedures.ts +++ b/packages/api/src/router/allocation-conflict-procedures.ts @@ -1,7 +1,8 @@ import { validateAvailability } from "@capakraken/engine"; -import { - type AllocationConflictCheckResult, - type WeekdayAvailability, +import type { + AllocationConflictCheckResult, + AllocationStatus, + WeekdayAvailability, } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; @@ -24,7 +25,7 @@ export const allocationConflictProcedures = { * Read-only — no mutations. */ checkConflicts: managerProcedure - .input(CheckConflictsInputSchema) + .input(CheckConflictsInputSchema as z.ZodType>) .query(async ({ ctx, input }): Promise => { const resource = await ctx.db.resource.findUnique({ where: { id: input.resourceId }, @@ -39,8 +40,7 @@ export const allocationConflictProcedures = { throw new TRPCError({ code: "NOT_FOUND", message: "Resource not found" }); } - const fallbackDailyHours = - (resource.country?.dailyWorkingHours ?? 8) * (resource.fte ?? 1); + const fallbackDailyHours = (resource.country?.dailyWorkingHours ?? 8) * (resource.fte ?? 1); const availability = (resource.availability as WeekdayAvailability | null) ?? { monday: fallbackDailyHours, tuesday: fallbackDailyHours, @@ -82,7 +82,12 @@ export const allocationConflictProcedures = { input.endDate, input.hoursPerDay, availability, - existingAssignments as { startDate: Date; endDate: Date; hoursPerDay: number; status: import("@capakraken/shared").AllocationStatus }[], + existingAssignments as { + startDate: Date; + endDate: Date; + hoursPerDay: number; + status: AllocationStatus; + }[], ); // Compute max overbook percentage for the worst day @@ -90,9 +95,7 @@ export const allocationConflictProcedures = { for (const conflict of availabilityResult.conflicts) { const totalBooked = conflict.existingHours + conflict.requestedHours; const overbookPct = - conflict.availableHours > 0 - ? ((totalBooked / conflict.availableHours) - 1) * 100 - : 100; + conflict.availableHours > 0 ? (totalBooked / conflict.availableHours - 1) * 100 : 100; if (overbookPct > maxOverbookPercent) maxOverbookPercent = overbookPct; } diff --git a/packages/api/src/router/allocation/assignment-procedures.ts b/packages/api/src/router/allocation/assignment-procedures.ts index ccf10b4..51b8dc1 100644 --- a/packages/api/src/router/allocation/assignment-procedures.ts +++ b/packages/api/src/router/allocation/assignment-procedures.ts @@ -1,7 +1,4 @@ -import { - createAssignment, - updateAssignment, -} from "@capakraken/application"; +import { createAssignment, updateAssignment } from "@capakraken/application"; import { AllocationStatus, CreateAllocationSchema, @@ -10,6 +7,12 @@ import { UpdateAllocationSchema, UpdateAssignmentSchema, } from "@capakraken/shared"; +import type { + CreateAllocationInput, + CreateAssignmentInput, + UpdateAllocationInput, + UpdateAssignmentInput, +} from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../../db/helpers.js"; import { @@ -28,40 +31,36 @@ import { ensureAssignmentRecord, updateAllocationWithAudit, } from "./assignment-mutations.js"; -import { - managerProcedure, - requirePermission, -} from "../../trpc.js"; +import { managerProcedure, requirePermission } from "../../trpc.js"; export const allocationAssignmentProcedures = { create: managerProcedure - .input(CreateAllocationSchema) + .input(CreateAllocationSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const allocation = await ctx.db.$transaction(async (tx) => - createAllocationReadModelEntry( - tx as Parameters[0], - input, - )); + createAllocationReadModelEntry(tx as Parameters[0], input), + ); - publishAllocationCreated(ctx.db, { - id: allocation.id, - projectId: allocation.projectId, - resourceId: allocation.resourceId, - }, { dispatchWebhook: true }); + publishAllocationCreated( + ctx.db, + { + id: allocation.id, + projectId: allocation.projectId, + resourceId: allocation.resourceId, + }, + { dispatchWebhook: true }, + ); return allocation; }), createAssignment: managerProcedure - .input(CreateAssignmentSchema) + .input(CreateAssignmentSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const assignment = await ctx.db.$transaction(async (tx) => { - return createAssignment( - tx as unknown as Parameters[0], - input, - ); + return createAssignment(tx as unknown as Parameters[0], input); }); publishAllocationCreated(ctx.db, { @@ -74,14 +73,16 @@ export const allocationAssignmentProcedures = { }), ensureAssignment: managerProcedure - .input(z.object({ - resourceId: z.string(), - projectId: z.string(), - startDate: z.coerce.date(), - endDate: z.coerce.date(), - hoursPerDay: z.number().min(0.5).max(24), - role: z.string().optional(), - })) + .input( + z.object({ + resourceId: z.string(), + projectId: z.string(), + startDate: z.coerce.date(), + endDate: z.coerce.date(), + hoursPerDay: z.number().min(0.5).max(24), + role: z.string().optional(), + }), + ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const result = await ensureAssignmentRecord(ctx.db, { @@ -94,11 +95,15 @@ export const allocationAssignmentProcedures = { }); if (result.action === "reactivated") { - publishAllocationUpdated(ctx.db, { - id: result.assignment.id, - projectId: result.assignment.projectId, - resourceId: result.assignment.resourceId, - }, { dispatchWebhook: true }); + publishAllocationUpdated( + ctx.db, + { + id: result.assignment.id, + projectId: result.assignment.projectId, + resourceId: result.assignment.resourceId, + }, + { dispatchWebhook: true }, + ); return result; } @@ -113,7 +118,12 @@ export const allocationAssignmentProcedures = { }), updateAssignment: managerProcedure - .input(z.object({ id: z.string(), data: UpdateAssignmentSchema })) + .input( + z.object({ id: z.string(), data: UpdateAssignmentSchema }) as z.ZodType<{ + id: string; + data: UpdateAssignmentInput; + }>, + ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const existing = await findUniqueOrThrow( @@ -132,18 +142,27 @@ export const allocationAssignmentProcedures = { ); }); - publishAllocationUpdated(ctx.db, { - id: updated.id, - projectId: updated.projectId, - resourceId: updated.resourceId, - resourceIds: [existing.resourceId, updated.resourceId], - }, { dispatchWebhook: true }); + publishAllocationUpdated( + ctx.db, + { + id: updated.id, + projectId: updated.projectId, + resourceId: updated.resourceId, + resourceIds: [existing.resourceId, updated.resourceId], + }, + { dispatchWebhook: true }, + ); return updated; }), update: managerProcedure - .input(z.object({ id: z.string(), data: UpdateAllocationSchema })) + .input( + z.object({ id: z.string(), data: UpdateAllocationSchema }) as z.ZodType<{ + id: string; + data: UpdateAllocationInput; + }>, + ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const { existing, updated } = await updateAllocationWithAudit(ctx.db, input.id, input.data); @@ -177,20 +196,18 @@ export const allocationAssignmentProcedures = { return { success: true }; }), - delete: managerProcedure - .input(z.object({ id: z.string() })) - .mutation(async ({ ctx, input }) => { - requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); - const existing = await deleteAllocationWithAudit(ctx.db, input.id); + delete: managerProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => { + requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); + const existing = await deleteAllocationWithAudit(ctx.db, input.id); - publishAllocationDeleted(ctx.db, { - id: existing.entry.id, - projectId: existing.projectId, - resourceId: existing.entry.resourceId, - }); + publishAllocationDeleted(ctx.db, { + id: existing.entry.id, + projectId: existing.projectId, + resourceId: existing.entry.resourceId, + }); - return { success: true }; - }), + return { success: true }; + }), batchDelete: managerProcedure .input(z.object({ ids: z.array(z.string()).min(1).max(100) })) @@ -198,11 +215,14 @@ export const allocationAssignmentProcedures = { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const existing = await batchDeleteAllocationsWithAudit(ctx.db, input.ids); - publishBatchAllocationDeletes(ctx.db, existing.map((allocation) => ({ - id: allocation.entry.id, - projectId: allocation.projectId, - resourceId: allocation.entry.resourceId, - }))); + publishBatchAllocationDeletes( + ctx.db, + existing.map((allocation) => ({ + id: allocation.entry.id, + projectId: allocation.projectId, + resourceId: allocation.entry.resourceId, + })), + ); return { count: existing.length }; }), @@ -218,11 +238,14 @@ export const allocationAssignmentProcedures = { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const updated = await batchUpdateAllocationStatusWithAudit(ctx.db, input); - publishBatchAllocationStatusUpdates(ctx.db, updated.map((allocation) => ({ - id: allocation.id, - projectId: allocation.projectId, - resourceId: allocation.resourceId, - }))); + publishBatchAllocationStatusUpdates( + ctx.db, + updated.map((allocation) => ({ + id: allocation.id, + projectId: allocation.projectId, + resourceId: allocation.resourceId, + })), + ); return { count: updated.length }; }), diff --git a/packages/api/src/router/allocation/demand.ts b/packages/api/src/router/allocation/demand.ts index 7412203..6b76891 100644 --- a/packages/api/src/router/allocation/demand.ts +++ b/packages/api/src/router/allocation/demand.ts @@ -1,3 +1,4 @@ +import type { Prisma } from "@capakraken/db"; import { deleteDemandRequirement, fillOpenDemand, @@ -10,6 +11,11 @@ import { PermissionKey, UpdateDemandRequirementSchema, } from "@capakraken/shared"; +import type { + CreateDemandRequirementInput, + FillDemandRequirementInput, + FillOpenDemandByAllocationInput, +} from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../../db/helpers.js"; import { @@ -25,41 +31,34 @@ import { invalidateDashboardCacheInBackground, } from "./effects.js"; import { DEMAND_INCLUDE } from "./shared.js"; -import { - buildCreateDemandRequirementInput, - getDemandRequirementByIdOrThrow, -} from "./support.js"; -import { - managerProcedure, - requirePermission, -} from "../../trpc.js"; +import { buildCreateDemandRequirementInput, getDemandRequirementByIdOrThrow } from "./support.js"; +import { managerProcedure, requirePermission } from "../../trpc.js"; export const allocationDemandProcedures = { createDemandRequirement: managerProcedure - .input(CreateDemandRequirementSchema) + .input(CreateDemandRequirementSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); return createDemandRequirementWithEffects(ctx.db, input); }), createDemand: managerProcedure - .input(z.object({ - projectId: z.string(), - role: z.string().optional(), - roleId: z.string().optional(), - headcount: z.number().int().positive().default(1), - hoursPerDay: z.number().min(0.5).max(24), - startDate: z.coerce.date(), - endDate: z.coerce.date(), - budgetCents: z.number().int().min(0).optional(), - metadata: z.record(z.string(), z.unknown()).optional(), - })) + .input( + z.object({ + projectId: z.string(), + role: z.string().optional(), + roleId: z.string().optional(), + headcount: z.number().int().positive().default(1), + hoursPerDay: z.number().min(0.5).max(24), + startDate: z.coerce.date(), + endDate: z.coerce.date(), + budgetCents: z.number().int().min(0).optional(), + metadata: z.record(z.string(), z.unknown()).optional(), + }), + ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); - return createDemandRequirementWithEffects( - ctx.db, - buildCreateDemandRequirementInput(input), - ); + return createDemandRequirementWithEffects(ctx.db, buildCreateDemandRequirementInput(input)); }), updateDemandRequirement: managerProcedure @@ -110,7 +109,7 @@ export const allocationDemandProcedures = { entityType: "DemandRequirement", entityId: input.id, action: "DELETE", - changes: { before: existing } as unknown as import("@capakraken/db").Prisma.InputJsonValue, + changes: { before: existing } as unknown as Prisma.InputJsonValue, }, }); }); @@ -127,17 +126,19 @@ export const allocationDemandProcedures = { }), fillDemandRequirement: managerProcedure - .input(FillDemandRequirementSchema) + .input(FillDemandRequirementSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); return fillDemandRequirementWithEffects(ctx.db, input); }), assignResourceToDemand: managerProcedure - .input(z.object({ - demandRequirementId: z.string(), - resourceId: z.string(), - })) + .input( + z.object({ + demandRequirementId: z.string(), + resourceId: z.string(), + }), + ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const result = await fillDemandRequirementWithEffects(ctx.db, input); @@ -153,7 +154,7 @@ export const allocationDemandProcedures = { }), fillOpenDemandByAllocation: managerProcedure - .input(FillOpenDemandByAllocationSchema) + .input(FillOpenDemandByAllocationSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); diff --git a/packages/api/src/router/country.ts b/packages/api/src/router/country.ts index 0ad5195..5fa8706 100644 --- a/packages/api/src/router/country.ts +++ b/packages/api/src/router/country.ts @@ -23,6 +23,8 @@ import { updateMetroCity, } from "./country-procedure-support.js"; import { CreateCountrySchema, CreateMetroCitySchema } from "@capakraken/shared"; +import type { CreateCountryInput } from "@capakraken/shared"; +import type { z } from "zod"; export const countryRouter = createTRPCRouter({ list: protectedProcedure @@ -46,7 +48,13 @@ export const countryRouter = createTRPCRouter({ .query(({ ctx, input }) => getMetroCityById(ctx, input)), create: adminProcedure - .input(CreateCountrySchema) + .input( + CreateCountrySchema as z.ZodType< + CreateCountryInput, + z.ZodTypeDef, + z.input + >, + ) .mutation(({ ctx, input }) => createCountry(ctx, input)), update: adminProcedure diff --git a/packages/api/src/router/dashboard.ts b/packages/api/src/router/dashboard.ts index 67dae99..bf09ee4 100644 --- a/packages/api/src/router/dashboard.ts +++ b/packages/api/src/router/dashboard.ts @@ -1,3 +1,4 @@ +import type { z } from "zod"; import { createTRPCRouter, controllerProcedure } from "../trpc.js"; import { dashboardChargeabilityOverviewInputSchema, @@ -34,7 +35,13 @@ export const dashboardRouter = createTRPCRouter({ .query(({ ctx, input }) => getDashboardTopValueResourcesRead(ctx, input)), getDemand: controllerProcedure - .input(dashboardDemandInputSchema) + .input( + dashboardDemandInputSchema as z.ZodType< + z.infer, + z.ZodTypeDef, + z.input + >, + ) .query(({ ctx, input }) => getDashboardDemandRead(ctx, input)), getDetail: controllerProcedure @@ -47,7 +54,9 @@ export const dashboardRouter = createTRPCRouter({ getBudgetForecast: controllerProcedure.query(({ ctx }) => getDashboardBudgetForecastRead(ctx)), - getBudgetForecastDetail: controllerProcedure.query(({ ctx }) => getDashboardBudgetForecastDetail(ctx)), + getBudgetForecastDetail: controllerProcedure.query(({ ctx }) => + getDashboardBudgetForecastDetail(ctx), + ), getSkillGaps: controllerProcedure.query(({ ctx }) => getDashboardSkillGapsRead(ctx)), @@ -55,5 +64,7 @@ export const dashboardRouter = createTRPCRouter({ getProjectHealth: controllerProcedure.query(({ ctx }) => getDashboardProjectHealthRead(ctx)), - getProjectHealthDetail: controllerProcedure.query(({ ctx }) => getDashboardProjectHealthDetail(ctx)), + getProjectHealthDetail: controllerProcedure.query(({ ctx }) => + getDashboardProjectHealthDetail(ctx), + ), }); diff --git a/packages/api/src/router/project-cost-read.ts b/packages/api/src/router/project-cost-read.ts index cd2ecd9..8cf8b5e 100644 --- a/packages/api/src/router/project-cost-read.ts +++ b/packages/api/src/router/project-cost-read.ts @@ -4,13 +4,19 @@ import { z } from "zod"; import { CursorInputSchema, paginateCursor } from "../db/pagination.js"; import { controllerProcedure } from "../trpc.js"; +const ListWithCostsInputSchema = CursorInputSchema.extend({ + status: z.nativeEnum(ProjectStatus).optional(), + search: z.string().optional(), +}); + export const projectCostReadProcedures = { listWithCosts: controllerProcedure .input( - CursorInputSchema.extend({ - status: z.nativeEnum(ProjectStatus).optional(), - search: z.string().optional(), - }), + ListWithCostsInputSchema as z.ZodType< + z.infer, + z.ZodTypeDef, + z.input + >, ) .query(async ({ ctx, input }) => { const { status, search, cursor } = input; @@ -58,9 +64,8 @@ export const projectCostReadProcedures = { totalCostCents += booking.dailyCostCents * days; totalPersonDays += (booking.hoursPerDay * days) / 8; } - const utilizationPercent = project.budgetCents > 0 - ? Math.round((totalCostCents / project.budgetCents) * 100) - : 0; + const utilizationPercent = + project.budgetCents > 0 ? Math.round((totalCostCents / project.budgetCents) * 100) : 0; return { ...project, totalCostCents: Math.round(totalCostCents), diff --git a/packages/api/src/router/project-mutations.ts b/packages/api/src/router/project-mutations.ts index 82daf94..08a4ae5 100644 --- a/packages/api/src/router/project-mutations.ts +++ b/packages/api/src/router/project-mutations.ts @@ -1,4 +1,11 @@ -import { BlueprintTarget, CreateProjectSchema, PermissionKey, UpdateProjectSchema } from "@capakraken/shared"; +import { + BlueprintTarget, + CreateProjectSchema, + PermissionKey, + UpdateProjectSchema, +} from "@capakraken/shared"; +import type { CreateProjectInput } from "@capakraken/shared"; +import type { Prisma } from "@capakraken/db"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; @@ -21,10 +28,12 @@ function buildProjectCreateData( status: input.status, responsiblePerson: input.responsiblePerson, ...(input.color !== undefined ? { color: input.color } : {}), - staffingReqs: input.staffingReqs as unknown as import("@capakraken/db").Prisma.InputJsonValue, - dynamicFields: input.dynamicFields as unknown as import("@capakraken/db").Prisma.InputJsonValue, + staffingReqs: input.staffingReqs as unknown as Prisma.InputJsonValue, + dynamicFields: input.dynamicFields as unknown as Prisma.InputJsonValue, blueprintId: input.blueprintId, - ...(input.utilizationCategoryId !== undefined ? { utilizationCategoryId: input.utilizationCategoryId || null } : {}), + ...(input.utilizationCategoryId !== undefined + ? { utilizationCategoryId: input.utilizationCategoryId || null } + : {}), ...(input.clientId !== undefined ? { clientId: input.clientId || null } : {}), } as unknown as Parameters[0]["data"]; } @@ -41,24 +50,32 @@ function buildProjectUpdateData( ...(input.startDate !== undefined ? { startDate: input.startDate } : {}), ...(input.endDate !== undefined ? { endDate: input.endDate } : {}), ...(input.status !== undefined ? { status: input.status } : {}), - ...(input.responsiblePerson !== undefined ? { responsiblePerson: input.responsiblePerson } : {}), + ...(input.responsiblePerson !== undefined + ? { responsiblePerson: input.responsiblePerson } + : {}), ...(input.color !== undefined ? { color: input.color } : {}), - ...(input.staffingReqs !== undefined ? { staffingReqs: input.staffingReqs as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}), - ...(input.dynamicFields !== undefined ? { dynamicFields: input.dynamicFields as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}), + ...(input.staffingReqs !== undefined + ? { staffingReqs: input.staffingReqs as unknown as Prisma.InputJsonValue } + : {}), + ...(input.dynamicFields !== undefined + ? { dynamicFields: input.dynamicFields as unknown as Prisma.InputJsonValue } + : {}), ...(input.blueprintId !== undefined ? { blueprintId: input.blueprintId } : {}), - ...(input.utilizationCategoryId !== undefined ? { utilizationCategoryId: input.utilizationCategoryId || null } : {}), + ...(input.utilizationCategoryId !== undefined + ? { utilizationCategoryId: input.utilizationCategoryId || null } + : {}), ...(input.clientId !== undefined ? { clientId: input.clientId || null } : {}), ...(input.shoringThreshold !== undefined ? { shoringThreshold: input.shoringThreshold } : {}), - ...(input.onshoreCountryCode !== undefined ? { onshoreCountryCode: input.onshoreCountryCode } : {}), + ...(input.onshoreCountryCode !== undefined + ? { onshoreCountryCode: input.onshoreCountryCode } + : {}), } as unknown as Parameters[0]["data"]; } -export function createProjectMutationProcedures( - backgroundEffects: ProjectBackgroundEffects, -) { +export function createProjectMutationProcedures(backgroundEffects: ProjectBackgroundEffects) { return { create: managerProcedure - .input(CreateProjectSchema) + .input(CreateProjectSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_PROJECTS); @@ -119,7 +136,9 @@ export function createProjectMutationProcedures( ); const nextBlueprintId = input.data.blueprintId ?? existing.blueprintId ?? undefined; - const nextDynamicFields = (input.data.dynamicFields ?? existing.dynamicFields ?? {}) as Record; + const nextDynamicFields = (input.data.dynamicFields ?? + existing.dynamicFields ?? + {}) as Record; await assertBlueprintDynamicFields({ db: ctx.db, diff --git a/packages/api/src/router/project.ts b/packages/api/src/router/project.ts index 79cc3a7..77aeb94 100644 --- a/packages/api/src/router/project.ts +++ b/packages/api/src/router/project.ts @@ -1,3 +1,4 @@ +import type { z } from "zod"; import { projectCostReadProcedures } from "./project-cost-read.js"; import { projectCoverProcedures } from "./project-cover.js"; import { projectIdentifierReadProcedures } from "./project-identifier-read.js"; @@ -21,13 +22,20 @@ export const projectRouter = createTRPCRouter({ ...projectCoverProcedures, ...projectIdentifierReadProcedures, ...createProjectLifecycleProcedures({ - invalidateDashboardCacheInBackground: projectBackgroundEffects.invalidateDashboardCacheInBackground, + invalidateDashboardCacheInBackground: + projectBackgroundEffects.invalidateDashboardCacheInBackground, dispatchProjectWebhookInBackground: projectBackgroundEffects.dispatchProjectWebhookInBackground, }), ...createProjectMutationProcedures(projectBackgroundEffects), list: controllerProcedure - .input(ProjectListInputSchema) + .input( + ProjectListInputSchema as z.ZodType< + z.infer, + z.ZodTypeDef, + z.input + >, + ) .query(({ ctx, input }) => listProjects(ctx, input)), getById: controllerProcedure @@ -37,5 +45,4 @@ export const projectRouter = createTRPCRouter({ getShoringRatio: controllerProcedure .input(ProjectShoringRatioInputSchema) .query(({ ctx, input }) => getProjectShoringRatioData(ctx, input)), - }); diff --git a/packages/api/src/router/resource-summary-read.ts b/packages/api/src/router/resource-summary-read.ts index cb6e57b..6b30859 100644 --- a/packages/api/src/router/resource-summary-read.ts +++ b/packages/api/src/router/resource-summary-read.ts @@ -1,8 +1,6 @@ +import type { z } from "zod"; import { protectedProcedure, resourceOverviewProcedure } from "../trpc.js"; -import { - ResourceDirectoryQuerySchema, - ResourceListQuerySchema, -} from "./resource-read-shared.js"; +import { ResourceDirectoryQuerySchema, ResourceListQuerySchema } from "./resource-read-shared.js"; import { ResolveResponsiblePersonNameInputSchema, ResourceChargeabilitySummaryInputSchema, @@ -38,7 +36,13 @@ export const resourceSummaryReadProcedures = { .query(({ ctx, input }) => listResourceDirectory(ctx, input)), listStaff: resourceOverviewProcedure - .input(ResourceListQuerySchema) + .input( + ResourceListQuerySchema as z.ZodType< + z.infer, + z.ZodTypeDef, + z.input + >, + ) .query(({ ctx, input }) => listStaffResourceEntries(ctx, input)), chapters: protectedProcedure.query(({ ctx }) => listResourceChapters(ctx)), diff --git a/packages/api/src/router/staffing-capacity-read.ts b/packages/api/src/router/staffing-capacity-read.ts index dae53d6..aa349f3 100644 --- a/packages/api/src/router/staffing-capacity-read.ts +++ b/packages/api/src/router/staffing-capacity-read.ts @@ -18,17 +18,23 @@ import { toIsoDate, } from "./staffing-shared.js"; +const SearchCapacityInputSchema = z.object({ + startDate: z.coerce.date(), + endDate: z.coerce.date(), + minHoursPerDay: z.number().optional().default(4), + roleName: z.string().optional(), + chapter: z.string().optional(), + limit: z.number().int().min(1).max(100).optional().default(20), +}); + export const staffingCapacityReadProcedures = { searchCapacity: planningReadProcedure .input( - z.object({ - startDate: z.coerce.date(), - endDate: z.coerce.date(), - minHoursPerDay: z.number().optional().default(4), - roleName: z.string().optional(), - chapter: z.string().optional(), - limit: z.number().int().min(1).max(100).optional().default(20), - }), + SearchCapacityInputSchema as z.ZodType< + z.infer, + z.ZodTypeDef, + z.input + >, ) .query(async ({ ctx, input }) => { const where: Record = { isActive: true }; @@ -107,7 +113,8 @@ export const staffingCapacityReadProcedures = { }); const bookedHours = (bookingsByResourceId.get(resource.id) ?? []).reduce( (sum, booking) => - sum + calculateEffectiveBookedHours({ + sum + + calculateEffectiveBookedHours({ availability, startDate: booking.startDate, endDate: booking.endDate, @@ -179,15 +186,17 @@ export const staffingCapacityReadProcedures = { const availability = resource.availability as unknown as WeekdayAvailability; const contexts = await loadResourceDailyAvailabilityContexts( ctx.db, - [{ - id: resource.id, - availability, - countryId: resource.countryId, - countryCode: resource.country?.code, - federalState: resource.federalState, - metroCityId: resource.metroCityId, - metroCityName: resource.metroCity?.name, - }], + [ + { + id: resource.id, + availability, + countryId: resource.countryId, + countryCode: resource.country?.code, + federalState: resource.federalState, + metroCityId: resource.metroCityId, + metroCityName: resource.metroCity?.name, + }, + ], input.startDate, input.endDate, ); @@ -231,9 +240,8 @@ export const staffingCapacityReadProcedures = { cursor.setUTCDate(cursor.getUTCDate() + 1); } - const currentChargeability = totalAvailableHours > 0 - ? (totalChargeableHours / totalAvailableHours) * 100 - : 0; + const currentChargeability = + totalAvailableHours > 0 ? (totalChargeableHours / totalAvailableHours) * 100 : 0; return { resourceId: resource.id, @@ -291,15 +299,17 @@ export const staffingCapacityReadProcedures = { const availability = resource.availability as unknown as WeekdayAvailability; const contexts = await loadResourceDailyAvailabilityContexts( ctx.db, - [{ - id: resource.id, - availability, - countryId: resource.countryId, - countryCode: resource.country?.code, - federalState: resource.federalState, - metroCityId: resource.metroCityId, - metroCityName: resource.metroCity?.name, - }], + [ + { + id: resource.id, + availability, + countryId: resource.countryId, + countryCode: resource.country?.code, + federalState: resource.federalState, + metroCityId: resource.metroCityId, + metroCityName: resource.metroCity?.name, + }, + ], input.startDate, input.endDate, ); diff --git a/packages/api/src/router/staffing-suggestions-read.ts b/packages/api/src/router/staffing-suggestions-read.ts index 87b697c..b7c099d 100644 --- a/packages/api/src/router/staffing-suggestions-read.ts +++ b/packages/api/src/router/staffing-suggestions-read.ts @@ -1,6 +1,7 @@ import { rankResources } from "@capakraken/staffing"; import { listAssignmentBookings } from "@capakraken/application"; -import { PermissionKey, toIsoDateOrNull, type WeekdayAvailability } from "@capakraken/shared"; +import { PermissionKey, toIsoDateOrNull } from "@capakraken/shared"; +import type { SkillEntry, WeekdayAvailability } from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { loadResourceDailyAvailabilityContexts } from "../lib/resource-capacity.js"; @@ -30,10 +31,8 @@ type StaffingSuggestionInput = { minProficiency?: number | undefined; }; -type StaffingSuggestionsDbClient = - Parameters[0] - & Parameters[0] - & { +type StaffingSuggestionsDbClient = Parameters[0] & + Parameters[0] & { resource: { findMany: (args: Record) => Promise; }; @@ -77,7 +76,7 @@ async function queryStaffingSuggestions( mainSkillsOnly, minProficiency, } = input; - const resources = await db.resource.findMany({ + const resources = (await db.resource.findMany({ where: { isActive: true, ...(chapter ? { chapter } : {}), @@ -100,7 +99,7 @@ async function queryStaffingSuggestions( metroCity: { select: { name: true } }, areaRole: { select: { name: true } }, }, - }) as StaffingResourceRecord[]; + })) as StaffingResourceRecord[]; const bookings = await listAssignmentBookings(db, { startDate, endDate, @@ -133,7 +132,9 @@ async function queryStaffingSuggestions( const availability = resource.availability as unknown as WeekdayAvailability; const context = contexts.get(resource.id); const resourceBookings = bookingsByResourceId.get(resource.id) ?? []; - const activeBookings = resourceBookings.filter((booking) => ACTIVE_STATUSES.has(booking.status)); + const activeBookings = resourceBookings.filter((booking) => + ACTIVE_STATUSES.has(booking.status), + ); const capacity = buildResourceCapacitySummary({ availability, periodStart: startDate, @@ -192,13 +193,17 @@ async function queryStaffingSuggestions( } const allocatedHours = capacity.bookedHours; - const remainingHours = capacity.remainingHours; const remainingHoursPerDay = capacity.remainingHoursPerDay; const utilizationPercent = capacity.availableHours > 0 ? Math.min(100, (allocatedHours / capacity.availableHours) * 100) : 0; - type SkillRow = { skill: string; category?: string; proficiency: number; isMainSkill?: boolean }; + type SkillRow = { + skill: string; + category?: string; + proficiency: number; + isMainSkill?: boolean; + }; let skills = resource.skills as unknown as SkillRow[]; if (mainSkillsOnly) { skills = skills.filter((skill) => skill.isMainSkill); @@ -217,7 +222,7 @@ async function queryStaffingSuggestions( fte: resource.fte, chapter: resource.chapter, role: resource.areaRole?.name ?? null, - skills: skills as unknown as import("@capakraken/shared").SkillEntry[], + skills: skills as unknown as SkillEntry[], lcrCents: resource.lcrCents, chargeabilityTarget: resource.chargeabilityTarget, currentUtilizationPercent: utilizationPercent, @@ -267,97 +272,138 @@ async function queryStaffingSuggestions( budgetLcrCentsPerHour, } as unknown as Parameters[0]); const baseRankIndex = new Map(ranked.map((suggestion, index) => [suggestion.resourceId, index])); - return [...ranked].sort((left, right) => { - if (Math.abs(left.score - right.score) <= 2) { - const leftValue = enrichedResources.find((resource) => resource.id === left.resourceId)?.valueScore ?? 0; - const rightValue = enrichedResources.find((resource) => resource.id === right.resourceId)?.valueScore ?? 0; - return rightValue - leftValue; - } - return 0; - }).map((suggestion, index) => { - const resource = enrichedResources.find((entry) => entry.id === suggestion.resourceId); - const fallbackBreakdown = "breakdown" in suggestion - ? (suggestion as { breakdown?: { skillScore: number; availabilityScore: number; costScore: number; utilizationScore: number } }).breakdown - : undefined; - const scoreBreakdown = suggestion.scoreBreakdown ?? { - skillScore: fallbackBreakdown?.skillScore ?? 0, - availabilityScore: fallbackBreakdown?.availabilityScore ?? 0, - costScore: fallbackBreakdown?.costScore ?? 0, - utilizationScore: fallbackBreakdown?.utilizationScore ?? 0, - total: suggestion.score, - }; - const baseRank = (baseRankIndex.get(suggestion.resourceId) ?? index) + 1; - const tieBreakerApplied = baseRank !== index + 1; + return [...ranked] + .sort((left, right) => { + if (Math.abs(left.score - right.score) <= 2) { + const leftValue = + enrichedResources.find((resource) => resource.id === left.resourceId)?.valueScore ?? 0; + const rightValue = + enrichedResources.find((resource) => resource.id === right.resourceId)?.valueScore ?? 0; + return rightValue - leftValue; + } + return 0; + }) + .map((suggestion, index) => { + const resource = enrichedResources.find((entry) => entry.id === suggestion.resourceId); + const fallbackBreakdown = + "breakdown" in suggestion + ? ( + suggestion as { + breakdown?: { + skillScore: number; + availabilityScore: number; + costScore: number; + utilizationScore: number; + }; + } + ).breakdown + : undefined; + const scoreBreakdown = suggestion.scoreBreakdown ?? { + skillScore: fallbackBreakdown?.skillScore ?? 0, + availabilityScore: fallbackBreakdown?.availabilityScore ?? 0, + costScore: fallbackBreakdown?.costScore ?? 0, + utilizationScore: fallbackBreakdown?.utilizationScore ?? 0, + total: suggestion.score, + }; + const baseRank = (baseRankIndex.get(suggestion.resourceId) ?? index) + 1; + const tieBreakerApplied = baseRank !== index + 1; - return { - ...suggestion, - resourceName: suggestion.resourceName ?? resource?.displayName ?? "", - eid: suggestion.eid ?? resource?.eid ?? "", - fte: resource?.fte ?? 0, - chapter: resource?.chapter ?? null, - role: resource?.role ?? null, - scoreBreakdown, - matchedSkills: suggestion.matchedSkills ?? requiredSkills.filter((skill) => - resource?.skills.some((entry) => entry.skill.toLowerCase() === skill.trim().toLowerCase()), - ), - missingSkills: suggestion.missingSkills ?? requiredSkills.filter((skill) => - !resource?.skills.some((entry) => entry.skill.toLowerCase() === skill.trim().toLowerCase()), - ), - availabilityConflicts: suggestion.availabilityConflicts ?? resource?.conflictDays ?? [], - estimatedDailyCostCents: suggestion.estimatedDailyCostCents ?? ((resource?.lcrCents ?? 0) * 8), - currentUtilization: suggestion.currentUtilization ?? round1(resource?.currentUtilizationPercent ?? 0), - valueScore: resource?.valueScore ?? 0, - location: resource?.transparency.location ?? { - countryCode: null, - countryName: null, - federalState: null, - metroCityName: null, - label: "", - }, - capacity: resource?.transparency.capacity ?? { - requestedHoursPerDay: round1(hoursPerDay), - requestedHoursTotal: 0, - baseWorkingDays: 0, - effectiveWorkingDays: 0, - baseAvailableHours: 0, - effectiveAvailableHours: 0, - bookedHours: 0, - remainingHours: 0, - remainingHoursPerDay: 0, - holidayCount: 0, - holidayWorkdayCount: 0, - holidayHoursDeduction: 0, - absenceDayEquivalent: 0, - absenceHoursDeduction: 0, - }, - conflicts: resource?.transparency.conflicts ?? { - count: 0, - conflictDays: [], - details: [], - }, - ranking: { - rank: index + 1, - baseRank, - tieBreakerApplied, - tieBreakerReason: tieBreakerApplied - ? "Within 2 score points, higher value score moves the candidate up." - : null, - model: "Composite ranking across skill fit, availability, cost, and utilization.", - components: [ - { key: "skillScore", label: "Skills", score: scoreBreakdown.skillScore }, - { key: "availabilityScore", label: "Availability", score: scoreBreakdown.availabilityScore }, - { key: "costScore", label: "Cost", score: scoreBreakdown.costScore }, - { key: "utilizationScore", label: "Utilization", score: scoreBreakdown.utilizationScore }, - ], - }, - remainingHoursPerDay: resource?.transparency.capacity.remainingHoursPerDay ?? 0, - remainingHours: resource?.transparency.capacity.remainingHours ?? 0, - effectiveAvailableHours: resource?.transparency.capacity.effectiveAvailableHours ?? 0, - baseAvailableHours: resource?.transparency.capacity.baseAvailableHours ?? 0, - holidayHoursDeduction: resource?.transparency.capacity.holidayHoursDeduction ?? 0, - }; - }); + return { + ...suggestion, + resourceName: suggestion.resourceName ?? resource?.displayName ?? "", + eid: suggestion.eid ?? resource?.eid ?? "", + fte: resource?.fte ?? 0, + chapter: resource?.chapter ?? null, + role: resource?.role ?? null, + scoreBreakdown, + matchedSkills: + suggestion.matchedSkills ?? + requiredSkills.filter((skill) => + resource?.skills.some( + (entry) => entry.skill.toLowerCase() === skill.trim().toLowerCase(), + ), + ), + missingSkills: + suggestion.missingSkills ?? + requiredSkills.filter( + (skill) => + !resource?.skills.some( + (entry) => entry.skill.toLowerCase() === skill.trim().toLowerCase(), + ), + ), + availabilityConflicts: suggestion.availabilityConflicts ?? resource?.conflictDays ?? [], + estimatedDailyCostCents: + suggestion.estimatedDailyCostCents ?? (resource?.lcrCents ?? 0) * 8, + currentUtilization: + suggestion.currentUtilization ?? round1(resource?.currentUtilizationPercent ?? 0), + valueScore: resource?.valueScore ?? 0, + location: resource?.transparency.location ?? { + countryCode: null, + countryName: null, + federalState: null, + metroCityName: null, + label: "", + }, + capacity: resource?.transparency.capacity ?? { + requestedHoursPerDay: round1(hoursPerDay), + requestedHoursTotal: 0, + baseWorkingDays: 0, + effectiveWorkingDays: 0, + baseAvailableHours: 0, + effectiveAvailableHours: 0, + bookedHours: 0, + remainingHours: 0, + remainingHoursPerDay: 0, + holidayCount: 0, + holidayWorkdayCount: 0, + holidayHoursDeduction: 0, + absenceDayEquivalent: 0, + absenceHoursDeduction: 0, + }, + conflicts: resource?.transparency.conflicts ?? { + count: 0, + conflictDays: [], + details: [], + }, + ranking: { + rank: index + 1, + baseRank, + tieBreakerApplied, + tieBreakerReason: tieBreakerApplied + ? "Within 2 score points, higher value score moves the candidate up." + : null, + model: "Composite ranking across skill fit, availability, cost, and utilization.", + components: [ + { key: "skillScore", label: "Skills", score: scoreBreakdown.skillScore }, + { + key: "availabilityScore", + label: "Availability", + score: scoreBreakdown.availabilityScore, + }, + { key: "costScore", label: "Cost", score: scoreBreakdown.costScore }, + { + key: "utilizationScore", + label: "Utilization", + score: scoreBreakdown.utilizationScore, + }, + ], + }, + remainingHoursPerDay: resource?.transparency.capacity.remainingHoursPerDay ?? 0, + remainingHours: resource?.transparency.capacity.remainingHours ?? 0, + effectiveAvailableHours: resource?.transparency.capacity.effectiveAvailableHours ?? 0, + baseAvailableHours: resource?.transparency.capacity.baseAvailableHours ?? 0, + holidayHoursDeduction: resource?.transparency.capacity.holidayHoursDeduction ?? 0, + }; + }); } +const GetProjectStaffingSuggestionsInputSchema = z.object({ + projectId: z.string().min(1), + roleName: z.string().optional(), + startDate: z.coerce.date().optional(), + endDate: z.coerce.date().optional(), + limit: z.number().int().min(1).max(50).optional().default(5), +}); + export const staffingSuggestionsReadProcedures = { getSuggestions: planningReadProcedure .input( @@ -380,35 +426,39 @@ export const staffingSuggestionsReadProcedures = { }), getProjectStaffingSuggestions: planningReadProcedure .input( - z.object({ - projectId: z.string().min(1), - roleName: z.string().optional(), - startDate: z.coerce.date().optional(), - endDate: z.coerce.date().optional(), - limit: z.number().int().min(1).max(50).optional().default(5), - }), + GetProjectStaffingSuggestionsInputSchema as z.ZodType< + z.infer, + z.ZodTypeDef, + z.input + >, ) .query(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.VIEW_COSTS); - const project = await findUniqueOrThrow(ctx.db.project.findUnique({ - where: { id: input.projectId }, - select: { - id: true, - shortCode: true, - name: true, - startDate: true, - endDate: true, - }, - }), "Project"); + const project = await findUniqueOrThrow( + ctx.db.project.findUnique({ + where: { id: input.projectId }, + select: { + id: true, + shortCode: true, + name: true, + startDate: true, + endDate: true, + }, + }), + "Project", + ); const startDate = input.startDate ?? project.startDate ?? new Date(); const endDate = input.endDate ?? project.endDate ?? new Date(); const normalizedRoleFilter = input.roleName?.trim().toLowerCase(); - const suggestions = await queryStaffingSuggestions(ctx.db as unknown as StaffingSuggestionsDbClient, { - requiredSkills: [], - startDate, - endDate, - hoursPerDay: 8, - }); + const suggestions = await queryStaffingSuggestions( + ctx.db as unknown as StaffingSuggestionsDbClient, + { + requiredSkills: [], + startDate, + endDate, + hoursPerDay: 8, + }, + ); return { project: `${project.name} (${project.shortCode})`, period: `${toIsoDateOrNull(startDate)} to ${toIsoDateOrNull(endDate)}`, diff --git a/packages/api/src/router/system-role-config.ts b/packages/api/src/router/system-role-config.ts index 48cbc4d..3e4cecc 100644 --- a/packages/api/src/router/system-role-config.ts +++ b/packages/api/src/router/system-role-config.ts @@ -1,3 +1,4 @@ +import type { z } from "zod"; import { adminProcedure, createTRPCRouter } from "../trpc.js"; import { listSystemRoleConfigs, @@ -11,6 +12,10 @@ export const systemRoleConfigRouter = createTRPCRouter({ /** Update a role's default permissions, label, description, and color */ update: adminProcedure - .input(systemRoleConfigUpdateInputSchema) + .input( + systemRoleConfigUpdateInputSchema as z.ZodType< + z.infer + >, + ) .mutation(({ ctx, input }) => updateSystemRoleConfig(ctx, input)), }); diff --git a/packages/api/src/router/timeline-mutations.ts b/packages/api/src/router/timeline-mutations.ts index b8876a0..e23ca49 100644 --- a/packages/api/src/router/timeline-mutations.ts +++ b/packages/api/src/router/timeline-mutations.ts @@ -1,4 +1,6 @@ import { PermissionKey, ShiftProjectSchema } from "@capakraken/shared"; +import type { ShiftProjectInput } from "@capakraken/shared"; +import type { z } from "zod"; import { managerProcedure, requirePermission } from "../trpc.js"; import { timelineAllocationMutationProcedures } from "./timeline-allocation-mutations.js"; import { applyTimelineProjectShiftMutation } from "./timeline-shift-router-support.js"; @@ -7,7 +9,7 @@ export const timelineMutationProcedures = { ...timelineAllocationMutationProcedures, applyShift: managerProcedure - .input(ShiftProjectSchema) + .input(ShiftProjectSchema as z.ZodType) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index 456e47f..564c118 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -1,4 +1,11 @@ -import { adminProcedure, createTRPCRouter, managerProcedure, protectedProcedure, publicProcedure } from "../trpc.js"; +import type { z } from "zod"; +import { + adminProcedure, + createTRPCRouter, + managerProcedure, + protectedProcedure, + publicProcedure, +} from "../trpc.js"; import { autoLinkUsersByEmail, countActiveUsers, @@ -92,7 +99,9 @@ export const userRouter = createTRPCRouter({ .mutation(({ ctx, input }) => toggleFavoriteProject(ctx, input)), setPermissions: adminProcedure - .input(SetUserPermissionsInputSchema) + .input( + SetUserPermissionsInputSchema as z.ZodType>, + ) .mutation(({ ctx, input }) => setUserPermissions(ctx, input)), resetPermissions: adminProcedure diff --git a/packages/api/src/router/vacation.ts b/packages/api/src/router/vacation.ts index 8a45a14..ac7995b 100644 --- a/packages/api/src/router/vacation.ts +++ b/packages/api/src/router/vacation.ts @@ -1,7 +1,9 @@ import { createTRPCRouter, protectedProcedure } from "../trpc.js"; +import type { z } from "zod"; import { createVacationRequest, CreateVacationRequestSchema, + type CreateVacationRequestInput, } from "./vacation-create-support.js"; import { vacationManagementProcedures } from "./vacation-management-procedures.js"; import { vacationReadProcedures } from "./vacation-read.js"; @@ -11,6 +13,6 @@ export const vacationRouter = createTRPCRouter({ ...vacationManagementProcedures, create: protectedProcedure - .input(CreateVacationRequestSchema) + .input(CreateVacationRequestSchema as z.ZodType) .mutation(({ ctx, input }) => createVacationRequest(ctx, input)), });