diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index 080ff17d1a..46bba3fae8 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -28,6 +28,7 @@ import se.su.dsv.scipro.file.ProjectFileRepository; import se.su.dsv.scipro.file.ProjectFileService; import se.su.dsv.scipro.file.ProjectFileServiceImpl; import se.su.dsv.scipro.finalseminar.AuthorRepository; +import se.su.dsv.scipro.finalseminar.ExpireUnfulfilledOppositionImprovementsWorker; import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationRepository; import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl; import se.su.dsv.scipro.finalseminar.FinalSeminarCreationSubscribers; @@ -204,6 +205,7 @@ import se.su.dsv.scipro.system.UserRepo; import se.su.dsv.scipro.system.UserService; import se.su.dsv.scipro.system.UserServiceImpl; import se.su.dsv.scipro.thesislink.ExternalLinkServiceImpl; +import se.su.dsv.scipro.workerthreads.Scheduler; import se.su.dsv.scipro.workerthreads.WorkerDataServiceImpl; @Configuration(proxyBeanMethods = false) @@ -1167,4 +1169,19 @@ public class CoreConfig { notificationController ); } + + @Bean + public ExpireUnfulfilledOppositionImprovementsWorker expireUnfulfilledOppositionImprovementsWorker( + FinalSeminarOppositionServiceImpl finalSeminarOppositionService + ) { + return new ExpireUnfulfilledOppositionImprovementsWorker(finalSeminarOppositionService); + } + + @Bean + public ExpireUnfulfilledOppositionImprovementsWorker.Schedule expireUnfulfilledOppositionImprovementsWorkerSchedule( + Scheduler scheduler, + Provider<ExpireUnfulfilledOppositionImprovementsWorker> worker + ) { + return new ExpireUnfulfilledOppositionImprovementsWorker.Schedule(scheduler, worker); + } } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/ExpireUnfulfilledOppositionImprovementsWorker.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/ExpireUnfulfilledOppositionImprovementsWorker.java new file mode 100644 index 0000000000..9a92f0c18b --- /dev/null +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/ExpireUnfulfilledOppositionImprovementsWorker.java @@ -0,0 +1,32 @@ +package se.su.dsv.scipro.finalseminar; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import java.util.concurrent.TimeUnit; +import se.su.dsv.scipro.workerthreads.AbstractWorker; +import se.su.dsv.scipro.workerthreads.Scheduler; + +public class ExpireUnfulfilledOppositionImprovementsWorker extends AbstractWorker { + + private final FinalSeminarOppositionServiceImpl oppositionService; + + public ExpireUnfulfilledOppositionImprovementsWorker(FinalSeminarOppositionServiceImpl oppositionService) { + this.oppositionService = oppositionService; + } + + @Override + protected void doWork() { + oppositionService.expireUnfulfilledOppositionImprovements(); + } + + public static class Schedule { + + @Inject + public Schedule(Scheduler scheduler, Provider<ExpireUnfulfilledOppositionImprovementsWorker> worker) { + scheduler + .schedule("Fail opponents that have not submitted improvements") + .runBy(worker) + .every(1, TimeUnit.HOURS); + } + } +} diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepo.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepo.java index e636597075..b6e7a348c5 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepo.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepo.java @@ -11,4 +11,6 @@ import se.su.dsv.scipro.system.User; public interface FinalSeminarOppositionRepo extends JpaRepository<FinalSeminarOpposition, Long>, QueryDslPredicateExecutor<FinalSeminarOpposition> { List<FinalSeminarOpposition> findByOpposingUserAndType(User user, ProjectType projectType); + + Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements(); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepoImpl.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepoImpl.java index 00ad589634..a48da4e198 100644 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepoImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionRepoImpl.java @@ -24,4 +24,13 @@ public class FinalSeminarOppositionRepoImpl .where(QFinalSeminarOpposition.finalSeminarOpposition.project.projectType.eq(projectType)) .fetch(); } + + @Override + public Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements() { + return createQuery() + .innerJoin(QFinalSeminarOpposition.finalSeminarOpposition.oppositionReport) + .where(QFinalSeminarOpposition.finalSeminarOpposition.improvementsRequestedAt.isNotNull()) + .where(QFinalSeminarOpposition.finalSeminarOpposition.oppositionReport.submitted.isFalse()) + .fetch(); + } } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java index 693ed21351..1de2066727 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java @@ -7,6 +7,7 @@ import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import java.time.Clock; import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.Optional; import se.su.dsv.scipro.misc.DaysService; @@ -65,11 +66,24 @@ public class FinalSeminarOppositionServiceImpl throw new PointNotValidException(points, List.of(0, 1)); } + FinalSeminarGrade notApproved = criteriaForOpposition.pointsToPass() > points + ? FinalSeminarGrade.NOT_APPROVED + : FinalSeminarGrade.APPROVED; + return internalGradeOpponent(opposition, points, feedback, notApproved); + } + + private FinalSeminarOpposition internalGradeOpponent( + FinalSeminarOpposition opposition, + int points, + String feedback, + FinalSeminarGrade grade + ) { + opposition.setGrade(grade); opposition.setPoints(points); opposition.setFeedback(feedback); FinalSeminarOpposition assessedOpposition = finalSeminarOppositionRepository.save(opposition); - if (criteriaForOpposition.pointsToPass() > points) { + if (grade == FinalSeminarGrade.NOT_APPROVED) { eventBus.post(new OppositionFailedEvent(assessedOpposition)); } else { eventBus.post(new OppositionApprovedEvent(assessedOpposition)); @@ -128,4 +142,36 @@ public class FinalSeminarOppositionServiceImpl return Optional.empty(); } } + + void expireUnfulfilledOppositionImprovements() { + Collection<FinalSeminarOpposition> unfulfilledOppositions = + finalSeminarOppositionRepository.findUnfulfilledOppositionImprovements(); + + Instant now = clock.instant(); + int workDaysToFixRequestedImprovementsToOppositionReport = finalSeminarSettingsService + .getInstance() + .getWorkDaysToFixRequestedImprovementsToOppositionReport(); + for (FinalSeminarOpposition unfulfilledOpposition : unfulfilledOppositions) { + Instant deadline = daysService.workDaysAfter( + unfulfilledOpposition.getImprovementsRequestedAt(), + workDaysToFixRequestedImprovementsToOppositionReport + ); + if (now.isAfter(deadline)) { + internalGradeOpponent( + unfulfilledOpposition, + 0, + unfulfilledOpposition.getSupervisorCommentForImprovements(), + FinalSeminarGrade.NOT_APPROVED + ); + + OppositionReport oppositionReport = unfulfilledOpposition.getOppositionReport(); + if (oppositionReport != null) { + // Lock the report so it's not possible to submit it again + oppositionReport.setSubmitted(true); + } + + finalSeminarOppositionRepository.save(unfulfilledOpposition); + } + } + } }