feat: prominenterer Bestätigungsdialog + move_lesson/delete_lesson (Nutzer-Feedback)
Nach dem ersten Live-Test mit echtem Bridge-Prozess: der bisherige ConfirmDialog fiel zu wenig auf, wenn LehrerApp im Hintergrund lief (Normalfall, da der Anstoß vom KI-Client in einem anderen Fenster kommt). Neuer, eigenständiger McpConfirmDialog statt Änderung am geteilten ConfirmDialog (hätte alle anderen Aufrufer mitbetroffen): breiter, auffälliger Kopfbereich, Topmost. AvaloniaMcpConfirmationService holt das Hauptfenster zusätzlich aus einer möglichen Minimierung und aktiviert es vor dem Anzeigen. move_lesson kapselt die bereits vorhandene LessonSchedulingService.Move (shiftFollowingLessons öffnet eine Lücke für eine neue Stunde, indem spätere Stunden derselben Einheit mitverschoben werden) - keine neue Terminlogik, nur Wiederverwendung. delete_lesson ist eine bewusste, gezielte Ausnahme von "v1 ohne Lösch-Tools" auf expliziten Nutzerwunsch: eigene AllowedDestructiveWriteTools-Liste, Destructive=true-Annotation, Bestätigungstext betont ausdrücklich die fehlende Papierkorb-Deckung für Lessons. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using System.ComponentModel;
|
||||
using System.Text;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
@@ -16,7 +17,12 @@ namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
/// Stunde) und liefern einen für den Bestätigungsdialog tatsächlich lesbaren Diff statt eines
|
||||
/// kompletten Objekt-Dumps. Ein Tool, das die ganze Stunde überschreibt, ist bewusst NICHT
|
||||
/// vorgesehen; wo es fehlt, ist die Kombination aus update_lesson (Metadaten) +
|
||||
/// add/update/remove_lesson_phase (je eine Phase) der vorgesehene Weg.</summary>
|
||||
/// add/update/remove_lesson_phase (je eine Phase) der vorgesehene Weg.
|
||||
///
|
||||
/// "delete_lesson" ist (Stand Nutzer-Nachtrag) das einzige Lösch-Tool im gesamten MCP-Katalog —
|
||||
/// eine bewusste, gezielte Ausnahme von der sonst geltenden "v1 ohne Lösch-Tools"-Regel (siehe
|
||||
/// Planungsdokument), nicht deren Aufhebung. Entsprechend als "Destructive" annotiert
|
||||
/// (siehe McpServerHostedService) und mit besonders deutlicher Bestätigungsnachricht.</summary>
|
||||
public class LessonPlanTools(
|
||||
IUnitRepository units, ILessonRepository lessons, IGroupRepository groups,
|
||||
IAttachmentStorage attachments, IMcpConfirmationService confirmation)
|
||||
@@ -294,6 +300,63 @@ public class LessonPlanTools(
|
||||
return new WriteResultDto(true, phase.Id, "Phase entfernt.");
|
||||
}
|
||||
|
||||
// ── Einzelstunden (Lesson) — Verschieben & Löschen ───────────────────────────────────────
|
||||
|
||||
[Description("Verschiebt eine Einzelstunde auf ein neues Datum (und optional eine neue Stundennummer). Mit shiftFollowingLessons=true verschieben sich alle noch nicht durchgeführten, späteren Stunden derselben Einheit um denselben Tages-Versatz mit — so lässt sich eine Lücke für eine neue Stunde öffnen: diese Stunde auf den Termin der übernächsten verschieben (mit shiftFollowingLessons), dann create_lesson auf das dadurch freigewordene ursprüngliche Datum. Muss der Nutzer erst bestätigen.")]
|
||||
public async Task<WriteResultDto> MoveLesson(
|
||||
[Description("ID der zu verschiebenden Einzelstunde.")] Guid lessonId,
|
||||
[Description("Neues Datum, Format YYYY-MM-DD.")] DateOnly newDate,
|
||||
[Description("Neue Stundennummer im Tagesraster. Unverändert lassen: weglassen.")] int? newPeriod = null,
|
||||
[Description("Alle späteren, noch nicht durchgeführten Stunden derselben Einheit um denselben Tages-Versatz mitverschieben.")] bool shiftFollowingLessons = false,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var lesson = lessons.GetById(lessonId);
|
||||
if (lesson is null) return new WriteResultDto(false, null, "Unbekannte Stunden-ID.");
|
||||
|
||||
var oldDate = lesson.Date;
|
||||
var affectedCount = shiftFollowingLessons
|
||||
? lessons.GetByUnit(lesson.UnitId).Count(l => l.Id != lesson.Id && l.Status != LessonStatus.Conducted && l.Date > oldDate)
|
||||
: 0;
|
||||
|
||||
var message = $"„{lesson.Topic}“ von {oldDate:dd.MM.yyyy} auf {newDate:dd.MM.yyyy} verschieben?" +
|
||||
(newPeriod is not null ? $"\nNeue Stundennummer: {newPeriod}." : "") +
|
||||
(shiftFollowingLessons
|
||||
? affectedCount > 0
|
||||
? $"\n{affectedCount} spätere Stunde(n) derselben Einheit verschieben sich um denselben Versatz mit."
|
||||
: "\nKeine späteren, noch offenen Stunden derselben Einheit betroffen."
|
||||
: "");
|
||||
|
||||
if (!await confirmation.ConfirmAsync("Stunde verschieben?", message, ct))
|
||||
return new WriteResultDto(false, null, "Vom Nutzer abgelehnt oder nicht bestätigt.");
|
||||
|
||||
try
|
||||
{
|
||||
new LessonSchedulingService(lessons).Move(lesson, newDate, newPeriod, shiftFollowingLessons);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return new WriteResultDto(false, null, ex.Message);
|
||||
}
|
||||
return new WriteResultDto(true, lesson.Id, "Stunde verschoben.");
|
||||
}
|
||||
|
||||
[Description("Löscht eine Einzelstunde endgültig, inklusive ihrer Anhänge. Anders als die meisten anderen Löschvorgänge in LehrerApp landet eine gelöschte Stunde NICHT im Papierkorb — nicht rückgängig zu machen. Muss der Nutzer erst bestätigen.")]
|
||||
public async Task<WriteResultDto> DeleteLesson(
|
||||
[Description("ID der zu löschenden Einzelstunde.")] Guid lessonId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var lesson = lessons.GetById(lessonId);
|
||||
if (lesson is null) return new WriteResultDto(false, null, "Unbekannte Stunden-ID.");
|
||||
|
||||
var message = $"Die Stunde „{lesson.Topic}“ vom {lesson.Date:dd.MM.yyyy} wird endgültig gelöscht. " +
|
||||
"Das kann NICHT rückgängig gemacht werden (kein Papierkorb für Stunden).";
|
||||
if (!await confirmation.ConfirmAsync("Stunde endgültig löschen?", message, ct))
|
||||
return new WriteResultDto(false, null, "Vom Nutzer abgelehnt oder nicht bestätigt.");
|
||||
|
||||
lessons.Delete(lessonId);
|
||||
return new WriteResultDto(true, lessonId, "Stunde gelöscht.");
|
||||
}
|
||||
|
||||
private static LessonDto ToDto(Lesson l) => new(
|
||||
l.Id, l.UnitId, l.GroupId, l.Date, l.LessonNumber, l.Topic, l.Homework, l.Status,
|
||||
l.Phases.Select(p => new LessonPhaseDto(p.Id, p.Name, p.DurationMinutes, p.Activity, p.Material, p.Shorthand)).ToList(),
|
||||
|
||||
Reference in New Issue
Block a user