Add a plan sync endpoint that recomputes, persists and reports the delta
The calendar is becoming the plan: a user edits a period and the schedule updates immediately, with no separate propose and apply steps. POST /api/plan/sync does both and returns a per-date diff against a snapshot taken before any write, so the interface can say what actually moved rather than guessing from coarse counters. Pinned days now mean something. A day the user hand-edited keeps its type and its parent: the loader hides unpinned wish-attributed rows from the allocator so a re-plan is free to move them, while pinned rows stay visible and the existing occupancy check refuses to touch them. Their cost is debited off the pool before any window is funded, following the precedent already set for preschool planning days. Propose and apply are untouched. The allocator stays pure.
This commit is contained in:
@@ -20,6 +20,8 @@ from app.services.allocation_service import PlanProposal
|
||||
from app.services.planning_service import (
|
||||
DayCollision,
|
||||
PlanApplyResult,
|
||||
PlanChange,
|
||||
PlanSyncResult,
|
||||
ProposedDayInput,
|
||||
Rejection,
|
||||
)
|
||||
@@ -328,3 +330,61 @@ class PlanApplyResponse(BaseModel):
|
||||
rejections=[RejectionResponse.from_domain(r) for r in result.rejections],
|
||||
created_days=[PlannedLeaveDayResponse.from_entity(d) for d in result.created],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /api/plan/sync
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class PlanSyncRequest(BaseModel):
|
||||
workspace_id: UUID
|
||||
|
||||
|
||||
class PlanChangeResponse(BaseModel):
|
||||
"""One (child, parent, date) slot's delta from a `sync` call — see
|
||||
|
||||
`PlanChange`'s docstring for the `kind` vocabulary. Structured data only;
|
||||
the frontend composes the toast copy ("Sommar 2028 flyttades 2 dagar —
|
||||
Ångra") itself.
|
||||
"""
|
||||
|
||||
kind: str
|
||||
date: date
|
||||
parent_personal_data_uuid: UUID
|
||||
child_id: UUID
|
||||
old_day_type: str | None
|
||||
new_day_type: str | None
|
||||
old_is_dubbeldag: bool | None
|
||||
new_is_dubbeldag: bool | None
|
||||
old_wish_id: UUID | None
|
||||
new_wish_id: UUID | None
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, change: PlanChange) -> "PlanChangeResponse":
|
||||
return cls(
|
||||
kind=change.kind,
|
||||
date=change.date,
|
||||
parent_personal_data_uuid=change.parent_personal_data_uuid,
|
||||
child_id=change.child_id,
|
||||
old_day_type=change.old_day_type,
|
||||
new_day_type=change.new_day_type,
|
||||
old_is_dubbeldag=change.old_is_dubbeldag,
|
||||
new_is_dubbeldag=change.new_is_dubbeldag,
|
||||
old_wish_id=change.old_wish_id,
|
||||
new_wish_id=change.new_wish_id,
|
||||
)
|
||||
|
||||
|
||||
class PlanSyncResponse(BaseModel):
|
||||
proposal: PlanProposalResponse
|
||||
apply_result: PlanApplyResponse
|
||||
changes: list[PlanChangeResponse]
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, result: PlanSyncResult) -> "PlanSyncResponse":
|
||||
return cls(
|
||||
proposal=PlanProposalResponse.from_domain(result.proposal),
|
||||
apply_result=PlanApplyResponse.from_domain(result.apply_result),
|
||||
changes=[PlanChangeResponse.from_domain(c) for c in result.changes],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user