refactor(api): reorganise allocation router into allocation/ subdirectory

Moves read, assignment-procedures, assignment-mutations, and demand
procedures into allocation/ so the domain boundary is discoverable
without grep.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 14:44:17 +02:00
parent d3bfa8ca98
commit b2c8d98b25
14 changed files with 46 additions and 46 deletions
@@ -0,0 +1,172 @@
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 "./effects.js";
import { DEMAND_INCLUDE } from "./shared.js";
import {
buildCreateDemandRequirementInput,
getDemandRequirementByIdOrThrow,
} from "./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<typeof updateDemandRequirement>[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<typeof deleteDemandRequirement>[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;
}),
};