Compare commits

..

5 Commits

13 changed files with 170 additions and 29 deletions

@ -435,13 +435,19 @@ public class CoreConfig {
Provider<EntityManager> em, Provider<EntityManager> em,
FinalSeminarOppositionGrading finalSeminarOppositionGrading, FinalSeminarOppositionGrading finalSeminarOppositionGrading,
EventBus eventBus, EventBus eventBus,
FinalSeminarOppositionRepo finalSeminarOppositionRepository FinalSeminarOppositionRepo finalSeminarOppositionRepository,
Clock clock,
FinalSeminarSettingsService finalSeminarSettingsService,
DaysService daysService
) { ) {
return new FinalSeminarOppositionServiceImpl( return new FinalSeminarOppositionServiceImpl(
em, em,
finalSeminarOppositionGrading, finalSeminarOppositionGrading,
eventBus, eventBus,
finalSeminarOppositionRepository finalSeminarOppositionRepository,
clock,
finalSeminarSettingsService,
daysService
); );
} }
@ -680,15 +686,13 @@ public class CoreConfig {
OppositionReportRepo oppositionReportRepository, OppositionReportRepo oppositionReportRepository,
GradingReportTemplateRepo gradingReportTemplateRepository, GradingReportTemplateRepo gradingReportTemplateRepository,
FileService fileService, FileService fileService,
FinalSeminarOppositionRepo finalSeminarOppositionRepository, FinalSeminarOppositionRepo finalSeminarOppositionRepository
Clock clock
) { ) {
return new OppositionReportServiceImpl( return new OppositionReportServiceImpl(
oppositionReportRepository, oppositionReportRepository,
gradingReportTemplateRepository, gradingReportTemplateRepository,
fileService, fileService,
finalSeminarOppositionRepository, finalSeminarOppositionRepository
clock
); );
} }

@ -4,12 +4,18 @@ import jakarta.inject.Inject;
import jakarta.inject.Provider; import jakarta.inject.Provider;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.Month; import java.time.Month;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.*; import java.util.*;
import java.util.function.Function;
import se.su.dsv.scipro.checklist.ChecklistCategory; import se.su.dsv.scipro.checklist.ChecklistCategory;
import se.su.dsv.scipro.file.FileReference;
import se.su.dsv.scipro.file.FileService;
import se.su.dsv.scipro.file.FileUpload;
import se.su.dsv.scipro.finalseminar.FinalSeminar; import se.su.dsv.scipro.finalseminar.FinalSeminar;
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
import se.su.dsv.scipro.match.ApplicationPeriod; import se.su.dsv.scipro.match.ApplicationPeriod;
@ -42,6 +48,9 @@ public class DataInitializer implements Lifecycle {
@Inject @Inject
private MilestoneActivityTemplateService milestoneActivityTemplateService; private MilestoneActivityTemplateService milestoneActivityTemplateService;
@Inject
private FileService fileService;
@Inject @Inject
private CurrentProfile profile; private CurrentProfile profile;
@ -102,11 +111,17 @@ public class DataInitializer implements Lifecycle {
} }
private void createPastFinalSeminar() { private void createPastFinalSeminar() {
FileReference document = fileService.storeFile(
new SimpleTextFile(sture_student, "document.txt", "Hello World")
);
FinalSeminar finalSeminar = new FinalSeminar(); FinalSeminar finalSeminar = new FinalSeminar();
finalSeminar.setStartDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant())); finalSeminar.setStartDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant()));
finalSeminar.setProject(project1); finalSeminar.setProject(project1);
finalSeminar.setRoom("zoom"); finalSeminar.setRoom("zoom");
finalSeminar.setPresentationLanguage(Language.ENGLISH); finalSeminar.setPresentationLanguage(Language.ENGLISH);
finalSeminar.setDocument(document);
finalSeminar.setDocumentUploadDate(document.getFileDescription().getDateCreated());
FinalSeminarOpposition opponent = new FinalSeminarOpposition(); FinalSeminarOpposition opponent = new FinalSeminarOpposition();
opponent.setProject(project2); opponent.setProject(project2);
@ -1924,4 +1939,42 @@ public class DataInitializer implements Lifecycle {
em.get().persist(entity); em.get().persist(entity);
return entity; return entity;
} }
private static final class SimpleTextFile implements FileUpload {
private final User uploader;
private final String fileName;
private final String content;
private SimpleTextFile(User uploader, String fileName, String content) {
this.uploader = uploader;
this.fileName = fileName;
this.content = content;
}
@Override
public String getFileName() {
return fileName;
}
@Override
public String getContentType() {
return "text/plain";
}
@Override
public User getUploader() {
return uploader;
}
@Override
public long getSize() {
return content.length();
}
@Override
public <T> T handleData(Function<InputStream, T> handler) {
return handler.apply(new ByteArrayInputStream(content.getBytes()));
}
}
} }

@ -1,5 +1,6 @@
package se.su.dsv.scipro.finalseminar; package se.su.dsv.scipro.finalseminar;
import java.time.Instant;
import se.su.dsv.scipro.system.GenericService; import se.su.dsv.scipro.system.GenericService;
public interface FinalSeminarOppositionService extends GenericService<FinalSeminarOpposition, Long> { public interface FinalSeminarOppositionService extends GenericService<FinalSeminarOpposition, Long> {
@ -10,4 +11,9 @@ public interface FinalSeminarOppositionService extends GenericService<FinalSemin
FinalSeminarOpposition gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback) FinalSeminarOpposition gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback)
throws PointNotValidException; throws PointNotValidException;
/**
* @return the deadline by which the improvements must have been submitted
*/
Instant requestImprovements(FinalSeminarOpposition opposition, String supervisorComment);
} }

@ -5,7 +5,11 @@ import jakarta.inject.Inject;
import jakarta.inject.Provider; import jakarta.inject.Provider;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.time.Clock;
import java.time.Instant;
import java.util.List; import java.util.List;
import se.su.dsv.scipro.misc.DaysService;
import se.su.dsv.scipro.report.OppositionReport;
import se.su.dsv.scipro.system.AbstractServiceImpl; import se.su.dsv.scipro.system.AbstractServiceImpl;
public class FinalSeminarOppositionServiceImpl public class FinalSeminarOppositionServiceImpl
@ -15,18 +19,27 @@ public class FinalSeminarOppositionServiceImpl
private final FinalSeminarOppositionGrading finalSeminarOppositionGrading; private final FinalSeminarOppositionGrading finalSeminarOppositionGrading;
private final EventBus eventBus; private final EventBus eventBus;
private final FinalSeminarOppositionRepo finalSeminarOppositionRepository; private final FinalSeminarOppositionRepo finalSeminarOppositionRepository;
private final Clock clock;
private final FinalSeminarSettingsService finalSeminarSettingsService;
private final DaysService daysService;
@Inject @Inject
public FinalSeminarOppositionServiceImpl( public FinalSeminarOppositionServiceImpl(
Provider<EntityManager> em, Provider<EntityManager> em,
FinalSeminarOppositionGrading finalSeminarOppositionGrading, FinalSeminarOppositionGrading finalSeminarOppositionGrading,
EventBus eventBus, EventBus eventBus,
FinalSeminarOppositionRepo finalSeminarOppositionRepository FinalSeminarOppositionRepo finalSeminarOppositionRepository,
Clock clock,
FinalSeminarSettingsService finalSeminarSettingsService,
DaysService daysService
) { ) {
super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition); super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition);
this.finalSeminarOppositionGrading = finalSeminarOppositionGrading; this.finalSeminarOppositionGrading = finalSeminarOppositionGrading;
this.eventBus = eventBus; this.eventBus = eventBus;
this.finalSeminarOppositionRepository = finalSeminarOppositionRepository; this.finalSeminarOppositionRepository = finalSeminarOppositionRepository;
this.clock = clock;
this.finalSeminarSettingsService = finalSeminarSettingsService;
this.daysService = daysService;
} }
@Override @Override
@ -59,4 +72,27 @@ public class FinalSeminarOppositionServiceImpl
return assessedOpposition; return assessedOpposition;
} }
@Override
@Transactional
public Instant requestImprovements(FinalSeminarOpposition opposition, String supervisorComment) {
OppositionReport oppositionReport = opposition.getOppositionReport();
if (oppositionReport == null) {
throw new IllegalStateException("There is no opposition report submitted");
}
FinalSeminarSettings finalSeminarSettings = finalSeminarSettingsService.getInstance();
Instant now = clock.instant();
Instant deadline = daysService.workDaysAfter(
now,
finalSeminarSettings.getWorkDaysToFixRequestedImprovementsToOppositionReport()
);
oppositionReport.setSubmitted(false);
opposition.setImprovementsRequestedAt(now);
opposition.setSupervisorCommentForImprovements(supervisorComment);
return deadline;
}
} }

@ -34,6 +34,9 @@ public class FinalSeminarSettings extends DomainObject {
@Column(name = "days_ahead_to_upload_thesis", nullable = false) @Column(name = "days_ahead_to_upload_thesis", nullable = false)
private int daysAheadToUploadThesis = DEFAULT_DAYS_AHEAD_TO_UPLOAD_THESIS; private int daysAheadToUploadThesis = DEFAULT_DAYS_AHEAD_TO_UPLOAD_THESIS;
@Column(name = "work_days_to_fix_requested_improvements_to_opposition_report", nullable = false)
private int workDaysToFixRequestedImprovementsToOppositionReport = 10;
@Column(name = "thesis_must_be_pdf", nullable = false) @Column(name = "thesis_must_be_pdf", nullable = false)
private boolean thesisMustBePDF = false; private boolean thesisMustBePDF = false;
@ -113,6 +116,17 @@ public class FinalSeminarSettings extends DomainObject {
this.oppositionPriorityDays = oppositionPriorityDays; this.oppositionPriorityDays = oppositionPriorityDays;
} }
public int getWorkDaysToFixRequestedImprovementsToOppositionReport() {
return workDaysToFixRequestedImprovementsToOppositionReport;
}
public void setWorkDaysToFixRequestedImprovementsToOppositionReport(
int workDaysToFixRequestedImprovementsToOppositionReport
) {
this.workDaysToFixRequestedImprovementsToOppositionReport =
workDaysToFixRequestedImprovementsToOppositionReport;
}
@Override @Override
public String toString() { public String toString() {
return ( return (

@ -1,6 +1,9 @@
package se.su.dsv.scipro.misc; package se.su.dsv.scipro.misc;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date; import java.util.Date;
public interface DaysService { public interface DaysService {
@ -9,4 +12,11 @@ public interface DaysService {
int workDaysBetween(Date startDate, Date endDate); int workDaysBetween(Date startDate, Date endDate);
LocalDate workDaysAhead(LocalDate date, int days); LocalDate workDaysAhead(LocalDate date, int days);
LocalDate workDaysAfter(LocalDate date, int days); LocalDate workDaysAfter(LocalDate date, int days);
default Instant workDaysAfter(Instant instant, int days) {
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
LocalDate localDate = zonedDateTime.toLocalDate();
LocalDate newDate = workDaysAfter(localDate, days);
return newDate.atTime(zonedDateTime.toLocalTime()).atZone(ZoneId.systemDefault()).toInstant();
}
} }

@ -7,6 +7,4 @@ public interface OppositionReportService {
void save(OppositionReport oppositionReport); void save(OppositionReport oppositionReport);
void deleteOppositionReport(FinalSeminarOpposition finalSeminarOpposition); void deleteOppositionReport(FinalSeminarOpposition finalSeminarOpposition);
void deleteOpponentReport(FinalSeminarOpposition modelObject); void deleteOpponentReport(FinalSeminarOpposition modelObject);
void requestCompletion(FinalSeminarOpposition opposition, String supervisorComment);
} }

@ -3,7 +3,6 @@ package se.su.dsv.scipro.report;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Named; import jakarta.inject.Named;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.time.Clock;
import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.file.FileReference;
import se.su.dsv.scipro.file.FileService; import se.su.dsv.scipro.file.FileService;
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
@ -16,21 +15,18 @@ public class OppositionReportServiceImpl implements OppositionReportService {
private GradingReportTemplateRepo gradingReportTemplateRepo; private GradingReportTemplateRepo gradingReportTemplateRepo;
private FileService fileService; private FileService fileService;
private FinalSeminarOppositionRepo finalSeminarOppositionRepo; private FinalSeminarOppositionRepo finalSeminarOppositionRepo;
private final Clock clock;
@Inject @Inject
public OppositionReportServiceImpl( public OppositionReportServiceImpl(
OppositionReportRepo oppositionReportRepo, OppositionReportRepo oppositionReportRepo,
GradingReportTemplateRepo gradingReportTemplateRepo, GradingReportTemplateRepo gradingReportTemplateRepo,
FileService fileService, FileService fileService,
FinalSeminarOppositionRepo finalSeminarOppositionRepo, FinalSeminarOppositionRepo finalSeminarOppositionRepo
Clock clock
) { ) {
this.oppositionReportRepo = oppositionReportRepo; this.oppositionReportRepo = oppositionReportRepo;
this.gradingReportTemplateRepo = gradingReportTemplateRepo; this.gradingReportTemplateRepo = gradingReportTemplateRepo;
this.fileService = fileService; this.fileService = fileService;
this.finalSeminarOppositionRepo = finalSeminarOppositionRepo; this.finalSeminarOppositionRepo = finalSeminarOppositionRepo;
this.clock = clock;
} }
@Override @Override
@ -78,16 +74,4 @@ public class OppositionReportServiceImpl implements OppositionReportService {
finalSeminarOppositionRepo.save(finalSeminarOpposition); finalSeminarOppositionRepo.save(finalSeminarOpposition);
} }
} }
@Override
@Transactional
public void requestCompletion(FinalSeminarOpposition opposition, String supervisorComment) {
OppositionReport oppositionReport = opposition.getOppositionReport();
if (oppositionReport == null) {
return;
}
oppositionReport.setSubmitted(false);
opposition.setImprovementsRequestedAt(clock.instant());
opposition.setSupervisorCommentForImprovements(supervisorComment);
}
} }

@ -0,0 +1,2 @@
ALTER TABLE `final_seminar_settings`
ADD COLUMN `work_days_to_fix_requested_improvements_to_opposition_report` INT(11) NOT NULL DEFAULT 10;

@ -42,6 +42,13 @@
</div> </div>
</div> </div>
<div class="mb-3">
<label class="col-lg-4">How many work days opponents have to resubmit their report</label>
<div class="col-lg-1">
<input class="form-control" type="text" wicket:id="work_days_to_fix_requested_improvements_to_opposition_report" />
</div>
</div>
<div class="mb-3"> <div class="mb-3">
<div class="col-lg-offset-4 col-lg-4"> <div class="col-lg-offset-4 col-lg-4">
<div class="form-check"> <div class="form-check">

@ -131,6 +131,17 @@ public class AdminFinalSeminarSettingsPage extends AbstractAdminSystemPage {
Integer.class Integer.class
) )
); );
add(
new RequiredTextField<>(
"work_days_to_fix_requested_improvements_to_opposition_report",
LambdaModel.of(
model,
FinalSeminarSettings::getWorkDaysToFixRequestedImprovementsToOppositionReport,
FinalSeminarSettings::setWorkDaysToFixRequestedImprovementsToOppositionReport
),
Integer.class
)
);
add( add(
new CheckBox( new CheckBox(
SEMINAR_PDF, SEMINAR_PDF,

@ -2,6 +2,9 @@ package se.su.dsv.scipro.finalseminar;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -357,8 +360,18 @@ public class SeminarOppositionPanel extends Panel {
@Override @Override
protected void onSubmit() { protected void onSubmit() {
System.out.println("Request improvements submitted"); Instant deadline = finalSeminarOppositionService.requestImprovements(
System.out.println("Feedback to opponent: " + feedbackToOpponentModel.getObject()); getModelObject(),
feedbackToOpponentModel.getObject()
);
record ImprovementFeedback(String fullName, ZonedDateTime deadline) {}
ZonedDateTime localDeadline = deadline.atZone(ZoneId.systemDefault());
success(
getString("feedback.opponent.requested.improvements", () ->
new ImprovementFeedback(getModelObject().getUser().getFullName(), localDeadline)
)
);
} }
} }
} }

@ -16,4 +16,7 @@ removed= Opponent ${user.fullName} successfully removed
opposition.report.removed= Opposition report successfully removed opposition.report.removed= Opposition report successfully removed
are.you.sure= Are you sure you want to remove this opponent report? are.you.sure= Are you sure you want to remove this opponent report?
no.opponents= There are no opponents registered yet. no.opponents= There are no opponents registered yet.
noOppositionReportYet= No opposition report has been submitted yet. noOppositionReportYet= No opposition report has been submitted yet.
feedback.opponent.requested.improvements = You've requested improvements from ${fullName}. \
They have until ${deadline} to make the changes. If they fail to resubmit by that point they \
will automatically get a failing grade.