feat: period-scoped holiday management, archive lifecycle, and docs/release sync
- add period-scoped holiday architecture end-to-end - model: scope `SchoolHoliday` to `academic_period_id` - migrations: add holiday-period scoping, academic-period archive lifecycle, and merge migration head - API: extend holidays with manual CRUD, period validation, duplicate prevention, and overlap merge/conflict handling - recurrence: regenerate holiday exceptions using period-scoped holiday sets - improve frontend settings and holiday workflows - bind holiday import/list/manual CRUD to selected academic period - show detailed import outcomes (inserted/updated/merged/skipped/conflicts) - fix file-picker UX (visible selected filename) - align settings controls/dialogs with defined frontend design rules - scope appointments/dashboard holiday loading to active period - add shared date formatting utility - strengthen academic period lifecycle handling - add archive/restore/delete flow and backend validations/blocker checks - extend API client support for lifecycle operations - release/docs updates and cleanup - bump user-facing version to `2026.1.0-alpha.15` with new changelog entry - add tech changelog entry for alpha.15 backend changes - refactor README to concise index and archive historical implementation docs - fix Copilot instruction link diagnostics via local `.github` design-rules reference
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"""Add archive lifecycle fields to academic_periods
|
||||
|
||||
Revision ID: a7b8c9d0e1f2
|
||||
Revises: 910951fd300a
|
||||
Create Date: 2026-03-31 00:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'a7b8c9d0e1f2'
|
||||
down_revision: Union[str, None] = '910951fd300a'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Add archive lifecycle fields to academic_periods table
|
||||
op.add_column('academic_periods', sa.Column('is_archived', sa.Boolean(), nullable=False, server_default='0'))
|
||||
op.add_column('academic_periods', sa.Column('archived_at', sa.TIMESTAMP(timezone=True), nullable=True))
|
||||
op.add_column('academic_periods', sa.Column('archived_by', sa.Integer(), nullable=True))
|
||||
|
||||
# Add foreign key for archived_by
|
||||
op.create_foreign_key(
|
||||
'fk_academic_periods_archived_by_users_id',
|
||||
'academic_periods',
|
||||
'users',
|
||||
['archived_by'],
|
||||
['id'],
|
||||
ondelete='SET NULL'
|
||||
)
|
||||
|
||||
# Add indexes for performance
|
||||
op.create_index('ix_academic_periods_archived', 'academic_periods', ['is_archived'])
|
||||
op.create_index('ix_academic_periods_name_not_archived', 'academic_periods', ['name', 'is_archived'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Drop indexes
|
||||
op.drop_index('ix_academic_periods_name_not_archived', 'academic_periods')
|
||||
op.drop_index('ix_academic_periods_archived', 'academic_periods')
|
||||
|
||||
# Drop foreign key
|
||||
op.drop_constraint('fk_academic_periods_archived_by_users_id', 'academic_periods')
|
||||
|
||||
# Drop columns
|
||||
op.drop_column('academic_periods', 'archived_by')
|
||||
op.drop_column('academic_periods', 'archived_at')
|
||||
op.drop_column('academic_periods', 'is_archived')
|
||||
@@ -0,0 +1,28 @@
|
||||
"""merge academic periods and client monitoring heads
|
||||
|
||||
Revision ID: dd100f3958dc
|
||||
Revises: a7b8c9d0e1f2, c1d2e3f4g5h6
|
||||
Create Date: 2026-03-31 07:55:09.999917
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'dd100f3958dc'
|
||||
down_revision: Union[str, None] = ('a7b8c9d0e1f2', 'c1d2e3f4g5h6')
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
pass
|
||||
@@ -0,0 +1,54 @@
|
||||
"""scope school holidays to academic periods
|
||||
|
||||
Revision ID: f3c4d5e6a7b8
|
||||
Revises: dd100f3958dc
|
||||
Create Date: 2026-03-31 12:20:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'f3c4d5e6a7b8'
|
||||
down_revision: Union[str, None] = 'dd100f3958dc'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column('school_holidays', sa.Column('academic_period_id', sa.Integer(), nullable=True))
|
||||
op.create_index(
|
||||
op.f('ix_school_holidays_academic_period_id'),
|
||||
'school_holidays',
|
||||
['academic_period_id'],
|
||||
unique=False,
|
||||
)
|
||||
op.create_foreign_key(
|
||||
'fk_school_holidays_academic_period_id',
|
||||
'school_holidays',
|
||||
'academic_periods',
|
||||
['academic_period_id'],
|
||||
['id'],
|
||||
ondelete='SET NULL',
|
||||
)
|
||||
op.drop_constraint('uq_school_holidays_unique', 'school_holidays', type_='unique')
|
||||
op.create_unique_constraint(
|
||||
'uq_school_holidays_unique',
|
||||
'school_holidays',
|
||||
['name', 'start_date', 'end_date', 'region', 'academic_period_id'],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint('uq_school_holidays_unique', 'school_holidays', type_='unique')
|
||||
op.create_unique_constraint(
|
||||
'uq_school_holidays_unique',
|
||||
'school_holidays',
|
||||
['name', 'start_date', 'end_date', 'region'],
|
||||
)
|
||||
op.drop_constraint('fk_school_holidays_academic_period_id', 'school_holidays', type_='foreignkey')
|
||||
op.drop_index(op.f('ix_school_holidays_academic_period_id'), table_name='school_holidays')
|
||||
op.drop_column('school_holidays', 'academic_period_id')
|
||||
Reference in New Issue
Block a user