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:
@@ -73,15 +73,22 @@ class AcademicPeriod(Base):
|
||||
nullable=False, default=AcademicPeriodType.schuljahr)
|
||||
# nur eine aktive Periode zur Zeit
|
||||
is_active = Column(Boolean, default=False, nullable=False)
|
||||
# Archive lifecycle fields
|
||||
is_archived = Column(Boolean, default=False, nullable=False, index=True)
|
||||
archived_at = Column(TIMESTAMP(timezone=True), nullable=True)
|
||||
archived_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
|
||||
created_at = Column(TIMESTAMP(timezone=True),
|
||||
server_default=func.current_timestamp())
|
||||
updated_at = Column(TIMESTAMP(timezone=True), server_default=func.current_timestamp(
|
||||
), onupdate=func.current_timestamp())
|
||||
|
||||
# Constraint: nur eine aktive Periode zur Zeit
|
||||
# Constraint: nur eine aktive Periode zur Zeit; name unique among non-archived periods
|
||||
__table_args__ = (
|
||||
Index('ix_academic_periods_active', 'is_active'),
|
||||
UniqueConstraint('name', name='uq_academic_periods_name'),
|
||||
Index('ix_academic_periods_archived', 'is_archived'),
|
||||
# Unique constraint on active (non-archived) periods only is handled in code
|
||||
# This index facilitates the query for checking uniqueness
|
||||
Index('ix_academic_periods_name_not_archived', 'name', 'is_archived'),
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
@@ -93,6 +100,9 @@ class AcademicPeriod(Base):
|
||||
"end_date": self.end_date.isoformat() if self.end_date else None,
|
||||
"period_type": self.period_type.value if self.period_type else None,
|
||||
"is_active": self.is_active,
|
||||
"is_archived": self.is_archived,
|
||||
"archived_at": self.archived_at.isoformat() if self.archived_at else None,
|
||||
"archived_by": self.archived_by,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
@@ -276,6 +286,7 @@ class EventMedia(Base):
|
||||
class SchoolHoliday(Base):
|
||||
__tablename__ = 'school_holidays'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
academic_period_id = Column(Integer, ForeignKey('academic_periods.id', ondelete='SET NULL'), nullable=True, index=True)
|
||||
name = Column(String(150), nullable=False)
|
||||
start_date = Column(Date, nullable=False, index=True)
|
||||
end_date = Column(Date, nullable=False, index=True)
|
||||
@@ -284,14 +295,17 @@ class SchoolHoliday(Base):
|
||||
imported_at = Column(TIMESTAMP(timezone=True),
|
||||
server_default=func.current_timestamp())
|
||||
|
||||
academic_period = relationship("AcademicPeriod", foreign_keys=[academic_period_id])
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint('name', 'start_date', 'end_date',
|
||||
'region', name='uq_school_holidays_unique'),
|
||||
'region', 'academic_period_id', name='uq_school_holidays_unique'),
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"academic_period_id": self.academic_period_id,
|
||||
"name": self.name,
|
||||
"start_date": self.start_date.isoformat() if self.start_date else None,
|
||||
"end_date": self.end_date.isoformat() if self.end_date else None,
|
||||
|
||||
Reference in New Issue
Block a user