fix(capacity): stabilize PTO flows and calendar consistency

Make PTO creation immediately approved, add PTO deletion, and ensure cache invalidation updates individual/team/revenue capacity consistently.

Harden holiday duplicate handling (422), support PTO-day availability overrides without disabling edits, and align tests plus OpenSpec artifacts with the new behavior.
This commit is contained in:
2026-02-19 22:47:39 -05:00
parent 0a9fdd248b
commit b821713cc7
21 changed files with 1081 additions and 128 deletions

View File

@@ -71,23 +71,28 @@ import type { Capacity, Holiday, PTO } from '$lib/types/capacity';
const iso = toIso(current);
const dayOfWeek = current.getDay();
const detail = detailsMap.get(iso);
const defaultAvailability = detail?.availability ?? (dayOfWeek % 6 === 0 ? 0 : 1);
const availability = overrides[iso] ?? defaultAvailability;
const effectiveHours = Math.round(availability * 8 * 10) / 10;
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
const isHoliday = holidayMap.has(iso);
const holidayName = holidayMap.get(iso);
const isPto = ptoDates.has(iso) || !!detail?.is_pto;
const isBlocked = isWeekend || isHoliday;
const fallbackAvailability = isWeekend ? 0 : isPto ? 0 : 1;
const sourceAvailability = overrides[iso] ?? detail?.availability ?? fallbackAvailability;
const availability = isPto ? sourceAvailability : (isBlocked ? 0 : sourceAvailability);
const effectiveHours = Math.round(availability * 8 * 10) / 10;
return {
iso,
day: i + 1,
dayName: weekdayLabels[dayOfWeek],
isWeekend: dayOfWeek === 0 || dayOfWeek === 6,
isWeekend,
isHoliday,
holidayName,
isPto: ptoDates.has(iso) || detail?.is_pto,
isPto,
isBlocked,
availability,
effectiveHours,
defaultAvailability
defaultAvailability: fallbackAvailability
};
});
@@ -147,12 +152,12 @@ import type { Capacity, Holiday, PTO } from '$lib/types/capacity';
<select
class="select select-sm mt-3"
aria-label={`Availability for ${day.iso}`}
value={day.availability}
disabled={day.isWeekend || day.isHoliday}
on:change={(event) => handleAvailabilityChange(day.iso, Number(event.currentTarget.value))}
>
<option value="1">Full day (1.0)</option>
<option value="0.5">Half day (0.5)</option>
<option value="0">Off (0.0)</option>
<option value="1" selected={day.availability === 1}>Full day (1.0)</option>
<option value="0.5" selected={day.availability === 0.5}>Half day (0.5)</option>
<option value="0" selected={day.availability === 0}>Off (0.0)</option>
</select>
</div>
{/each}