feat: Teilnehmerlisten in Lerngruppen importieren
This commit is contained in:
@@ -43,7 +43,19 @@ public sealed class StudentImportService
|
||||
|
||||
public async Task<StudentImportPreview> AnalyzeAsync(
|
||||
ImportFile file,
|
||||
CancellationToken cancellationToken = default)
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await AnalyzeInternalAsync(file, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
public async Task<StudentImportPreview> AnalyzeAsync(
|
||||
ImportFile file,
|
||||
Guid targetGroupId,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await AnalyzeInternalAsync(file, targetGroupId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
private async Task<StudentImportPreview> AnalyzeInternalAsync(
|
||||
ImportFile file,
|
||||
Guid? targetGroupId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(file);
|
||||
var detected = await _handlers.DetectAsync(file, cancellationToken).ConfigureAwait(false);
|
||||
@@ -61,6 +73,15 @@ public sealed class StudentImportService
|
||||
|
||||
var existing = _students.GetAll(includeInactive: true);
|
||||
var groups = _groups.GetAll(includeInactive: true);
|
||||
var groupMemberships = groups.SelectMany(group => _memberships.GetByGroup(group.Id)).ToList();
|
||||
var targetGroup = targetGroupId is null
|
||||
? null
|
||||
: groups.SingleOrDefault(group => group.Id == targetGroupId.Value);
|
||||
if (targetGroupId is not null && targetGroup is not { IsActive: true })
|
||||
messages.Add(new ImportMessage(
|
||||
ImportMessageSeverity.Error,
|
||||
"student.target-group.unavailable",
|
||||
"Die Ziel-Lerngruppe existiert nicht oder ist archiviert."));
|
||||
var currentSchoolYear = _schoolYears.CurrentSchoolYear();
|
||||
var currentGroups = groups
|
||||
.Where(group => group.IsActive && group.SchoolYear == currentSchoolYear)
|
||||
@@ -107,6 +128,17 @@ public sealed class StudentImportService
|
||||
? sameName.Where(student => student.DateOfBirth == candidate.DateOfBirth).ToList()
|
||||
: [];
|
||||
|
||||
if (candidate.DateOfBirth is null && exact.Count == 0 && targetGroup is not null)
|
||||
{
|
||||
var memberIds = groupMemberships
|
||||
.Where(membership => membership.GroupId == targetGroup.Id)
|
||||
.Select(membership => membership.StudentId)
|
||||
.ToHashSet();
|
||||
var memberMatches = sameName.Where(student => memberIds.Contains(student.Id)).ToList();
|
||||
if (memberMatches.Count == 1)
|
||||
exact = memberMatches;
|
||||
}
|
||||
|
||||
if (exact.Count == 1)
|
||||
{
|
||||
AddDifferenceWarning(candidate, exact[0], messages);
|
||||
@@ -139,8 +171,11 @@ public sealed class StudentImportService
|
||||
entries.Add(new StudentImportEntry(entryId, candidate, null, conflictId, []));
|
||||
}
|
||||
|
||||
var groupAssignments = BuildGroupAssignments(imported, currentGroups, conflicts, messages);
|
||||
var groupMemberships = groups.SelectMany(group => _memberships.GetByGroup(group.Id)).ToList();
|
||||
var groupAssignments = targetGroup is { IsActive: true }
|
||||
? [new StudentImportGroupAssignment(targetGroup.Name, targetGroup.Id, null)]
|
||||
: targetGroupId is null
|
||||
? BuildGroupAssignments(imported, currentGroups, conflicts, messages)
|
||||
: [];
|
||||
|
||||
return new StudentImportPreview(
|
||||
detected.Handler.FormatId,
|
||||
@@ -149,6 +184,7 @@ public sealed class StudentImportService
|
||||
messages,
|
||||
conflicts,
|
||||
groupAssignments,
|
||||
targetGroupId,
|
||||
Fingerprint(existing),
|
||||
GroupStateFingerprint(groups, groupMemberships, currentSchoolYear));
|
||||
}
|
||||
@@ -235,10 +271,9 @@ public sealed class StudentImportService
|
||||
foreach (var entry in preview.Entries)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (entry.Student.GroupName is not { } sourceGroup
|
||||
|| !studentIdsByEntry.TryGetValue(entry.Id, out var studentId)
|
||||
if (!studentIdsByEntry.TryGetValue(entry.Id, out var studentId)
|
||||
|| studentId is null
|
||||
|| !groupResolutions.TryGetValue(sourceGroup, out var groupId)
|
||||
|| !TryResolveTargetGroup(preview, entry, groupResolutions, out var groupId)
|
||||
|| groupId is null
|
||||
|| _memberships.GetByStudentAndGroup(studentId.Value, groupId.Value) is not null)
|
||||
continue;
|
||||
@@ -262,6 +297,24 @@ public sealed class StudentImportService
|
||||
createdIds.AsReadOnly()));
|
||||
}
|
||||
|
||||
private static bool TryResolveTargetGroup(
|
||||
StudentImportPreview preview,
|
||||
StudentImportEntry entry,
|
||||
IReadOnlyDictionary<string, Guid?> groupResolutions,
|
||||
out Guid? groupId)
|
||||
{
|
||||
if (preview.TargetGroupId is not null)
|
||||
{
|
||||
groupId = preview.TargetGroupId;
|
||||
return true;
|
||||
}
|
||||
if (entry.Student.GroupName is { } sourceGroup
|
||||
&& groupResolutions.TryGetValue(sourceGroup, out groupId))
|
||||
return true;
|
||||
groupId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ImportedStudent Normalize(ImportedStudent student) => student with
|
||||
{
|
||||
FirstName = student.FirstName?.Trim() ?? "",
|
||||
|
||||
Reference in New Issue
Block a user