diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/FeeController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/FeeController.java index ecaa43fcc..984463591 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/controller/FeeController.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/FeeController.java @@ -92,8 +92,6 @@ public List getFeesByStudentId( @RequestParam PageFromOne page, @RequestParam("page_size") BoundedPageSize pageSize, @RequestParam(required = false) FeeStatusEnum status) { - // Verify pending mpbs before fetching fees so results reflect the current vola state. - // Orchestrated here to avoid circular dependency (MpbsService -> FeeService). mpbsVerificationService.verifyPendingMpbsForStudent(studentId); return feeService.getFeesByStudentId(studentId, page, pageSize, status).stream() .map(feeMapper::toRestFee) diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/GradeController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/GradeController.java index a7017f37f..f1ccb06e9 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/controller/GradeController.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/GradeController.java @@ -144,7 +144,7 @@ public ExamGradeStats getExamGradeStats(@PathVariable(value = "exam_id") String public YearlyResult getYearlyResult( @PathVariable("student_id") String studentId, @PathVariable("student_level") StudentLevel studentLevel) { - return gradeResultService.getLeveledYearlyResultByStudentId(studentLevel, studentId); + return gradeResultService.getYearlyResultByStudentIdAndByLevel(studentId, studentLevel); } @GetMapping("/students/{student_id}/results_summary") diff --git a/src/main/java/school/hei/haapi/model/dto/GroupFlowPeriod.java b/src/main/java/school/hei/haapi/model/dto/GroupFlowPeriod.java new file mode 100644 index 000000000..96f88bab3 --- /dev/null +++ b/src/main/java/school/hei/haapi/model/dto/GroupFlowPeriod.java @@ -0,0 +1,12 @@ +package school.hei.haapi.model.dto; + +import java.time.Instant; +import school.hei.haapi.model.Group; + +public record GroupFlowPeriod(Group group, Instant start, Instant end) { + public boolean contains(Instant instant) { + boolean afterStart = !instant.isBefore(start); + boolean beforeEnd = (end == null) || instant.isBefore(end); + return afterStart && beforeEnd; + } +} diff --git a/src/main/java/school/hei/haapi/model/dto/GroupFlowPeriodCourseAssignment.java b/src/main/java/school/hei/haapi/model/dto/GroupFlowPeriodCourseAssignment.java new file mode 100644 index 000000000..91ffaf574 --- /dev/null +++ b/src/main/java/school/hei/haapi/model/dto/GroupFlowPeriodCourseAssignment.java @@ -0,0 +1,7 @@ +package school.hei.haapi.model.dto; + +import java.util.List; +import school.hei.haapi.model.CourseAssignment; + +public record GroupFlowPeriodCourseAssignment( + GroupFlowPeriod groupFlowPeriod, List courseAssignments) {} diff --git a/src/main/java/school/hei/haapi/repository/CourseAssignmentRepository.java b/src/main/java/school/hei/haapi/repository/CourseAssignmentRepository.java index e0f964f51..7fe2a04b7 100644 --- a/src/main/java/school/hei/haapi/repository/CourseAssignmentRepository.java +++ b/src/main/java/school/hei/haapi/repository/CourseAssignmentRepository.java @@ -6,7 +6,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import school.hei.haapi.endpoint.rest.model.StudentLevel; import school.hei.haapi.model.CourseAssignment; import school.hei.haapi.model.User; @@ -16,10 +15,10 @@ public interface CourseAssignmentRepository extends JpaRepository findAllByGroupId(@Param("groupId") String groupId, Pageable pageable); + @Query("SELECT ca FROM CourseAssignment ca JOIN ca.groups g WHERE g.id = :groupId") + List findAllByGroupId(@Param("groupId") String groupId); + List findAllByMainTeacher(User teacher, Pageable pageable); List findAllByCourseId(String courseId, Pageable pageable); - - List findAllByCourse_StudentLevelAndGroupsIn( - StudentLevel courseStudentLevel, List groupIds); } diff --git a/src/main/java/school/hei/haapi/repository/ExamRepository.java b/src/main/java/school/hei/haapi/repository/ExamRepository.java index 4de787f32..b885cce49 100644 --- a/src/main/java/school/hei/haapi/repository/ExamRepository.java +++ b/src/main/java/school/hei/haapi/repository/ExamRepository.java @@ -1,8 +1,6 @@ package school.hei.haapi.repository; import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -17,23 +15,4 @@ public interface ExamRepository extends JpaRepository { @Query("select e from Exam e where e.courseAssignment.id in :course_assignment_ids ") List findExamsByCourseAssignmentIdIn( @Param("course_assignment_ids") List courseAssignmentIds); - - @Query( - "select e from Exam e join e.courseAssignment.groups g where g.id = :group_id and " - + "e.courseAssignment.id = :course_assignment_id") - Page findExamsByGroupIdAndCourseAssignmentId( - @Param("group_id") String courseId, - @Param("course_assignment_id") String courseAssignmentId, - Pageable pageable); - - @Query( - """ - select distinct u.ref - from Exam e - join e.courseAssignment.groups g - join g.groupFlows gf - join gf.student u - where e.id = :exam_id and gf.student.status = 'ENABLED' - """) - List findStudentRefsByExamId(@Param("exam_id") String examId); } diff --git a/src/main/java/school/hei/haapi/repository/GroupFlowRepository.java b/src/main/java/school/hei/haapi/repository/GroupFlowRepository.java index 5fd403f0f..276a94098 100644 --- a/src/main/java/school/hei/haapi/repository/GroupFlowRepository.java +++ b/src/main/java/school/hei/haapi/repository/GroupFlowRepository.java @@ -1,12 +1,25 @@ package school.hei.haapi.repository; import java.util.List; -import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import school.hei.haapi.endpoint.rest.model.StudentLevel; import school.hei.haapi.model.GroupFlow; +import school.hei.haapi.model.User; @Repository public interface GroupFlowRepository extends JpaRepository { - List findAllByGroupId(String groupId, Sort sortable); + @Query( + """ + SELECT gf FROM GroupFlow gf + JOIN gf.group g + JOIN CourseAssignment ca ON g MEMBER OF ca.groups + JOIN ca.course c + WHERE gf.student = :student + AND c.studentLevel = :level + """) + List findByFlowTypeAndStudentAndLevel( + @Param("student") User student, @Param("level") StudentLevel level); } diff --git a/src/main/java/school/hei/haapi/service/CourseAssignmentService.java b/src/main/java/school/hei/haapi/service/CourseAssignmentService.java index 8cc3b5b69..8a9ba1665 100644 --- a/src/main/java/school/hei/haapi/service/CourseAssignmentService.java +++ b/src/main/java/school/hei/haapi/service/CourseAssignmentService.java @@ -57,6 +57,11 @@ public List getByGroupId( return courseAssignmentRepository.findAllByGroupId(groupId, pageable); } + @Transactional + public List getByGroupId(String groupId) { + return courseAssignmentRepository.findAllByGroupId(groupId); + } + public CourseAssignment getById(String id) { return courseAssignmentRepository.getById(id); } diff --git a/src/main/java/school/hei/haapi/service/CourseResultService.java b/src/main/java/school/hei/haapi/service/CourseResultService.java index d13bc1b92..3e0d42656 100644 --- a/src/main/java/school/hei/haapi/service/CourseResultService.java +++ b/src/main/java/school/hei/haapi/service/CourseResultService.java @@ -5,6 +5,7 @@ import static java.math.MathContext.DECIMAL128; import static java.util.Comparator.comparing; import static java.util.Objects.nonNull; +import static java.util.regex.Pattern.compile; import static school.hei.haapi.endpoint.rest.model.ResultOverviewStatus.INVALIDATED; import static school.hei.haapi.endpoint.rest.model.ResultOverviewStatus.IN_PROGRESS; import static school.hei.haapi.endpoint.rest.model.ResultOverviewStatus.NOT_STARTED; @@ -16,112 +17,131 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import school.hei.haapi.endpoint.rest.mapper.CourseMapper; import school.hei.haapi.endpoint.rest.model.CourseResult; import school.hei.haapi.endpoint.rest.model.CourseResultStatus; import school.hei.haapi.endpoint.rest.model.ResultOverviewStatus; import school.hei.haapi.endpoint.rest.model.StudentLevel; -import school.hei.haapi.model.Course; import school.hei.haapi.model.CourseAssignment; -import school.hei.haapi.model.Exam; -import school.hei.haapi.model.Group; import school.hei.haapi.model.User; +import school.hei.haapi.model.dto.GroupFlowPeriod; +import school.hei.haapi.model.dto.GroupFlowPeriodCourseAssignment; import school.hei.haapi.model.exception.CoursesCreditSumZero; import school.hei.haapi.model.exception.ExamsCoefficientSumZero; import school.hei.haapi.repository.dao.GradeDao; -import school.hei.haapi.service.utils.CollectionUtils; @Service @AllArgsConstructor @Slf4j public class CourseResultService { - private final CourseService courseService; private final GradeDao gradeDao; private final CourseMapper courseMapper; - private final ExamService examService; private final UserService userService; - private final CollectionUtils collectionUtils; + private final CourseAssignmentService courseAssignmentService; + private final GroupFlowService groupFlowService; private static final BigDecimal VALIDATED_YEAR_CREDIT = BigDecimal.valueOf(30); private static final BigDecimal VALIDATED_YEAR_AVERAGE = TEN; - - private List findStudentGroupByExams(User student, List exams) { - return collectionUtils - .filterDistinctByField( - exams.stream() - .map(exam -> student.findGroupAt(exam.getExaminationDate())) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(), - Group::getId) - .stream() - .toList(); - } + private static final Pattern GROUP_TRAILING_DIGITS = compile("\\d+$"); @Transactional - public List getCourseResultsForLevelOfStudent( - StudentLevel level, String studentId) { + public List getCourseResultsByStudentIdAndLevel( + String studentId, StudentLevel level) { var student = userService.getById(studentId); - var coursesForSpecificLevel = courseService.getByStudentLevel(level); - log.info("Course for specific level : {}", coursesForSpecificLevel); - var coursesForSpecificStudent = - collectionUtils.filterDistinctByField( - getCourseInStudentGroup(coursesForSpecificLevel, student), Course::getId); - return coursesForSpecificStudent.stream() - .map(course -> computeCourseResult(course, student)) + var studentGroupFlowsAtLevel = groupFlowService.getStudentGroupFlowAtLevel(student, level); + var studentLatestGroupFlowsAtLevel = findLatestGroupFlowPeriods(studentGroupFlowsAtLevel); + var studentGroupCourseAssignmentsAtLevel = + getGroupsCourseAssignmentsByGroupFlowsAtLevel(studentLatestGroupFlowsAtLevel, level); + return studentGroupCourseAssignmentsAtLevel.stream() + .flatMap( + groupCourseAssignments -> + computeStudentCourseResults(groupCourseAssignments, student).stream()) .sorted(comparing(courseResult -> courseResult.getStatus().ordinal())) .toList(); } - @NotNull - private List getCourseInStudentGroup(List coursesForSpecificLevel, User student) { - return coursesForSpecificLevel.stream() - .map(Course::getCourseAssignments) - .filter(courseAssignments -> studentGroupIsAssignedToCourse(courseAssignments, student)) - .flatMap(courseAssignments -> courseAssignments.stream().map(CourseAssignment::getCourse)) + private List findLatestGroupFlowPeriods(List groupFlowPeriods) { + if (groupFlowPeriods.isEmpty()) { + return List.of(); + } + + var latestPromotion = latestPromotion(groupFlowPeriods); + + return groupFlowPeriods.stream() + .filter( + groupFlowPeriod -> + extractGroupPromotion(groupFlowPeriod.group().getName()).equals(latestPromotion)) .toList(); } - private boolean studentGroupIsAssignedToCourse( - List courseAssignments, User student) { - var courseAssignmentGroupIds = - courseAssignments.stream() - .map(CourseAssignment::getGroups) - .flatMap(groups -> groups.stream().map(Group::getId)) - .toList(); - var courseAssignmentIds = courseAssignments.stream().map(CourseAssignment::getId).toList(); - log.info("All student group flows : {}", student.getGroupFlows()); - var studentGroupIds = - student.getGroupFlows().stream().map(groupFlow -> groupFlow.getGroup().getId()).toList(); - var exams = examService.getExamsByCourseAssignmentIds(courseAssignmentIds); - if (exams.isEmpty()) { - return courseAssignmentGroupIds.stream().anyMatch(studentGroupIds::contains); - } + private String latestPromotion(List groupFlowPeriods) { + return extractGroupPromotion( + groupFlowPeriods.stream() + .max(comparing(GroupFlowPeriod::start)) + .orElseThrow() + .group() + .getName()); + } - var studentExamGroupIds = - findStudentGroupByExams(student, exams).stream().map(Group::getId).toList(); - return courseAssignmentGroupIds.stream().anyMatch(studentExamGroupIds::contains); + private String extractGroupPromotion(String groupName) { + return GROUP_TRAILING_DIGITS.matcher(groupName).replaceAll(""); } - private CourseResult computeCourseResult(Course course, User student) { - var courseExams = examService.getExamsByCourseId(course.getId()); - var studentGrades = gradeDao.getStudentGradesByCourseId(course.getId(), student.getId()); - var examsOfTheCourse = examService.getExamsByCourseId(course.getId()); - var studentGroupInExam = findStudentGroupByExams(student, examsOfTheCourse); - var examOfTheStudent = - examsOfTheCourse.stream() + private List getGroupsCourseAssignmentsByGroupFlowsAtLevel( + List studentLatestGroupFlows, StudentLevel level) { + return studentLatestGroupFlows.stream() + .map( + groupFlowPeriod -> + getGroupCourseAssignmentsByLevelBetweenPeriod(groupFlowPeriod, level)) + .toList(); + } + + private GroupFlowPeriodCourseAssignment getGroupCourseAssignmentsByLevelBetweenPeriod( + GroupFlowPeriod groupFlowPeriod, StudentLevel level) { + var courseAssignments = + courseAssignmentService.getByGroupId(groupFlowPeriod.group().getId()).stream() + .filter( + courseAssignment -> level.equals(courseAssignment.getCourse().getStudentLevel())) .filter( - e -> - e.getCourseAssignment().getGroups().stream() - .anyMatch(studentGroupInExam::contains)) + courseAssignment -> hasExamOrAssignedBeforeLeave(courseAssignment, groupFlowPeriod)) .toList(); - var courseResult = new CourseResult().course(courseMapper.toRest(course)); + return new GroupFlowPeriodCourseAssignment(groupFlowPeriod, courseAssignments); + } + + private boolean hasExamOrAssignedBeforeLeave( + CourseAssignment courseAssignment, GroupFlowPeriod groupFlowPeriod) { + var courseExams = courseAssignment.getExams(); + if (courseExams.isEmpty() && groupFlowPeriod.end() == null) { + return true; + } + return courseExams.stream() + .anyMatch(courseExam -> groupFlowPeriod.contains(courseExam.getExaminationDate())); + } + + private List computeStudentCourseResults( + GroupFlowPeriodCourseAssignment groupCourseAssignments, User student) { + var groupFlowPeriod = groupCourseAssignments.groupFlowPeriod(); + var courseAssignments = groupCourseAssignments.courseAssignments(); + return courseAssignments.stream() + .map( + courseAssignment -> + getStudentCourseResultByCourseAssignmentWithGroupFlowPeriod( + groupFlowPeriod, courseAssignment, student)) + .toList(); + } + + private CourseResult getStudentCourseResultByCourseAssignmentWithGroupFlowPeriod( + GroupFlowPeriod groupFlowPeriod, CourseAssignment courseAssignment, User student) { + var courseExams = courseAssignment.getExams(); + var studentGrades = + gradeDao.getStudentGradesByCourseId(courseAssignment.getCourse().getId(), student.getId()); + var courseResult = new CourseResult().course(courseMapper.toRest(courseAssignment.getCourse())); - if (courseExams.isEmpty()) { + if (groupFlowPeriod.end() != null && courseExams.isEmpty()) { return courseResult.status(CourseResultStatus.NOT_STARTED); } @@ -134,7 +154,7 @@ private CourseResult computeCourseResult(Course course, User student) { if (studentGrades.isEmpty()) { return courseResult.status(CourseResultStatus.IN_PROGRESS); - } else if (studentGrades.size() < examOfTheStudent.size()) { + } else if (studentGrades.size() < courseExams.size()) { return courseResult.status(CourseResultStatus.IN_PROGRESS); } else if (TEN.compareTo(courseResult.getWeightedAverage()) > 0) { return courseResult.status(CourseResultStatus.INCOMPLETE); diff --git a/src/main/java/school/hei/haapi/service/ExamService.java b/src/main/java/school/hei/haapi/service/ExamService.java index 5a7b27697..bc4d97dbf 100644 --- a/src/main/java/school/hei/haapi/service/ExamService.java +++ b/src/main/java/school/hei/haapi/service/ExamService.java @@ -24,15 +24,6 @@ public class ExamService { private final ExamDao examDao; private final ExamValidator validator; - public List getExamsFromAwardedCourseIdAndGroupId( - String groupId, String awardedCourseId, PageFromOne page, BoundedPageSize pageSize) { - Pageable pageable = - PageRequest.of(page.getValue() - 1, pageSize.getValue(), Sort.by(DESC, "examinationDate")); - return examRepository - .findExamsByGroupIdAndCourseAssignmentId(groupId, awardedCourseId, pageable) - .toList(); - } - public List updateOrSaveAll(List exams) { validator.accept(exams); return examRepository.saveAll(exams); diff --git a/src/main/java/school/hei/haapi/service/GradeResultService.java b/src/main/java/school/hei/haapi/service/GradeResultService.java index 476a4817c..69f588025 100644 --- a/src/main/java/school/hei/haapi/service/GradeResultService.java +++ b/src/main/java/school/hei/haapi/service/GradeResultService.java @@ -57,11 +57,13 @@ public class GradeResultService { private static final Duration TRANSCRIPT_GENERATION_TIMEOUT = Duration.ofMinutes(5); private static final Duration TRANSCRIPT_VALIDATION_DURATION = Duration.ofHours(12); - public YearlyResult getLeveledYearlyResultByStudentId(StudentLevel level, String studentId) { - var courseResults = courseResultService.getCourseResultsForLevelOfStudent(level, studentId); + public YearlyResult getYearlyResultByStudentIdAndByLevel(String studentId, StudentLevel level) { + var courseResults = courseResultService.getCourseResultsByStudentIdAndLevel(studentId, level); var yearlyResult = new YearlyResult().level(level); - if (courseResults.isEmpty()) return yearlyResult.status(NOT_STARTED); + if (courseResults.isEmpty()) { + return yearlyResult.status(NOT_STARTED); + } return yearlyResult .weightedAverage(courseResultService.weightedSumOfCourseResults(courseResults).orElse(null)) @@ -74,7 +76,7 @@ public YearlyResult getLeveledYearlyResultByStudentId(StudentLevel level, String public Optional findLeveledYearlyResultByStudentId( StudentLevel level, String studentId) { try { - return Optional.of(getLeveledYearlyResultByStudentId(level, studentId)); + return Optional.of(getYearlyResultByStudentIdAndByLevel(studentId, level)); } catch (CoursesCreditSumZero e) { log.error( "Course results for the level {} of the student id {} coefficient sum is 0", @@ -157,7 +159,7 @@ private static ResultOverviewStatus resultSummaryStatusFromYearlyResults( public YearlyResultGenerationTranscript getYearlyResultTranscript( String studentId, StudentLevel level) { - var studentYearlyResult = getLeveledYearlyResultByStudentId(level, studentId); + var studentYearlyResult = getYearlyResultByStudentIdAndByLevel(studentId, level); if (NOT_STARTED.equals(studentYearlyResult.getStatus())) throw new BadRequestException( diff --git a/src/main/java/school/hei/haapi/service/GroupFlowService.java b/src/main/java/school/hei/haapi/service/GroupFlowService.java index 0bf5d62bb..8effafca7 100644 --- a/src/main/java/school/hei/haapi/service/GroupFlowService.java +++ b/src/main/java/school/hei/haapi/service/GroupFlowService.java @@ -1,5 +1,8 @@ package school.hei.haapi.service; +import static school.hei.haapi.model.GroupFlow.GroupFlowType.JOIN; +import static school.hei.haapi.model.GroupFlow.GroupFlowType.LEAVE; + import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -8,9 +11,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import school.hei.haapi.endpoint.rest.model.CreateGroupFlow; +import school.hei.haapi.endpoint.rest.model.StudentLevel; import school.hei.haapi.model.Group; import school.hei.haapi.model.GroupFlow; import school.hei.haapi.model.User; +import school.hei.haapi.model.dto.GroupFlowPeriod; import school.hei.haapi.model.exception.NotFoundException; import school.hei.haapi.model.validator.GroupFlowValidator; import school.hei.haapi.repository.GroupFlowRepository; @@ -21,7 +26,6 @@ @Service @AllArgsConstructor public class GroupFlowService { - private final GroupFlowRepository repository; private final GroupRepository groupRepository; private final UserRepository userRepository; @@ -77,4 +81,28 @@ private GroupFlow fromCreateGroupFlowsToGroupFlows(CreateGroupFlow toMap) { .groupFlowType(GroupFlow.GroupFlowType.fromValue(toMap.getMoveType().getValue())) .build(); } + + public List getStudentGroupFlowAtLevel(User student, StudentLevel level) { + var groupFlows = repository.findByFlowTypeAndStudentAndLevel(student, level); + var groupFlowsByGroup = groupFlows.stream().collect(Collectors.groupingBy(GroupFlow::getGroup)); + return groupFlowsByGroup.entrySet().stream() + .map(entry -> toGroupFlowPeriod(entry.getKey(), entry.getValue())) + .toList(); + } + + private GroupFlowPeriod toGroupFlowPeriod(Group group, List groupFlows) { + var start = + groupFlows.stream() + .filter(groupFlow -> JOIN.equals(groupFlow.getGroupFlowType())) + .map(GroupFlow::getFlowDatetime) + .min(Instant::compareTo) + .orElse(null); + var end = + groupFlows.stream() + .filter(groupFlow -> LEAVE.equals(groupFlow.getGroupFlowType())) + .map(GroupFlow::getFlowDatetime) + .max(Instant::compareTo) + .orElse(null); + return new GroupFlowPeriod(group, start, end); + } } diff --git a/src/main/java/school/hei/haapi/service/GroupService.java b/src/main/java/school/hei/haapi/service/GroupService.java index dd91ce21d..d461c931e 100644 --- a/src/main/java/school/hei/haapi/service/GroupService.java +++ b/src/main/java/school/hei/haapi/service/GroupService.java @@ -95,10 +95,6 @@ public List saveAll(List cr return groups; } - public List getByUserId(String studentId) { - return groupRepository.findByStudentId(studentId); - } - public List saveDomainGroup(List domainsGroup) { return repository.saveAll(domainsGroup); } diff --git a/src/main/java/school/hei/haapi/service/UserService.java b/src/main/java/school/hei/haapi/service/UserService.java index bfe1f93b6..bb64b687f 100644 --- a/src/main/java/school/hei/haapi/service/UserService.java +++ b/src/main/java/school/hei/haapi/service/UserService.java @@ -19,10 +19,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -35,7 +34,6 @@ import school.hei.haapi.endpoint.event.model.UserUpserted; import school.hei.haapi.endpoint.rest.model.PaymentFrequency; import school.hei.haapi.endpoint.rest.model.Statistics; -import school.hei.haapi.endpoint.rest.model.Student; import school.hei.haapi.endpoint.rest.model.StudentImportValidationResult; import school.hei.haapi.endpoint.rest.model.StudentLevel; import school.hei.haapi.endpoint.rest.model.WorkStudyStatus; @@ -61,7 +59,7 @@ import school.hei.haapi.service.utils.excel.ExcelParser; @Service -@AllArgsConstructor +@RequiredArgsConstructor @Slf4j public class UserService { private final UserRepository userRepository; @@ -79,7 +77,6 @@ public class UserService { private final XlsxCellsGenerator userXlsxCellsGenerator; private final XlsxCellsGenerator eventParticipantXlsxCellsGenerator; private final BucketComponent bucketComponent; - private static final String STUDENT_XLSX_IMPORT_BUCKET_KEY = "/STUDENT_XLSX_IMPORT/"; @Transactional @@ -99,12 +96,14 @@ public void suspendStudentById(String suspendedStudentId) { userManagerDao.updateUserStatusById(SUSPENDED, suspendedStudentId); } + @Transactional public User updateUser(User user, String userId) { var toUpdate = refreshUserById(userId, user); return userRepository.save(toUpdate); } - private User refreshUserById(String userId, User refreshedUser) { + @Transactional + public User refreshUserById(String userId, User refreshedUser) { var userToRefresh = getById(userId); userToRefresh.setAddress(refreshedUser.getAddress()); @@ -198,7 +197,7 @@ public List getByRoleAndStatus(User.Role role, User.Status status) { public byte[] getByRoleAndStatusAsXlsx( User.Role role, User.Status status, Function mapper) { - List users = getByRoleAndStatus(role, status); + var users = getByRoleAndStatus(role, status); XlsxCellsGenerator generator = new XlsxCellsGenerator<>(); List mappedUsers = users.stream().map(mapper).toList(); @@ -376,31 +375,17 @@ public List getByGroupIdWithFilter( .getContent(); } - public List getAllStudentNotDisabled() { - return userRepository.findAllStudentNotDisabled(); - } - public Statistics getStudentsStat() { var studentStatisticsDao = userRepository.getStudentsStatistics(); var alternatingStatisticsDao = workDocumentRepository.getStudentAlternatingStatistics(); return studentStatisticsDao.toRestStatistics(alternatingStatisticsDao); } - public long getStudentsAlternatingSize(List students, WorkStudyStatus workStudyStatus) { - return students.stream() - .filter(student -> Objects.equals(student.getWorkStudyStatus(), workStudyStatus)) - .count(); - } - // Todo: try to move in MonitoringStudentService public List findMonitorsByStudentId(String studentId) { return monitoringStudentService.getMonitorsByStudentId(studentId); } - public List getStudentsByPromotionId(String promotionId) { - return userRepository.findAllStudentsByPromotionId(promotionId); - } - public List getStudentsWithLateFee() { return userRepository.getStudentsWithLateFees(); } diff --git a/src/test/java/school/hei/haapi/integration/GradeIT.java b/src/test/java/school/hei/haapi/integration/GradeIT.java index baa0f36e8..9cdf65ff6 100644 --- a/src/test/java/school/hei/haapi/integration/GradeIT.java +++ b/src/test/java/school/hei/haapi/integration/GradeIT.java @@ -352,18 +352,6 @@ void monitor_get_other_yearly_result_ko() { () -> monitorApi.getYearlyResult(studentTolojanahary.getId(), L1)); } - @Test - void monitor_get_own_yearly_result_transcript_ok() { - setUpCasdoorMonitor(casdoorAuthServiceMock, certificateLoaderMock, monitorOfAxel); - GradesApi monitorApi = new GradesApi(anApiClient(AXEL_MONITOR_TOKEN)); - - assertBadRequestException( - "Cannot generate transcript for this level. This level has not yet been started", - () -> { - monitorApi.getYearlyResultTranscript(studentAxel.getId(), L1); - }); - } - @Test void monitor_get_else_yearly_result_transcript_ko() { setUpCasdoorMonitor(casdoorAuthServiceMock, certificateLoaderMock, monitorOfAxel); @@ -374,17 +362,6 @@ void monitor_get_else_yearly_result_transcript_ko() { () -> monitorApi.getYearlyResultTranscript(studentTolojanahary.getId(), L1)); } - @Test - void manager_get_any_yearly_result() { - GradesApi managerApi = new GradesApi(anApiClient(MANAGER1_TOKEN)); - - assertBadRequestException( - "Cannot generate transcript for this level. This level has not yet been started", - () -> { - managerApi.getYearlyResultTranscript(studentAxel.getId(), L1); - }); - } - @Test void monitor_get_exam_own_grades_ok() throws ApiException { setUpCasdoorMonitor(casdoorAuthServiceMock, certificateLoaderMock, monitorOfAxel); diff --git a/src/test/java/school/hei/haapi/integration/MonitoringStudentIT.java b/src/test/java/school/hei/haapi/integration/MonitoringStudentIT.java index 9705a9f3c..0ac5c050a 100644 --- a/src/test/java/school/hei/haapi/integration/MonitoringStudentIT.java +++ b/src/test/java/school/hei/haapi/integration/MonitoringStudentIT.java @@ -49,7 +49,12 @@ import school.hei.haapi.endpoint.rest.client.ApiClient; import school.hei.haapi.endpoint.rest.client.ApiException; import school.hei.haapi.endpoint.rest.mapper.UserMapper; -import school.hei.haapi.endpoint.rest.model.*; +import school.hei.haapi.endpoint.rest.model.Fee; +import school.hei.haapi.endpoint.rest.model.LinkStudentsByMonitorIdRequest; +import school.hei.haapi.endpoint.rest.model.MonitorStudentLinkStatus; +import school.hei.haapi.endpoint.rest.model.Student; +import school.hei.haapi.endpoint.rest.model.UpdateMonitorStudentLink; +import school.hei.haapi.endpoint.rest.model.UpdateMonitorStudentLinkStatusRequest; import school.hei.haapi.endpoint.rest.security.casdoorAuthentication.config.CertificateLoader; import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; import school.hei.haapi.integration.conf.TestUtils; @@ -78,7 +83,7 @@ private ApiClient anApiClient(String token) { } @BeforeEach - public void setUp() { + void setUp() { setUpCasdoor(casdoorAuthServiceMock, certificateLoaderMock); setUpFreddyMonitorCasdoorUser(casdoorAuthServiceMock); setUpCognito(cognitoComponentMock); @@ -203,7 +208,7 @@ void monitor_follow_students_ko() { List.of(monitor1Link(someStudentsRefsToLinkToAMonitor())))); } - public static List someStudentsRefsToLinkToAMonitor() { + static List someStudentsRefsToLinkToAMonitor() { return List.of(student1().getRef(), student2().getRef()); } diff --git a/src/test/java/school/hei/haapi/integration/PaginationIT.java b/src/test/java/school/hei/haapi/integration/PaginationIT.java index 23e0306d2..125f0f739 100644 --- a/src/test/java/school/hei/haapi/integration/PaginationIT.java +++ b/src/test/java/school/hei/haapi/integration/PaginationIT.java @@ -46,7 +46,7 @@ private ApiClient anApiClient(String token) { } @BeforeEach - public void setUp() throws ApiException { + void setUp() throws ApiException { setUpCasdoor(casdoorAuthServiceMock, certificateLoaderMock); setUpCognito(cognitoComponentMock); setUpEventBridge(eventBridgeClientMock); diff --git a/src/test/java/school/hei/haapi/integration/StudentRetakeExamListIT.java b/src/test/java/school/hei/haapi/integration/StudentRetakeExamListIT.java index ce1e388c8..d9bf80d6f 100644 --- a/src/test/java/school/hei/haapi/integration/StudentRetakeExamListIT.java +++ b/src/test/java/school/hei/haapi/integration/StudentRetakeExamListIT.java @@ -49,7 +49,7 @@ void should_be_see_my_all_exams_INCOMPLETED() throws ApiException { void should_be_see_all_exams_with_all_status() throws ApiException { var api = new RetakeExamApi(anApiClient(MANAGER1_TOKEN)); var results = api.getListStudentRetakeExams(STUDENT1_ID, null).size(); - var expected = 2; + var expected = 3; assertEquals(expected, results); } } diff --git a/src/test/java/school/hei/haapi/integration/test_data/CourseTestData.java b/src/test/java/school/hei/haapi/integration/test_data/CourseTestData.java index b9f009e90..a7794dec5 100644 --- a/src/test/java/school/hei/haapi/integration/test_data/CourseTestData.java +++ b/src/test/java/school/hei/haapi/integration/test_data/CourseTestData.java @@ -1,6 +1,9 @@ package school.hei.haapi.integration.test_data; import static java.util.UUID.randomUUID; +import static school.hei.haapi.endpoint.rest.model.StudentLevel.L1; +import static school.hei.haapi.endpoint.rest.model.StudentLevel.L2; +import static school.hei.haapi.endpoint.rest.model.StudentLevel.L3; import school.hei.haapi.model.Course; @@ -15,6 +18,7 @@ public static Course prog1() { .code(randomUUID().toString()) .name("Algorithmique") .credits(6) + .studentLevel(L1) .totalHours(80) .build(); } @@ -25,6 +29,7 @@ public static Course prog2() { .code(randomUUID().toString()) .name("Programmation Orientée-Objet") .credits(10) + .studentLevel(L1) .totalHours(60) .build(); } @@ -35,6 +40,7 @@ public static Course prog3() { .code(randomUUID().toString()) .name("Implémentation d'API Backend") .credits(6) + .studentLevel(L2) .totalHours(80) .build(); } @@ -45,10 +51,22 @@ public static Course prog4() { .code(randomUUID().toString()) .name("Advanced Programming") .credits(7) + .studentLevel(L2) .totalHours(70) .build(); } + public static Course secu1() { + return Course.builder() + .id(randomUUID().toString()) + .code(randomUUID().toString()) + .name("Cybersecurity") + .credits(6) + .studentLevel(L3) + .totalHours(30) + .build(); + } + public static Course course1Model() { return Course.builder() .id(COURSE1_ID) diff --git a/src/test/java/school/hei/haapi/integration/test_data/GroupTestData.java b/src/test/java/school/hei/haapi/integration/test_data/GroupTestData.java index 0d5f87773..902c6ebd4 100644 --- a/src/test/java/school/hei/haapi/integration/test_data/GroupTestData.java +++ b/src/test/java/school/hei/haapi/integration/test_data/GroupTestData.java @@ -28,6 +28,16 @@ public static Group g2() { .build(); } + public static Group h1() { + return Group.builder() + .id(randomUUID().toString()) + .name("H1") + .ref(randomUUID().toString()) + .attributedColor("blue") + .creationDatetime(Instant.parse("2026-07-28T10:00:00Z")) + .build(); + } + public static GroupFlow createGroupFlow(User student, Group toJoin) { return GroupFlow.builder() .id(randomUUID().toString()) diff --git a/src/test/java/school/hei/haapi/service/CourseResultServiceTest.java b/src/test/java/school/hei/haapi/service/CourseResultServiceTest.java new file mode 100644 index 000000000..196af242b --- /dev/null +++ b/src/test/java/school/hei/haapi/service/CourseResultServiceTest.java @@ -0,0 +1,221 @@ +package school.hei.haapi.service; + +import static java.time.temporal.ChronoUnit.DAYS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static school.hei.haapi.endpoint.rest.model.CourseResultStatus.VALIDATED; +import static school.hei.haapi.endpoint.rest.model.StudentLevel.L1; +import static school.hei.haapi.integration.conf.TestUtils.setUpCasdoor; +import static school.hei.haapi.integration.test_data.CourseTestData.prog1; +import static school.hei.haapi.integration.test_data.CourseTestData.prog3; +import static school.hei.haapi.integration.test_data.CourseTestData.secu1; +import static school.hei.haapi.integration.test_data.ExamTestData.createExam; +import static school.hei.haapi.integration.test_data.GroupTestData.g1; +import static school.hei.haapi.integration.test_data.GroupTestData.g2; +import static school.hei.haapi.integration.test_data.GroupTestData.h1; +import static school.hei.haapi.integration.test_data.StudentTestData.axel; +import static school.hei.haapi.integration.test_data.StudentTestData.tolojanahary; +import static school.hei.haapi.integration.test_data.TeacherTestData.ryan; +import static school.hei.haapi.model.GroupFlow.GroupFlowType.JOIN; +import static school.hei.haapi.model.GroupFlow.GroupFlowType.LEAVE; + +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; +import school.hei.haapi.model.Course; +import school.hei.haapi.model.CourseAssignment; +import school.hei.haapi.model.Exam; +import school.hei.haapi.model.Grade; +import school.hei.haapi.model.Group; +import school.hei.haapi.model.GroupFlow; +import school.hei.haapi.model.User; +import school.hei.haapi.repository.CourseAssignmentRepository; +import school.hei.haapi.repository.CourseRepository; +import school.hei.haapi.repository.ExamRepository; +import school.hei.haapi.repository.GradeRepository; +import school.hei.haapi.repository.GroupFlowRepository; +import school.hei.haapi.repository.GroupRepository; +import school.hei.haapi.repository.UserRepository; + +class CourseResultServiceTest extends FacadeITMockedThirdParties { + @Autowired private CourseRepository courseRepository; + @Autowired private UserRepository userRepository; + @Autowired private GroupRepository groupRepository; + @Autowired private CourseAssignmentRepository courseAssignmentRepository; + @Autowired private GroupFlowRepository groupFlowRepository; + @Autowired private CourseResultService courseResultService; + + private Instant now; + private User studentAxel; + private User repeatingStudentTolojanahary; + private User teacherRyan; + private GroupFlow axelGroupFlow; + private Course anL1Course; + private Course anL2Course; + private Course anL3Course; + private CourseAssignment anL1Assignment; + private CourseAssignment anL2Assignment; + private CourseAssignment anL3Assignment; + private CourseAssignment newL1Assignment; + private Group group1; + private Group group2; + private Group group3; + private GroupFlow tolojanaharyNewGroupFlow; + private GroupFlow tolojanaharyJoinOldGroupFlow; + private GroupFlow tolojanaharyLeaveOldGroupFlow; + private Exam prog1Exam; + private Exam groupHProg1Exam; + private Exam prog3Exam; + private Exam secu1Exam; + private Grade repeatingYearGrade; + @Autowired private ExamService examService; + @Autowired private ExamRepository examRepository; + @Autowired private GradeRepository gradeRepository; + + @BeforeEach + void setUp() { + setUpCasdoor(casdoorAuthServiceMock, certificateLoaderMock); + setUpTestData(); + } + + void setUpTestData() { + now = Instant.now(); + studentAxel = axel(); + repeatingStudentTolojanahary = tolojanahary(); + teacherRyan = ryan(); + group1 = g1(); + group2 = g2(); + group3 = h1(); + anL1Course = prog1(); + anL2Course = prog3(); + anL3Course = secu1(); + axelGroupFlow = + GroupFlow.builder() + .student(studentAxel) + .group(group1) + .groupFlowType(JOIN) + .flowDatetime(now.minus(365, DAYS)) + .build(); + tolojanaharyJoinOldGroupFlow = + GroupFlow.builder() + .student(repeatingStudentTolojanahary) + .group(group1) + .groupFlowType(JOIN) + .flowDatetime(now.minus(365, DAYS)) + .build(); + tolojanaharyLeaveOldGroupFlow = + GroupFlow.builder() + .student(repeatingStudentTolojanahary) + .group(group1) + .groupFlowType(LEAVE) + .flowDatetime(now.minus(50, DAYS)) + .build(); + tolojanaharyNewGroupFlow = + GroupFlow.builder() + .student(repeatingStudentTolojanahary) + .group(group3) + .groupFlowType(JOIN) + .flowDatetime(now.minus(40, DAYS)) + .build(); + anL1Assignment = + CourseAssignment.builder() + .course(anL1Course) + .groups(List.of(group1, group2)) + .mainTeacher(teacherRyan) + .build(); + newL1Assignment = + CourseAssignment.builder() + .course(anL1Course) + .groups(List.of(group3)) + .mainTeacher(teacherRyan) + .build(); + anL2Assignment = + CourseAssignment.builder() + .course(anL2Course) + .groups(List.of(group1, group2)) + .mainTeacher(teacherRyan) + .build(); + anL3Assignment = + CourseAssignment.builder() + .course(anL3Course) + .groups(List.of(group1, group2)) + .mainTeacher(teacherRyan) + .build(); + + prog1Exam = createExam(now.minus(200, DAYS), anL1Assignment); + groupHProg1Exam = createExam(now.minus(10, DAYS), newL1Assignment); + prog3Exam = createExam(now.minus(200, DAYS), anL2Assignment); + secu1Exam = createExam(now.minus(200, DAYS), anL3Assignment); + repeatingYearGrade = + Grade.builder() + .student(repeatingStudentTolojanahary) + .exam(groupHProg1Exam) + .score(12.) + .build(); + + userRepository.saveAll(List.of(studentAxel, repeatingStudentTolojanahary, teacherRyan)); + groupRepository.saveAll(List.of(group1, group2, group3)); + courseRepository.saveAll(List.of(anL1Course, anL2Course, anL3Course)); + courseAssignmentRepository.saveAll( + List.of(anL1Assignment, newL1Assignment, anL2Assignment, anL3Assignment)); + examRepository.saveAll(List.of(prog1Exam, prog3Exam, secu1Exam, groupHProg1Exam)); + gradeRepository.save(repeatingYearGrade); + groupFlowRepository.saveAll( + List.of( + axelGroupFlow, + tolojanaharyJoinOldGroupFlow, + tolojanaharyLeaveOldGroupFlow, + tolojanaharyNewGroupFlow)); + } + + @AfterEach + void tearDown() { + courseAssignmentRepository.deleteAll( + List.of(anL1Assignment, newL1Assignment, anL2Assignment, anL3Assignment)); + courseRepository.deleteAll(List.of(anL1Course, anL2Course, anL3Course)); + groupFlowRepository.deleteAll( + List.of( + axelGroupFlow, + tolojanaharyJoinOldGroupFlow, + tolojanaharyLeaveOldGroupFlow, + tolojanaharyNewGroupFlow)); + groupRepository.deleteAll(List.of(group1, group2, group3)); + userRepository.deleteAll(List.of(studentAxel, repeatingStudentTolojanahary, teacherRyan)); + } + + @Test + void getCourseResultsByStudentIdAndLevel_forPassingStudent_ok() { + var actualCourseResults = + courseResultService.getCourseResultsByStudentIdAndLevel(studentAxel.getId(), L1); + + assertFalse(actualCourseResults.isEmpty()); + assertTrue( + actualCourseResults.stream() + .allMatch(courseResult -> courseResult.getCourse().getLevel() == L1)); + } + + @Test + void getCourseResultsByStudentIdAndLevel_forRepeatingStudent_returns_itsMostRecentGrade() { + var actualCourseResults = + courseResultService.getCourseResultsByStudentIdAndLevel( + repeatingStudentTolojanahary.getId(), L1); + + assertFalse(actualCourseResults.isEmpty()); + assertTrue(actualCourseResults.stream().allMatch(r -> r.getCourse().getLevel() == L1)); + assertTrue( + actualCourseResults.stream() + .allMatch( + courseResult -> + courseAssignmentRepository.findAllByGroupId(group3.getId()).stream() + .map(courseAssignment -> courseAssignment.getCourse().getId()) + .toList() + .contains(courseResult.getCourse().getId()))); + assertEquals(12., actualCourseResults.getFirst().getWeightedAverage().doubleValue()); + assertEquals(VALIDATED, actualCourseResults.getFirst().getStatus()); + } +} diff --git a/src/test/java/school/hei/haapi/service/GradeResultServiceTest.java b/src/test/java/school/hei/haapi/service/GradeResultServiceTest.java index 66f3b5664..422128c52 100644 --- a/src/test/java/school/hei/haapi/service/GradeResultServiceTest.java +++ b/src/test/java/school/hei/haapi/service/GradeResultServiceTest.java @@ -1,6 +1,7 @@ package school.hei.haapi.service; import static java.time.Instant.now; +import static java.time.Instant.parse; import static java.util.Optional.empty; import static java.util.UUID.randomUUID; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -37,6 +38,8 @@ import java.net.URI; import java.net.URL; import java.time.Duration; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -62,18 +65,21 @@ import school.hei.haapi.model.Promotion; import school.hei.haapi.model.User; import school.hei.haapi.model.YearlyResultGenerationRequest; +import school.hei.haapi.model.dto.GroupFlowPeriod; import school.hei.haapi.model.exception.CoursesCreditSumZero; import school.hei.haapi.repository.YearlyResultGenerationRequestRepository; import school.hei.haapi.repository.dao.GradeDao; import school.hei.haapi.service.event.YearlyResultTranscriptGenerationService; import school.hei.haapi.service.utils.Base64Converter; import school.hei.haapi.service.utils.ClassPathResourceResolver; -import school.hei.haapi.service.utils.CollectionUtils; import school.hei.haapi.service.utils.HtmlParser; import school.hei.haapi.service.utils.PdfRenderer; @Slf4j class GradeResultServiceTest { + private final PromotionService promotionService = mock(); + private final CourseAssignmentService courseAssignmentService = mock(); + private final GroupFlowService groupFlowService = mock(); private final GradeDao gradeDao = mock(); private final CourseService courseService = mock(); private final ExamService examService = mock(); @@ -94,12 +100,7 @@ class GradeResultServiceTest { private final GradeResultService subject = new GradeResultService( new CourseResultService( - courseService, - gradeDao, - new CourseMapper(), - examService, - userService, - new CollectionUtils()), + gradeDao, new CourseMapper(), userService, courseAssignmentService, groupFlowService), yearlyResultGenerationService, bucketComponent, userService, @@ -134,50 +135,53 @@ private static Promotion promotion() { private static final String SECU3_COURSE_ASSIGNMENT_ID = "secu3-ca"; private static final String L2_COURSE_ASSIGNMENT_ID = "l2-ca"; private static final String L3_COURSE_ASSIGNMENT_ID = "l3-ca"; - private static final String BAD_COURSE_ASSIGNMENT_ID = "bad-ca"; private static User teacher() { return mockUser(TEACHER_ID); } private static CourseAssignment mgt1CourseAssignment() { - return mockCourseAssignment(MGT1_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(mgt1Course(), teacher(), List.of(mgt1Exam())); } private static CourseAssignment prog1CourseAssignment() { - return mockCourseAssignment(PROG1_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(prog1Course(), teacher(), List.of(prog1Exam())); } private static CourseAssignment donne1CourseAssignment() { - return mockCourseAssignment(DONNE1_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(donne1Course(), teacher(), List.of(donnees1Exam())); } private static CourseAssignment web1CourseAssignment() { - return mockCourseAssignment(WEB1_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(web1Course(), teacher(), List.of(web1Exam())); } private static CourseAssignment sys1CourseAssignment() { - return mockCourseAssignment(SYS1_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(sys1Course(), teacher(), List.of(sys1Exam())); } private static CourseAssignment lv1CourseAssignment() { - return mockCourseAssignment(LV1_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(lv1Course(), teacher(), List.of(lv1Exam())); } private static CourseAssignment secu3CourseAssignment() { - return mockCourseAssignment(SECU3_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(secu3Course(), teacher(), List.of()); } private static CourseAssignment l2CourseAssignment() { - return mockCourseAssignment(L2_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(l2Course(), teacher(), List.of(l2Exam())); } private static CourseAssignment l3CourseAssignment() { - return mockCourseAssignment(L3_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(l3Course(), teacher(), List.of(l3Exam())); + } + + private static CourseAssignment m1CourseAssignment() { + return mockCourseAssignment(m1Course(), teacher(), List.of(m1Exam())); } private static CourseAssignment badCourseAssignment() { - return mockCourseAssignment(BAD_COURSE_ASSIGNMENT_ID, teacher()); + return mockCourseAssignment(badCourse(), teacher(), List.of(badExam())); } private static final String MGT1_EXAM_ID = "mgt1 exam"; @@ -188,42 +192,47 @@ private static CourseAssignment badCourseAssignment() { private static final String LV1_EXAM_ID = "lv1 exam"; private static final String L2_EXAM_ID = "l2 exam"; private static final String L3_EXAM_ID = "l3 exam"; + private static final String M1_EXAM_ID = "m1 exam"; private static final String BAD_EXAM_ID = "bad exam"; private static Exam mgt1Exam() { - return mockExam(MGT1_EXAM_ID, 1, 1, mgt1CourseAssignment()); + return mockExam(MGT1_EXAM_ID, 1, 1); } private static Exam prog1Exam() { - return mockExam(PROG1_EXAM_ID, 1, 1, prog1CourseAssignment()); + return mockExam(PROG1_EXAM_ID, 1, 1); } private static Exam donnees1Exam() { - return mockExam(DONNEES1_EXAM_ID, 1, 1, donne1CourseAssignment()); + return mockExam(DONNEES1_EXAM_ID, 1, 1); } private static Exam web1Exam() { - return mockExam(WEB1_EXAM_ID, 1, 1, web1CourseAssignment()); + return mockExam(WEB1_EXAM_ID, 1, 1); } private static Exam sys1Exam() { - return mockExam(SYS1_EXAM_ID, 1, 1, sys1CourseAssignment()); + return mockExam(SYS1_EXAM_ID, 1, 1); } private static Exam lv1Exam() { - return mockExam(LV1_EXAM_ID, 1, 1, lv1CourseAssignment()); + return mockExam(LV1_EXAM_ID, 1, 1); } private static Exam l2Exam() { - return mockExam(L2_EXAM_ID, 1, 1, l2CourseAssignment()); + return mockExam(L2_EXAM_ID, 1, 1); } private static Exam l3Exam() { - return mockExam(L3_EXAM_ID, 1, 1, l3CourseAssignment()); + return mockExam(L3_EXAM_ID, 1, 1); + } + + private static Exam m1Exam() { + return mockExam(M1_EXAM_ID, 1, 1); } private static Exam badExam() { - return mockExam(BAD_EXAM_ID, 0, 1, badCourseAssignment()); + return mockExam(BAD_EXAM_ID, 0, 1); } private static Group group() { @@ -337,6 +346,9 @@ private static Grade student3GradeForBadExam() { private static final String L3_COURSE_ID = "l3"; private static final String L3_COURSE_CODE = "L3"; private static final String L3_COURSE_NAME = "Cours L3"; + private static final String M1_COURSE_ID = "m1"; + private static final String M1_COURSE_CODE = "m1"; + private static final String M1_COURSE_NAME = "Cours m1"; private static final String BAD1_COURSE_ID = "bad course"; private static final String BAD1_COURSE_CODE = "bad course"; private static final String BAD1_COURSE_NAME = "Bad course"; @@ -345,56 +357,57 @@ private static GroupFlow groupFlow() { return GroupFlow.builder().group(group()).build(); } - private static User student1WithGroupFlow() { - return User.builder().id(STUDENT1_ID).groupFlows(List.of(groupFlow())).build(); + private static GroupFlowPeriod groupFLowPeriod() { + return new GroupFlowPeriod( + group(), parse("2021-02-01T00:00:00Z"), parse("2029-01-01T00:00:00Z")); + } + + private static GroupFlowPeriod groupFLowPeriodWithNullEnd() { + return new GroupFlowPeriod(group(), parse("2021-02-01T00:00:00Z"), null); } private static Course mgt1Course() { - return mockCourse( - MGT1_COURSE_ID, MGT1_COURSE_CODE, MGT1_COURSE_NAME, 8, mgt1CourseAssignment(), L1); + return mockCourse(MGT1_COURSE_ID, MGT1_COURSE_CODE, MGT1_COURSE_NAME, 8, L1); } private static Course prog1Course() { - return mockCourse( - PROG1_COURSE_ID, PROG1_COURSE_CODE, PROG1_COURSE_NAME, 12, prog1CourseAssignment(), L1); + return mockCourse(PROG1_COURSE_ID, PROG1_COURSE_CODE, PROG1_COURSE_NAME, 12, L1); } private static Course donne1Course() { - return mockCourse( - DONNE1_COURSE_ID, DONNE1_COURSE_CODE, DONNE1_COURSE_NAME, 8, donne1CourseAssignment(), L1); + return mockCourse(DONNE1_COURSE_ID, DONNE1_COURSE_CODE, DONNE1_COURSE_NAME, 8, L1); } private static Course web1Course() { - return mockCourse( - WEB1_COURSE_ID, WEB1_COURSE_CODE, WEB1_COURSE_NAME, 12, web1CourseAssignment(), L1); + return mockCourse(WEB1_COURSE_ID, WEB1_COURSE_CODE, WEB1_COURSE_NAME, 12, L1); } private static Course sys1Course() { - return mockCourse( - SYS1_COURSE_ID, SYS1_COURSE_CODE, SYS1_COURSE_NAME, 12, sys1CourseAssignment(), L1); + return mockCourse(SYS1_COURSE_ID, SYS1_COURSE_CODE, SYS1_COURSE_NAME, 12, L1); } private static Course lv1Course() { - return mockCourse( - LV1_COURSE_ID, LV1_COURSE_CODE, LV1_COURSE_NAME, 8, lv1CourseAssignment(), L1); + return mockCourse(LV1_COURSE_ID, LV1_COURSE_CODE, LV1_COURSE_NAME, 8, L1); } private static Course secu3Course() { - return mockCourse( - SECU3_COURSE_ID, SECU3_COURSE_CODE, SECU3_COURSE_NAME, 8, secu3CourseAssignment(), M1); + return mockCourse(SECU3_COURSE_ID, SECU3_COURSE_CODE, SECU3_COURSE_NAME, 8, M1); } private static Course l2Course() { - return mockCourse(L2_COURSE_ID, L2_COURSE_CODE, L2_COURSE_NAME, 60, l2CourseAssignment(), L2); + return mockCourse(L2_COURSE_ID, L2_COURSE_CODE, L2_COURSE_NAME, 60, L2); } private static Course l3Course() { - return mockCourse(L3_COURSE_ID, L3_COURSE_CODE, L3_COURSE_NAME, 60, l3CourseAssignment(), L3); + return mockCourse(L3_COURSE_ID, L3_COURSE_CODE, L3_COURSE_NAME, 60, L3); } private static Course badCourse() { - return mockCourse( - BAD1_COURSE_ID, BAD1_COURSE_CODE, BAD1_COURSE_NAME, 0, badCourseAssignment(), L1); + return mockCourse(BAD1_COURSE_ID, BAD1_COURSE_CODE, BAD1_COURSE_NAME, 0, L1); + } + + private static Course m1Course() { + return mockCourse(M1_COURSE_ID, M1_COURSE_CODE, M1_COURSE_NAME, 8, M1); } @BeforeEach @@ -408,7 +421,7 @@ void setUp() { when(student1.findCurrentGroup()).thenReturn(Optional.of(group())); doReturn(List.of(groupFlow())).when(student1).getGroupFlows(); doReturn(List.of(groupFlow())).when(student2).getGroupFlows(); - doReturn(List.of(groupFlow())).when(student1).getGroupFlows(); + doReturn(List.of(groupFlow())).when(student3).getGroupFlows(); // Mock student Ids when(student2.getId()).thenReturn(STUDENT2_ID); @@ -515,13 +528,28 @@ void setUp() { when(examService.getExamsByCourseId(eq(SECU3_COURSE_ID))).thenReturn(List.of()); when(examService.getExamsByCourseId(L2_COURSE_ID)).thenReturn(List.of(l2Exam())); when(examService.getExamsByCourseId(L3_COURSE_ID)).thenReturn(List.of(l3Exam())); + when(promotionService.getAllStudentPromotions(anyString())).thenReturn(new LinkedHashSet<>()); + when(groupFlowService.getStudentGroupFlowAtLevel(any(), any())) + .thenReturn(Collections.singletonList(groupFLowPeriod())); + when(courseAssignmentService.getByGroupId(group().getId())) + .thenReturn( + List.of( + mgt1CourseAssignment(), + prog1CourseAssignment(), + donne1CourseAssignment(), + web1CourseAssignment(), + sys1CourseAssignment(), + lv1CourseAssignment(), + secu3CourseAssignment(), + l2CourseAssignment(), + l3CourseAssignment())); } @Test void correct_result_yearly_result_student1_L1_validate() throws CoursesCreditSumZero { var targetLevel = L1; - var result = subject.getLeveledYearlyResultByStudentId(targetLevel, student1.getId()); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT1_ID, targetLevel); assertEquals(targetLevel, result.getLevel()); assertEquals(60., result.getObtainedCredits().doubleValue()); @@ -535,7 +563,7 @@ void correct_result_yearly_result_student1_L1_validate() throws CoursesCreditSum void correct_result_yearly_result_student2_L1_invalidate() throws CoursesCreditSumZero { var targetLevel = L1; - var result = subject.getLeveledYearlyResultByStudentId(targetLevel, STUDENT2_ID); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT2_ID, targetLevel); assertEquals(targetLevel, result.getLevel()); assertEquals(20., result.getObtainedCredits().doubleValue()); @@ -548,8 +576,10 @@ void correct_result_yearly_result_student2_L1_invalidate() throws CoursesCreditS @Test void correct_result_yearly_result_student2_M1_notStated() throws CoursesCreditSumZero { var targetLevel = M1; - - var result = subject.getLeveledYearlyResultByStudentId(targetLevel, STUDENT2_ID); + when(groupFlowService.getStudentGroupFlowAtLevel(any(), any())) + .thenReturn(Collections.singletonList(groupFLowPeriodWithNullEnd())); + when(courseAssignmentService.getByGroupId(any())).thenReturn(List.of(m1CourseAssignment())); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT2_ID, targetLevel); assertEquals(targetLevel, result.getLevel()); assertEquals(0, result.getObtainedCredits().doubleValue()); @@ -564,7 +594,7 @@ void correct_result_yearly_result_student2_M1_notStated() throws CoursesCreditSu void correct_result_yearly_result_student3_L1_inProgress() throws CoursesCreditSumZero { var targetLevel = L1; - var result = subject.getLeveledYearlyResultByStudentId(targetLevel, STUDENT3_ID); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT3_ID, targetLevel); assertEquals(targetLevel, result.getLevel()); assertEquals(52., result.getObtainedCredits().doubleValue()); @@ -580,7 +610,7 @@ void correct_result_yearly_result_student3_L1_inProgress() throws CoursesCreditS @Test void generate_result_pdf_okay() throws CoursesCreditSumZero { - var result = subject.getLeveledYearlyResultByStudentId(L1, STUDENT1_ID); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT1_ID, L1); var resultFile = yearlyResultGenerationService.generateYearlyResultTranscript(student1, result); assertTrue(resultFile.isFile()); } @@ -595,15 +625,15 @@ void generate_result_summary_pdf_okay() { @Test void generate_result_pdf_year_in_progress_okay() throws CoursesCreditSumZero { - var result = subject.getLeveledYearlyResultByStudentId(L1, STUDENT3_ID); - var resultFile = yearlyResultGenerationService.generateYearlyResultTranscript(student1, result); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT3_ID, L1); + var resultFile = yearlyResultGenerationService.generateYearlyResultTranscript(student3, result); assertTrue(resultFile.isFile()); } @Test void correct_result_yearly_result_M2_empty_notStarted() { var expectedLevel = M2; - var yearlyResult = subject.getLeveledYearlyResultByStudentId(expectedLevel, STUDENT1_ID); + var yearlyResult = subject.getYearlyResultByStudentIdAndByLevel(STUDENT1_ID, expectedLevel); assertEquals(expectedLevel, yearlyResult.getLevel()); assertEquals(NOT_STARTED, yearlyResult.getStatus()); @@ -645,24 +675,26 @@ void correct_result_result_summary_student3_in_progress() { @Test void yearly_result_with_course_credits_sum_zero_ko() { - when(gradeDao.getStudentGradesByCourseId(MGT1_COURSE_ID, STUDENT1_ID)) - .thenReturn(List.of(student1Mgt1Grade())); - when(examService.getExamsByCourseId(BAD1_COURSE_ID)).thenReturn(List.of(mgt1Exam())); - when(courseService.getByStudentLevel(eq(L1))).thenReturn(List.of(badCourse())); + when(gradeDao.getStudentGradesByCourseId(BAD1_COURSE_ID, STUDENT1_ID)) + .thenReturn(List.of((student3GradeForBadExam()))); + when(groupFlowService.getStudentGroupFlowAtLevel(any(), any())) + .thenReturn(Collections.singletonList(groupFLowPeriod())); + when(courseAssignmentService.getByGroupId(any())).thenReturn(List.of(badCourseAssignment())); assertThrows( CoursesCreditSumZero.class, - () -> subject.getLeveledYearlyResultByStudentId(L1, STUDENT1_ID)); + () -> subject.getYearlyResultByStudentIdAndByLevel(STUDENT1_ID, L1)); } @Test void course_result_with_exams_coefficient_sum_zero_is_inProgress() { when(gradeDao.getStudentGradesByCourseId(MGT1_COURSE_ID, STUDENT1_ID)) .thenReturn(List.of(student3GradeForBadExam())); - when(examService.getExamsByCourseId(MGT1_COURSE_ID)).thenReturn(List.of(badExam())); - when(courseService.getByStudentLevel(eq(L1))).thenReturn(List.of(mgt1Course())); + when(groupFlowService.getStudentGroupFlowAtLevel(any(), any())) + .thenReturn(Collections.singletonList(groupFLowPeriod())); + when(courseAssignmentService.getByGroupId(any())).thenReturn(List.of(mgt1CourseAssignment())); - var result = subject.getLeveledYearlyResultByStudentId(L1, STUDENT1_ID); + var result = subject.getYearlyResultByStudentIdAndByLevel(STUDENT1_ID, L1); assertEquals(NOT_STARTED, result.getStatus()); assertNull(result.getWeightedAverage()); @@ -742,7 +774,8 @@ void yearly_result_generation_should_return_bad_request_when_level_in_notStarted @Test void yearly_result_event_handler_ok() { - YearlyResult student1YearlyResult = subject.getLeveledYearlyResultByStudentId(L1, STUDENT1_ID); + YearlyResult student1YearlyResult = + subject.getYearlyResultByStudentIdAndByLevel(STUDENT1_ID, L1); when(userService.getById(anyString())).thenReturn(student1); when(yearlyResultGenerationRequestRepository.save(any())).thenAnswer(e -> e.getArgument(0)); @@ -757,13 +790,12 @@ void yearly_result_event_handler_ok() { }); } - private static Exam mockExam( - String id, int coefficientNumerator, int coefficientDenominator, CourseAssignment ca) { + private static Exam mockExam(String id, int coefficientNumerator, int coefficientDenominator) { return Exam.builder() .id(id) .coefficientNumerator(coefficientNumerator) .coefficientDenominator(coefficientDenominator) - .courseAssignment(ca) + .examinationDate(parse("2025-01-01T10:00:00Z")) .build(); } @@ -798,21 +830,23 @@ private static Grade mockGrade(Exam exam, double score) { } private static Course mockCourse( - String id, String code, String name, int credits, CourseAssignment ca, StudentLevel level) { - var course = - Course.builder() - .id(id) - .courseAssignments(List.of(ca)) - .code(code) - .name(name) - .credits(credits) - .studentLevel(level) - .build(); - ca.setCourse(course); - return course; - } - - private static CourseAssignment mockCourseAssignment(String id, User teacher) { - return CourseAssignment.builder().id(id).mainTeacher(teacher).groups(List.of(group())).build(); + String id, String code, String name, int credits, StudentLevel level) { + return Course.builder() + .id(id) + .code(code) + .name(name) + .credits(credits) + .studentLevel(level) + .build(); + } + + private static CourseAssignment mockCourseAssignment( + Course course, User teacher, List exams) { + return CourseAssignment.builder() + .course(course) + .mainTeacher(teacher) + .groups(List.of(group())) + .exams(exams) + .build(); } } diff --git a/src/test/java/school/hei/haapi/service/TranscriptGenerationTest.java b/src/test/java/school/hei/haapi/service/TranscriptGenerationTest.java index 0b286defa..aae6a4b97 100644 --- a/src/test/java/school/hei/haapi/service/TranscriptGenerationTest.java +++ b/src/test/java/school/hei/haapi/service/TranscriptGenerationTest.java @@ -47,7 +47,7 @@ void get_generated_transcript_ok() throws MalformedURLException { yearlyResultGenerationRequestRepository.saveAll( List.of(generatingRequest(), availableRequest(savedAvailableFileInfo))); - when(courseResultServiceMock.getCourseResultsForLevelOfStudent(any(), any())) + when(courseResultServiceMock.getCourseResultsByStudentIdAndLevel(any(), any())) .thenReturn(List.of(mgt1CourseResult(), prog1CourseResult(), sys1CourseResult())); when(userServiceMock.getById(any())) .thenReturn(User.builder().id(STUDENT_ID).ref(STUDENT_REF).build());