import type { PrismaClient } from "@capakraken/db"; import { computeChargeability } from "@capakraken/engine"; import type { WeekdayAvailability } from "@capakraken/shared"; import { isChargeabilityActualBooking, isChargeabilityRelevantProject, } from "../allocation/chargeability-bookings.js"; import { listAssignmentBookings } from "../allocation/list-assignment-bookings.js"; export interface GetDashboardChargeabilityOverviewInput { includeProposed?: boolean; topN: number; watchlistThreshold: number; countryIds?: string[]; departed?: boolean; now?: Date; } export async function getDashboardChargeabilityOverview( db: PrismaClient, input: GetDashboardChargeabilityOverviewInput, ) { const now = input.now ?? new Date(); const start = new Date(now.getFullYear(), now.getMonth(), 1); const end = new Date(now.getFullYear(), now.getMonth() + 1, 0); const resources = await db.resource.findMany({ where: { isActive: true, ...(input.countryIds && input.countryIds.length > 0 ? { countryId: { in: input.countryIds } } : {}), ...(input.departed !== undefined ? { departed: input.departed } : {}), }, select: { id: true, eid: true, displayName: true, chapter: true, countryId: true, departed: true, chargeabilityTarget: true, availability: true, }, }); const bookings = await listAssignmentBookings(db, { startDate: start, endDate: end, resourceIds: resources.map((resource) => resource.id), }); const stats = resources.map((resource) => { const availability = resource.availability as unknown as WeekdayAvailability; const resourceBookings = bookings.filter((booking) => booking.resourceId === resource.id); const actualAllocations = resourceBookings.filter((booking) => isChargeabilityActualBooking(booking, input.includeProposed === true), ); const expectedAllocations = resourceBookings.filter( (booking) => isChargeabilityRelevantProject(booking.project, true), ); const actual = computeChargeability( availability, actualAllocations, start, end, ); const expected = computeChargeability( availability, expectedAllocations, start, end, ); return { id: resource.id, eid: resource.eid, displayName: resource.displayName, chapter: resource.chapter, countryId: resource.countryId, departed: resource.departed, chargeabilityTarget: resource.chargeabilityTarget, actualChargeability: actual.chargeability, expectedChargeability: expected.chargeability, }; }); return { top: [...stats] .sort((left, right) => right.actualChargeability - left.actualChargeability), watchlist: [...stats] .filter( (resource) => resource.actualChargeability < resource.chargeabilityTarget - input.watchlistThreshold, ) .sort((left, right) => left.actualChargeability - right.actualChargeability), month: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`, }; }