Stundenplan: Wochenraster, Wochennavigation, Ferien/Feiertage (Kapitel 4.3)

Neuer Stundenplan mit "Heute"-Standardansicht (Tagesliste unten angedockt,
gruppenübergreifendes Wochenraster mit Fach/Klasse/Raum/Thema, Vor-/Zurück-
Navigation zwischen Kalenderwochen) und separatem Bearbeiten-Raster für die
wöchentliche Zuordnung. Badges für Ferien-/Klausur-Nähe und ausgegraute
Ferientage direkt im Plan statt einer separaten Liste. Ferien-/Feiertage-
Pflege (Bundesland, Schulferien) sitzt jetzt in den Einstellungen statt im
Stundenplan selbst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 02:29:18 +02:00
co-authored by Claude Sonnet 5
parent ee1a47641e
commit 99b2de6519
26 changed files with 2154 additions and 18 deletions
@@ -85,6 +85,19 @@ public interface ILessonRepository
void Save(Lesson lesson);
void Delete(Guid id);
}
public interface ITimetableSlotRepository
{
List<TimetableSlot> GetAll();
List<TimetableSlot> GetByGroup(Guid groupId);
void Save(TimetableSlot slot);
void Delete(Guid id);
}
public interface ISchoolHolidayRepository
{
List<SchoolHoliday> GetAll();
void Save(SchoolHoliday holiday);
void Delete(Guid id);
}
public interface IDocumentationRepository
{
List<Documentation> GetByStudent(Guid studentId);
+33
View File
@@ -110,6 +110,39 @@ public class AlternativeLessonPath
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
/// <summary>
/// Ein fester Termin im wöchentlichen Stundenplan (4.3): eine Lerngruppe trifft sich an einem
/// Wochentag zu einer bestimmten Stunde. Wiederkehrendes Muster, kein konkretes Datum — für
/// tatsächlich gehaltene Einzelstunden siehe <see cref="Lesson"/>. Pro Wochentag/Stunde ist
/// höchstens eine Gruppe eingetragen (ein Lehrer kann nicht gleichzeitig an zwei Orten sein).
/// </summary>
public class TimetableSlot
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid GroupId { get; set; }
public DayOfWeek Weekday { get; set; }
public int PeriodNumber { get; set; }
public string? Room { get; set; }
}
/// <summary>
/// Unterrichtsfreier Zeitraum (4.3.5): Schulferien werden manuell gepflegt (nicht algorithmisch
/// herleitbar, jährlich neu von den Bundesländern festgelegt). Gesetzliche Feiertage werden
/// dagegen berechnet (PublicHolidayService) und nicht in der Datenbank gespeichert.
/// </summary>
public class SchoolHoliday
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "";
public DateOnly StartDate { get; set; }
public DateOnly EndDate { get; set; }
}
public enum GermanState
{
BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH
}
/// <summary>
/// Zeugnisnote eines Schülers in einer Lerngruppe für einen Zeitraum (Halbjahr/Gesamtjahr).
/// <see cref="CalculatedValue"/> ist das zuletzt berechnete Ergebnis; <see cref="OverrideValue"/>
@@ -0,0 +1,79 @@
using LehrerApp.Core.Models;
namespace LehrerApp.Core.Services;
public record PublicHoliday(DateOnly Date, string Name);
/// <summary>
/// Berechnet die gesetzlichen Feiertage eines Bundeslands für ein Kalenderjahr (4.3.5).
/// Im Gegensatz zu Schulferien sind Feiertage über eine feste Regel je Bundesland herleitbar und
/// werden deshalb nicht in der Datenbank gespeichert.
/// </summary>
public class PublicHolidayService
{
public List<PublicHoliday> GetHolidays(int year, GermanState state)
{
var easterSunday = EasterSunday(year);
var holidays = new List<PublicHoliday>
{
new(new DateOnly(year, 1, 1), "Neujahr"),
new(easterSunday.AddDays(-2), "Karfreitag"),
new(easterSunday.AddDays(1), "Ostermontag"),
new(new DateOnly(year, 5, 1), "Tag der Arbeit"),
new(easterSunday.AddDays(39), "Christi Himmelfahrt"),
new(easterSunday.AddDays(50), "Pfingstmontag"),
new(new DateOnly(year, 10, 3), "Tag der Deutschen Einheit"),
new(new DateOnly(year, 12, 25), "1. Weihnachtstag"),
new(new DateOnly(year, 12, 26), "2. Weihnachtstag"),
};
if (state is GermanState.BW or GermanState.BY or GermanState.ST)
holidays.Add(new(new DateOnly(year, 1, 6), "Heilige Drei Könige"));
if (state is GermanState.BE)
holidays.Add(new(new DateOnly(year, 3, 8), "Internationaler Frauentag"));
if (state is GermanState.BW or GermanState.BY or GermanState.HE or GermanState.NW
or GermanState.RP or GermanState.SL)
holidays.Add(new(easterSunday.AddDays(60), "Fronleichnam"));
if (state is GermanState.SL)
holidays.Add(new(new DateOnly(year, 8, 15), "Mariä Himmelfahrt"));
if (state is GermanState.BB or GermanState.MV or GermanState.SN or GermanState.ST
or GermanState.TH or GermanState.HB or GermanState.HH or GermanState.NI
or GermanState.SH)
holidays.Add(new(new DateOnly(year, 10, 31), "Reformationstag"));
if (state is GermanState.BW or GermanState.BY or GermanState.NW or GermanState.RP
or GermanState.SL)
holidays.Add(new(new DateOnly(year, 11, 1), "Allerheiligen"));
if (state is GermanState.SN)
holidays.Add(new(BussUndBettag(year), "Buß- und Bettag"));
return holidays.OrderBy(h => h.Date).ToList();
}
/// Gauß'sche Osterformel.
private static DateOnly EasterSunday(int year)
{
int a = year % 19, b = year / 100, c = year % 100;
int d = b / 4, e = b % 4, f = (b + 8) / 25, g = (b - f + 1) / 3;
int h = (19 * a + b - d - g + 15) % 30;
int i = c / 4, k = c % 4, l = (32 + 2 * e + 2 * i - h - k) % 7;
int m = (a + 11 * h + 22 * l) / 451;
int month = (h + l - 7 * m + 114) / 31;
int day = (h + l - 7 * m + 114) % 31 + 1;
return new DateOnly(year, month, day);
}
/// Buß- und Bettag: Mittwoch vor dem 23. November (entspricht dem letzten Mittwoch vor dem
/// ersten Advent).
private static DateOnly BussUndBettag(int year)
{
var date = new DateOnly(year, 11, 23);
while (date.DayOfWeek != DayOfWeek.Wednesday) date = date.AddDays(-1);
return date;
}
}
@@ -0,0 +1,42 @@
using System.Text.Json;
using LehrerApp.Core.Models;
namespace LehrerApp.Core.Services;
internal class SchoolCalendarConfig
{
public GermanState State { get; set; } = GermanState.NW;
}
/// <summary>Welches Bundesland für die Feiertagsberechnung (4.3.5) gilt.</summary>
public class SchoolCalendarSettingsService
{
private readonly string _configPath;
private SchoolCalendarConfig _config;
public GermanState State => _config.State;
public SchoolCalendarSettingsService(string appDataPath)
{
_configPath = Path.Combine(appDataPath, "schoolcalendar.json");
_config = Load();
}
public void SetState(GermanState state)
{
_config.State = state;
File.WriteAllText(_configPath, JsonSerializer.Serialize(_config));
}
private SchoolCalendarConfig Load()
{
try
{
if (File.Exists(_configPath))
return JsonSerializer.Deserialize<SchoolCalendarConfig>(File.ReadAllText(_configPath))
?? new SchoolCalendarConfig();
}
catch { /* beschädigte Konfiguration -> Standardwert */ }
return new SchoolCalendarConfig();
}
}