Allows admins to manage grading report templates #14
@ -1,6 +1,10 @@
|
|||||||
package se.su.dsv.scipro.grading;
|
package se.su.dsv.scipro.grading;
|
||||||
|
|
||||||
|
import se.su.dsv.scipro.report.DuplicateDateException;
|
||||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||||
|
import se.su.dsv.scipro.report.ValidDateMustBeInTheFutureException;
|
||||||
|
import se.su.dsv.scipro.report.NoSuchTemplateException;
|
||||||
|
import se.su.dsv.scipro.report.TemplateLockedException;
|
||||||
import se.su.dsv.scipro.system.ProjectType;
|
import se.su.dsv.scipro.system.ProjectType;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@ -22,4 +26,11 @@ public interface GradingReportTemplateService {
|
|||||||
LocalDate getEndDate(GradingReportTemplate gradingReportTemplate);
|
LocalDate getEndDate(GradingReportTemplate gradingReportTemplate);
|
||||||
|
|
||||||
List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType);
|
List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType);
|
||||||
|
|
||||||
|
GradingReportTemplate update(long templateId, GradingReportTemplateUpdate update)
|
||||||
|
throws
|
||||||
|
ValidDateMustBeInTheFutureException,
|
||||||
|
NoSuchTemplateException,
|
||||||
|
DuplicateDateException,
|
||||||
|
TemplateLockedException;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package se.su.dsv.scipro.grading;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record GradingReportTemplateUpdate(
|
||||||
|
LocalDate validFrom,
|
||||||
|
@Nullable String note,
|
||||||
|
String defaultGrade,
|
||||||
|
List<Grade> grades,
|
||||||
|
List<Criteria> criteria)
|
||||||
|
{
|
||||||
|
public GradingReportTemplateUpdate {
|
||||||
|
Objects.requireNonNull(validFrom, "Valid from must not be null");
|
||||||
|
Objects.requireNonNull(defaultGrade, "Default grade must not be null");
|
||||||
|
Objects.requireNonNull(grades, "Grades must not be null");
|
||||||
|
Objects.requireNonNull(criteria, "Criteria must not be null");
|
||||||
|
|
||||||
|
for (Grade grade1 : grades) {
|
||||||
|
for (Grade grade2 : grades) {
|
||||||
|
if (grade1 != grade2 && grade1.minimumPoints() == grade2.minimumPoints()) {
|
||||||
|
throw new IllegalArgumentException("Duplicate minimum points on grades: %s and %s".formatted(
|
||||||
|
grade1.grade(),
|
||||||
|
grade2.grade()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Grade(String grade, int minimumPoints) {
|
||||||
|
public Grade {
|
||||||
|
Objects.requireNonNull(grade, "Grade must not be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Criteria(LocalizedString title, @Nullable Flag flag, List<Requirement> requirements)
|
||||||
|
{
|
||||||
|
public enum Flag {OPPOSITION, REFLECTION}
|
||||||
|
|
||||||
|
public Criteria {
|
||||||
|
Objects.requireNonNull(title, "Title must not be null");
|
||||||
|
Objects.requireNonNull(requirements, "Requirements must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Requirement(int points, LocalizedString description) {
|
||||||
|
public Requirement {
|
||||||
|
Objects.requireNonNull(description, "Description must not be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package se.su.dsv.scipro.grading;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record LocalizedString(String english, String swedish) {
|
||||||
|
public LocalizedString {
|
||||||
|
Objects.requireNonNull(english, "English must not be null");
|
||||||
|
Objects.requireNonNull(swedish, "Swedish must not be null");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import se.su.dsv.scipro.system.ProjectType;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class DuplicateDateException extends Throwable {
|
||||||
|
private final LocalDate validFrom;
|
||||||
|
private final ProjectType projectType;
|
||||||
|
|
||||||
|
public DuplicateDateException(LocalDate validFrom, ProjectType projectType) {
|
||||||
|
this.validFrom = validFrom;
|
||||||
|
this.projectType = projectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate validFrom() {
|
||||||
|
return validFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProjectType projectType() {
|
||||||
|
return projectType;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import com.google.inject.persist.Transactional;
|
|||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||||
import se.su.dsv.scipro.grading.GradingBasis;
|
import se.su.dsv.scipro.grading.GradingBasis;
|
||||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||||
|
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||||
import se.su.dsv.scipro.grading.ThesisSubmissionHistoryService;
|
import se.su.dsv.scipro.grading.ThesisSubmissionHistoryService;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
import se.su.dsv.scipro.system.Language;
|
import se.su.dsv.scipro.system.Language;
|
||||||
@ -237,4 +238,37 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G
|
|||||||
return gradingReportTemplateRepo.getTemplatesValidAfter(projectType, current.getValidFrom());
|
return gradingReportTemplateRepo.getTemplatesValidAfter(projectType, current.getValidFrom());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GradingReportTemplate update(long templateId, GradingReportTemplateUpdate update)
|
||||||
|
throws ValidDateMustBeInTheFutureException,
|
||||||
|
NoSuchTemplateException,
|
||||||
|
DuplicateDateException,
|
||||||
|
TemplateLockedException
|
||||||
|
{
|
||||||
|
LocalDate today = LocalDate.now(clock);
|
||||||
|
if (!update.validFrom().isAfter(today)) {
|
||||||
|
throw new ValidDateMustBeInTheFutureException(update.validFrom(), today.plusDays(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
GradingReportTemplate template = gradingReportTemplateRepo.getTemplateById(templateId);
|
||||||
|
if (template == null) {
|
||||||
|
throw new NoSuchTemplateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
GradingReportTemplate currentTemplate = gradingReportTemplateRepo.getCurrentTemplate(
|
||||||
|
template.getProjectType(),
|
||||||
|
update.validFrom());
|
||||||
|
if (currentTemplate.getId() != templateId &&
|
||||||
|
Objects.equals(currentTemplate.getValidFrom(), update.validFrom()))
|
||||||
|
{
|
||||||
|
throw new DuplicateDateException(update.validFrom(), template.getProjectType());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!template.getValidFrom().isAfter(today)) {
|
||||||
|
throw new TemplateLockedException(template.getValidFrom());
|
||||||
|
}
|
||||||
|
|
||||||
|
return gradingReportTemplateRepo.updateTemplate(templateId, update);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ public class GradingReportTemplate extends DomainObject {
|
|||||||
return gradingCriterionTemplate;
|
return gradingCriterionTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterable<GradingCriterionTemplate> getCriteria() {
|
public Collection<GradingCriterionTemplate> getCriteria() {
|
||||||
return criteria;
|
return criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package se.su.dsv.scipro.report;
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
import se.su.dsv.scipro.system.ProjectType;
|
import se.su.dsv.scipro.system.ProjectType;
|
||||||
|
|
||||||
@ -15,4 +16,8 @@ public interface GradingReportTemplateRepo extends JpaRepository<GradingReportTe
|
|||||||
GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate);
|
GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate);
|
||||||
|
|
||||||
List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date);
|
List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date);
|
||||||
|
|
||||||
|
GradingReportTemplate getTemplateById(long templateId);
|
||||||
|
|
||||||
|
GradingReportTemplate updateTemplate(long templateId, GradingReportTemplateUpdate update);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package se.su.dsv.scipro.report;
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import com.google.inject.persist.Transactional;
|
||||||
import com.querydsl.jpa.JPAExpressions;
|
import com.querydsl.jpa.JPAExpressions;
|
||||||
import com.querydsl.jpa.JPQLQuery;
|
import com.querydsl.jpa.JPQLQuery;
|
||||||
|
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
import se.su.dsv.scipro.system.GenericRepo;
|
import se.su.dsv.scipro.system.GenericRepo;
|
||||||
|
|
||||||
@ -53,4 +55,16 @@ public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemp
|
|||||||
return findAll(QGradingReportTemplate.gradingReportTemplate.projectType.eq(projectType)
|
return findAll(QGradingReportTemplate.gradingReportTemplate.projectType.eq(projectType)
|
||||||
.and(QGradingReportTemplate.gradingReportTemplate.validFrom.gt(date)));
|
.and(QGradingReportTemplate.gradingReportTemplate.validFrom.gt(date)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GradingReportTemplate getTemplateById(long templateId) {
|
||||||
|
return findOne(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public GradingReportTemplate updateTemplate(long templateId, GradingReportTemplateUpdate update) {
|
||||||
|
GradingReportTemplate gradingReportTemplate = findOne(templateId);
|
||||||
|
return gradingReportTemplate;
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
public class NoSuchTemplateException extends Exception {
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class TemplateLockedException extends Exception {
|
||||||
|
private final LocalDate becameValidAt;
|
||||||
|
|
||||||
|
public TemplateLockedException(LocalDate becameValidAt) {
|
||||||
|
this.becameValidAt = becameValidAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate becameValidAt() {
|
||||||
|
return becameValidAt;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class ValidDateMustBeInTheFutureException extends Exception {
|
||||||
|
private final LocalDate validFrom;
|
||||||
|
private final LocalDate earliestAllowedValidFrom;
|
||||||
|
|
||||||
|
public ValidDateMustBeInTheFutureException(LocalDate validFrom, LocalDate earliestAllowedValidFrom) {
|
||||||
|
this.validFrom = validFrom;
|
||||||
|
this.earliestAllowedValidFrom = earliestAllowedValidFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate validFrom() {
|
||||||
|
return validFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate earliestAllowedValidFrom() {
|
||||||
|
return earliestAllowedValidFrom;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||||
<body>
|
<body>
|
||||||
<wicket:extend>
|
<wicket:extend>
|
||||||
|
<div wicket:id="feedback"></div>
|
||||||
<form wicket:id="form">
|
<form wicket:id="form">
|
||||||
<div wicket:id="editing" class="mb-3"></div>
|
<div wicket:id="editing" class="mb-3"></div>
|
||||||
<div class="position-sticky bottom-0 bg-white p-3 border line-length-limit">
|
<div class="position-sticky bottom-0 bg-white p-3 border line-length-limit">
|
||||||
|
@ -1,15 +1,29 @@
|
|||||||
package se.su.dsv.scipro.admin.pages.grading;
|
package se.su.dsv.scipro.admin.pages.grading;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import org.apache.wicket.RestartResponseException;
|
||||||
import org.apache.wicket.markup.html.form.Form;
|
import org.apache.wicket.markup.html.form.Form;
|
||||||
|
import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
||||||
import org.apache.wicket.model.Model;
|
import org.apache.wicket.model.Model;
|
||||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||||
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
|
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
|
||||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||||
|
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||||
|
import se.su.dsv.scipro.grading.LocalizedString;
|
||||||
|
import se.su.dsv.scipro.report.DuplicateDateException;
|
||||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||||
|
import se.su.dsv.scipro.report.ValidDateMustBeInTheFutureException;
|
||||||
|
import se.su.dsv.scipro.report.NoSuchTemplateException;
|
||||||
|
import se.su.dsv.scipro.report.TemplateLockedException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
|
public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
|
||||||
private static final String GRADING_REPORT_TEMPLATE_ID_PARAMETER = "id";
|
private static final String GRADING_REPORT_TEMPLATE_ID_PARAMETER = "id";
|
||||||
|
public static final GradingReportTemplateUpdate.Criteria.Requirement ZERO_REQUIREMENT = new GradingReportTemplateUpdate.Criteria.Requirement(
|
||||||
|
0,
|
||||||
|
new LocalizedString("", ""));
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GradingReportTemplateService gradingReportTemplateService;
|
GradingReportTemplateService gradingReportTemplateService;
|
||||||
@ -23,10 +37,27 @@ public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage imple
|
|||||||
|
|
||||||
editingGradingTemplate = new EditingGradingTemplate(template);
|
editingGradingTemplate = new EditingGradingTemplate(template);
|
||||||
|
|
||||||
|
add(new FeedbackPanel("feedback"));
|
||||||
|
|
||||||
Form<EditingGradingTemplate> form = new Form<>("form") {
|
Form<EditingGradingTemplate> form = new Form<>("form") {
|
||||||
@Override
|
@Override
|
||||||
protected void onSubmit() {
|
protected void onSubmit() {
|
||||||
super.onSubmit();
|
super.onSubmit();
|
||||||
|
try {
|
||||||
|
GradingReportTemplateUpdate update = toUpdate(
|
||||||
|
editingGradingTemplate);
|
||||||
|
|
||||||
|
gradingReportTemplateService.update(id, update);
|
||||||
|
success(getString("template_updated"));
|
||||||
|
} catch (ValidDateMustBeInTheFutureException e) {
|
||||||
|
error(getString("valid_from_must_be_in_the_future", () -> e));
|
||||||
|
} catch (NoSuchTemplateException e) {
|
||||||
|
throw new RestartResponseException(AdminGradingTemplatesOverviewPage.class);
|
||||||
|
} catch (DuplicateDateException e) {
|
||||||
|
error(getString("another_template_exists_for_the_given_date_date", () -> e));
|
||||||
|
} catch (TemplateLockedException e) {
|
||||||
|
error(getString("template_is_locked", () -> e));
|
||||||
|
}
|
||||||
System.out.println(editingGradingTemplate);
|
System.out.println(editingGradingTemplate);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -35,6 +66,58 @@ public class AdminGradingTemplateEditPage extends AbstractAdminProjectPage imple
|
|||||||
add(form);
|
add(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GradingReportTemplateUpdate toUpdate(EditingGradingTemplate editingGradingTemplate) {
|
||||||
|
List<GradingReportTemplateUpdate.Grade> grades = editingGradingTemplate
|
||||||
|
.getGradeLimits()
|
||||||
|
.getGradeLimits()
|
||||||
|
.stream()
|
||||||
|
.map(this::toGrade)
|
||||||
|
.toList();
|
||||||
|
List<GradingReportTemplateUpdate.Criteria> criteria = editingGradingTemplate
|
||||||
|
.getCriteria()
|
||||||
|
.stream()
|
||||||
|
.map(this::toCriteria)
|
||||||
|
.toList();
|
||||||
|
return new GradingReportTemplateUpdate(
|
||||||
|
editingGradingTemplate.getValidFrom(),
|
||||||
|
editingGradingTemplate.getNote(),
|
||||||
|
editingGradingTemplate.getGradeLimits().getDefaultGrade(),
|
||||||
|
grades,
|
||||||
|
criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GradingReportTemplateUpdate.Criteria toCriteria(EditingGradingTemplate.Criteria criteria) {
|
||||||
|
ArrayList<GradingReportTemplateUpdate.Criteria.Requirement> requirements = new ArrayList<>();
|
||||||
|
requirements.add(ZERO_REQUIREMENT);
|
||||||
|
for (int i = 0; i < criteria.getPoints().size(); i++) {
|
||||||
|
EditingGradingTemplate.Criteria.Point point = criteria.getPoints().get(i);
|
||||||
|
requirements.add(new GradingReportTemplateUpdate.Criteria.Requirement(
|
||||||
|
i + 1,
|
||||||
|
new LocalizedString(point.getRequirementEn(), point.getRequirementSv())));
|
||||||
|
}
|
||||||
|
return new GradingReportTemplateUpdate.Criteria(
|
||||||
|
new LocalizedString(criteria.getTitleEn(), criteria.getTitleSv()),
|
||||||
|
getFlag(criteria),
|
||||||
|
requirements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GradingReportTemplateUpdate.Criteria.Flag getFlag(EditingGradingTemplate.Criteria criteria) {
|
||||||
|
if (criteria.getFlag() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return switch (criteria.getFlag()) {
|
||||||
|
case OPPOSITION -> GradingReportTemplateUpdate.Criteria.Flag.OPPOSITION;
|
||||||
|
case REFLECTION -> GradingReportTemplateUpdate.Criteria.Flag.REFLECTION;
|
||||||
|
//case null -> null; sigh old versions of Java
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private GradingReportTemplateUpdate.Grade toGrade(GradeLimits.GradeLimit gradeLimit) {
|
||||||
|
return new GradingReportTemplateUpdate.Grade(
|
||||||
|
gradeLimit.getGrade(),
|
||||||
|
gradeLimit.getLowerLimit());
|
||||||
|
}
|
||||||
|
|
||||||
public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
|
public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
|
||||||
PageParameters pageParameters = new PageParameters();
|
PageParameters pageParameters = new PageParameters();
|
||||||
pageParameters.add(GRADING_REPORT_TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
|
pageParameters.add(GRADING_REPORT_TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
template_updated=Template updated
|
||||||
|
valid_from_must_be_in_the_future=The templates valid date must be in the future. The given date was ${validFrom} but it must be at least ${earliestAllowedValidFrom}.
|
||||||
|
another_template_exists_for_the_given_date_date=There is already another ${projectType.name} template that becomes valid at ${validFrom}, please pick another date.
|
||||||
|
template_is_locked=You can not edit templates that have become current. The template you are trying to edit became current at ${validFrom}.
|
Loading…
x
Reference in New Issue
Block a user