157 lines
5.0 KiB
TypeScript
157 lines
5.0 KiB
TypeScript
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;
|
|
}
|