chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import type { WeekdayAvailability } from "@planarchy/shared";
|
||||
|
||||
export interface ChargeabilityAllocation {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
hoursPerDay: number;
|
||||
}
|
||||
|
||||
export interface ChargeabilityResult {
|
||||
availableHours: number;
|
||||
bookedHours: number;
|
||||
chargeability: number; // 0-100, rounded
|
||||
}
|
||||
|
||||
// Maps JS getDay() (0=Sun..6=Sat) to WeekdayAvailability keys
|
||||
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
|
||||
"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday",
|
||||
];
|
||||
|
||||
/** Count working hours a resource has available in [start, end] based on their schedule. */
|
||||
export function computeAvailableHours(
|
||||
availability: WeekdayAvailability,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): number {
|
||||
let hours = 0;
|
||||
const cur = new Date(start);
|
||||
cur.setHours(0, 0, 0, 0);
|
||||
const endNorm = new Date(end);
|
||||
endNorm.setHours(0, 0, 0, 0);
|
||||
while (cur <= endNorm) {
|
||||
const key = DAY_KEYS[cur.getDay()];
|
||||
hours += key ? (availability[key] ?? 0) : 0;
|
||||
cur.setDate(cur.getDate() + 1);
|
||||
}
|
||||
return hours;
|
||||
}
|
||||
|
||||
/** Count booked hours from allocations overlapping [start, end], working days only. */
|
||||
export function computeBookedHours(
|
||||
availability: WeekdayAvailability,
|
||||
allocations: ChargeabilityAllocation[],
|
||||
start: Date,
|
||||
end: Date,
|
||||
): number {
|
||||
let hours = 0;
|
||||
const startNorm = new Date(start); startNorm.setHours(0, 0, 0, 0);
|
||||
const endNorm = new Date(end); endNorm.setHours(0, 0, 0, 0);
|
||||
for (const alloc of allocations) {
|
||||
const aStart = new Date(alloc.startDate); aStart.setHours(0, 0, 0, 0);
|
||||
const aEnd = new Date(alloc.endDate); aEnd.setHours(0, 0, 0, 0);
|
||||
const overlapStart = aStart > startNorm ? aStart : startNorm;
|
||||
const overlapEnd = aEnd < endNorm ? aEnd : endNorm;
|
||||
if (overlapStart > overlapEnd) continue;
|
||||
const cur = new Date(overlapStart);
|
||||
while (cur <= overlapEnd) {
|
||||
const key = DAY_KEYS[cur.getDay()];
|
||||
if (key && (availability[key] ?? 0) > 0) {
|
||||
hours += alloc.hoursPerDay;
|
||||
}
|
||||
cur.setDate(cur.getDate() + 1);
|
||||
}
|
||||
}
|
||||
return hours;
|
||||
}
|
||||
|
||||
/** Compute chargeability metrics for a resource over a date range. */
|
||||
export function computeChargeability(
|
||||
availability: WeekdayAvailability,
|
||||
allocations: ChargeabilityAllocation[],
|
||||
start: Date,
|
||||
end: Date,
|
||||
): ChargeabilityResult {
|
||||
const availableHours = computeAvailableHours(availability, start, end);
|
||||
const bookedHours = computeBookedHours(availability, allocations, start, end);
|
||||
const chargeability = availableHours > 0
|
||||
? Math.min(100, Math.round((bookedHours / availableHours) * 100))
|
||||
: 0;
|
||||
return { availableHours, bookedHours, chargeability };
|
||||
}
|
||||
Reference in New Issue
Block a user