Notenverwaltung (Kapitel 2) und Mitarbeits-Assistent
Notenübersicht der Gruppe (Matrix, Gesamt-Spalte, Sortierung, Halbjahresfilter), Einzelnoten-Pflege samt Sammelerfassung, Gewichtungsschema mit Voreinstellung je Gruppentyp, Zeugnisnoten-Berechnung mit Übersteuern/Festschreiben/Export und Notenentwicklung im Schülerdetail. Dazu Anwesenheits-/Hausaufgaben-Tracking je Mitarbeit-Sitzung, ein neuer Mitarbeits-Assistent (Zeitleiste mit Abschnitten, automatischer Notenvorschlag, Zusammenzug zur Halbjahresnote) und eine Dashboard-Kachel für offene Entschuldigungen. Außerdem: verbliebene englische Begriffe in Auswahlfeldern und Buttons auf Deutsch umgestellt.
This commit is contained in:
@@ -56,6 +56,20 @@ public interface IGradeRepository
|
||||
void Save(Grade grade);
|
||||
void Delete(Guid id);
|
||||
}
|
||||
public interface IGradingSchemeRepository
|
||||
{
|
||||
GradingScheme? GetByGroup(Guid groupId);
|
||||
GradingScheme? GetDefaultForType(GroupType type);
|
||||
void Save(GradingScheme scheme);
|
||||
void Delete(Guid id);
|
||||
}
|
||||
public interface IReportGradeRepository
|
||||
{
|
||||
List<ReportGrade> GetByGroup(Guid groupId);
|
||||
ReportGrade? GetByStudentGroupPeriod(Guid studentId, Guid groupId, string period);
|
||||
void Save(ReportGrade grade);
|
||||
void Delete(Guid id);
|
||||
}
|
||||
public interface IUnitRepository
|
||||
{
|
||||
Unit? GetById(Guid id);
|
||||
@@ -116,6 +130,12 @@ public interface IParticipationAspectRepository
|
||||
void Save(ParticipationAspect aspect);
|
||||
void Delete(Guid id);
|
||||
}
|
||||
public interface IParticipationSectionRepository
|
||||
{
|
||||
List<ParticipationSection> GetByGroup(Guid groupId);
|
||||
void Save(ParticipationSection section);
|
||||
void Delete(Guid id);
|
||||
}
|
||||
public interface ISubjectRepository
|
||||
{
|
||||
List<Subject> GetAll();
|
||||
|
||||
@@ -20,9 +20,34 @@ public class ParticipationEntry
|
||||
public List<AspectRating> Ratings { get; set; } = [];
|
||||
public List<CompetencyRating> CompetencyRatings { get; set; } = [];
|
||||
public string? Note { get; set; }
|
||||
public bool HomeworkMissing { get; set; }
|
||||
public AttendanceStatus? Attendance { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anwesenheitsstatus einer Sitzung; null (Standard) bedeutet anwesend.
|
||||
/// <see cref="ExcusePending"/> ist ein bewusster Zwischenzustand, da die Entschuldigung meist
|
||||
/// erst später eintrifft — er wird beim Erfassen des Fehltags gesetzt und danach manuell auf
|
||||
/// <see cref="Excused"/> oder <see cref="Unexcused"/> nachgetragen.
|
||||
/// </summary>
|
||||
public enum AttendanceStatus { ExcusePending, Excused, Unexcused }
|
||||
|
||||
/// <summary>
|
||||
/// Ein Bewertungsabschnitt einer Lerngruppe (z.B. alle 4–7 Wochen), an dessen Ende eine
|
||||
/// Abschnittsnote Mitarbeit vergeben wird. Nur abgeschlossene Abschnitte werden gespeichert;
|
||||
/// der aktuell laufende Zeitraum ergibt sich aus dem Ende des letzten Abschnitts bis heute.
|
||||
/// </summary>
|
||||
public class ParticipationSection
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid GroupId { get; set; }
|
||||
public string Label { get; set; } = "";
|
||||
public DateOnly StartDate { get; set; }
|
||||
public DateOnly EndDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class CompetencyRating
|
||||
{
|
||||
public string Code { get; set; } = "";
|
||||
|
||||
@@ -14,6 +14,22 @@ public class Grade
|
||||
}
|
||||
public enum GradeCategory { Oral, Homework, Participation, Project, Other }
|
||||
|
||||
/// <summary>
|
||||
/// Prozentuale Gewichtung von Klausuren/Mitarbeit/sonstigen Leistungen für die Zeugnisnote.
|
||||
/// Entweder gruppenspezifisch (<see cref="GroupId"/> gesetzt) oder als Voreinstellung je
|
||||
/// Gruppentyp (<see cref="GroupType"/> gesetzt, <see cref="GroupId"/> null).
|
||||
/// </summary>
|
||||
public class GradingScheme
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? GroupId { get; set; }
|
||||
public GroupType? GroupType { get; set; }
|
||||
public double ExamsPercent { get; set; }
|
||||
public double ParticipationPercent { get; set; }
|
||||
public double OtherPercent { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class Unit
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
@@ -45,3 +61,22 @@ public class Lesson
|
||||
}
|
||||
public enum UnitStatus { Planned, Active, Completed }
|
||||
public enum LessonStatus { Planned, Conducted }
|
||||
|
||||
/// <summary>
|
||||
/// Zeugnisnote eines Schülers in einer Lerngruppe für einen Zeitraum (Halbjahr/Gesamtjahr).
|
||||
/// <see cref="CalculatedValue"/> ist das zuletzt berechnete Ergebnis; <see cref="OverrideValue"/>
|
||||
/// überschreibt es bei pädagogischem Ermessen (erfordert <see cref="OverrideReason"/>).
|
||||
/// Nach dem Festschreiben (<see cref="IsLocked"/>) wird der Datensatz nicht mehr neu berechnet.
|
||||
/// </summary>
|
||||
public class ReportGrade
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid StudentId { get; set; }
|
||||
public Guid GroupId { get; set; }
|
||||
public string Period { get; set; } = "";
|
||||
public string CalculatedValue { get; set; } = "";
|
||||
public string? OverrideValue { get; set; }
|
||||
public string? OverrideReason { get; set; }
|
||||
public bool IsLocked { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,16 @@ public class GradingService
|
||||
return CalculateGrade(percent, 100, key);
|
||||
}
|
||||
|
||||
public string? ValidateGradingScheme(GradingScheme scheme)
|
||||
{
|
||||
if (scheme.ExamsPercent < 0 || scheme.ParticipationPercent < 0 || scheme.OtherPercent < 0)
|
||||
return "Anteile dürfen nicht negativ sein.";
|
||||
var sum = scheme.ExamsPercent + scheme.ParticipationPercent + scheme.OtherPercent;
|
||||
return Math.Abs(sum - 100.0) > 0.01
|
||||
? $"Die Anteile müssen zusammen 100 % ergeben (aktuell {sum.ToString("0.#")} %)."
|
||||
: null;
|
||||
}
|
||||
|
||||
public double WeightedAverage(List<(string Grade, double Weight)> grades)
|
||||
{
|
||||
var numeric = grades
|
||||
@@ -73,4 +83,51 @@ public class GradingService
|
||||
var total = numeric.Sum(g => g.Weight);
|
||||
return total == 0 ? 0 : numeric.Sum(g => g.Value!.Value * g.Weight) / total;
|
||||
}
|
||||
|
||||
/// Rundet einen rechnerischen Notenwert auf eine ganze Note/Punktzahl.
|
||||
/// "Kaufmännisch" rundet bei genau 0,5 immer vom Nullpunkt weg (Standard).
|
||||
/// "Pädagogisch" rundet bei genau 0,5 in Richtung der besseren Note
|
||||
/// (Grades1To6: kleinere Zahl ist besser → abrunden; Points0To15: größere Zahl ist besser → aufrunden).
|
||||
public string RoundToGrade(double value, GradingSystem system, RoundingRule rule)
|
||||
{
|
||||
int rounded;
|
||||
var floor = Math.Floor(value);
|
||||
var isExactHalf = Math.Abs(value - floor - 0.5) < 0.0001;
|
||||
|
||||
if (rule == RoundingRule.Pedagogical && isExactHalf)
|
||||
{
|
||||
var betterIsLower = system == GradingSystem.Grades1To6;
|
||||
rounded = betterIsLower ? (int)floor : (int)floor + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
rounded = (int)Math.Round(value, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
var (min, max) = system == GradingSystem.Grades1To6 ? (1, 6) : (0, 15);
|
||||
return Math.Clamp(rounded, min, max).ToString();
|
||||
}
|
||||
|
||||
/// Berechnet die Zeugnisnote (2.4.1) aus den drei Leistungsbereichen gemäß Gewichtungsschema.
|
||||
/// Bereiche ohne Werte werden ausgelassen; die verbleibenden Prozentanteile werden neu normiert.
|
||||
/// Liefert null, wenn in keinem Bereich Werte vorliegen.
|
||||
public string? CalculateReportGrade(
|
||||
List<(string Grade, double Weight)> examGrades,
|
||||
List<(string Grade, double Weight)> participationGrades,
|
||||
List<(string Grade, double Weight)> otherGrades,
|
||||
GradingScheme scheme, GradingSystem system, RoundingRule rounding)
|
||||
{
|
||||
var buckets = new List<(double Avg, double Percent)>();
|
||||
if (examGrades.Count > 0) buckets.Add((WeightedAverage(examGrades), scheme.ExamsPercent));
|
||||
if (participationGrades.Count > 0) buckets.Add((WeightedAverage(participationGrades), scheme.ParticipationPercent));
|
||||
if (otherGrades.Count > 0) buckets.Add((WeightedAverage(otherGrades), scheme.OtherPercent));
|
||||
|
||||
var totalPercent = buckets.Sum(b => b.Percent);
|
||||
if (buckets.Count == 0 || totalPercent <= 0) return null;
|
||||
|
||||
var weighted = buckets.Sum(b => b.Avg * b.Percent) / totalPercent;
|
||||
return RoundToGrade(weighted, system, rounding);
|
||||
}
|
||||
}
|
||||
|
||||
public enum RoundingRule { Commercial, Pedagogical }
|
||||
|
||||
Reference in New Issue
Block a user