Files
adminandClaude Sonnet 5 9567d8d616 feat: lokaler MCP-Server, Phase 2 (Write-Tools + Bestätigungsdialog + Lesson-Plans)
Neue Write-Tools create_time_entry, create_grade_entry und
update_student_group_assignment schreiben nie direkt: jeder Aufruf zeigt
zuerst einen menschenlesbaren Bestätigungsdialog (bestehender
ConfirmDialog, über Dispatcher.UIThread aus dem Pipe-Session-Thread
angezeigt) und schreibt erst nach Bestätigung, mit 2-Minuten-Timeout
gegen eine hängende Session. Zusätzliches Read-Tool get_lesson_plans.

create_note bewusst nicht umgesetzt (kollidiert mit dem bestehenden
Dokumentations-Ausschluss aus Phase 1), create_lesson_plan/
update_lesson_plan wegen der Modellkomplexität von Lesson zurückgestellt
(siehe TODO.md 4.5.26).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 20:14:51 +02:00

70 lines
3.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using System.Text;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
namespace LehrerApp.Desktop.Services.Mcp.Tools;
/// <summary>MCP-Write-Tool "update_student_group_assignment" (Phase 2, siehe Planungsdokument).
/// Legt eine <see cref="GroupMembership"/> an, falls noch keine für Schüler+Gruppe existiert, sonst
/// werden nur die übergebenen (nicht-null) Felder überschrieben.</summary>
public class GroupMembershipTools(
IGroupMembershipRepository memberships, IStudentRepository students, IGroupRepository groups,
IMcpConfirmationService confirmation)
{
[Description("Legt eine Gruppenmitgliedschaft eines Schülers an oder ändert Niveau/Zeitraum einer bestehenden. Muss der Nutzer erst in einem Dialog in LehrerApp bestätigen.")]
public async Task<WriteResultDto> UpdateStudentGroupAssignment(
[Description("Schüler-ID.")] Guid studentId,
[Description("Lerngruppen-ID.")] Guid groupId,
[Description("Niveau: E, G oder Foerder. Unverändert lassen: weglassen.")] Niveau? niveau = null,
[Description("Zeitraum: FullYear, H1Only, H2Only oder Custom. Unverändert lassen: weglassen.")] MembershipPeriod? period = null,
[Description("Beitrittsdatum bei Custom-Zeitraum, Format YYYY-MM-DD.")] DateOnly? joinedAt = null,
[Description("Austrittsdatum bei Custom-Zeitraum, Format YYYY-MM-DD.")] DateOnly? leftAt = null,
CancellationToken ct = default)
{
var student = students.GetById(studentId);
var group = groups.GetById(groupId);
if (student is null || group is null)
return new WriteResultDto(false, null, "Unbekannte Schüler- oder Gruppen-ID.");
var existing = memberships.GetByStudentAndGroup(studentId, groupId);
var target = existing is null
? new GroupMembership { StudentId = studentId, GroupId = groupId }
: Clone(existing);
var changes = new StringBuilder();
if (niveau is not null && niveau != target.Niveau) { changes.AppendLine($"Niveau: {NiveauName(target.Niveau)} → {NiveauName(niveau)}"); target.Niveau = niveau; }
if (period is not null && period != target.Period) { changes.AppendLine($"Zeitraum: {target.Period} → {period}"); target.Period = period.Value; }
if (joinedAt is not null && joinedAt != target.JoinedAt) { changes.AppendLine($"Beitritt: {target.JoinedAt:dd.MM.yyyy} → {joinedAt:dd.MM.yyyy}"); target.JoinedAt = joinedAt; }
if (leftAt is not null && leftAt != target.LeftAt) { changes.AppendLine($"Austritt: {target.LeftAt:dd.MM.yyyy} → {leftAt:dd.MM.yyyy}"); target.LeftAt = leftAt; }
if (existing is not null && changes.Length == 0)
return new WriteResultDto(true, existing.Id, "Keine Änderung nötig, Mitgliedschaft besteht bereits unverändert.");
var title = existing is null ? "Gruppenmitgliedschaft anlegen?" : "Gruppenmitgliedschaft ändern?";
var message = $"{student.FullName} — {group.Name}" +
(existing is null ? "\nNeue Mitgliedschaft anlegen." : "") +
(changes.Length > 0 ? "\n" + changes.ToString().TrimEnd() : "");
if (!await confirmation.ConfirmAsync(title, message, ct))
return new WriteResultDto(false, null, "Vom Nutzer abgelehnt oder nicht bestätigt.");
memberships.Save(target);
return new WriteResultDto(true, target.Id, "Gruppenmitgliedschaft gespeichert.");
}
private static GroupMembership Clone(GroupMembership m) => new()
{
Id = m.Id, StudentId = m.StudentId, GroupId = m.GroupId, AddedOn = m.AddedOn,
Period = m.Period, JoinedAt = m.JoinedAt, LeftAt = m.LeftAt, Niveau = m.Niveau,
};
private static string NiveauName(Niveau? n) => n switch
{
Core.Models.Niveau.E => "E-Niveau",
Core.Models.Niveau.G => "G-Niveau",
Core.Models.Niveau.Foerder => "Förderniveau",
_ => "",
};
}