refactor(api): extract allocation router support modules
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { createDemandRequirement, fillDemandRequirement } from "@capakraken/application";
|
||||
import { buildTaskAction, CreateDemandRequirementSchema, FillDemandRequirementSchema } from "@capakraken/shared";
|
||||
import { z } from "zod";
|
||||
import { checkBudgetThresholds } from "../lib/budget-alerts.js";
|
||||
import { generateAutoSuggestions } from "../lib/auto-staffing.js";
|
||||
import { invalidateDashboardCache } from "../lib/cache.js";
|
||||
import { logger } from "../lib/logger.js";
|
||||
import { dispatchWebhooks } from "../lib/webhook-dispatcher.js";
|
||||
import { emitAllocationCreated, emitAllocationUpdated, emitNotificationCreated } from "../sse/event-bus.js";
|
||||
|
||||
export function runAllocationBackgroundEffect(
|
||||
effectName: string,
|
||||
execute: () => unknown,
|
||||
metadata: Record<string, unknown> = {},
|
||||
): void {
|
||||
void Promise.resolve()
|
||||
.then(execute)
|
||||
.catch((error) => {
|
||||
logger.error(
|
||||
{ err: error, effectName, ...metadata },
|
||||
"Allocation background side effect failed",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function invalidateDashboardCacheInBackground(): void {
|
||||
runAllocationBackgroundEffect("invalidateDashboardCache", () => invalidateDashboardCache());
|
||||
}
|
||||
|
||||
export function checkBudgetThresholdsInBackground(
|
||||
db: import("@capakraken/db").PrismaClient,
|
||||
projectId: string,
|
||||
): void {
|
||||
runAllocationBackgroundEffect(
|
||||
"checkBudgetThresholds",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
() => checkBudgetThresholds(db as any, projectId),
|
||||
{ projectId },
|
||||
);
|
||||
}
|
||||
|
||||
export function dispatchAllocationWebhookInBackground(
|
||||
db: import("@capakraken/db").PrismaClient,
|
||||
event: string,
|
||||
payload: Record<string, unknown>,
|
||||
): void {
|
||||
runAllocationBackgroundEffect(
|
||||
"dispatchWebhooks",
|
||||
() => dispatchWebhooks(db, event, payload),
|
||||
{ event },
|
||||
);
|
||||
}
|
||||
|
||||
export function generateAutoSuggestionsInBackground(
|
||||
db: import("@capakraken/db").PrismaClient,
|
||||
demandRequirementId: string,
|
||||
): void {
|
||||
runAllocationBackgroundEffect(
|
||||
"generateAutoSuggestions",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
() => generateAutoSuggestions(db as any, demandRequirementId),
|
||||
{ demandRequirementId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function createDemandRequirementWithEffects(
|
||||
db: import("@capakraken/db").PrismaClient,
|
||||
input: z.infer<typeof CreateDemandRequirementSchema>,
|
||||
) {
|
||||
const demandRequirement = await db.$transaction(async (tx) => {
|
||||
return createDemandRequirement(
|
||||
tx as unknown as Parameters<typeof createDemandRequirement>[0],
|
||||
input,
|
||||
);
|
||||
});
|
||||
|
||||
emitAllocationCreated({
|
||||
id: demandRequirement.id,
|
||||
projectId: demandRequirement.projectId,
|
||||
resourceId: null,
|
||||
});
|
||||
invalidateDashboardCacheInBackground();
|
||||
|
||||
const [project, roleEntity, managers] = await Promise.all([
|
||||
db.project.findUnique({
|
||||
where: { id: demandRequirement.projectId },
|
||||
select: { name: true },
|
||||
}),
|
||||
demandRequirement.roleId
|
||||
? db.role.findUnique({
|
||||
where: { id: demandRequirement.roleId },
|
||||
select: { name: true },
|
||||
})
|
||||
: Promise.resolve(null),
|
||||
db.user.findMany({
|
||||
where: { systemRole: { in: ["ADMIN", "MANAGER"] } },
|
||||
select: { id: true },
|
||||
}),
|
||||
]);
|
||||
const roleName = roleEntity?.name ?? demandRequirement.role ?? "Unspecified role";
|
||||
const projectName = project?.name ?? "Unknown project";
|
||||
const headcount = demandRequirement.headcount ?? 1;
|
||||
|
||||
for (const manager of managers) {
|
||||
const task = await db.notification.create({
|
||||
data: {
|
||||
userId: manager.id,
|
||||
category: "TASK",
|
||||
type: "DEMAND_FILL",
|
||||
priority: "NORMAL",
|
||||
title: `Staff demand: ${roleName} for ${projectName}`,
|
||||
body: `${headcount} ${roleName} needed for project ${projectName}`,
|
||||
taskStatus: "OPEN",
|
||||
taskAction: buildTaskAction("fill_demand", demandRequirement.id),
|
||||
entityId: demandRequirement.id,
|
||||
entityType: "demand",
|
||||
link: `/projects/${demandRequirement.projectId}`,
|
||||
channel: "in_app",
|
||||
},
|
||||
});
|
||||
emitNotificationCreated(manager.id, task.id);
|
||||
}
|
||||
|
||||
checkBudgetThresholdsInBackground(db, demandRequirement.projectId);
|
||||
generateAutoSuggestionsInBackground(db, demandRequirement.id);
|
||||
|
||||
return demandRequirement;
|
||||
}
|
||||
|
||||
export async function fillDemandRequirementWithEffects(
|
||||
db: import("@capakraken/db").PrismaClient,
|
||||
input: z.infer<typeof FillDemandRequirementSchema>,
|
||||
) {
|
||||
const result = await fillDemandRequirement(db, input);
|
||||
|
||||
emitAllocationCreated({
|
||||
id: result.assignment.id,
|
||||
projectId: result.assignment.projectId,
|
||||
resourceId: result.assignment.resourceId,
|
||||
});
|
||||
|
||||
emitAllocationUpdated({
|
||||
id: result.updatedDemandRequirement.id,
|
||||
projectId: result.updatedDemandRequirement.projectId,
|
||||
resourceId: null,
|
||||
});
|
||||
invalidateDashboardCacheInBackground();
|
||||
checkBudgetThresholdsInBackground(db, result.assignment.projectId);
|
||||
|
||||
if (result.updatedDemandRequirement.headcount > 0
|
||||
&& result.updatedDemandRequirement.status !== "COMPLETED") {
|
||||
generateAutoSuggestionsInBackground(db, result.updatedDemandRequirement.id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user