Allow supervisors to request improvements from final seminar opponents #78

Merged
niat8586 merged 41 commits from opponent-completion into develop 2025-03-05 10:05:38 +01:00
5 changed files with 107 additions and 1 deletions
Showing only changes of commit 08302f9719 - Show all commits

View File

@ -28,6 +28,7 @@ import se.su.dsv.scipro.file.ProjectFileRepository;
import se.su.dsv.scipro.file.ProjectFileService; import se.su.dsv.scipro.file.ProjectFileService;
import se.su.dsv.scipro.file.ProjectFileServiceImpl; import se.su.dsv.scipro.file.ProjectFileServiceImpl;
import se.su.dsv.scipro.finalseminar.AuthorRepository; 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.FinalSeminarActiveParticipationRepository;
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl; import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl;
import se.su.dsv.scipro.finalseminar.FinalSeminarCreationSubscribers; 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.UserService;
import se.su.dsv.scipro.system.UserServiceImpl; import se.su.dsv.scipro.system.UserServiceImpl;
import se.su.dsv.scipro.thesislink.ExternalLinkServiceImpl; import se.su.dsv.scipro.thesislink.ExternalLinkServiceImpl;
import se.su.dsv.scipro.workerthreads.Scheduler;
import se.su.dsv.scipro.workerthreads.WorkerDataServiceImpl; import se.su.dsv.scipro.workerthreads.WorkerDataServiceImpl;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ -1167,4 +1169,19 @@ public class CoreConfig {
notificationController 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);
}
} }

View File

@ -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);
}
}
}

View File

@ -11,4 +11,6 @@ import se.su.dsv.scipro.system.User;
public interface FinalSeminarOppositionRepo public interface FinalSeminarOppositionRepo
extends JpaRepository<FinalSeminarOpposition, Long>, QueryDslPredicateExecutor<FinalSeminarOpposition> { extends JpaRepository<FinalSeminarOpposition, Long>, QueryDslPredicateExecutor<FinalSeminarOpposition> {
List<FinalSeminarOpposition> findByOpposingUserAndType(User user, ProjectType projectType); List<FinalSeminarOpposition> findByOpposingUserAndType(User user, ProjectType projectType);
Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements();
} }

View File

@ -24,4 +24,13 @@ public class FinalSeminarOppositionRepoImpl
.where(QFinalSeminarOpposition.finalSeminarOpposition.project.projectType.eq(projectType)) .where(QFinalSeminarOpposition.finalSeminarOpposition.project.projectType.eq(projectType))
.fetch(); .fetch();
} }
@Override
public Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements() {
return createQuery()
.innerJoin(QFinalSeminarOpposition.finalSeminarOpposition.oppositionReport)
.where(QFinalSeminarOpposition.finalSeminarOpposition.improvementsRequestedAt.isNotNull())
.where(QFinalSeminarOpposition.finalSeminarOpposition.oppositionReport.submitted.isFalse())
.fetch();
}
} }

View File

@ -7,6 +7,7 @@ import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.time.Clock; import java.time.Clock;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import se.su.dsv.scipro.misc.DaysService; import se.su.dsv.scipro.misc.DaysService;
@ -65,11 +66,24 @@ public class FinalSeminarOppositionServiceImpl
throw new PointNotValidException(points, List.of(0, 1)); 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.setPoints(points);
opposition.setFeedback(feedback); opposition.setFeedback(feedback);
FinalSeminarOpposition assessedOpposition = finalSeminarOppositionRepository.save(opposition); FinalSeminarOpposition assessedOpposition = finalSeminarOppositionRepository.save(opposition);
if (criteriaForOpposition.pointsToPass() > points) { if (grade == FinalSeminarGrade.NOT_APPROVED) {
eventBus.post(new OppositionFailedEvent(assessedOpposition)); eventBus.post(new OppositionFailedEvent(assessedOpposition));
} else { } else {
eventBus.post(new OppositionApprovedEvent(assessedOpposition)); eventBus.post(new OppositionApprovedEvent(assessedOpposition));
@ -128,4 +142,36 @@ public class FinalSeminarOppositionServiceImpl
return Optional.empty(); 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);
}
}
}
} }