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:
82
.github/copilot-instructions.md
vendored
82
.github/copilot-instructions.md
vendored
@@ -129,7 +129,8 @@ Keep docs synced with code. When you change services/MQTT/API/UTC/env or dev/pro
|
||||
- Event model & API (new): Added `muted` (Boolean) for video events; create/update and GET endpoints accept, persist, and return `muted` alongside `autoplay`, `loop`, and `volume`.
|
||||
- Dashboard — Settings: Settings page refactored to nested tabs; added Events → Videos defaults (autoplay, loop, volume, mute) backed by system settings keys (`video_autoplay`, `video_loop`, `video_volume`, `video_muted`).
|
||||
- Dashboard — Events UI: CustomEventModal now exposes per-event video `muted` and initializes all video fields from system defaults when creating a new event.
|
||||
- Dashboard — Academic Calendar: Merged “School Holidays Import” and “List” into a single “📥 Import & Liste” tab; nested tab selection is persisted with controlled `selectedItem` state to avoid jumps.
|
||||
- Dashboard — Academic Calendar: Holiday management now uses a single “📥 Ferienkalender: Import/Anzeige” tab; admins select the target academic period first, and import/list content redraws for that period.
|
||||
- Dashboard — Holiday Management Hardening: The same tab now supports manual holiday CRUD in addition to CSV/TXT import. Imports and manual saves validate date ranges against the selected academic period, prevent duplicates, auto-merge same normalized name+region overlaps (including adjacent ranges), and report conflicting overlaps.
|
||||
|
||||
Note: these edits are intentionally backwards-compatible — if the probe fails, the scheduler still emits the stream URL and the client should fallback to a direct play attempt or request richer metadata when available.
|
||||
|
||||
@@ -201,15 +202,28 @@ Keep docs synced with code. When you change services/MQTT/API/UTC/env or dev/pro
|
||||
- Session usage: instantiate `Session()` per request, commit when mutating, and always `session.close()` before returning.
|
||||
- Examples:
|
||||
- Clients: `server/routes/clients.py` includes bulk group updates and MQTT sync (`publish_multiple_client_groups`).
|
||||
- Groups: `server/routes/groups.py` computes “alive” using a grace period that varies by `ENV`. - `GET /api/groups/order` — retrieve saved group display order
|
||||
- `POST /api/groups/order` — persist group display order (array of group IDs) - Events: `server/routes/events.py` serializes enum values to strings and normalizes times to UTC. Recurring events are only deactivated after their recurrence_end (UNTIL); non-recurring events deactivate after their end time. Event exceptions are respected and rendered in scheduler output.
|
||||
- Groups: `server/routes/groups.py` computes “alive” using a grace period that varies by `ENV`.
|
||||
- `GET /api/groups/order` — retrieve saved group display order
|
||||
- `POST /api/groups/order` — persist group display order (array of group IDs)
|
||||
- Events: `server/routes/events.py` serializes enum values to strings and normalizes times to UTC. Recurring events are only deactivated after their recurrence_end (UNTIL); non-recurring events deactivate after their end time. Event exceptions are respected and rendered in scheduler output.
|
||||
- Holidays: `server/routes/holidays.py` supports period-scoped list/import/manual CRUD (`GET/POST /api/holidays`, `POST /api/holidays/upload`, `PUT/DELETE /api/holidays/<id>`), validates date ranges against the target period, prevents duplicates, merges same normalized `name+region` overlaps (including adjacent ranges), and rejects conflicting overlaps.
|
||||
- Media: `server/routes/eventmedia.py` implements a simple file manager API rooted at `server/media/`.
|
||||
- System settings: `server/routes/system_settings.py` exposes key–value CRUD (`/api/system-settings`) and a convenience endpoint for WebUntis/Vertretungsplan supplement-table: `GET/POST /api/system-settings/supplement-table` (admin+).
|
||||
- Academic periods: `server/routes/academic_periods.py` exposes:
|
||||
- `GET /api/academic_periods` — list all periods
|
||||
- `GET /api/academic_periods/active` — currently active period
|
||||
- `POST /api/academic_periods/active` — set active period (deactivates others)
|
||||
- `GET /api/academic_periods/for_date?date=YYYY-MM-DD` — period covering given date
|
||||
- Academic periods: `server/routes/academic_periods.py` exposes full lifecycle management (admin+ only):
|
||||
- `GET /api/academic_periods` — list all non-archived periods ordered by start_date
|
||||
- `GET /api/academic_periods/<id>` — get single period by ID (including archived)
|
||||
- `GET /api/academic_periods/active` — get currently active period
|
||||
- `GET /api/academic_periods/for_date?date=YYYY-MM-DD` — period covering given date (non-archived)
|
||||
- `GET /api/academic_periods/<id>/usage` — check linked events/media and recurrence spillover blockers
|
||||
- `POST /api/academic_periods` — create period (validates name uniqueness among non-archived, date range, overlaps within periodType)
|
||||
- `PUT /api/academic_periods/<id>` — update period (cannot update archived periods)
|
||||
- `POST /api/academic_periods/<id>/activate` — activate period (deactivates all others; cannot activate archived)
|
||||
- `POST /api/academic_periods/<id>/archive` — soft-delete period (blocked if active or has active recurrence)
|
||||
- `POST /api/academic_periods/<id>/restore` — restore archived period (returns to inactive)
|
||||
- `DELETE /api/academic_periods/<id>` — hard-delete archived inactive period (blocked if linked events exist)
|
||||
- All responses use camelCase: `startDate`, `endDate`, `periodType`, `isActive`, `isArchived`, `archivedAt`, `archivedBy`
|
||||
- Validation: name required/trimmed/unique among non-archived; startDate ≤ endDate; periodType in {schuljahr, semester, trimester}; overlaps blocked within same periodType
|
||||
- Recurrence spillover detection: archive/delete blocked if recurring master events assigned to period still have current/future occurrences
|
||||
- User management: `server/routes/users.py` exposes comprehensive CRUD for users (admin+):
|
||||
- `GET /api/users` — list all users (role-filtered: admin sees user/editor/admin, superadmin sees all); includes audit fields in camelCase (lastLoginAt, lastPasswordChangeAt, failedLoginAttempts, deactivatedAt, deactivatedBy)
|
||||
- `POST /api/users` — create user with username, password (min 6 chars), role, and status; admin cannot create superadmin; initializes audit fields
|
||||
@@ -228,7 +242,7 @@ Keep docs synced with code. When you change services/MQTT/API/UTC/env or dev/pro
|
||||
Documentation maintenance: keep this file aligned with real patterns; update when routes/session/UTC rules change. Avoid long prose; link exact paths.
|
||||
|
||||
## Frontend patterns (dashboard)
|
||||
- **UI design rules**: Component choices, layout structure, button variants, badge colors, dialog patterns, toast conventions, and tab structure are defined in [`FRONTEND_DESIGN_RULES.md`](../FRONTEND_DESIGN_RULES.md). Follow that file for all dashboard work.
|
||||
- **UI design rules**: Component choices, layout structure, button variants, badge colors, dialog patterns, toast conventions, and tab structure are defined in [`FRONTEND_DESIGN_RULES.md`](./FRONTEND_DESIGN_RULES.md). Follow that file for all dashboard work.
|
||||
- Vite React app; proxies `/api` and `/screenshots` to API in dev (`vite.config.ts`).
|
||||
- Uses Syncfusion components; Vite config pre-bundles specific packages to avoid alias issues.
|
||||
- Environment: `VITE_API_URL` provided at build/run; in dev compose, proxy handles `/api` so local fetches can use relative `/api/...` paths.
|
||||
@@ -292,8 +306,20 @@ Keep docs synced with code. When you change services/MQTT/API/UTC/env or dev/pro
|
||||
- Settings page (`dashboard/src/settings.tsx`):
|
||||
- Structure: Syncfusion TabComponent with role-gated tabs
|
||||
- 📅 Academic Calendar (all users)
|
||||
- 📥 Import & Liste: CSV/TXT import and list combined
|
||||
- 🗂️ Perioden: select and set active period (uses `/api/academic_periods` routes)
|
||||
- **🗂️ Perioden (first sub-tab)**: Full period lifecycle management (admin+)
|
||||
- List non-archived periods with active/archived badges and action buttons
|
||||
- Create: dialog for name, displayName, startDate, endDate, periodType with validation
|
||||
- Edit: update name, displayName, dates, type (cannot edit archived)
|
||||
- Activate: set as active (deactivates all others)
|
||||
- Archive: soft-delete with blocker checks (blocks if active or has active recurrence)
|
||||
- Restore: restore archived periods to inactive state
|
||||
- Delete: hard-delete archived periods with blocker checks (blocks if linked events)
|
||||
- Archive visibility: toggle to show/hide archived periods
|
||||
- Blockers: display prevents action with clear list of reasons (linked events, active recurrence, active status)
|
||||
- **📥 Ferienkalender: Import/Anzeige (second sub-tab)**: CSV/TXT holiday import plus manual holiday create/edit/delete scoped to the selected academic period; changing the period redraws the import/list body.
|
||||
- Import summary surfaces inserted/updated/merged/skipped/conflict counts and detailed conflict lines.
|
||||
- File selection uses Syncfusion-styled trigger button and visible selected filename state.
|
||||
- Manual date inputs guide users with bidirectional start/end constraints and prefill behavior.
|
||||
- 🖥️ Display & Clients (admin+)
|
||||
- Default Settings: placeholders for heartbeat, screenshots, defaults
|
||||
- Client Configuration: quick links to Clients and Groups pages
|
||||
@@ -308,8 +334,9 @@ Keep docs synced with code. When you change services/MQTT/API/UTC/env or dev/pro
|
||||
- ⚙️ System (superadmin)
|
||||
- Organization Info and Advanced Configuration placeholders
|
||||
- Role gating: Admin/Superadmin tabs are hidden if the user lacks permission; System is superadmin-only
|
||||
- API clients use relative `/api/...` URLs so Vite dev proxy handles requests without CORS issues. The settings UI calls are centralized in `dashboard/src/apiSystemSettings.ts`.
|
||||
- API clients use relative `/api/...` URLs so Vite dev proxy handles requests without CORS issues. The settings UI calls are centralized in `dashboard/src/apiSystemSettings.ts` (system settings) and `dashboard/src/apiAcademicPeriods.ts` (periods CRUD).
|
||||
- Nested tabs: implemented as controlled components using `selectedItem` with stateful handlers to prevent sub-tab resets during updates.
|
||||
- Academic periods API client (`dashboard/src/apiAcademicPeriods.ts`): provides type-safe camelCase accessors (listAcademicPeriods, getAcademicPeriod, createAcademicPeriod, updateAcademicPeriod, setActiveAcademicPeriod, archiveAcademicPeriod, restoreAcademicPeriod, getAcademicPeriodUsage, deleteAcademicPeriod).
|
||||
|
||||
- Dashboard page (`dashboard/src/dashboard.tsx`):
|
||||
- Card-based overview of all Raumgruppen (room groups) with real-time status monitoring
|
||||
@@ -401,7 +428,10 @@ Docs maintenance guardrails (solo-friendly): Update this file alongside code cha
|
||||
### Recurrence & holidays: conventions
|
||||
- Do not pre-expand recurrences on the backend. Always send master events with `RecurrenceRule` + `RecurrenceException`.
|
||||
- Ensure EXDATE tokens are RFC 5545 timestamps (`yyyyMMddTHHmmssZ`) matching the occurrence start time (UTC) so Syncfusion can exclude them natively.
|
||||
- When `skip_holidays` or recurrence changes, regenerate `EventException` rows so `RecurrenceException` stays in sync.
|
||||
- School holidays are scoped by `academic_period_id`; holiday imports and queries should use the relevant academic period rather than treating holiday rows as global.
|
||||
- Holiday write operations (manual/import) must validate date ranges against the selected academic period.
|
||||
- Overlap policy: same normalized `name+region` overlaps (including adjacent ranges) are merged; overlaps with different identity are conflicts (manual blocked, import skipped with details).
|
||||
- When `skip_holidays` or recurrence changes, regenerate `EventException` rows so `RecurrenceException` stays in sync, using the event's `academic_period_id` holidays (or only unassigned holidays for legacy events without a period).
|
||||
- Single occurrence detach: Use `POST /api/events/<id>/occurrences/<date>/detach` to create standalone events and add EXDATE entries without modifying master events. The frontend persists edits via `actionComplete` (`requestType='eventChanged'`).
|
||||
|
||||
## Quick examples
|
||||
@@ -422,11 +452,29 @@ Docs maintenance guardrails (solo-friendly): Update this file alongside code cha
|
||||
Questions or unclear areas? Tell us if you need: exact devcontainer debugging steps, stricter Alembic workflow, or a seed dataset beyond `init_defaults.py`.
|
||||
|
||||
## Academic Periods System
|
||||
- **Purpose**: Organize events and media by educational cycles (school years, semesters, trimesters).
|
||||
- **Purpose**: Organize events and media by educational cycles (school years, semesters, trimesters) with full lifecycle management.
|
||||
- **Design**: Fully backward compatible - existing events/media continue to work without period assignment.
|
||||
- **Usage**: New events/media can optionally reference `academic_period_id` for better organization and filtering.
|
||||
- **Constraints**: Only one period can be active at a time; use `init_academic_periods.py` for Austrian school year setup.
|
||||
- **UI Integration**: The dashboard highlights the currently selected period and whether a holiday plan exists within that date range. Holiday linkage currently uses date overlap with `school_holidays`; an explicit `academic_period_id` on `school_holidays` can be added later if tighter association is required.
|
||||
- **Lifecycle States**:
|
||||
- Active: exactly one period at a time (all others deactivated when activated)
|
||||
- Inactive: saved period, not currently active
|
||||
- Archived: soft-deleted; hidden from normal list; can be restored
|
||||
- Deleted: hard-deleted; permanent removal (only when no linked events exist and no active recurrence)
|
||||
- **Archive Rules**: Cannot archive active periods or periods with recurring master events that have current/future occurrences
|
||||
- **Delete Rules**: Only archived inactive periods can be hard-deleted; blocked if linked events exist
|
||||
- **Validation Rules**:
|
||||
- Name: required, trimmed, unique among non-archived periods
|
||||
- Dates: startDate ≤ endDate
|
||||
- Type: schuljahr, semester, or trimester
|
||||
- Overlaps: disallowed within same periodType (allowed across types)
|
||||
- **Recurrence Spillover Detection**: Archive/delete blocked if recurring master events assigned to period still generate current/future occurrences
|
||||
- **Model Fields**: `id`, `name`, `display_name`, `start_date`, `end_date`, `period_type`, `is_active`, `is_archived`, `archived_at`, `archived_by`, `created_at`, `updated_at`
|
||||
- **Events/Media Association**: Both `Event` and `EventMedia` have optional `academic_period_id` FK for organizational grouping
|
||||
- **UI Integration** (`dashboard/src/settings.tsx` > 🗂️ Perioden):
|
||||
- List with badges (Active/Archived)
|
||||
- Create/Edit dialogs with validation
|
||||
- Activate, Archive, Restore, Delete actions with blocker preflight checks
|
||||
- Archive visibility toggle to show/hide retired periods
|
||||
- Error dialogs showing exact blockers (linked events, active recurrence, active status)
|
||||
|
||||
## Changelog Style Guide (Program info)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user