Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<GradeChangeHistory, String> {
@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<GradeDto> findByGradeIdsOrderedByChangeInstantAsc(@Param("gradeIds") List<String> gradeIds);
}
public interface GradeChangeHistoryRepository extends JpaRepository<GradeChangeHistory, String> {}
16 changes: 16 additions & 0 deletions src/main/java/school/hei/haapi/repository/GradeRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,20 @@ List<Grade> getGradesByStudentIdAndCourseId(
+ " where g.exam.id = :exam_id and g.student.status in :statuses")
List<GradeDto> getGradesByExamId(
@Param("exam_id") String examId, @Param("statuses") List<User.Status> 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<GradeDto> getLatestGradesByExamId(
@Param("exam_id") String examId, @Param("statuses") List<User.Status> statuses);
}
202 changes: 62 additions & 140 deletions src/main/java/school/hei/haapi/service/GradeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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<String> HEADERS = List.of("ref", "score");
private final PromotionService promotionService;

public List<Grade> getGradesByStudentId(String studentId) {
Expand Down Expand Up @@ -202,7 +195,7 @@ public ImportGradeResult initStudentExamGradeImportFromXlsx(

var skippedRefs = skippedGrades.stream().map(GradeImportDto::getRef).toList();
var gradeInvalidScoreRefs = gradeInvalidScores.stream().map(GradeImportDto::getRef).toList();
List<String> allInvalidRefs = new ArrayList<>(skippedRefs);
var allInvalidRefs = new ArrayList<>(skippedRefs);
allInvalidRefs.addAll(gradeInvalidScoreRefs);

importResults =
Expand Down Expand Up @@ -261,13 +254,10 @@ private List<GradeValidRow> 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<GradeInvalidRow> mergeGradeInvalidRows(
Expand All @@ -285,13 +275,10 @@ private static List<GradeInvalidRow> 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<GradeImportDto> checkDuplicateGrade(List<GradeImportDto> parseResult) {
Expand All @@ -304,34 +291,25 @@ public List<GradeImportDto> checkDuplicateGrade(List<GradeImportDto> parseResult

public List<GradeImportDto> filterExistingGrades(
List<GradeImportDto> 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<GradeImportDto> 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<GradeImportDto> checkSkippedRows(ParseResult<GradeImportDto> parseResult) {
Expand All @@ -343,18 +321,7 @@ public List<GradeImportDto> checkSkippedRows(ParseResult<GradeImportDto> 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);
Expand All @@ -365,6 +332,17 @@ public List<GradeImportDto> checkSkippedRows(ParseResult<GradeImportDto> 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<GradeImportDto> checkInvalidScore(ParseResult<GradeImportDto> parseResult) {
return parseResult.parsedResult().stream()
.filter(gradeDto -> gradeDto.getScore() < 0 || gradeDto.getScore() > 20)
Expand All @@ -380,62 +358,57 @@ private List<GradeImportDto> checkInvalidScore(ParseResult<GradeImportDto> parse

private static List<GradeInvalidRow> mapGradeInvalidRows(
List<GradeImportDto> gradeInvalidScores) {
var invalidGrades = new ArrayList<GradeInvalidRow>();
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<GradeInvalidRow> mapExistingGrades(
List<GradeImportDto> existingGrades, String comment) {
var invalidGrades = new ArrayList<GradeInvalidRow>();
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<GradeInvalidRow> mapDuplicateGrades(List<GradeImportDto> duplicateGrades) {
var invalidGrades = new ArrayList<GradeInvalidRow>();
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) {
Expand All @@ -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<GradeDto> 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<String, Double> 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<GradeDto>();
var existingGrades = gradeRepository.getLatestGradesByExamId(examId, List.of(ENABLED, ALUMNI));
return xlsxCellGenerator.apply(existingGrades, HEADERS);
}
}
Loading