Spend vacation on the days worth having, and name the ones about to expire
The planner knew exactly how many semesterdagar a parent was about to forfeit and never placed a single day to prevent it. It also had no weekend-wrap logic at all: REASON_VACATION_WRAP_WEEKEND was stamped on every tier-3 interior vacation day, including a lone Tuesday in February. Two changes. Spend better. A workday adjacent to a free stretch, or in the middle of a gap of three or fewer fenced by free days, now takes vacation -- _vacation_bridge_reason, consulted for every parent type. This departs from spec 6.1's "wrapping suppressed for hourly": the FP-preservation argument is salary-type independent, only the pay argument was hourly-moot. In a both-parents window, both now take vacation on such a date rather than one paying with FP. After funding, reallocate_vacation_by_value swaps vacation onto higher-ranked dates 1-for-1 with FP days, so which dates hold vacation improves while how many never drops -- greedy first, re-timed after, never withheld. An invariant test asserts the count can only rise. Name the losses. A new pass proposes concrete dates for vacation that would otherwise expire, current year only, ranked red-day bridge then weekend bridge then interior, later dates first. They ride on PlanProposal.rescue_days, kept off `days` so nothing downstream commits them, and surface on the Dashboard with accept-one, accept-all (one batched /api/plan/apply, not N) and dismiss. Dismissal reuses the tip_dismissal table, keyed on (parent, year) identity rather than the group's dates -- those are recomputed from the whole plan's residual on every propose, so date-hashing made dismissal self-defeating. Dismiss does not bump the plan version; it changes no data and must not trigger a write. Measured on the seeded demo: 12 forfeiting days for one parent now yield 12 proposed dates, all Fridays or Mondays. Total vacation placed is unchanged at 203 days, confirming the reallocation only re-times. Three defects here were found by driving a browser, while 2273 unit tests passed -- all three lived in the seam between layers that unit tests mock away. Adds API-level tests over the real request sequence. Rate limiting is now off in tests (one file consumed the whole registration budget) with the suite's first real 429 assertion. PROPOSE_VS_SYNC_BUG.md documents a pre-existing, unfixed issue found along the way: Dashboard figures swing on the first sync with no user action. Backend 946 tests, frontend 1341, coverage above all gates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZaX6xbuMEMXuyKA1fq2Mm
This commit is contained in:
Vendored
+14
@@ -29,6 +29,7 @@ import type { useChildSettings } from '@/hooks/useChildSettings'
|
||||
import type { useCalendarDays } from '@/hooks/useCalendarDays'
|
||||
import type { usePreschoolPlanningDays } from '@/hooks/usePreschoolPlanningDays'
|
||||
import type { usePreschoolPlanningDaysList } from '@/hooks/usePreschoolPlanningDaysList'
|
||||
import type { useRescueDayActions } from '@/hooks/useRescueDayActions'
|
||||
import type { useShowWeekNumbers } from '@/hooks/useCalendarPreferences'
|
||||
import { makeChild, makePersonalData, makeVacationDays, makeWorkspace, makeParentalLeaveFetch } from './mockData'
|
||||
import { makeWorkspaceOverview } from './analytics'
|
||||
@@ -296,6 +297,19 @@ export function makePreschoolPlanningDaysResult(
|
||||
}
|
||||
}
|
||||
|
||||
export function makeRescueDayActionsResult(
|
||||
overrides: Partial<ReturnType<typeof useRescueDayActions>> = {},
|
||||
): ReturnType<typeof useRescueDayActions> {
|
||||
return {
|
||||
isLoading: false,
|
||||
error: null,
|
||||
acceptDay: vi.fn(),
|
||||
acceptAll: vi.fn(),
|
||||
dismiss: vi.fn(),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
export function makePreschoolPlanningDaysListResult(
|
||||
overrides: Partial<ReturnType<typeof usePreschoolPlanningDaysList>> = {},
|
||||
): ReturnType<typeof usePreschoolPlanningDaysList> {
|
||||
|
||||
Vendored
+17
@@ -19,6 +19,7 @@ import type {
|
||||
ProposedDayApplyItem,
|
||||
Rejection,
|
||||
ResourceShortage,
|
||||
RescueDay,
|
||||
ShortfallRemedy,
|
||||
SurplusAdvice,
|
||||
VacationBalance,
|
||||
@@ -141,6 +142,21 @@ export function makePlanAge4Projection(overrides: Partial<PlanAge4Projection> =
|
||||
}
|
||||
}
|
||||
|
||||
/** §vacation-rescue — one suggest-only concrete date (see `RescueDay` in
|
||||
* src/types/entities.ts). Defaults to a date within `makePlanProposal`'s
|
||||
* implicit current-year assumption for tests that don't care about the
|
||||
* exact date. */
|
||||
export function makeRescueDay(overrides: Partial<RescueDay> = {}): RescueDay {
|
||||
return {
|
||||
date: '2026-12-28',
|
||||
parentPersonalDataUuid: 'pd-a',
|
||||
childId: 'child-1',
|
||||
year: 2026,
|
||||
reason: 'vacation_rescue',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
export function makePlanProposal(overrides: Partial<PlanProposal> = {}): PlanProposal {
|
||||
return {
|
||||
days: [makeProposedDay()],
|
||||
@@ -150,6 +166,7 @@ export function makePlanProposal(overrides: Partial<PlanProposal> = {}): PlanPro
|
||||
vacationBalances: [],
|
||||
surplusAdvice: [],
|
||||
age4Projections: [],
|
||||
rescueDays: [],
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user