Files
LehrerApp/LehrerApp.Desktop/Views/Groups/GradeOverviewTabView.axaml.cs
T
adminandClaude Opus 5.5 d9eac0d1d2
CI / build-and-test (push) Canceled after 0s
Zwischenstand: vorläufige Noten zu Stichtag und Anlass
Ein Zwischenstand ist ein Durchgang aus Stichtag, Anlass und
Schülerauswahl (ganze Gruppe, Teilgruppe oder Einzelfall, mit
Schnellfilter für gefährdete Schüler). Der Zeugnisnoten-Assistent geht
die gewählten Schüler im Zwischenstand-Modus durch und notiert
vorläufige schriftliche, mündliche und Gesamtnote samt Notiz.

Gespeichert wird das als eigenes Modell InterimGrade, getrennt von der
Zeugnisnote. Die Berechnung endet am Stichtag und rechnet den laufenden
Mitarbeits-Abschnitt mit ein. Zwischenstände erscheinen im Assistenten
als Liste und Marker im Verlauf sowie als graue Spalte in der
Notenübersicht (nicht in "Gesamt", nicht im PDF).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:01:34 +02:00

209 lines
9.7 KiB
C#

using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Interactivity;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop.Views.Groups;
public partial class GradeOverviewTabView : UserControl
{
private GradeOverviewTabViewModel? _vm;
public GradeOverviewTabView() => InitializeComponent();
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is GradeOverviewTabViewModel vm)
{
_vm = vm;
vm.OnManageStudentGrades = ShowStudentGradesDialog;
vm.OnCollectiveGrade = ShowCollectiveGradeDialog;
vm.OnReportGrades = ShowReportGradesDialog;
vm.OnInterimGrades = async studentId =>
{
if (TopLevel.GetTopLevel(this) is Window owner) await ShowInterimRun(owner, studentId);
};
vm.OnShowPerformanceOverview = ShowPerformanceOverviewDialog;
vm.PropertyChanged += (_, pe) =>
{
if (pe.PropertyName == nameof(GradeOverviewTabViewModel.RebuildColumnsSignal))
BuildColumns();
};
BuildColumns();
}
}
private async Task ShowStudentGradesDialog(GradeOverviewRow row)
{
var dialogVm = new StudentGradesDialogViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
row.StudentId, _vm!.GroupId, row.Name);
var dialog = new StudentGradesDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null) await dialog.ShowDialog(owner);
}
private async Task ShowCollectiveGradeDialog()
{
var dialogVm = new CollectiveGradeDialogViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<IStudentRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
_vm!.GroupId);
var dialog = new CollectiveGradeDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null) await dialog.ShowDialog(owner);
}
private async Task ShowReportGradesDialog()
{
var dialogVm = new ReportGradeDialogViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<IExamRepository>(),
App.Services.GetRequiredService<IExamResultRepository>(),
App.Services.GetRequiredService<IStudentRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGradingSchemeRepository>(),
App.Services.GetRequiredService<IReportGradeRepository>(),
App.Services.GetRequiredService<IParticipationSessionRepository>(),
App.Services.GetRequiredService<IParticipationRepository>(),
App.Services.GetRequiredService<AttendanceBalanceService>(),
App.Services.GetRequiredService<GradingService>(),
_vm!.GroupId, _vm.GroupType, _vm.GradingSystem, _vm.GroupLabel, _vm.SchoolYear);
var dialog = new ReportGradeDialog { DataContext = dialogVm, OnOpenAssistant = owner => ShowReportGradeAssistant(owner) };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null) await dialog.ShowDialog(owner);
}
private async Task ShowReportGradeAssistant(Window owner, InterimRunOptions? interim = null)
{
var dialogVm = new ReportGradeAssistantViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<IExamRepository>(),
App.Services.GetRequiredService<IExamResultRepository>(),
App.Services.GetRequiredService<IStudentRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGradingSchemeRepository>(),
App.Services.GetRequiredService<IReportGradeRepository>(),
App.Services.GetRequiredService<IParticipationSessionRepository>(),
App.Services.GetRequiredService<IParticipationRepository>(),
App.Services.GetRequiredService<IParticipationAspectRepository>(),
App.Services.GetRequiredService<IParticipationSectionRepository>(),
App.Services.GetRequiredService<IInterimGradeRepository>(),
App.Services.GetRequiredService<AttendanceBalanceService>(),
App.Services.GetRequiredService<GradingService>(),
_vm!.GroupId, _vm.GroupType, _vm.GradingSystem, _vm.GroupLabel, _vm.SchoolYear,
App.Services.GetRequiredService<IGroupRepository>().GetById(_vm.GroupId)?.GradeLevel ?? 0,
interim: interim);
var dialog = new ReportGradeAssistantDialog { DataContext = dialogVm };
dialogVm.OnStartInterim = studentId => ShowInterimRun(dialog, studentId);
await dialog.ShowDialog(owner);
}
/// Zwischenstand: erst Stichtag, Anlass und Schüler festlegen, dann geht der Assistent genau
/// diese Schüler durch.
private async Task ShowInterimRun(Window owner, Guid? preselectedStudentId)
{
var dialogVm = new InterimRunDialogViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<IExamRepository>(),
App.Services.GetRequiredService<IExamResultRepository>(),
App.Services.GetRequiredService<IStudentRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGradingSchemeRepository>(),
App.Services.GetRequiredService<IReportGradeRepository>(),
App.Services.GetRequiredService<IParticipationSessionRepository>(),
App.Services.GetRequiredService<IParticipationRepository>(),
App.Services.GetRequiredService<IParticipationAspectRepository>(),
App.Services.GetRequiredService<IParticipationSectionRepository>(),
App.Services.GetRequiredService<IInterimGradeRepository>(),
App.Services.GetRequiredService<GradingService>(),
_vm!.GroupId, _vm.GroupType, _vm.GradingSystem, _vm.GroupLabel, _vm.SchoolYear,
preselectedStudentId);
var dialog = new InterimRunDialog { DataContext = dialogVm };
if (await dialog.ShowDialog<bool>(owner) && dialogVm.Result is { } run)
await ShowReportGradeAssistant(owner, run);
}
private async Task ShowPerformanceOverviewDialog(GradeOverviewRow row)
{
var dialogVm = new StudentPerformanceOverviewViewModel(
App.Services.GetRequiredService<IGradeRepository>(),
App.Services.GetRequiredService<IExamRepository>(),
App.Services.GetRequiredService<IExamResultRepository>(),
App.Services.GetRequiredService<IGroupMembershipRepository>(),
App.Services.GetRequiredService<IGradingSchemeRepository>(),
App.Services.GetRequiredService<GradingService>(),
_vm!.GroupId, row.StudentId, _vm.GroupType, _vm.GradingSystem, row.Name, _vm.GroupLabel, _vm.SchoolYear);
var dialog = new StudentPerformanceOverviewDialog { DataContext = dialogVm };
var owner = TopLevel.GetTopLevel(this) as Window;
if (owner is not null) await dialog.ShowDialog(owner);
}
private async void OnExportPdfClick(object? sender, RoutedEventArgs e)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is null || _vm is null) return;
// Vorläufige Zwischenstände gehören nicht in die gedruckte Notenliste. Ihre Spalten
// stehen immer am Ende, die Zellen lassen sich also einfach abschneiden.
var printed = _vm.Columns.Where(c => !c.IsInterim).ToList();
var data = new GradeListPrintData(
_vm.GroupLabel, _vm.SchoolYear, _vm.SelectedPeriod.Label,
printed.Select(c => c.Header).ToList(),
_vm.Rows.Select(r => new GradeListPrintRow(r.Name, r.Cells.Take(printed.Count).ToList(), r.TotalDisplay)).ToList());
var pdf = App.Services.GetRequiredService<PdfExportService>().BuildGradeListPdf(data);
await App.Services.GetRequiredService<ExportService>().SaveAsync(topLevel.StorageProvider,
ExportFile.Pdf("Notenliste als PDF speichern",
$"Notenliste_{_vm.GroupLabel}_{_vm.SchoolYear.Replace('/', '-')}", pdf));
}
private void BuildColumns()
{
var grid = this.FindControl<DataGrid>("GradesGrid");
if (grid is null || _vm is null) return;
grid.Columns.Clear();
grid.Columns.Add(new DataGridTextColumn
{
Header = "Schüler",
Binding = new Binding("Name"),
Width = new DataGridLength(160, DataGridLengthUnitType.Pixel),
});
foreach (var (col, i) in _vm.Columns.Select((c, i) => (c, i)))
{
var idx = i;
var column = new DataGridTextColumn
{
Header = col.Header,
Binding = new Binding($"Cells[{idx}]"),
Width = new DataGridLength(120, DataGridLengthUnitType.Pixel),
};
// Zwischenstände sind vorläufig — grau, damit sie nicht wie eingetragene Noten wirken.
if (col.IsInterim) column.Foreground = Avalonia.Media.Brushes.Gray;
grid.Columns.Add(column);
}
grid.Columns.Add(new DataGridTextColumn
{
Header = "Gesamt",
Binding = new Binding("TotalDisplay"),
Width = new DataGridLength(80, DataGridLengthUnitType.Pixel),
});
}
}