import { createAssignment, updateAssignment, } from "@capakraken/application"; import { AllocationStatus, CreateAllocationSchema, CreateAssignmentSchema, PermissionKey, UpdateAllocationSchema, UpdateAssignmentSchema, } from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../../db/helpers.js"; import { publishAllocationCreated, publishAllocationDeleted, publishAllocationUpdated, publishBatchAllocationDeletes, publishBatchAllocationStatusUpdates, } from "./assignment-effects.js"; import { batchDeleteAllocationsWithAudit, batchUpdateAllocationStatusWithAudit, createAllocationReadModelEntry, deleteAllocationWithAudit, deleteAssignmentWithAudit, ensureAssignmentRecord, updateAllocationWithAudit, } from "./assignment-mutations.js"; import { managerProcedure, requirePermission, } from "../../trpc.js"; export const allocationAssignmentProcedures = { create: managerProcedure .input(CreateAllocationSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const allocation = await ctx.db.$transaction(async (tx) => createAllocationReadModelEntry( tx as Parameters[0], input, )); publishAllocationCreated(ctx.db, { id: allocation.id, projectId: allocation.projectId, resourceId: allocation.resourceId, }, { dispatchWebhook: true }); return allocation; }), createAssignment: managerProcedure .input(CreateAssignmentSchema) .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, ); }); publishAllocationCreated(ctx.db, { id: assignment.id, projectId: assignment.projectId, resourceId: assignment.resourceId, }); return assignment; }), 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(), })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const result = await ensureAssignmentRecord(ctx.db, { resourceId: input.resourceId, projectId: input.projectId, startDate: input.startDate, endDate: input.endDate, hoursPerDay: input.hoursPerDay, ...(input.role !== undefined ? { role: input.role } : {}), }); if (result.action === "reactivated") { publishAllocationUpdated(ctx.db, { id: result.assignment.id, projectId: result.assignment.projectId, resourceId: result.assignment.resourceId, }, { dispatchWebhook: true }); return result; } publishAllocationCreated(ctx.db, { id: result.assignment.id, projectId: result.assignment.projectId, resourceId: result.assignment.resourceId, }); return result; }), updateAssignment: managerProcedure .input(z.object({ id: z.string(), data: UpdateAssignmentSchema })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const existing = await findUniqueOrThrow( ctx.db.assignment.findUnique({ where: { id: input.id }, select: { resourceId: true }, }), "Assignment", ); const updated = await ctx.db.$transaction(async (tx) => { return updateAssignment( tx as unknown as Parameters[0], input.id, input.data, ); }); 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 })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const { existing, updated } = await updateAllocationWithAudit(ctx.db, input.id, input.data); const affectedResourceIds = [existing.entry.resourceId, updated.resourceId].filter( (resourceId): resourceId is string => Boolean(resourceId), ); publishAllocationUpdated(ctx.db, { id: updated.id, projectId: updated.projectId, resourceId: updated.resourceId, resourceIds: affectedResourceIds, }); return updated; }), deleteAssignment: managerProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const existing = await deleteAssignmentWithAudit(ctx.db, input.id); publishAllocationDeleted(ctx.db, { id: existing.id, projectId: existing.projectId, resourceId: existing.resourceId, }); 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); publishAllocationDeleted(ctx.db, { id: existing.entry.id, projectId: existing.projectId, resourceId: existing.entry.resourceId, }); return { success: true }; }), batchDelete: managerProcedure .input(z.object({ ids: z.array(z.string()).min(1).max(100) })) .mutation(async ({ ctx, input }) => { 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, }))); return { count: existing.length }; }), batchUpdateStatus: managerProcedure .input( z.object({ ids: z.array(z.string()).min(1).max(100), status: z.nativeEnum(AllocationStatus), }), ) .mutation(async ({ ctx, input }) => { 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, }))); return { count: updated.length }; }), };