diff --git a/src/main/java/school/hei/haapi/repository/GradeChangeHistoryRepository.java b/src/main/java/school/hei/haapi/repository/GradeChangeHistoryRepository.java index 1a27f53fd..893271216 100644 --- a/src/main/java/school/hei/haapi/repository/GradeChangeHistoryRepository.java +++ b/src/main/java/school/hei/haapi/repository/GradeChangeHistoryRepository.java @@ -1,19 +1,6 @@ package school.hei.haapi.repository; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import school.hei.haapi.model.GradeChangeHistory; -import school.hei.haapi.model.dto.GradeDto; -public interface GradeChangeHistoryRepository extends JpaRepository { - @Query( - """ - SELECT new school.hei.haapi.model.dto.GradeDto(gch.id, gch.grade.student.ref, gch.score) - FROM GradeChangeHistory gch - WHERE gch.grade.id IN :gradeIds - ORDER BY gch.changeInstant ASC - """) - List findByGradeIdsOrderedByChangeInstantAsc(@Param("gradeIds") List gradeIds); -} +public interface GradeChangeHistoryRepository extends JpaRepository {} diff --git a/src/main/java/school/hei/haapi/repository/GradeRepository.java b/src/main/java/school/hei/haapi/repository/GradeRepository.java index aa34f6eee..5f5ecb1b4 100644 --- a/src/main/java/school/hei/haapi/repository/GradeRepository.java +++ b/src/main/java/school/hei/haapi/repository/GradeRepository.java @@ -35,4 +35,20 @@ List getGradesByStudentIdAndCourseId( + " where g.exam.id = :exam_id and g.student.status in :statuses") List getGradesByExamId( @Param("exam_id") String examId, @Param("statuses") List statuses); + + @Query( + """ +SELECT new school.hei.haapi.model.dto.GradeDto(g.id, g.student.ref, CAST(COALESCE(gch.score, g.score) AS double)) +FROM Grade g +LEFT JOIN GradeChangeHistory gch + ON gch.grade.id = g.id + AND gch.changeInstant = ( + SELECT MAX(gch2.changeInstant) + FROM GradeChangeHistory gch2 + WHERE gch2.grade.id = g.id + ) +WHERE g.exam.id = :exam_id AND g.student.status IN :statuses +""") + List getLatestGradesByExamId( + @Param("exam_id") String examId, @Param("statuses") List statuses); } diff --git a/src/main/java/school/hei/haapi/service/GradeService.java b/src/main/java/school/hei/haapi/service/GradeService.java index abc31a844..cbf454a6c 100644 --- a/src/main/java/school/hei/haapi/service/GradeService.java +++ b/src/main/java/school/hei/haapi/service/GradeService.java @@ -4,26 +4,21 @@ import static org.apache.poi.ss.usermodel.Row.MissingCellPolicy.CREATE_NULL_AS_BLANK; import static school.hei.haapi.model.User.Status.ALUMNI; import static school.hei.haapi.model.User.Status.ENABLED; -import static school.hei.haapi.model.exception.ApiException.ExceptionType.SERVER_EXCEPTION; import jakarta.transaction.Transactional; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import school.hei.haapi.endpoint.event.EventProducer; @@ -40,16 +35,15 @@ import school.hei.haapi.model.GroupFlow; import school.hei.haapi.model.dto.GradeDto; import school.hei.haapi.model.dto.GradeImportDto; -import school.hei.haapi.model.exception.ApiException; import school.hei.haapi.model.exception.BadRequestException; import school.hei.haapi.model.exception.NotFoundException; import school.hei.haapi.model.notEntity.UpdateGrade; import school.hei.haapi.model.validator.IsNewGradeChecker; import school.hei.haapi.repository.CourseAssignmentRepository; import school.hei.haapi.repository.ExamRepository; -import school.hei.haapi.repository.GradeChangeHistoryRepository; import school.hei.haapi.repository.GradeRepository; import school.hei.haapi.repository.dao.GradeDao; +import school.hei.haapi.service.utils.XlsxCellsGenerator; import school.hei.haapi.service.utils.excel.ExcelParser; import school.hei.haapi.service.utils.excel.ParseResult; @@ -66,10 +60,9 @@ public class GradeService { private final EventProducer eventProducer; private final GradeMapper gradeMapper; private final ExamRepository examRepository; - private final ExamParticipantService examParticipantService; private static final String GRADE_XLSX_IMPORT_BUCKET_KEY = "/STUDENT_EXAM_GRADE_XLSX_IMPORT/"; - private final GradeChangeHistoryRepository gradeChangeHistoryRepository; + private static final List HEADERS = List.of("ref", "score"); private final PromotionService promotionService; public List getGradesByStudentId(String studentId) { @@ -202,7 +195,7 @@ public ImportGradeResult initStudentExamGradeImportFromXlsx( var skippedRefs = skippedGrades.stream().map(GradeImportDto::getRef).toList(); var gradeInvalidScoreRefs = gradeInvalidScores.stream().map(GradeImportDto::getRef).toList(); - List allInvalidRefs = new ArrayList<>(skippedRefs); + var allInvalidRefs = new ArrayList<>(skippedRefs); allInvalidRefs.addAll(gradeInvalidScoreRefs); importResults = @@ -261,13 +254,10 @@ private List mergeGradeValidRows( var grades = gradeMapper.toDomainList(gradeFiltered, examId); validGrades = gradeMapper.toRestListValidGrade(createParticipantGrade(grades)); } - validGrades = - validGrades.stream() - .sorted( - Comparator.comparing( - GradeValidRow::getRef, Comparator.nullsLast(String::compareTo))) - .toList(); - return validGrades; + return validGrades.stream() + .sorted( + Comparator.comparing(GradeValidRow::getRef, Comparator.nullsLast(String::compareTo))) + .toList(); } private static List mergeGradeInvalidRows( @@ -285,13 +275,10 @@ private static List mergeGradeInvalidRows( allInvalidGrades.addAll(duplicateGradesMapped); allInvalidGrades.addAll(gradeInvalidScoresMapped); - allInvalidGrades = - allInvalidGrades.stream() - .sorted( - Comparator.comparing( - GradeInvalidRow::getRef, Comparator.nullsLast(String::compareTo))) - .toList(); - return allInvalidGrades; + return allInvalidGrades.stream() + .sorted( + Comparator.comparing(GradeInvalidRow::getRef, Comparator.nullsLast(String::compareTo))) + .toList(); } public List checkDuplicateGrade(List parseResult) { @@ -304,34 +291,25 @@ public List checkDuplicateGrade(List parseResult public List filterExistingGrades( List grades, String examId, String comment) { - var savedGrades = gradeRepository.getGradesByExamId(examId, List.of(ENABLED, ALUMNI)); - var savedGradeIds = savedGrades.stream().map(GradeDto::getId).toList(); - var gradeChangeHistories = - gradeChangeHistoryRepository.findByGradeIdsOrderedByChangeInstantAsc(savedGradeIds); - List filterGrades; + var existingGrades = gradeRepository.getLatestGradesByExamId(examId, List.of(ENABLED, ALUMNI)); if (comment == null) { - filterGrades = - grades.stream() - .filter( - grade -> - savedGrades.stream() - .anyMatch(gradeSaved -> grade.getRef().equals(gradeSaved.getRef()))) - .toList(); + return grades.stream() + .filter( + grade -> + existingGrades.stream() + .anyMatch(gradeSaved -> grade.getRef().equals(gradeSaved.getRef()))) + .toList(); } else { - var existingGrades = - Stream.concat(gradeChangeHistories.stream(), savedGrades.stream()).toList(); - filterGrades = - grades.stream() - .filter( - grade -> - existingGrades.stream() - .anyMatch( - existingGrade -> - existingGrade.getRef().equals(grade.getRef()) - && existingGrade.getScore().equals(grade.getScore()))) - .toList(); + return grades.stream() + .filter( + grade -> + existingGrades.stream() + .anyMatch( + existingGrade -> + existingGrade.getRef().equals(grade.getRef()) + && existingGrade.getScore().equals(grade.getScore()))) + .toList(); } - return filterGrades; } public List checkSkippedRows(ParseResult parseResult) { @@ -343,18 +321,7 @@ public List checkSkippedRows(ParseResult parseRe } Object refValue = row.getCell(0, CREATE_NULL_AS_BLANK).getStringCellValue(); Cell scoreCell = row.getCell(1, CREATE_NULL_AS_BLANK); - Double score = null; - - if (scoreCell != null) { - if (scoreCell.getCellType() == NUMERIC) { - score = scoreCell.getNumericCellValue(); - } else { - var stringScore = scoreCell.getStringCellValue().trim(); - if (!stringScore.isBlank()) { - score = Double.valueOf(stringScore); - } - } - } + Double score = extractScoreCell(scoreCell); var ref = refValue != null ? refValue.toString() : null; var invalid = new GradeImportDto(); invalid.setRef(ref); @@ -365,6 +332,17 @@ public List checkSkippedRows(ParseResult parseRe return allInvalids; } + private static Double extractScoreCell(Cell scoreCell) { + if (scoreCell == null) { + return null; + } + if (scoreCell.getCellType() == NUMERIC) { + return scoreCell.getNumericCellValue(); + } + var stringScore = scoreCell.getStringCellValue().trim(); + return stringScore.isBlank() ? null : Double.valueOf(stringScore); + } + private List checkInvalidScore(ParseResult parseResult) { return parseResult.parsedResult().stream() .filter(gradeDto -> gradeDto.getScore() < 0 || gradeDto.getScore() > 20) @@ -380,62 +358,57 @@ private List checkInvalidScore(ParseResult parse private static List mapGradeInvalidRows( List gradeInvalidScores) { - var invalidGrades = new ArrayList(); - gradeInvalidScores.forEach( - gradeImportDto -> - invalidGrades.add( + return gradeInvalidScores.stream() + .map( + gradeImportDto -> new GradeInvalidRow() .ref(gradeImportDto.getRef()) .score( gradeImportDto.getScore() != null ? BigDecimal.valueOf(gradeImportDto.getScore()) : null) - .reason(validateRow(gradeImportDto.getRef(), gradeImportDto.getScore())))); - - return invalidGrades; + .reason(validateRow(gradeImportDto.getRef(), gradeImportDto.getScore()))) + .toList(); } public static List mapExistingGrades( List existingGrades, String comment) { - var invalidGrades = new ArrayList(); if (comment != null) { - existingGrades.forEach( - gradeImportDto -> - invalidGrades.add( + return existingGrades.stream() + .map( + gradeImportDto -> new GradeInvalidRow() .ref(gradeImportDto.getRef()) .score(BigDecimal.valueOf(gradeImportDto.getScore())) .reason( "La note insérée est identique à la note existante. Aucune mise à jour" - + " n’a été effectuée."))); + + " n’a été effectuée.")) + .toList(); } else { - existingGrades.forEach( - gradeImportDto -> - invalidGrades.add( + return existingGrades.stream() + .map( + gradeImportDto -> new GradeInvalidRow() .ref(gradeImportDto.getRef()) .score(BigDecimal.valueOf(gradeImportDto.getScore())) .reason( "L'étudiant(e) a déjà une note pour cet examen. Veuillez choisir l'option" - + " mettre à jour pour modifier."))); + + " mettre à jour pour modifier.")) + .toList(); } - - return invalidGrades; } private static List mapDuplicateGrades(List duplicateGrades) { - var invalidGrades = new ArrayList(); - duplicateGrades.forEach( - gradeImportDto -> - invalidGrades.add( + return duplicateGrades.stream() + .map( + gradeImportDto -> new GradeInvalidRow() .ref(gradeImportDto.getRef()) .score(BigDecimal.valueOf(gradeImportDto.getScore())) .reason( "La réference étudiant(e) est dupliquée, veuillez supprimer les autres pour" - + " ajouter une note."))); - - return invalidGrades; + + " ajouter une note.")) + .toList(); } private static String validateRow(String ref, Double score) { @@ -447,59 +420,8 @@ private static String validateRow(String ref, Double score) { } public byte[] generateGradesTemplate(String examId) { - var existingGrades = gradeRepository.getGradesByExamId(examId, List.of(ENABLED, ALUMNI)); - var gradeIds = existingGrades.stream().map(GradeDto::getId).toList(); - var gradeHistories = - gradeChangeHistoryRepository.findByGradeIdsOrderedByChangeInstantAsc(gradeIds); - List allGrades = - existingGrades.stream() - .map( - existing -> { - var lastGradeChangeHistory = - gradeHistories.stream() - .filter(gradeDto -> Objects.equals(gradeDto.getRef(), existing.getRef())) - .toList(); - if (!lastGradeChangeHistory.isEmpty()) { - return lastGradeChangeHistory.getLast(); - } - return GradeDto.builder() - .id(existing.getId()) - .ref(existing.getRef()) - .score(existing.getScore()) - .build(); - }) - .toList(); - Map studentScoreAndRefs = new HashMap<>(); - for (var grade : allGrades) { - studentScoreAndRefs.put(grade.getRef(), grade.getScore()); - } - var participants = examParticipantService.getExamParticipantsGrade(examId, null, null, null); - try (var workbook = new XSSFWorkbook()) { - var fileName = "note_" + examId; - var sheet = workbook.createSheet(fileName); - var headerRow = sheet.createRow(0); - headerRow.createCell(0).setCellValue("ref"); - headerRow.createCell(1).setCellValue("score"); - int rowIndex = 1; - for (var participant : participants) { - var row = sheet.createRow(rowIndex++); - var ref = participant.getStudent().getRef(); - row.createCell(0).setCellValue(ref); - var score = studentScoreAndRefs.get(ref); - if (score != null) { - row.createCell(1).setCellValue(score); - } else { - row.createCell(1); - } - } - sheet.autoSizeColumn(0); - sheet.autoSizeColumn(1); - try (ByteArrayOutputStream template = new ByteArrayOutputStream()) { - workbook.write(template); - return template.toByteArray(); - } - } catch (IOException e) { - throw new ApiException(SERVER_EXCEPTION, e); - } + var xlsxCellGenerator = new XlsxCellsGenerator(); + var existingGrades = gradeRepository.getLatestGradesByExamId(examId, List.of(ENABLED, ALUMNI)); + return xlsxCellGenerator.apply(existingGrades, HEADERS); } }