cd78f72f33
Complete rename of all technical identifiers across the codebase: Package names (11 packages): - @planarchy/* → @capakraken/* in all package.json, tsconfig, imports Import statements: 277 files, 548 occurrences replaced Database & Docker: - PostgreSQL user/db: planarchy → capakraken - Docker volumes: planarchy_pgdata → capakraken_pgdata - Connection strings updated in docker-compose, .env, CI CI/CD: - GitHub Actions workflow: all filter commands updated - Test database credentials updated Infrastructure: - Redis channel: planarchy:sse → capakraken:sse - Logger service name: planarchy-api → capakraken-api - Anonymization seed updated - Start/stop/restart scripts updated Test data: - Seed emails: @planarchy.dev → @capakraken.dev - E2E test credentials: all 11 spec files updated - Email defaults: @planarchy.app → @capakraken.app - localStorage keys: planarchy_* → capakraken_* Documentation: 30+ .md files updated Verification: - pnpm install: workspace resolution works - TypeScript: only pre-existing TS2589 (no new errors) - Engine: 310/310 tests pass - Staffing: 37/37 tests pass Co-Authored-By: claude-flow <ruv@ruv.net>
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
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")}`,
|
|
};
|
|
}
|