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>
70 lines
3.9 KiB
C#
70 lines
3.9 KiB
C#
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",
|
||
_ => "–",
|
||
};
|
||
}
|