Skip to content

Commit

Permalink
Merge pull request #206 from mensagymnazium/feature/subject-registrat…
Browse files Browse the repository at this point in the history
…ion-progress-list

Feature/subject registration progress list
  • Loading branch information
hakenr authored Jun 2, 2024
2 parents d29a23d + 6361728 commit 729fb1a
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 125 deletions.
8 changes: 6 additions & 2 deletions Contracts/ISubjectRegistrationProgressValidationFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
[ApiContract]
public interface ISubjectRegistrationProgressValidationFacade
{
public Task<StudentRegistrationProgressDto> GetProgressOfCurrentStudentAsync();
public Task<StudentRegistrationProgressDto> GetProgressOfStudentAsync(Dto<int> studentId);
public Task<StudentRegistrationProgressDto> GetProgressOfCurrentStudentAsync(
CancellationToken cancellationToken = default);

public Task<List<StudentSubjectRegistrationProgressListItemDto>> GetProgressListAsync(
StudentSubjectRegistrationProgressListFilter request,
CancellationToken cancellationToken = default);
}
2 changes: 1 addition & 1 deletion Contracts/StudentSubjectRegistrationListQueryFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ public record StudentSubjectRegistrationListQueryFilter
public int? GradeId { get; set; }
public int? StudentId { get; set; }
public StudentRegistrationType? RegistrationType { get; set; }
}
}
13 changes: 13 additions & 0 deletions Contracts/StudentSubjectRegistrationProgressListFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace MensaGymnazium.IntranetGen3.Contracts;

public record StudentSubjectRegistrationProgressListFilter
{
public int? StudentId { get; set; }
public int? GradeId { get; set; }
/// <summary>
/// True: only valid,
/// False: only invalid registrations,
/// Null: Property is omitted in filter -> both valid and invalid
/// </summary>
public bool? ValidationState { get; set; }
}
8 changes: 8 additions & 0 deletions Contracts/StudentSubjectRegistrationProgressListItemDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace MensaGymnazium.IntranetGen3.Contracts;

public record StudentSubjectRegistrationProgressListItemDto
{
public int StudentId { get; set; }
public bool IsRegistrationValid { get; set; }
public List<string> MissingCriteriaMessages { get; set; }
}
65 changes: 0 additions & 65 deletions DataLayer/Queries/StudentRegistrationsQuery.cs

This file was deleted.

5 changes: 1 addition & 4 deletions DataLayer/Repositories/IGradeRepository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
namespace MensaGymnazium.IntranetGen3.DataLayer.Repositories;

public partial interface IGradeRepository
{

}
public partial interface IGradeRepository;
1 change: 0 additions & 1 deletion DataLayer/Repositories/Security/StudentDbRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ protected override IEnumerable<Expression<Func<Student, object>>> GetLoadReferen
yield return s => s.User;
yield return s => s.Grade;
}

}
52 changes: 46 additions & 6 deletions Facades/SubjectRegistrationProgressValidationFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class SubjectRegistrationProgressValidationFacade : ISubjectRegistrationP
{
private readonly ISubjectRegistrationProgressValidationService _subjectRegistrationProgressValidationService;
private readonly IApplicationAuthenticationService _applicationAuthenticationService;

public SubjectRegistrationProgressValidationFacade(
ISubjectRegistrationProgressValidationService subjectRegistrationProgressValidationService,
IApplicationAuthenticationService applicationAuthenticationService)
Expand All @@ -21,21 +22,60 @@ public SubjectRegistrationProgressValidationFacade(
}

[Authorize(Roles = nameof(Role.Student))]
public async Task<StudentRegistrationProgressDto> GetProgressOfCurrentStudentAsync()
public async Task<StudentRegistrationProgressDto> GetProgressOfCurrentStudentAsync(CancellationToken cancellationToken = default)
{
var currentUser = _applicationAuthenticationService.GetCurrentUser();
Contract.Requires<SecurityException>(currentUser.StudentId is not null);

var progress = await _subjectRegistrationProgressValidationService.GetRegistrationProgressOfStudentAsync(currentUser.StudentId.Value);
var progress = await _subjectRegistrationProgressValidationService.GetRegistrationProgressOfStudentAsync(
currentUser.StudentId.Value,
cancellationToken);

return MapToDto(progress);
}

[Authorize(Roles = nameof(Role.Administrator))]
public Task<StudentRegistrationProgressDto> GetProgressOfStudentAsync(Dto<int> studentId)
[Authorize]
public async Task<List<StudentSubjectRegistrationProgressListItemDto>> GetProgressListAsync(
StudentSubjectRegistrationProgressListFilter filter,
CancellationToken cancellationToken = default)
{
var progresses = await _subjectRegistrationProgressValidationService.GetRegistrationProgressOfAllStudentsAsync(
filter,
cancellationToken);

var mappedProgress = progresses
.Select(progressPair => new StudentSubjectRegistrationProgressListItemDto()
{
StudentId = progressPair.Key, // Key = StudentId
IsRegistrationValid = progressPair.Value.IsRegistrationValid,
MissingCriteriaMessages = GetMissingCriteriaMessages(progressPair.Value)
})
.ToList();

return mappedProgress;
}

private List<string> GetMissingCriteriaMessages(StudentRegistrationProgress progress)
{
//Todo: Implement
throw new NotImplementedException();
var missingCriteria = new List<string>();

if (!progress.HoursPerWeekProgress.IsProgressComplete)
{
missingCriteria.Add("Počet hodin týdně");
}

if (!progress.CsOrCpRegistrationProgress.IsProgressComplete)
{
missingCriteria.Add("Počet hodin v ČS/ČP");
}

if (progress.LanguageRegistrationProgress.IsLanguageRequired &&
!progress.LanguageRegistrationProgress.HasRegisteredLanguage)
{
missingCriteria.Add("Jazyk");
}

return missingCriteria;
}

private StudentRegistrationProgressDto MapToDto(StudentRegistrationProgress obj)
Expand Down
14 changes: 13 additions & 1 deletion Model/Security/Student.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Havit.Data.EntityFrameworkCore.Attributes;
using System.ComponentModel.DataAnnotations.Schema;
using Havit.Data.EntityFrameworkCore.Attributes;
using Havit.Model.Collections.Generic;

namespace MensaGymnazium.IntranetGen3.Model.Security;

Expand All @@ -14,7 +16,17 @@ public class Student

public int? SeedEntityId { get; set; }

public List<StudentSubjectRegistration> SubjectRegistrationsIncludingDeleted { get; } = new();

[NotMapped]
public FilteringCollection<StudentSubjectRegistration> SubjectRegistrations { get; }

public DateTime Created { get; set; }

public DateTime? Deleted { get; set; }

public Student()
{
SubjectRegistrations = new FilteringCollection<StudentSubjectRegistration>(this.SubjectRegistrationsIncludingDeleted, h => h.Deleted is null);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

using MensaGymnazium.IntranetGen3.Contracts;
using MensaGymnazium.IntranetGen3.Model.Security;

namespace MensaGymnazium.IntranetGen3.Services.SubjectRegistration.ProgressValidation;

/// <summary>
Expand All @@ -7,5 +9,13 @@ namespace MensaGymnazium.IntranetGen3.Services.SubjectRegistration.ProgressValid
/// </summary>
public interface ISubjectRegistrationProgressValidationService
{
Task<StudentRegistrationProgress> GetRegistrationProgressOfStudentAsync(int studentId, CancellationToken cancellationToken = default);
/// <returns>The progress of a single student</returns>
Task<StudentRegistrationProgress> GetRegistrationProgressOfStudentAsync(
int studentId,
CancellationToken cancellationToken = default);

/// <returns>Progress of multiple students. Key: <see cref="Student.Id"/>, Value: His progress</returns>
Task<Dictionary<int, StudentRegistrationProgress>> GetRegistrationProgressOfAllStudentsAsync(
StudentSubjectRegistrationProgressListFilter filter,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,101 @@
using Havit;
using MensaGymnazium.IntranetGen3.Contracts;
using MensaGymnazium.IntranetGen3.DataLayer.DataSources.Security;
using MensaGymnazium.IntranetGen3.DataLayer.Repositories;
using MensaGymnazium.IntranetGen3.DataLayer.Repositories.Security;
using MensaGymnazium.IntranetGen3.Model;
using MensaGymnazium.IntranetGen3.Model.Security;
using MensaGymnazium.IntranetGen3.Primitives;
using Microsoft.EntityFrameworkCore;

namespace MensaGymnazium.IntranetGen3.Services.SubjectRegistration.ProgressValidation;

[Service]
public sealed class SubjectRegistrationProgressValidationService : ISubjectRegistrationProgressValidationService
{
private readonly IStudentRepository studentRepository;
private readonly IStudentSubjectRegistrationRepository subjectRegistrationRepository;
private readonly IGradeRepository gradeRepository;
private readonly IStudentRepository _studentRepository;
private readonly IStudentSubjectRegistrationRepository _subjectRegistrationRepository;
private readonly IGradeRepository _gradeRepository;

private readonly IStudentDataSource _studentDataSource; // Used to simplify filtering
private readonly IDataLoader _dataLoader;

public SubjectRegistrationProgressValidationService(
IStudentRepository studentRepository,
IStudentSubjectRegistrationRepository subjectRegistrationRepository,
IGradeRepository gradeRepository)
IGradeRepository gradeRepository,
IStudentDataSource studentDatSource,
IDataLoader dataLoader)
{
this.studentRepository = studentRepository;
this.subjectRegistrationRepository = subjectRegistrationRepository;
this.gradeRepository = gradeRepository;
_studentRepository = studentRepository;
_subjectRegistrationRepository = subjectRegistrationRepository;
_gradeRepository = gradeRepository;
_studentDataSource = studentDatSource;
_dataLoader = dataLoader;
}

public async Task<StudentRegistrationProgress> GetRegistrationProgressOfStudentAsync(int studentId, CancellationToken cancellationToken = default)
{
Contract.Requires<ArgumentException>(studentId != default);

var student = await studentRepository.GetObjectAsync(studentId, cancellationToken);
var student = await _studentRepository.GetObjectAsync(studentId, cancellationToken);

await _dataLoader.LoadAsync(student, s => s.SubjectRegistrations, cancellationToken);
await _dataLoader.LoadAllAsync(student.SubjectRegistrations, ssr => ssr.Subject.EducationalAreaRelations, cancellationToken)
.ThenLoadAsync(ear => ear.EducationalArea, cancellationToken);
await _dataLoader.LoadAllAsync(student.SubjectRegistrations, ssr => ssr.Subject.Category, cancellationToken);

return await GetRegistrationProgressOfStudentImplAsync(student, cancellationToken);
}

public async Task<Dictionary<int, StudentRegistrationProgress>> GetRegistrationProgressOfAllStudentsAsync(
StudentSubjectRegistrationProgressListFilter filter,
CancellationToken cancellationToken = default)
{
// Filter students:
// 1. Don't fetch oktava students (they are not subject to progress)
// 2. Fetch based on given filter
var filteredStudents = await _studentDataSource.Data
.WhereIf(filter.StudentId is not null, s => s.Id == filter.StudentId)
.WhereIf(filter.GradeId is not null, s => s.GradeId == filter.GradeId)
.Where(s => (GradeEntry)s.GradeId != GradeEntry.Oktava)
.ToArrayAsync(cancellationToken: cancellationToken);

await _dataLoader.LoadAllAsync(filteredStudents, s => s.SubjectRegistrations, cancellationToken)
.ThenLoadAsync(ssr => ssr.Subject.EducationalAreaRelations, cancellationToken)
.ThenLoadAsync(ear => ear.EducationalArea, cancellationToken);
await _dataLoader.LoadAllAsync(filteredStudents, s => s.SubjectRegistrations, cancellationToken)
.ThenLoadAsync(ssr => ssr.Subject.Category, cancellationToken);

// Get progress of each student and store into dict
var result = new Dictionary<int, StudentRegistrationProgress>(filteredStudents.Length);
foreach (var student in filteredStudents)
{
// Get progress
Contract.Requires<ArgumentException>(student.Id != default);
var registrationProgress = await GetRegistrationProgressOfStudentImplAsync(student, cancellationToken);

// Filter again, based on ValidationState
if (filter.ValidationState is not null &&
filter.ValidationState != registrationProgress.IsRegistrationValid)
{
continue;
}

// Passed filtering, add to result dictP
result.Add(student.Id, registrationProgress);
}

return result;
}

private async Task<StudentRegistrationProgress> GetRegistrationProgressOfStudentImplAsync(Student student, CancellationToken cancellationToken = default)
{
Contract.Requires<OperationFailedException>(student.GradeId != (int)GradeEntry.Oktava, "Student nemá žádný další ročník");

// Logically we want to validate the rules for the next grade
var futureGrade = await gradeRepository.GetObjectAsync((int)((GradeEntry)student.GradeId).NextGrade(), cancellationToken);
var studentsRegistrations = await subjectRegistrationRepository.GetActiveRegistrationsByStudentAsync(studentId, cancellationToken);
// Logically, we want to validate the rules for the next grade
var futureGrade = await _gradeRepository.GetObjectAsync((int)((GradeEntry)student.GradeId).NextGrade(), cancellationToken);
var studentsRegistrations = student.SubjectRegistrations;

Contract.Requires<InvalidOperationException>(studentsRegistrations is not null);
Contract.Requires<InvalidOperationException>(futureGrade is not null);
Expand All @@ -52,7 +116,7 @@ public async Task<StudentRegistrationProgress> GetRegistrationProgressOfStudentA

private StudentLanguageRegistrationProgress GetLanguageRegistrationProgress(
Grade forGrade,
List<StudentSubjectRegistration> studentsRegistrations)
IEnumerable<StudentSubjectRegistration> studentsRegistrations)
{
var doesStudentHaveLanguage = studentsRegistrations
.Any(r => SubjectCategory.IsEntry(r.Subject.Category, SubjectCategoryEntry.ForeignLanguage));
Expand All @@ -64,7 +128,7 @@ private StudentLanguageRegistrationProgress GetLanguageRegistrationProgress(

private StudentHoursPerWeekProgress GetHoursPerWeekProgress(
Grade forGrade,
List<StudentSubjectRegistration> forRegistrations)
IEnumerable<StudentSubjectRegistration> forRegistrations)
{
var amOfHoursExcludingLanguages = forRegistrations
.Where(r => !SubjectCategory.IsEntry(r.Subject.Category, SubjectCategoryEntry.ForeignLanguage))
Expand All @@ -80,7 +144,7 @@ private StudentHoursPerWeekProgress GetHoursPerWeekProgress(

private StudentCsOrCpRegistrationProgress GetCsOrCpRegistrationProgress(
Grade forGrade,
List<StudentSubjectRegistration> forRegistrations)
IEnumerable<StudentSubjectRegistration> forRegistrations)
{
static bool IsRegistrationWithinAreaCsOrCp(StudentSubjectRegistration registration)
=> registration.Subject.EducationalAreas.Any(area =>
Expand Down
Loading

0 comments on commit 729fb1a

Please sign in to comment.