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:
@@ -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();
|
||||
|
||||
@@ -211,7 +211,7 @@ public class UnitRepository(LiteDbContext db) : IUnitRepository
|
||||
public class LessonRepository(LiteDbContext db) : ILessonRepository
|
||||
{
|
||||
public List<Lesson> GetByUnit(Guid id) =>
|
||||
db.Lessons.Find(l => l.UnitId == id).OrderBy(l => l.Date).ToList();
|
||||
db.Lessons.Find(l => l.UnitId == id).OrderBy(l => l.Date).ThenBy(l => l.LessonNumber).ToList();
|
||||
public List<Lesson> GetByGroupAndDate(Guid gid, DateOnly date) =>
|
||||
db.Lessons.Find(l => l.GroupId == gid && l.Date == date).ToList();
|
||||
public List<Lesson> GetByGroupAndRange(Guid gid, DateOnly from, DateOnly to) =>
|
||||
@@ -349,6 +349,24 @@ public class SubjectRepository(LiteDbContext db) : ISubjectRepository
|
||||
}
|
||||
}
|
||||
|
||||
public class ShorthandCodeRepository(LiteDbContext db) : IShorthandCodeRepository
|
||||
{
|
||||
public List<ShorthandCode> GetAll() => db.ShorthandCodes.FindAll().OrderBy(c => c.Code).ToList();
|
||||
public void Save(ShorthandCode c)
|
||||
{
|
||||
c.Code = c.Code.Trim();
|
||||
c.Label = c.Label.Trim();
|
||||
if (c.Code.Length == 0) throw new ArgumentException("Das Kürzel darf nicht leer sein.");
|
||||
var duplicate = db.ShorthandCodes.FindAll()
|
||||
.FirstOrDefault(x => string.Equals(x.Code, c.Code, StringComparison.OrdinalIgnoreCase));
|
||||
if (duplicate is not null && duplicate.Id != c.Id)
|
||||
throw new InvalidOperationException("Ein Kürzel mit diesem Code existiert bereits.");
|
||||
c.UpdatedAt = DateTime.UtcNow;
|
||||
db.ShorthandCodes.Upsert(c);
|
||||
}
|
||||
public void Delete(Guid id) => db.ShorthandCodes.Delete(id);
|
||||
}
|
||||
|
||||
public class CompetencyDomainRepository(LiteDbContext db) : ICompetencyDomainRepository
|
||||
{
|
||||
public List<CompetencyDomain> GetBySubjectAndGrade(Guid subjectId, int gradeLevel) =>
|
||||
|
||||
Reference in New Issue
Block a user