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:
@@ -127,7 +127,7 @@ async def get_context(
|
||||
children = await child_repo.list_by_workspace(pd.workspace_id)
|
||||
workspace = await ws_repo.get_by_id(pd.workspace_id)
|
||||
if workspace is None:
|
||||
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, "No workspace found")
|
||||
raise HTTPException(status.HTTP_422_UNPROCESSABLE_CONTENT, "No workspace found")
|
||||
return _UserContext(
|
||||
personal_data_uuid=str(pd.uuid),
|
||||
workspace_id=str(pd.workspace_id),
|
||||
|
||||
@@ -19,6 +19,7 @@ from app.models.plan import (
|
||||
PlanSyncResponse,
|
||||
PlanSyncWithWishRequest,
|
||||
PlanSyncWithWishResponse,
|
||||
RescueDayDismissRequest,
|
||||
)
|
||||
from app.utils.errors import NotFoundError, ValidationError
|
||||
|
||||
@@ -149,3 +150,27 @@ async def sync_plan_with_wish(
|
||||
except ValidationError as exc:
|
||||
raise HTTPException(status.HTTP_422_UNPROCESSABLE_CONTENT, str(exc)) from exc
|
||||
return PlanSyncWithWishResponse.from_domain(result)
|
||||
|
||||
|
||||
@router.post("/rescue-days/dismiss", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def dismiss_rescue_days(
|
||||
payload: RescueDayDismissRequest,
|
||||
service: PlanningEngineServiceDep,
|
||||
scope: WorkspaceScopeDep,
|
||||
) -> None:
|
||||
"""Dismiss a suggested vacation-rescue group (§vacation-rescue) so it
|
||||
stops appearing in `/propose`/`/sync` — until the underlying dates
|
||||
change, at which point it resurfaces (see
|
||||
`PlanningEngineService.dismiss_rescue_days`).
|
||||
"""
|
||||
scope.check_workspace(payload.workspace_id)
|
||||
# The parent must belong to the caller's own workspace too — otherwise a
|
||||
# foreign parent_personal_data_uuid could dismiss (or probe for) another
|
||||
# tenant's rescue-day suggestion even though workspace_id itself checks out.
|
||||
await scope.check_member(payload.parent_personal_data_uuid)
|
||||
try:
|
||||
await service.dismiss_rescue_days(
|
||||
payload.workspace_id, payload.parent_personal_data_uuid, payload.year
|
||||
)
|
||||
except NotFoundError as exc:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc
|
||||
|
||||
Reference in New Issue
Block a user