Merge branch 'vacation-rescue-and-wrap'

Vacation rescue/wrap in allocation_service, new plan models/endpoints,
VacationRescueTip, useRescueDayActions, vacationSwaps util, sv+en i18n,
Dashboard/Calendar wiring.

Conflicts came from main's "day assertions" restructure and the earlier
merges; both intents kept:

- plan.py: both import sets; main's sync-with-wish handler kept next to the
  branch's new /rescue-days/dismiss endpoint; HTTP_422_UNPROCESSABLE_CONTENT
  applied to the branch's code (and a survivor in auth.py).
- Accepting a rescue day now goes through POST /api/day-assertions (an
  ad-hoc 'vacation' assertion, no wish) instead of /plan/apply with an
  `is_pinned` flag that no longer exists: on main a pin IS a day assertion
  (see PlanningService.add_planned_day's docstring), and an assertion is
  what keeps a later sync from retyping the day. useRescueDayActions,
  its tests, VacationRescueTip's tests and the backend integration flow
  follow that contract; applyPlan stays as a client for the live endpoint
  minus the dropped field. The hook also gets the security branch's
  useLatestRequest guard with an unconditionally cleared busy flag.
- Calendar.tsx: `locale` threaded into main's applyWishSyncResult call of
  buildSyncToastMessage (the branch's new parameter); the branch's
  swap-visibility toast tests drive the preset path through syncWithWish,
  which is how main saves a wish now.
- types/api.ts: ProposedDayApplyItem kept (used by applyPlan) without
  isPinned; the Add/UpdatePlannedDayRequest types stay removed as on main.
- fixtures/plan.ts: ResourceShortage import dropped (type removed on main).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4wjgbVL4HAC5Rdaw75VbV
This commit is contained in:
2026-09-21 07:48:33 +02:00
42 changed files with 5531 additions and 103 deletions
+6
View File
@@ -95,6 +95,12 @@ export const calendar = {
'{name} ({start}–{end}): only {daysFunded} of {daysWished} days funded — {shortfallDays} {shortfallDays|day|days} short',
'calendar.toastRedDayShift':
'{name}: moved to {start} – {end} to catch {days} extra free {days|day|days}',
// Reallocation pass — `reallocate_vacation_by_value` swaps vacation onto
// better dates 1-for-1 with parental leave. `{from}`/`{to}` already carry
// the period name plus its dates (e.g. "Summer (Jun 20, Jun 21)"), or the
// dates alone when no single period can be named — see vacationSwaps.ts.
'calendar.toastVacationSwap':
'{count} {count|vacation day|vacation days} moved from {from} to {to} — replaced with parental leave',
'calendar.undoButton': 'Undo',
'calendar.undoError': 'Failed to undo the change',
'calendar.dismissToastAria': 'Dismiss the message',