import { deleteDemandRequirement, fillOpenDemand, updateDemandRequirement, } from "@capakraken/application"; import { CreateDemandRequirementSchema, FillDemandRequirementSchema, FillOpenDemandByAllocationSchema, PermissionKey, UpdateDemandRequirementSchema, } from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { emitAllocationCreated, emitAllocationDeleted, emitAllocationUpdated, } from "../sse/event-bus.js"; import { checkBudgetThresholdsInBackground, createDemandRequirementWithEffects, dispatchAllocationWebhookInBackground, fillDemandRequirementWithEffects, invalidateDashboardCacheInBackground, } from "./allocation-effects.js"; import { DEMAND_INCLUDE } from "./allocation-shared.js"; import { buildCreateDemandRequirementInput, getDemandRequirementByIdOrThrow, } from "./allocation-support.js"; import { managerProcedure, requirePermission, } from "../trpc.js"; export const allocationDemandProcedures = { createDemandRequirement: managerProcedure .input(CreateDemandRequirementSchema) .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(), })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); return createDemandRequirementWithEffects( ctx.db, buildCreateDemandRequirementInput(input), ); }), updateDemandRequirement: managerProcedure .input(z.object({ id: z.string(), data: UpdateDemandRequirementSchema })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const updated = await ctx.db.$transaction(async (tx) => { return updateDemandRequirement( tx as unknown as Parameters[0], input.id, input.data, ); }); emitAllocationUpdated({ id: updated.id, projectId: updated.projectId, resourceId: null, }); invalidateDashboardCacheInBackground(); checkBudgetThresholdsInBackground(ctx.db, updated.projectId); return updated; }), deleteDemandRequirement: managerProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const existing = await findUniqueOrThrow( ctx.db.demandRequirement.findUnique({ where: { id: input.id }, include: DEMAND_INCLUDE, }), "Demand requirement", ); await ctx.db.$transaction(async (tx) => { await deleteDemandRequirement( tx as unknown as Parameters[0], input.id, ); await tx.auditLog.create({ data: { entityType: "DemandRequirement", entityId: input.id, action: "DELETE", changes: { before: existing } as unknown as import("@capakraken/db").Prisma.InputJsonValue, }, }); }); emitAllocationDeleted(existing.id, existing.projectId); dispatchAllocationWebhookInBackground(ctx.db, "allocation.deleted", { id: existing.id, projectId: existing.projectId, }); invalidateDashboardCacheInBackground(); checkBudgetThresholdsInBackground(ctx.db, existing.projectId); return { success: true }; }), fillDemandRequirement: managerProcedure .input(FillDemandRequirementSchema) .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(), })) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const result = await fillDemandRequirementWithEffects(ctx.db, input); const demandRequirement = await getDemandRequirementByIdOrThrow( ctx.db, input.demandRequirementId, ); return { ...result, demandRequirement, }; }), fillOpenDemandByAllocation: managerProcedure .input(FillOpenDemandByAllocationSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const result = await fillOpenDemand(ctx.db, input); emitAllocationCreated(result.createdAllocation); if (result.updatedAllocation) { emitAllocationUpdated(result.updatedAllocation); } invalidateDashboardCacheInBackground(); checkBudgetThresholdsInBackground(ctx.db, result.createdAllocation.projectId as string); return result; }), };