Unterrichtsplanung: Einheiten, Verlaufsplan-Editor, Kürzel-Katalog (Kapitel 4.1/4.2)

Neuer Tab "Planung" in GroupDetailView ersetzt den Platzhalter: Einheiten anlegen/bearbeiten/
als Vorlage in andere Gruppe kopieren, Stunden je Einheit mit Verschieben (inkl. Nachrücken
der Folgestunden). Stundeneditor als tabellarischer Verlaufsplan (Phase/Dauer/Tätigkeit/
Material/Kurzsymbol je Zeile, Uhrzeit aus optionalem Stundenbeginn abgeleitet) statt eines
einzelnen Phase-Felds mit Methoden-/Materialien-Chips — Kurzsymbol als Freitext mit
Vorschlägen aus neuem Kürzel-Katalog (Einstellungen) plus bisher verwendeten Werten.
Schema-Migrationen v1-v3 überführen bestehende Daten verlustfrei. 4.2.5 bewusst offen
gelassen (hängt an Stundenplan, Kapitel 4.3).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 00:50:57 +02:00
co-authored by Claude Sonnet 5
parent 27359794f7
commit de6ea001e7
28 changed files with 2372 additions and 32 deletions
+86 -1
View File
@@ -11,7 +11,7 @@ public class LiteDbContext : IDisposable
/// Aktuelle Schema-Version. Migrationsschritte werden versioniert unter
/// <see cref="RunVersionedMigrations"/> ergänzt, statt bei jedem Start erneut
/// (idempotent, aber unnötig) über alle Daten zu laufen.
private const int CurrentSchemaVersion = 1;
private const int CurrentSchemaVersion = 3;
private readonly LiteDatabase _db;
@@ -55,6 +55,7 @@ public class LiteDbContext : IDisposable
public ILiteCollection<ParticipationSection> ParticipationSections => _db.GetCollection<ParticipationSection>("participation_sections");
public ILiteCollection<Subject> Subjects => _db.GetCollection<Subject>("subjects");
public ILiteCollection<CompetencyDomain> CompetencyDomains => _db.GetCollection<CompetencyDomain>("competency_domains");
public ILiteCollection<ShorthandCode> ShorthandCodes => _db.GetCollection<ShorthandCode>("shorthand_codes");
public void Checkpoint() => _db.Checkpoint();
@@ -83,6 +84,16 @@ public class LiteDbContext : IDisposable
MigrateExistingData();
version = 1;
}
if (version < 2)
{
MigrateLessonPhases();
version = 2;
}
if (version < 3)
{
MigrateLessonShorthand();
version = 3;
}
WriteSchemaVersion(version);
}
@@ -174,6 +185,79 @@ public class LiteDbContext : IDisposable
}
}
/// Fasst die frühere flache Struktur einer Stunde (einzelnes Phase-Textfeld, Methoden-/
/// Materialien-Listen) verlustfrei in eine einzige <see cref="LessonPhaseStep"/>-Zeile der
/// neuen Verlaufsplan-Tabelle zusammen. Nur die alten Felder werden gelesen — die typisierte
/// <see cref="Lesson"/>-Klasse kennt sie nicht mehr, ein Zugriff über die typisierte
/// Lessons-Collection würde sie beim Deserialisieren bereits verwerfen.
private void MigrateLessonPhases()
{
var lessons = _db.GetCollection<BsonDocument>("lessons");
foreach (var lesson in lessons.FindAll().ToList())
{
if (lesson.ContainsKey(nameof(Lesson.Phases))) continue;
var phase = lesson.TryGetValue("Phase", out var p) && p.IsString ? p.AsString.Trim() : "";
var methods = lesson.TryGetValue("Methods", out var m) && m.IsArray
? string.Join("; ", m.AsArray.Select(x => x.AsString)) : "";
var materials = lesson.TryGetValue("Materials", out var mat) && mat.IsArray
? string.Join(", ", mat.AsArray.Select(x => x.AsString)) : "";
var phases = new BsonArray();
if (phase.Length > 0 || methods.Length > 0 || materials.Length > 0)
{
phases.Add(new BsonDocument
{
[nameof(LessonPhaseStep.Id)] = Guid.NewGuid(),
[nameof(LessonPhaseStep.Name)] = phase,
[nameof(LessonPhaseStep.DurationMinutes)] = 0,
[nameof(LessonPhaseStep.Activity)] = methods,
[nameof(LessonPhaseStep.Material)] = materials,
[nameof(LessonPhaseStep.Shorthand)] = "",
});
}
lesson[nameof(Lesson.Phases)] = phases;
lesson.Remove("Phase");
lesson.Remove("Methods");
lesson.Remove("Materials");
lessons.Update(lesson);
}
}
/// Führt die anfangs erzwungene Von/Nach-Struktur des Kurzsymbols (<c>ShorthandFrom</c>/
/// <c>ShorthandTo</c>) in ein einzelnes Freitextfeld zusammen — der Praxis nach ist ein
/// Kurzsymbol nicht immer ein Materialfluss-Pfeil, manchmal nur eine Sozialform ("Plenum").
/// Beide Felder gesetzt ergeben "Von->Nach", nur eines gesetzt bleibt als Einzelwert erhalten.
private void MigrateLessonShorthand()
{
var lessons = _db.GetCollection<BsonDocument>("lessons");
foreach (var lesson in lessons.FindAll().ToList())
{
if (!lesson.TryGetValue(nameof(Lesson.Phases), out var phasesValue) || !phasesValue.IsArray) continue;
var changed = false;
foreach (var phaseValue in phasesValue.AsArray)
{
if (phaseValue is not BsonDocument phase) continue;
if (phase.ContainsKey(nameof(LessonPhaseStep.Shorthand))) continue;
var from = phase.TryGetValue("ShorthandFrom", out var f) && f.IsString ? f.AsString : "";
var to = phase.TryGetValue("ShorthandTo", out var t) && t.IsString ? t.AsString : "";
phase[nameof(LessonPhaseStep.Shorthand)] = (from.Length > 0, to.Length > 0) switch
{
(true, true) => $"{from}->{to}",
(true, false) => from,
(false, true) => to,
_ => "",
};
phase.Remove("ShorthandFrom");
phase.Remove("ShorthandTo");
changed = true;
}
if (changed) lessons.Update(lesson);
}
}
private void RemoveRedundantLegacyFields()
{
RemoveFields("exams", "Subject");
@@ -237,6 +321,7 @@ public class LiteDbContext : IDisposable
Subjects.EnsureIndex("ux_subject_name", BsonExpression.Create("LOWER(TRIM($.Name))"), unique: true);
CompetencyDomains.EnsureIndex(x => x.SubjectId);
CompetencyDomains.EnsureIndex(x => x.GradeLevel);
ShorthandCodes.EnsureIndex("ux_shorthand_code", BsonExpression.Create("LOWER(TRIM($.Code))"), unique: true);
}
public void Dispose() => _db.Dispose();