feat: Sitzungsfreigabe für MCP-Bestätigungen + LessonStatus.Cancelled

Sitzungsfreigabe: McpConfirmDialog bietet zwei Checkboxen ("diese Art
von Aktion" / "alle KI-Aktionen für den Rest der Sitzung nicht mehr
nachfragen"), nur beim Bestätigen wirksam. IMcpConfirmationService.
ConfirmAsync bekommt einen per [CallerMemberName] automatisch
befüllten operationKey - kein bestehender Aufruf musste geändert
werden. Freigaben leben als In-Memory-Bookkeeping auf der
Confirmation-Service-Instanz, gelten bis App-Ende, werden aber trotzdem
geloggt, damit stillschweigende Bestätigungen nicht spurlos bleiben.
Grund: 17 identische Bestätigungen in Folge für eine neu generierte
Unterrichtseinheit sind unzumutbar.

LessonStatus.Cancelled ("Ausgefallen") als Alternative zu
delete_lesson für Stunden, die nur ausgefallen sind (Exkursion,
Feiertag), aber als Ereignis dokumentiert bleiben sollen. Als
Cancelled=4 angehängt (bestehende LiteDB-Werte 0/1 sind fix). Konsistent
wie "bereits durchgeführt" behandelt an allen Stellen, die bisher nur
auf Conducted prüften: Terminverschiebung, Fortschrittsanzeige,
Hausaufgabe-kontrollieren-Erinnerung, "nächste Stunde"-Vorschlag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 02:25:45 +02:00
co-authored by Claude Sonnet 5
parent a4733c156b
commit 19aa302487
16 changed files with 231 additions and 25 deletions
@@ -45,6 +45,39 @@ public sealed class LessonSchedulingServiceTests
Assert.Equal(new DateOnly(2026, 9, 4), conducted.Date);
}
[Fact]
public void Move_RuecktAuchAusgefalleneFolgestundenNichtNach()
{
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
var moved = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 1) };
var cancelledFollowing = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 4), Status = LessonStatus.Cancelled };
var repo = new FakeLessons();
repo.Add(moved); repo.Add(cancelledFollowing);
new LessonSchedulingService(repo).Move(moved, new(2026, 9, 8), null, shiftFollowing: true);
Assert.Equal(new DateOnly(2026, 9, 4), cancelledFollowing.Date);
}
[Fact]
public void SplitAndMoveSecondPart_AusgefalleneQuellstundeSetztFortsetzungAufEntwurfZurueck()
{
var source = new Lesson
{
UnitId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Date = new(2026, 9, 1),
Status = LessonStatus.Cancelled,
Phases = [new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 60 }],
};
var repo = new FakeLessons();
repo.Add(source);
var continuation = new LessonSchedulingService(repo).SplitAndMoveSecondPart(source, 30,
new(2026, 9, 3), 1, null);
Assert.Equal(LessonStatus.Draft, continuation.Status);
}
[Fact]
public void SplitAndMoveSecondPart_TeiltAuchEineUeberDieGrenzeLaufendePhase()
{