0e9b20ec37
Three strands of work that grew together across the same settings screen and
API client, so they land as one change.
Per-year preschool planning days ("studiedagar"): new table, repository
(SQLite + Postgres), service, router and migration 007, plus the hooks and
Settings UI to plan and record them per child and year. Allocation now
reserves the recorded figure for a year where one exists and only falls back
to the child-level estimate otherwise, marking that fallback as assumed.
Partner invitations: workspaces issue and list invitations, registration
accepts an invite token to join the inviter's workspace instead of creating a
personal one, and a /join/:token page plus the settings invite panel drive it.
Vacation forfeiture: tips and the dashboard now distinguish days a parent
genuinely loses from days that merely age, so the warning stops firing on
balances that were never at risk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""Add preschool_planning_days: per-year record of "studiedagar" taken.
|
|
|
|
`Child.preschool_planning_days_per_year` (see `app/entities/child.py`) is a
|
|
single default count of annual preschool closure/planning days used by
|
|
`app/services/allocation_service.py::_preschool_reserve_days`, which
|
|
multiplies it by the number of years in the planning horizon and subtracts
|
|
the lump sum from both parents' parental-leave pools. There was never a
|
|
per-year record of the true count, nor of how many were actually taken.
|
|
|
|
This table adds that per-year record: `planned_days` (may differ from the
|
|
child's default for a specific year) and `taken_days` (nullable — `NULL`
|
|
means "not recorded yet", distinct from "recorded as zero", which is the
|
|
whole point of the feature). A missing row for a (child, year) still falls
|
|
back to `child.preschool_planning_days_per_year` at the service layer (see
|
|
`app/services/preschool_planning_days_service.py`); wiring the allocation
|
|
engine to consume these rows is a follow-up, not part of this migration.
|
|
|
|
A plain `CREATE TABLE` suffices — nothing existing changes, and there is
|
|
nothing to backfill: before this migration no per-year figure could be
|
|
recorded, so the correct history is "none".
|
|
|
|
Must stay byte-for-byte equivalent to the `preschool_planning_days` table in
|
|
`app/db/connection.py`'s `_FULL_SCHEMA` —
|
|
`tests/integration/test_schema_parity.py` asserts exactly that.
|
|
|
|
Revision ID: 007
|
|
Revises: 006
|
|
Create Date: 2026-09-20
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "007"
|
|
down_revision: str | None = "006"
|
|
branch_labels: str | None = None
|
|
depends_on: str | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS preschool_planning_days (
|
|
id TEXT PRIMARY KEY,
|
|
child_id TEXT NOT NULL,
|
|
year INTEGER NOT NULL,
|
|
planned_days INTEGER NOT NULL,
|
|
taken_days INTEGER,
|
|
created_at TIMESTAMP NOT NULL,
|
|
updated_at TIMESTAMP NOT NULL,
|
|
UNIQUE(child_id, year),
|
|
FOREIGN KEY (child_id) REFERENCES child(id) ON DELETE CASCADE
|
|
)
|
|
"""
|
|
)
|
|
op.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_preschool_planning_days_child
|
|
ON preschool_planning_days (child_id)
|
|
"""
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute("DROP INDEX IF EXISTS idx_preschool_planning_days_child")
|
|
op.execute("DROP TABLE IF EXISTS preschool_planning_days")
|