Files
CapaKraken/packages/application/src/use-cases/dashboard/get-chargeability-overview.ts
T
Hartmut cd78f72f33 chore: full technical rename planarchy → capakraken
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>
2026-03-27 13:18:09 +01:00

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")}`,
};
}