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 = {}, ): 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, ): 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, ) { const demandRequirement = await db.$transaction(async (tx) => { return createDemandRequirement( tx as unknown as Parameters[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, ) { 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; }