Allows admins to manage grading report templates #14
|
@ -25,6 +25,8 @@ import se.su.dsv.scipro.report.GradingReportTemplateRepo;
|
|||
import se.su.dsv.scipro.report.GradingReportTemplateRepoImpl;
|
||||
import se.su.dsv.scipro.report.OppositionReportRepo;
|
||||
import se.su.dsv.scipro.report.OppositionReportRepoImpl;
|
||||
import se.su.dsv.scipro.report.SupervisorGradingReportRepository;
|
||||
import se.su.dsv.scipro.report.SupervisorGradingReportRepositoryImpl;
|
||||
import se.su.dsv.scipro.reviewing.DecisionRepository;
|
||||
import se.su.dsv.scipro.reviewing.DecisionRepositoryImpl;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerTargetRepository;
|
||||
|
@ -59,5 +61,6 @@ public class RepositoryModule extends AbstractModule {
|
|||
bind(FinalSeminarRepository.class).to(FinalSeminarRepositoryImpl.class);
|
||||
bind(ReviewerTargetRepository.class).to(ReviewerTargetRepositoryImpl.class);
|
||||
bind(DecisionRepository.class).to(DecisionRepositoryImpl.class);
|
||||
bind(SupervisorGradingReportRepository.class).to(SupervisorGradingReportRepositoryImpl.class);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService;
|
|||
import se.su.dsv.scipro.profiles.CurrentProfile;
|
||||
import se.su.dsv.scipro.profiles.Profiles;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.report.AbstractGradingCriterion;
|
||||
import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
|
@ -20,6 +21,7 @@ import jakarta.inject.Provider;
|
|||
import jakarta.persistence.EntityManager;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.util.*;
|
||||
|
||||
public class DataInitializer implements Lifecycle {
|
||||
|
@ -232,19 +234,20 @@ public class DataInitializer implements Lifecycle {
|
|||
}
|
||||
|
||||
private void createGradingCriterionTemplateIfNotDone() {
|
||||
save(getBachelorTemplate(0));
|
||||
save(getBachelorTemplate(ProjectType.HP_15));
|
||||
save(getBachelorTemplate(ProjectType.HP_30));
|
||||
save(getMasterTemplate(0));
|
||||
save(getMasterTemplate(ProjectType.HP_15));
|
||||
save(getMasterTemplate(ProjectType.HP_30));
|
||||
save(getMagisterTemplate(0));
|
||||
save(getMagisterTemplate(ProjectType.HP_15));
|
||||
save(getMagisterTemplate(ProjectType.HP_30));
|
||||
save(getBachelorTemplate());
|
||||
save(getBachelorTemplate());
|
||||
save(getBachelorTemplate());
|
||||
save(getMasterTemplate());
|
||||
save(getMasterTemplate());
|
||||
save(getMasterTemplate());
|
||||
save(getMagisterTemplate());
|
||||
save(getMagisterTemplate());
|
||||
save(getMagisterTemplate());
|
||||
}
|
||||
|
||||
private GradingReportTemplate getBachelorTemplate(int credits) {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelorClass, credits);
|
||||
private GradingReportTemplate getBachelorTemplate() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelorClass,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = initPointTemplates();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
|
@ -401,7 +404,7 @@ public class DataInitializer implements Lifecycle {
|
|||
.description("För 2 poäng krävs dessutom: att oppositionsrapporten ingående och välbalanserat beskriver styrkor och svagheter hos det utvärderade arbetet ur flera aspekter samt att den innehåller tydliga och välmotiverade förslag till förbättringar.")
|
||||
.descriptionEn("For 2 points the following is also required: that the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it offers clear and well-motivated suggestions for improvements.")
|
||||
.build());
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
|
||||
gradingCriterionPointTemplates = initPointTemplates();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
|
@ -441,13 +444,14 @@ public class DataInitializer implements Lifecycle {
|
|||
.description("För 1 poäng krävs: att förmåga har uppvisats att reflektera över det genomförda examensarbetet genom individuellt författande av ett reflektionsdokument.")
|
||||
.descriptionEn("Requirement for 1 point: that the ability to reflect about the thesis work has been demonstrated through the individual writing of a reflection document.")
|
||||
.build());
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, gradingCriterionPointTemplates).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
|
||||
return gradingReportTemplate;
|
||||
}
|
||||
|
||||
private GradingReportTemplate getMasterTemplate(int credits) {
|
||||
GradingReportTemplate gradingReportTemplateMaster = new GradingReportTemplate(masterClass, credits);
|
||||
private GradingReportTemplate getMasterTemplate() {
|
||||
GradingReportTemplate gradingReportTemplateMaster = new GradingReportTemplate(masterClass,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = initPointTemplates();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
|
@ -629,7 +633,8 @@ public class DataInitializer implements Lifecycle {
|
|||
.description("För 2 poäng krävs dessutom: att oppositionsrapporten ingående och välbalanserat beskriver styrkor och svagheter hos det utvärderade arbetet ur flera aspekter samt att den innehåller tydliga och välmotiverade förslag till förbättringar.")
|
||||
.descriptionEn("For 2 points the following is also required: that the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it offers clear and well-motivated suggestions for improvements.")
|
||||
.build());
|
||||
gradingReportTemplateMaster.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates);
|
||||
gradingReportTemplateMaster.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates,
|
||||
AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
|
||||
gradingCriterionPointTemplates = initPointTemplates();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
|
@ -669,13 +674,14 @@ public class DataInitializer implements Lifecycle {
|
|||
.description("För 1 poäng krävs: att förmåga har uppvisats att reflektera över det genomförda examensarbetet genom individuellt författande av ett reflektionsdokument.")
|
||||
.descriptionEn("Requirement for 1 point: that the ability to reflect about the thesis work has been demonstrated through the individual writing of a reflection document.")
|
||||
.build());
|
||||
gradingReportTemplateMaster.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates).setFx(false);
|
||||
gradingReportTemplateMaster.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
|
||||
return gradingReportTemplateMaster;
|
||||
}
|
||||
|
||||
private GradingReportTemplate getMagisterTemplate(int credits) {
|
||||
GradingReportTemplate gradingReportTemplateMagister = new GradingReportTemplate(magisterClass, credits);
|
||||
private GradingReportTemplate getMagisterTemplate() {
|
||||
GradingReportTemplate gradingReportTemplateMagister = new GradingReportTemplate(magisterClass,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = initPointTemplates();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
|
@ -837,7 +843,8 @@ public class DataInitializer implements Lifecycle {
|
|||
.description("För 2 poäng krävs dessutom: att oppositionsrapporten ingående och välbalanserat beskriver styrkor och svagheter hos det utvärderade arbetet ur flera aspekter samt att den innehåller tydliga och välmotiverade förslag till förbättringar.")
|
||||
.descriptionEn("For 2 points the following is also required: that the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it offers clear and well-motivated suggestions for improvements.")
|
||||
.build());
|
||||
gradingReportTemplateMagister.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates);
|
||||
gradingReportTemplateMagister.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, gradingCriterionPointTemplates,
|
||||
AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
|
||||
gradingCriterionPointTemplates = initPointTemplates();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
|
@ -877,7 +884,7 @@ public class DataInitializer implements Lifecycle {
|
|||
.description("För 1 poäng krävs: att förmåga har uppvisats att reflektera över det genomförda examensarbetet genom individuellt författande av ett reflektionsdokument.")
|
||||
.descriptionEn("Requirement for 1 point: that the ability to reflect about the thesis work has been demonstrated through the individual writing of a reflection document.")
|
||||
.build());
|
||||
gradingReportTemplateMagister.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates).setFx(false);
|
||||
gradingReportTemplateMagister.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 1, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
|
||||
return gradingReportTemplateMagister;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package se.su.dsv.scipro.grading;
|
|||
import com.google.inject.Key;
|
||||
import com.google.inject.PrivateModule;
|
||||
import com.google.inject.name.Names;
|
||||
import se.su.dsv.scipro.report.GradingReportServiceImpl;
|
||||
|
||||
public class GradingModule extends PrivateModule {
|
||||
@Override
|
||||
|
@ -29,5 +30,8 @@ public class GradingModule extends PrivateModule {
|
|||
bind(NationalSubjectCategoryRepository.class).to(NationalSubjectCategoryRepositoryImpl.class);
|
||||
bind(NationalSubjectCategoryService.class).to(NationalSubjectCategoryServiceImpl.class);
|
||||
expose(NationalSubjectCategoryService.class);
|
||||
|
||||
bind(GradingReportTemplateService.class).to(GradingReportServiceImpl.class);
|
||||
expose(GradingReportTemplateService.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
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.ValidDateMustBeInTheFutureException;
|
||||
import se.su.dsv.scipro.report.NoSuchTemplateException;
|
||||
import se.su.dsv.scipro.report.TemplateLockedException;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface GradingReportTemplateService {
|
||||
|
||||
List<ProjectType> getProjectTypes();
|
||||
|
||||
GradingReportTemplate getCurrentTemplate(ProjectType projectType);
|
||||
|
||||
GradingReportTemplate getTemplate(long templateId);
|
||||
|
||||
/**
|
||||
* Returns the end date of this grading report template.
|
||||
* End date is specified as the date before another template takes over.
|
||||
*
|
||||
* @return the end date of this grading report template, possibly {@code null}
|
||||
*/
|
||||
LocalDate getEndDate(GradingReportTemplate gradingReportTemplate);
|
||||
|
||||
List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType);
|
||||
|
||||
GradingReportTemplate update(long templateId, GradingReportTemplateUpdate update)
|
||||
throws
|
||||
ValidDateMustBeInTheFutureException,
|
||||
NoSuchTemplateException,
|
||||
DuplicateDateException,
|
||||
TemplateLockedException;
|
||||
|
||||
GradingReportTemplate create(ProjectType projectType, GradingReportTemplateUpdate update)
|
||||
throws
|
||||
ValidDateMustBeInTheFutureException,
|
||||
DuplicateDateException;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
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 failingGrade,
|
||||
List<GradeLimit> gradeLimits,
|
||||
List<Criteria> criteria)
|
||||
{
|
||||
public GradingReportTemplateUpdate {
|
||||
Objects.requireNonNull(validFrom, "Valid from must not be null");
|
||||
Objects.requireNonNull(failingGrade, "Failing grade must not be null");
|
||||
Objects.requireNonNull(gradeLimits, "Grades must not be null");
|
||||
Objects.requireNonNull(criteria, "Criteria must not be null");
|
||||
|
||||
for (GradeLimit gradeLimit1 : gradeLimits) {
|
||||
for (GradeLimit gradeLimit2 : gradeLimits) {
|
||||
if (gradeLimit1 != gradeLimit2 && gradeLimit1.minimumPoints() == gradeLimit2.minimumPoints()) {
|
||||
throw new IllegalArgumentException("Duplicate minimum points on grades: %s and %s".formatted(
|
||||
gradeLimit1.grade(),
|
||||
gradeLimit2.grade()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record GradeLimit(String grade, int minimumPoints) {
|
||||
public GradeLimit {
|
||||
Objects.requireNonNull(grade, "Grade must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
public record Criteria(
|
||||
LocalizedString title,
|
||||
Type type,
|
||||
int minimumPointsRequiredToPass,
|
||||
@Nullable Flag flag,
|
||||
List<Requirement> requirements)
|
||||
{
|
||||
public enum Type {THESIS, INDIVIDUAL}
|
||||
public enum Flag {OPPOSITION, REFLECTION}
|
||||
|
||||
public Criteria {
|
||||
Objects.requireNonNull(title, "Title must not be null");
|
||||
Objects.requireNonNull(type, "Type 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");
|
||||
}
|
||||
}
|
|
@ -1,17 +1,42 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
|
||||
@MappedSuperclass
|
||||
public abstract class AbstractGradingCriterion extends AbstractCriterion {
|
||||
|
||||
public enum Flag {
|
||||
/**
|
||||
* Criterion marked with this flag will add extra functionality related
|
||||
* to the authors submitted reflection. It should only be used on
|
||||
* individual criteria.
|
||||
*/
|
||||
REFLECTION,
|
||||
/**
|
||||
* Criterion marked with this flag will add extra functionality related
|
||||
* to the authors performed opposition. For example, it will have its
|
||||
* points and feedback filled in when the seminar supervisor assesses
|
||||
* their opposition and submitted report. It should only be used on
|
||||
* individual criteria.
|
||||
*/
|
||||
OPPOSITION
|
||||
}
|
||||
|
||||
@Basic(optional = false)
|
||||
protected int pointsRequiredToPass;
|
||||
|
||||
@Basic
|
||||
private boolean fx = true;
|
||||
|
||||
@Basic
|
||||
@Column(name = "flag")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Flag flag;
|
||||
|
||||
protected AbstractGradingCriterion() {
|
||||
|
||||
}
|
||||
|
@ -21,6 +46,17 @@ public abstract class AbstractGradingCriterion extends AbstractCriterion {
|
|||
this.pointsRequiredToPass = pointsRequiredToPass;
|
||||
}
|
||||
|
||||
protected AbstractGradingCriterion(
|
||||
String title,
|
||||
String titleEn,
|
||||
Integer sortOrder,
|
||||
int pointsRequiredToPass,
|
||||
Flag flag)
|
||||
{
|
||||
this(title, titleEn, sortOrder, pointsRequiredToPass);
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
public abstract boolean isProjectCriterion();
|
||||
|
||||
public abstract boolean isIndividualCriterion();
|
||||
|
@ -35,6 +71,14 @@ public abstract class AbstractGradingCriterion extends AbstractCriterion {
|
|||
return this.fx;
|
||||
}
|
||||
|
||||
public Flag getFlag() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
public void setFlag(Flag flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) return true;
|
||||
|
|
|
@ -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 Exception {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.report.calculators.original.SupervisorBachelorGradeCalculator;
|
||||
import se.su.dsv.scipro.report.calculators.original.SupervisorMaster15GradeCalculator;
|
||||
|
@ -8,8 +9,21 @@ import se.su.dsv.scipro.system.DegreeType;
|
|||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
public class GradeCalculatorServiceImpl implements GradeCalculatorService {
|
||||
|
||||
private final GradingReportService gradingReportTemplateService;
|
||||
|
||||
@Inject
|
||||
public GradeCalculatorServiceImpl(GradingReportService gradingReportService) {
|
||||
this.gradingReportTemplateService = gradingReportService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradeCalculator getSupervisorCalculator(final Project project) {
|
||||
GradingReportTemplate template = gradingReportTemplateService.getTemplate(project);
|
||||
if (!template.getGradeLimits().isEmpty()) {
|
||||
return new GradingReportTemplateGradeCalculator(template);
|
||||
}
|
||||
|
||||
DegreeType degreeType = project.getProjectType().getDegreeType();
|
||||
if (degreeType == DegreeType.BACHELOR) {
|
||||
if (getYear(project) >= 2017) {
|
||||
|
|
46
core/src/main/java/se/su/dsv/scipro/report/GradeLimit.java
Normal file
46
core/src/main/java/se/su/dsv/scipro/report/GradeLimit.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "grading_report_template_grade_limits")
|
||||
public class GradeLimit {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "grade")
|
||||
private String grade;
|
||||
|
||||
@Column(name = "lower_limit")
|
||||
private int lowerLimit;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public void setGrade(String grade) {
|
||||
this.grade = grade;
|
||||
}
|
||||
|
||||
public int getLowerLimit() {
|
||||
return lowerLimit;
|
||||
}
|
||||
|
||||
public void setLowerLimit(int lowerLimit) {
|
||||
this.lowerLimit = lowerLimit;
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ public abstract class GradingCriterion extends AbstractGradingCriterion {
|
|||
}
|
||||
|
||||
GradingCriterion(GradingReport gradingReport, GradingCriterionTemplate gradingCriterionTemplate) {
|
||||
super(gradingCriterionTemplate.getTitle(), gradingCriterionTemplate.getTitleEn(), gradingCriterionTemplate.getSortOrder(), gradingCriterionTemplate.getPointsRequiredToPass());
|
||||
super(gradingCriterionTemplate.getTitle(), gradingCriterionTemplate.getTitleEn(), gradingCriterionTemplate.getSortOrder(), gradingCriterionTemplate.getPointsRequiredToPass(), gradingCriterionTemplate.getFlag());
|
||||
this.gradingReport = gradingReport;
|
||||
for (GradingCriterionPointTemplate pointTemplate : gradingCriterionTemplate.getGradingCriterionPointTemplates()) {
|
||||
gradingCriterionPoints.add(new GradingCriterionPoint(
|
||||
|
|
|
@ -15,8 +15,14 @@ import java.util.stream.Collectors;
|
|||
@Entity
|
||||
public abstract class GradingReport extends Report {
|
||||
|
||||
public enum Grade {
|
||||
A, B, C, D, E, F, FX
|
||||
public record Grade(String name) {
|
||||
public static final Grade A = new Grade("A");
|
||||
public static final Grade B = new Grade("B");
|
||||
public static final Grade C = new Grade("C");
|
||||
public static final Grade D = new Grade("D");
|
||||
public static final Grade E = new Grade("E");
|
||||
public static final Grade F = new Grade("F");
|
||||
public static final Grade FX = new Grade("FX");
|
||||
}
|
||||
|
||||
public enum State { INITIAL, REVIEWING, FINALIZED }
|
||||
|
|
|
@ -3,14 +3,13 @@ package se.su.dsv.scipro.report;
|
|||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.grading.GradingBasis;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.GenericService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public interface GradingReportService extends GenericService<GradingReport, Long> {
|
||||
public interface GradingReportService {
|
||||
|
||||
SupervisorGradingReport getSupervisorGradingReport(Project project, User student);
|
||||
|
||||
|
|
|
@ -2,54 +2,60 @@ package se.su.dsv.scipro.report;
|
|||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.inject.persist.Transactional;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.grading.GradingBasis;
|
||||
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.project.Project;
|
||||
import se.su.dsv.scipro.system.AbstractServiceImpl;
|
||||
import se.su.dsv.scipro.system.Language;
|
||||
import se.su.dsv.scipro.system.ProjectModule;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Provider;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
|
||||
@Named
|
||||
public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport, Long> implements GradingReportService {
|
||||
public class GradingReportServiceImpl implements GradingReportTemplateService, GradingReportService {
|
||||
|
||||
public static final String OPPOSITION_SWEDISH = "Ö1 Oppositionsrapport";
|
||||
public static final String OPPOSITION_ENGLISH = "Ö1 Opposition report";
|
||||
private final EventBus eventBus;
|
||||
private final ThesisSubmissionHistoryService thesisSubmissionHistoryService;
|
||||
private final Clock clock;
|
||||
private final SupervisorGradingReportRepository supervisorGradingReportRepository;
|
||||
private final GradingReportTemplateRepo gradingReportTemplateRepo;
|
||||
private final ProjectTypeService projectTypeService;
|
||||
|
||||
@Inject
|
||||
public GradingReportServiceImpl(
|
||||
Provider<EntityManager> em,
|
||||
EventBus eventBus,
|
||||
ThesisSubmissionHistoryService thesisSubmissionHistoryService,
|
||||
Clock clock)
|
||||
Clock clock,
|
||||
SupervisorGradingReportRepository supervisorGradingReportRepository,
|
||||
GradingReportTemplateRepo gradingReportTemplateRepo,
|
||||
ProjectTypeService projectTypeService)
|
||||
{
|
||||
super(em, GradingReport.class, QGradingReport.gradingReport);
|
||||
this.eventBus = eventBus;
|
||||
this.thesisSubmissionHistoryService = thesisSubmissionHistoryService;
|
||||
this.clock = clock;
|
||||
this.supervisorGradingReportRepository = supervisorGradingReportRepository;
|
||||
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
||||
this.projectTypeService = projectTypeService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) {
|
||||
for (GradingCriterion gradingCriterion : report.getIndividualCriteria()) {
|
||||
boolean isOppositionCriterion = gradingCriterion.getTitle().equals(OPPOSITION_SWEDISH) || gradingCriterion.getTitle().equals(OPPOSITION_ENGLISH);
|
||||
boolean isOppositionCriterion = gradingCriterion.getFlag() == GradingCriterion.Flag.OPPOSITION;
|
||||
boolean betterGrade = gradingCriterion.getPoints() == null || opposition.getPoints() > gradingCriterion.getPoints();
|
||||
if (isOppositionCriterion && betterGrade) {
|
||||
gradingCriterion.setFeedback(opposition.getFeedback());
|
||||
gradingCriterion.setPoints(opposition.getPoints());
|
||||
save(report);
|
||||
supervisorGradingReportRepository.save(report);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +94,7 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
|
|||
}
|
||||
supervisorGradingReport.setMotivation(gradingBasis.getOverallMotivation());
|
||||
supervisorGradingReport.setRejectionCommentFeedback(gradingBasis.getRejectionCommentFeedback());
|
||||
save(supervisorGradingReport);
|
||||
supervisorGradingReportRepository.save(supervisorGradingReport);
|
||||
}
|
||||
return getGradingBasis(project);
|
||||
}
|
||||
|
@ -138,22 +144,18 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
|
|||
@Override
|
||||
@Transactional
|
||||
public SupervisorGradingReport getSupervisorGradingReport(Project project, User user) {
|
||||
SupervisorGradingReport supervisorGradingReport = from(QSupervisorGradingReport.supervisorGradingReport)
|
||||
.where(QSupervisorGradingReport.supervisorGradingReport.user.eq(user).and(
|
||||
QSupervisorGradingReport.supervisorGradingReport.project.eq(project)))
|
||||
.fetchOne();
|
||||
SupervisorGradingReport supervisorGradingReport = supervisorGradingReportRepository.getReport(project, user);
|
||||
if (supervisorGradingReport == null) {
|
||||
supervisorGradingReport = save(getTemplate(project).createSupervisorReport(project, user));
|
||||
GradingReportTemplate template = getTemplate(project);
|
||||
SupervisorGradingReport supervisorReport = template.createSupervisorReport(project, user);
|
||||
supervisorGradingReport = supervisorGradingReportRepository.save(supervisorReport);
|
||||
}
|
||||
return supervisorGradingReport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate getTemplate(Project project) {
|
||||
QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
|
||||
return from(template)
|
||||
.where(template.projectType.eq(project.getProjectType()).and(template.credits.eq(project.getCredits())))
|
||||
.fetchOne();
|
||||
return gradingReportTemplateRepo.getTemplate(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -192,11 +194,100 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
|
|||
rejectionCommentFeedback);
|
||||
}
|
||||
|
||||
save(supervisorGradingReport);
|
||||
supervisorGradingReportRepository.save(supervisorGradingReport);
|
||||
return Either.right(supervisorGradingReport);
|
||||
}
|
||||
|
||||
private static boolean isBlank(final String str) {
|
||||
return str == null || str.isBlank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProjectType> getProjectTypes() {
|
||||
return projectTypeService.findWithModule(ProjectModule.GRADING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate getCurrentTemplate(ProjectType projectType) {
|
||||
return gradingReportTemplateRepo.getCurrentTemplate(projectType, LocalDate.now(clock));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate getTemplate(long templateId) {
|
||||
return gradingReportTemplateRepo.findOne(templateId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate getEndDate(GradingReportTemplate gradingReportTemplate) {
|
||||
GradingReportTemplate next = gradingReportTemplateRepo.getNextTemplate(gradingReportTemplate);
|
||||
if (next == null) {
|
||||
return null;
|
||||
} else {
|
||||
return next.getValidFrom().minusDays(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GradingReportTemplate> getUpcomingTemplates(ProjectType projectType) {
|
||||
GradingReportTemplate current = getCurrentTemplate(projectType);
|
||||
if (current == null) {
|
||||
return gradingReportTemplateRepo.getTemplatesValidAfter(projectType, LocalDate.now(clock));
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate create(ProjectType projectType, GradingReportTemplateUpdate update)
|
||||
throws ValidDateMustBeInTheFutureException, DuplicateDateException
|
||||
{
|
||||
LocalDate today = LocalDate.now(clock);
|
||||
if (!update.validFrom().isAfter(today)) {
|
||||
throw new ValidDateMustBeInTheFutureException(update.validFrom(), today.plusDays(1));
|
||||
}
|
||||
|
||||
GradingReportTemplate currentTemplate = gradingReportTemplateRepo.getCurrentTemplate(
|
||||
projectType,
|
||||
update.validFrom());
|
||||
if (currentTemplate != null &&
|
||||
Objects.equals(currentTemplate.getValidFrom(), update.validFrom()))
|
||||
{
|
||||
throw new DuplicateDateException(update.validFrom(), projectType);
|
||||
}
|
||||
|
||||
return gradingReportTemplateRepo.createTemplate(projectType, update);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ import se.su.dsv.scipro.system.ProjectType;
|
|||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -23,22 +26,35 @@ public class GradingReportTemplate extends DomainObject {
|
|||
@OneToOne(optional = false)
|
||||
private ProjectType projectType;
|
||||
|
||||
@OneToMany(mappedBy = "gradingReportTemplate", cascade = {CascadeType.ALL})
|
||||
@OneToMany(mappedBy = "gradingReportTemplate", cascade = {CascadeType.ALL}, orphanRemoval = true)
|
||||
private Collection<GradingCriterionTemplate> criteria = new HashSet<>();
|
||||
|
||||
@Temporal(TemporalType.DATE)
|
||||
@Column(name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Basic
|
||||
private int credits;
|
||||
@Column(name = "note")
|
||||
private String note;
|
||||
|
||||
@Basic
|
||||
@Column(name = "failing_grade")
|
||||
private String failingGrade;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@JoinColumn(name = "grading_report_template_id")
|
||||
private Collection<GradeLimit> gradeLimits = new ArrayList<>();
|
||||
|
||||
protected GradingReportTemplate() {
|
||||
|
||||
}
|
||||
|
||||
public GradingReportTemplate(ProjectType projectType, int credits) {
|
||||
public GradingReportTemplate(ProjectType projectType, LocalDate validFrom) {
|
||||
if (projectType == null) {
|
||||
throw new IllegalArgumentException("ProjectType may not be null");
|
||||
}
|
||||
this.projectType = projectType;
|
||||
this.credits = credits;
|
||||
this.validFrom = validFrom;
|
||||
}
|
||||
|
||||
public SupervisorGradingReport createSupervisorReport(Project project, User student) {
|
||||
|
@ -53,18 +69,28 @@ public class GradingReportTemplate extends DomainObject {
|
|||
}
|
||||
|
||||
public GradingCriterionTemplate addProjectCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates) {
|
||||
return addProjectCriterion(title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates, null);
|
||||
}
|
||||
|
||||
public GradingCriterionTemplate addProjectCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates, AbstractGradingCriterion.Flag flag) {
|
||||
GradingCriterionTemplate gradingCriterionTemplate = new ProjectGradingCriterionTemplate(this, criteria.size(), title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates);
|
||||
gradingCriterionTemplate.setFlag(flag);
|
||||
criteria.add(gradingCriterionTemplate);
|
||||
return gradingCriterionTemplate;
|
||||
}
|
||||
|
||||
public GradingCriterionTemplate addIndividualCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates) {
|
||||
return addIndividualCriterion(title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates, null);
|
||||
}
|
||||
|
||||
public GradingCriterionTemplate addIndividualCriterion(String title, String titleEn, int pointsRequiredToPass, List<GradingCriterionPointTemplate> gradingCriterionPointTemplates, AbstractGradingCriterion.Flag flag) {
|
||||
GradingCriterionTemplate gradingCriterionTemplate = new IndividualGradingCriterionTemplate(this, criteria.size(), title, titleEn, pointsRequiredToPass, gradingCriterionPointTemplates);
|
||||
gradingCriterionTemplate.setFlag(flag);
|
||||
criteria.add(gradingCriterionTemplate);
|
||||
return gradingCriterionTemplate;
|
||||
}
|
||||
|
||||
public Iterable<GradingCriterionTemplate> getCriteria() {
|
||||
public Collection<GradingCriterionTemplate> getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
|
@ -73,6 +99,46 @@ public class GradingReportTemplate extends DomainObject {
|
|||
return this.id;
|
||||
}
|
||||
|
||||
public LocalDate getValidFrom() {
|
||||
return validFrom;
|
||||
}
|
||||
|
||||
public void setValidFrom(LocalDate validFrom) {
|
||||
this.validFrom = validFrom;
|
||||
}
|
||||
|
||||
public ProjectType getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
public void setProjectType(ProjectType projectType) {
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
public String getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
public void setNote(String note) {
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
public String getFailingGrade() {
|
||||
return failingGrade;
|
||||
}
|
||||
|
||||
public void setFailingGrade(String failingGrade) {
|
||||
this.failingGrade = failingGrade;
|
||||
}
|
||||
|
||||
public Collection<GradeLimit> getGradeLimits() {
|
||||
return gradeLimits;
|
||||
}
|
||||
|
||||
public void setGradeLimits(Collection<GradeLimit> gradeLimits) {
|
||||
this.gradeLimits = gradeLimits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) return true;
|
||||
|
@ -93,6 +159,6 @@ public class GradingReportTemplate extends DomainObject {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GradingReportTemplate(id=" + this.id + ", projectType=" + this.projectType + ", credits=" + this.credits + ")";
|
||||
return "GradingReportTemplate(id=" + this.id + ", projectType=" + this.projectType + ", validFrom=" + this.validFrom + ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
class GradingReportTemplateGradeCalculator implements GradeCalculator {
|
||||
private final GradingReportTemplate template;
|
||||
|
||||
GradingReportTemplateGradeCalculator(GradingReportTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReport.Grade getGrade(GradingReport gradingReport) {
|
||||
for (GradingCriterion gradingCriterion : gradingReport.getGradingCriteria()) {
|
||||
if (!gradingCriterion.meetsMinimumPointRequirement()) {
|
||||
return new GradingReport.Grade(template.getFailingGrade());
|
||||
}
|
||||
}
|
||||
long points = getPoints(gradingReport);
|
||||
String textualGrade = template.getGradeLimits()
|
||||
.stream()
|
||||
.filter(gradeLimit -> points >= gradeLimit.getLowerLimit())
|
||||
.max(Comparator.comparing(GradeLimit::getLowerLimit))
|
||||
.map(GradeLimit::getGrade)
|
||||
.orElseGet(template::getFailingGrade);
|
||||
return new GradingReport.Grade(textualGrade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPoints(GradingReport gradingReport) {
|
||||
return gradingReport.getGradingCriteria()
|
||||
.stream()
|
||||
.map(GradingCriterion::getPoints)
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(Integer::intValue)
|
||||
.sum();
|
||||
}
|
||||
}
|
|
@ -1,8 +1,25 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import se.su.dsv.scipro.system.JpaRepository;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface GradingReportTemplateRepo extends JpaRepository<GradingReportTemplate, Long> {
|
||||
GradingReportTemplate findByProjectTypeAndCredits(ProjectType projectType, int credits);
|
||||
GradingReportTemplate getTemplate(Project project);
|
||||
|
||||
GradingReportTemplate getCurrentTemplate(ProjectType projectType, LocalDate now);
|
||||
|
||||
GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate);
|
||||
|
||||
List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date);
|
||||
|
||||
GradingReportTemplate getTemplateById(long templateId);
|
||||
|
||||
GradingReportTemplate updateTemplate(long templateId, GradingReportTemplateUpdate update);
|
||||
|
||||
GradingReportTemplate createTemplate(ProjectType projectType, GradingReportTemplateUpdate update);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import com.google.inject.persist.Transactional;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.GenericRepo;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemplate, Long> implements GradingReportTemplateRepo {
|
||||
@Inject
|
||||
|
@ -15,10 +22,114 @@ public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemp
|
|||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate findByProjectTypeAndCredits(ProjectType projectType, int credits) {
|
||||
TypedQuery<GradingReportTemplate> query = em().createQuery("select distinct template from GradingReportTemplate template where template.projectType = :projectType and template.credits = :credits", GradingReportTemplate.class);
|
||||
query.setParameter("projectType", projectType);
|
||||
query.setParameter("credits", credits);
|
||||
return query.getSingleResult();
|
||||
public GradingReportTemplate getTemplate(Project project) {
|
||||
return getCurrentTemplate(project.getProjectType(), project.getStartDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate getCurrentTemplate(ProjectType projectType, LocalDate now) {
|
||||
QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
|
||||
// find the latest template that is valid for the project
|
||||
JPQLQuery<LocalDate> validFrom = JPAExpressions
|
||||
.select(template.validFrom.max())
|
||||
.from(template)
|
||||
.where(template.projectType.eq(projectType)
|
||||
.and(template.validFrom.loe(now)));
|
||||
return findOne(template.projectType.eq(projectType).and(template.validFrom.eq(validFrom)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradingReportTemplate getNextTemplate(GradingReportTemplate gradingReportTemplate) {
|
||||
QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
|
||||
// find the latest template that is valid for the project
|
||||
JPQLQuery<LocalDate> validFrom = JPAExpressions
|
||||
.select(template.validFrom.min())
|
||||
.from(template)
|
||||
.where(template.projectType.eq(gradingReportTemplate.getProjectType())
|
||||
.and(template.validFrom.gt(gradingReportTemplate.getValidFrom())));
|
||||
return findOne(template.projectType.eq(gradingReportTemplate.getProjectType()).and(template.validFrom.eq(validFrom)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GradingReportTemplate> getTemplatesValidAfter(ProjectType projectType, LocalDate date) {
|
||||
return findAll(QGradingReportTemplate.gradingReportTemplate.projectType.eq(projectType)
|
||||
.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 updateTemplate(gradingReportTemplate, update);
|
||||
}
|
||||
|
||||
private GradingReportTemplate updateTemplate(
|
||||
GradingReportTemplate gradingReportTemplate,
|
||||
GradingReportTemplateUpdate update)
|
||||
{
|
||||
gradingReportTemplate.setValidFrom(update.validFrom());
|
||||
gradingReportTemplate.setNote(update.note());
|
||||
gradingReportTemplate.setFailingGrade(update.failingGrade());
|
||||
|
||||
gradingReportTemplate.getCriteria().clear();
|
||||
for (var criteria : update.criteria()) {
|
||||
final List<GradingCriterionPointTemplate> pointTemplates = criteria.requirements()
|
||||
.stream()
|
||||
.map(this::toPointTemplate)
|
||||
.toList();
|
||||
AbstractGradingCriterion.Flag flag = criteria.flag() == null ? null : switch (criteria.flag()) {
|
||||
case OPPOSITION -> AbstractGradingCriterion.Flag.OPPOSITION;
|
||||
case REFLECTION -> AbstractGradingCriterion.Flag.REFLECTION;
|
||||
//case null -> null; sigh java 17
|
||||
};
|
||||
switch (criteria.type()) {
|
||||
case THESIS -> {
|
||||
gradingReportTemplate.addProjectCriterion(
|
||||
criteria.title().swedish(),
|
||||
criteria.title().english(),
|
||||
criteria.minimumPointsRequiredToPass(),
|
||||
pointTemplates,
|
||||
flag);
|
||||
}
|
||||
case INDIVIDUAL -> {
|
||||
gradingReportTemplate.addIndividualCriterion(
|
||||
criteria.title().swedish(),
|
||||
criteria.title().english(),
|
||||
criteria.minimumPointsRequiredToPass(),
|
||||
pointTemplates,
|
||||
flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gradingReportTemplate.getGradeLimits().clear();
|
||||
for (var grade : update.gradeLimits()) {
|
||||
GradeLimit gradeLimit = new GradeLimit();
|
||||
gradeLimit.setGrade(grade.grade());
|
||||
gradeLimit.setLowerLimit(grade.minimumPoints());
|
||||
gradingReportTemplate.getGradeLimits().add(gradeLimit);
|
||||
}
|
||||
|
||||
return save(gradingReportTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public GradingReportTemplate createTemplate(ProjectType projectType, GradingReportTemplateUpdate update) {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType, update.validFrom());
|
||||
return updateTemplate(gradingReportTemplate, update);
|
||||
}
|
||||
|
||||
private GradingCriterionPointTemplate toPointTemplate(GradingReportTemplateUpdate.Criteria.Requirement requirement) {
|
||||
return new GradingCriterionPointTemplate.Builder()
|
||||
.point(requirement.points())
|
||||
.description(requirement.description().swedish())
|
||||
.descriptionEn(requirement.description().english())
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
public class NoSuchTemplateException extends Exception {
|
||||
}
|
|
@ -33,7 +33,7 @@ public class OppositionReportServiceImpl implements OppositionReportService {
|
|||
if (oppositionReport != null) {
|
||||
return oppositionReport;
|
||||
} else {
|
||||
OppositionReport newReport = gradingReportTemplateRepo.findByProjectTypeAndCredits(finalSeminarOpposition.getProjectType(), finalSeminarOpposition.getProject().getCredits())
|
||||
OppositionReport newReport = gradingReportTemplateRepo.getTemplate(finalSeminarOpposition.getProject())
|
||||
.createOppositionReport(finalSeminarOpposition);
|
||||
return oppositionReportRepo.save(newReport);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public interface SupervisorGradingReportRepository {
|
||||
SupervisorGradingReport save(SupervisorGradingReport report);
|
||||
|
||||
SupervisorGradingReport getReport(Project project, User author);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package se.su.dsv.scipro.report;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.AbstractRepository;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public class SupervisorGradingReportRepositoryImpl extends AbstractRepository
|
||||
implements SupervisorGradingReportRepository
|
||||
{
|
||||
@Inject
|
||||
public SupervisorGradingReportRepositoryImpl(Provider<EntityManager> em) {
|
||||
super(em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupervisorGradingReport save(SupervisorGradingReport report) {
|
||||
EntityManager entityManager = em();
|
||||
if (entityManager.contains(report)) {
|
||||
return entityManager.merge(report);
|
||||
}
|
||||
else {
|
||||
entityManager.persist(report);
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupervisorGradingReport getReport(Project project, User author) {
|
||||
return from(QSupervisorGradingReport.supervisorGradingReport)
|
||||
.where(QSupervisorGradingReport.supervisorGradingReport.user.eq(author).and(
|
||||
QSupervisorGradingReport.supervisorGradingReport.project.eq(project)))
|
||||
.fetchOne();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
# For any given date there is only ever one valid template for a project type
|
||||
# That template is valid until the date a new template is valid from
|
||||
ALTER TABLE `grading_report_template`
|
||||
ADD COLUMN `valid_from` DATE NOT NULL DEFAULT '2001-01-01';
|
||||
|
||||
CREATE TEMPORARY TABLE pt_points
|
||||
(
|
||||
pt_id INT,
|
||||
credits INT,
|
||||
count INT
|
||||
);
|
||||
|
||||
# Count how many projects/credits each project type has
|
||||
INSERT INTO pt_points
|
||||
SELECT pt.id, credits, COUNT(*)
|
||||
FROM ProjectType pt
|
||||
INNER JOIN project p ON pt.id = p.projectType_id
|
||||
WHERE pt.id IN (SELECT projectType_id FROM grading_report_template)
|
||||
GROUP BY pt.id, credits;
|
||||
|
||||
CREATE TEMPORARY TABLE grading_templates_to_keep
|
||||
(
|
||||
id INT
|
||||
);
|
||||
|
||||
# Keep the most used grading template for each project type (based on projects/credits)
|
||||
INSERT INTO grading_templates_to_keep (id)
|
||||
SELECT t.id
|
||||
FROM grading_report_template t
|
||||
WHERE credits = (SELECT credits
|
||||
FROM pt_points m
|
||||
WHERE m.pt_id = t.projectType_id
|
||||
AND m.`count` = (SELECT MAX(count)
|
||||
FROM pt_points n
|
||||
WHERE n.pt_id = t.projectType_id));
|
||||
|
||||
DELETE FROM grading_criterion_point_template WHERE gradingCriterionTemplate_id IN (SELECT id FROM grading_criterion_template WHERE gradingReportTemplate_id NOT IN (SELECT id FROM grading_templates_to_keep));
|
||||
DELETE FROM grading_criterion_template WHERE gradingReportTemplate_id NOT IN (SELECT id FROM grading_templates_to_keep);
|
||||
DELETE FROM grading_report_template WHERE id NOT IN (SELECT id FROM grading_templates_to_keep);
|
||||
|
||||
DROP TABLE grading_templates_to_keep;
|
||||
DROP TABLE pt_points;
|
||||
|
||||
ALTER TABLE `grading_report_template`
|
||||
DROP COLUMN `credits`,
|
||||
ADD UNIQUE `UK_only_one_template_per_date_and_type` (`projectType_id`, `valid_from`);
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE `grading_report_template`
|
||||
ADD COLUMN `note` TEXT,
|
||||
ADD COLUMN `failing_grade` VARCHAR(32);
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE `grading_report_template_grade_limits` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`grading_report_template_id` BIGINT, -- can't be NOT NULL because of Hibernate using an INSERT followed by an UPDATE
|
||||
`grade` VARCHAR(32) NOT NULL,
|
||||
`lower_limit` INT NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `UK_one_grade_per_template` (`grading_report_template_id`, `grade`),
|
||||
FOREIGN KEY `FK_grade_limit_grading_report_template `(`grading_report_template_id`)
|
||||
REFERENCES `grading_report_template` (`id`) ON DELETE CASCADE
|
||||
);
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE `grading_criterion_template`
|
||||
ADD COLUMN `flag` VARCHAR(64);
|
||||
ALTER TABLE `GradingCriterion`
|
||||
ADD COLUMN `flag` VARCHAR(64);
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE `GradingCriterion` SET `flag` = 'OPPOSITION' WHERE title like 'Ö1 %';
|
||||
UPDATE `grading_criterion_template` SET `flag` = 'OPPOSITION' WHERE title like 'Ö1 %';
|
|
@ -0,0 +1,2 @@
|
|||
UPDATE `GradingCriterion` SET `flag` = 'REFLECTION' WHERE title like 'Ö6 %';
|
||||
UPDATE `grading_criterion_template` SET `flag` = 'REFLECTION' WHERE title like 'Ö6 %';
|
|
@ -13,6 +13,7 @@ import se.su.dsv.scipro.test.IntegrationTest;
|
|||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -52,7 +53,8 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio
|
|||
}
|
||||
|
||||
private GradingReportTemplate createGradingReportTemplate() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
return save(gradingReportTemplate);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import se.su.dsv.scipro.test.IntegrationTest;
|
|||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -316,7 +317,8 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
|||
}
|
||||
|
||||
private GradingReportTemplate createGradingReportTemplate(ProjectType projectType) {
|
||||
GradingReportTemplate template = new GradingReportTemplate(projectType, 30);
|
||||
GradingReportTemplate template = new GradingReportTemplate(projectType,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
return save(template);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ import se.su.dsv.scipro.test.InstanceProvider;
|
|||
import jakarta.persistence.EntityManager;
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -137,7 +139,8 @@ public class FinalSeminarServiceImplTest {
|
|||
|
||||
FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
|
||||
DomainObjects.injectId(finalSeminarOpposition, 1L);
|
||||
finalSeminarOpposition.setOppositionReport(new OppositionReport(new GradingReportTemplate(new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor"), 1), finalSeminarOpposition));
|
||||
finalSeminarOpposition.setOppositionReport(new OppositionReport(new GradingReportTemplate(new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor"),
|
||||
LocalDate.of(2024, Month.JANUARY, 1)), finalSeminarOpposition));
|
||||
finalSeminar.setOppositions(Collections.singletonList(finalSeminarOpposition));
|
||||
|
||||
seminarService.delete(finalSeminar);
|
||||
|
|
|
@ -15,14 +15,13 @@ import se.su.dsv.scipro.util.Either;
|
|||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static se.su.dsv.scipro.report.GradingReportServiceImpl.OPPOSITION_ENGLISH;
|
||||
import static se.su.dsv.scipro.report.GradingReportServiceImpl.OPPOSITION_SWEDISH;
|
||||
|
||||
public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
|
||||
|
@ -82,7 +81,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
addOppositionCriterion();
|
||||
boolean updated = updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||
|
@ -94,7 +93,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
addOppositionCriterion();
|
||||
boolean updated = updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findEnglishOppositionCriterion(OPPOSITION_ENGLISH);
|
||||
GradingCriterion oppositionCriterion = findEnglishOppositionCriterion("Ö1 Opposition report");
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||
|
@ -107,7 +106,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
assessAllCriteria(gradingReport);
|
||||
boolean updated = updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK, oppositionCriterion.getFeedback());
|
||||
assertEquals((Integer) oppositionCriterion.getMaxPoints(), oppositionCriterion.getPoints());
|
||||
|
@ -121,7 +120,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
setPointsOnAllCriteria(gradingReport, 1);
|
||||
updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||
assertEquals(OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||
|
@ -134,7 +133,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
setPointsOnAllCriteria(gradingReport, points);
|
||||
updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion(OPPOSITION_SWEDISH);
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals((Integer) points, oppositionCriterion.getPoints());
|
||||
assertNull(oppositionCriterion.getFeedback());
|
||||
|
@ -150,9 +149,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
return gradingReportService.updateOppositionCriteria(gradingReport, opposition);
|
||||
}
|
||||
|
||||
private GradingCriterion findOppositionCriterion(String title) {
|
||||
private GradingCriterion findOppositionCriterion() {
|
||||
for (GradingCriterion gradingCriterion : gradingReport.getIndividualCriteria()) {
|
||||
if (gradingCriterion.getTitle().equals(title)) {
|
||||
if (gradingCriterion.getFlag() == AbstractGradingCriterion.Flag.OPPOSITION) {
|
||||
return gradingCriterion;
|
||||
}
|
||||
}
|
||||
|
@ -246,12 +245,13 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||
|
||||
private GradingReportTemplate createOppositionCriteria(GradingReportTemplate gradingReportTemplate, int maxPoints) {
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = getPointTemplates(maxPoints, "Opposition");
|
||||
gradingReportTemplate.addIndividualCriterion(OPPOSITION_SWEDISH, OPPOSITION_ENGLISH, 0, gradingCriterionPointTemplates);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 0, gradingCriterionPointTemplates, AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
return save(gradingReportTemplate);
|
||||
}
|
||||
|
||||
private GradingReportTemplate createGradingReportTemplate(ProjectType projectType) {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(projectType,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
return save(gradingReportTemplate);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import se.su.dsv.scipro.system.ProjectType;
|
|||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -34,7 +35,7 @@ public class GradingReportTemplateTest {
|
|||
@Test
|
||||
public void creating_a_grading_report_template_with_null_project_type_should_fail() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
new GradingReportTemplate(null, 30));
|
||||
new GradingReportTemplate(null, LocalDate.of(2024, Month.JANUARY, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -100,6 +101,6 @@ public class GradingReportTemplateTest {
|
|||
}
|
||||
|
||||
private GradingReportTemplate createBachelorTemplate() {
|
||||
return new GradingReportTemplate(bachelor, 30);
|
||||
return new GradingReportTemplate(bachelor, LocalDate.of(2024, Month.JANUARY, 1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import se.su.dsv.scipro.file.FileService;
|
|||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
@ -20,12 +21,13 @@ import se.su.dsv.scipro.test.DomainObjects;
|
|||
import se.su.dsv.scipro.test.ObjectMother;
|
||||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class OppositionReportServiceImplTest {
|
||||
|
@ -49,7 +51,7 @@ public class OppositionReportServiceImplTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
GradingReportTemplate template = new GradingReportTemplate(BACHELOR, 30);
|
||||
GradingReportTemplate template = new GradingReportTemplate(BACHELOR, LocalDate.of(2024, Month.JANUARY, 1));
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
.point(1)
|
||||
|
@ -73,7 +75,7 @@ public class OppositionReportServiceImplTest {
|
|||
public void create_report_if_not_existing() {
|
||||
GradingReportTemplate template = Mockito.mock(GradingReportTemplate.class);
|
||||
Mockito.when(template.createOppositionReport(any(FinalSeminarOpposition.class))).thenReturn(oppositionReport);
|
||||
Mockito.when(gradingReportTemplateRepo.findByProjectTypeAndCredits(any(ProjectType.class), anyInt())).thenReturn(template);
|
||||
Mockito.when(gradingReportTemplateRepo.getTemplate(any(Project.class))).thenReturn(template);
|
||||
Mockito.when(oppositionReportRepo.save(oppositionReport)).thenReturn(oppositionReport);
|
||||
assertEquals(oppositionReport, oppositionReportService.findOrCreateReport(createFinalSeminarOpposition()));
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
|||
import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -20,7 +22,8 @@ public class OppositionReportTest {
|
|||
public void prepareTemplate() {
|
||||
ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor");
|
||||
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelor, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(bachelor,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
addCriteria(gradingReportTemplate);
|
||||
|
||||
oppositionReport = gradingReportTemplate.createOppositionReport(new FinalSeminarOpposition());
|
||||
|
|
|
@ -3,6 +3,8 @@ package se.su.dsv.scipro.report;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -16,7 +18,7 @@ public class SupervisorGradingReportFactoryTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
|
||||
gradingReportTemplate = new GradingReportTemplate(BACHELOR, LocalDate.of(2024, Month.JANUARY, 1));
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
.point(1)
|
||||
|
|
|
@ -8,6 +8,7 @@ import se.su.dsv.scipro.system.ProjectType;
|
|||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -25,7 +26,7 @@ public class SupervisorGradingReportTest {
|
|||
ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "bachelor", "bachelor");
|
||||
project = Project.builder().title("Foo").projectType(bachelor).startDate(LocalDate.now()).build();
|
||||
|
||||
gradingReportTemplate = new GradingReportTemplate(bachelor, 30);
|
||||
gradingReportTemplate = new GradingReportTemplate(bachelor, LocalDate.of(2024, Month.JANUARY, 1));
|
||||
addCriteria(gradingReportTemplate);
|
||||
|
||||
gradingReport = gradingReportTemplate.createSupervisorReport(project, new User());
|
||||
|
@ -73,12 +74,12 @@ public class SupervisorGradingReportTest {
|
|||
gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 0, getPointTemplates(3)).setFx(false);
|
||||
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
}
|
||||
|
||||
private List<GradingCriterionPointTemplate> getPointTemplates(int maxPoint) {
|
||||
|
|
|
@ -6,6 +6,8 @@ import se.su.dsv.scipro.report.*;
|
|||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.ObjectMother;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -85,7 +87,8 @@ public class SupervisorBachelorGradeCalculatorTest extends GradeCalculatorTest {
|
|||
|
||||
@Override
|
||||
protected GradingReportTemplate prepareTemplate() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.BACHELOR, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.BACHELOR,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
|
||||
gradingReportTemplate.addProjectCriterion("U1 Sammanfattning", "U1 Abstract", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addProjectCriterion("U2 Introduktion", "U2 Introduction", 1, getPointTemplates(1));
|
||||
|
@ -101,12 +104,12 @@ public class SupervisorBachelorGradeCalculatorTest extends GradeCalculatorTest {
|
|||
gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 0, getPointTemplates(3)).setFx(false);
|
||||
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
|
||||
return gradingReportTemplate;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import se.su.dsv.scipro.report.*;
|
|||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.ObjectMother;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -72,7 +74,8 @@ public class SupervisorMaster15GradeCalculatorTest extends GradeCalculatorTest {
|
|||
|
||||
@Override
|
||||
protected GradingReportTemplate prepareTemplate() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER, 15);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
|
||||
gradingReportTemplate.addProjectCriterion("U1 Sammanfattning", "U1 Abstract", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addProjectCriterion("U2 Introduktion", "U2 Introduction", 1, getPointTemplates(1));
|
||||
|
@ -88,12 +91,12 @@ public class SupervisorMaster15GradeCalculatorTest extends GradeCalculatorTest {
|
|||
gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 1, getPointTemplates(3));
|
||||
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
|
||||
return gradingReportTemplate;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import se.su.dsv.scipro.report.*;
|
|||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.ObjectMother;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -72,7 +74,8 @@ public class SupervisorMaster30GradeCalculatorTest extends GradeCalculatorTest {
|
|||
|
||||
@Override
|
||||
protected GradingReportTemplate prepareTemplate() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(ObjectMother.MASTER,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
|
||||
gradingReportTemplate.addProjectCriterion("U1 Sammanfattning", "U1 Abstract", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addProjectCriterion("U2 Introduktion", "U2 Introduction", 1, getPointTemplates(1));
|
||||
|
@ -88,12 +91,12 @@ public class SupervisorMaster30GradeCalculatorTest extends GradeCalculatorTest {
|
|||
gradingReportTemplate.addProjectCriterion("U12 Källhänvisningar och dokumentation", "U12 References and documentation", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addProjectCriterion("U13 Originalitet och signifikans", "U13 Originality and significance", 2, getPointTemplates(4));
|
||||
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö1 Oppositionsrapport", "Ö1 Opposition report", 1, getPointTemplates(2), AbstractGradingCriterion.Flag.OPPOSITION);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö2 Presentationer", "Ö2 Presentations", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö3 Aktivitet vid seminarier och möten", "Ö3 Participation in seminars and meetings", 1, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö4 Deadlines", "Ö4 Deadlines", 0, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö5 Revision efter slutseminarium", "Ö5 Revisions after the final seminar", 0, getPointTemplates(1));
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1)).setFx(false);
|
||||
gradingReportTemplate.addIndividualCriterion("Ö6 Reflektion", "Ö6 Reflection", 0, getPointTemplates(1), AbstractGradingCriterion.Flag.REFLECTION).setFx(false);
|
||||
|
||||
return gradingReportTemplate;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ import org.apache.wicket.util.convert.converter.LocalDateConverter;
|
|||
import org.apache.wicket.util.convert.converter.LocalDateTimeConverter;
|
||||
import se.su.dsv.scipro.activityplan.*;
|
||||
import se.su.dsv.scipro.admin.pages.*;
|
||||
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplateCreationPage;
|
||||
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplateEditPage;
|
||||
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatePage;
|
||||
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatesOverviewPage;
|
||||
import se.su.dsv.scipro.admin.pages.settings.*;
|
||||
import se.su.dsv.scipro.applicationperiod.AdminEditApplicationPeriodExemptionsPage;
|
||||
import se.su.dsv.scipro.applicationperiod.AdminEditApplicationPeriodPage;
|
||||
|
@ -282,6 +286,10 @@ public class SciProApplication extends LifecycleManagedWebApplication {
|
|||
mountPage("admin/project/survey", AdminSurveyPage.class);
|
||||
mountPage("admin/project/reviewer", AdminAssignReviewerPage.class);
|
||||
mountPage("admin/project/reviewer/capacity", AdminReviewerCapacityManagementPage.class);
|
||||
mountPage("admin/project/grading/template", AdminGradingTemplatesOverviewPage.class);
|
||||
mountPage("admin/project/grading/template/view", AdminGradingTemplatePage.class);
|
||||
mountPage("admin/project/grading/template/edit", AdminGradingTemplateEditPage.class);
|
||||
mountPage("admin/project/grading/template/create", AdminGradingTemplateCreationPage.class);
|
||||
mountPage("admin/edit", AdminEditProjectPage.class);
|
||||
mountPage("admin/finalseminars", AdminFinalSeminarPage.class);
|
||||
mountPage("admin/finalseminars/exemptions", AdminFinalSeminarExemptionPage.class);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package se.su.dsv.scipro.admin.pages;
|
||||
|
||||
import se.su.dsv.scipro.activityplan.AdminActivityPlanTemplatesPage;
|
||||
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatesOverviewPage;
|
||||
import se.su.dsv.scipro.admin.pages.grading.MenuHighlightGradingTemplates;
|
||||
import se.su.dsv.scipro.checklists.AdminChecklistPage;
|
||||
import se.su.dsv.scipro.components.AbstractMenuPanel;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminActivityPlanTemplates;
|
||||
|
@ -28,7 +30,8 @@ public class AbstractAdminProjectPage extends AbstractAdminPage {
|
|||
new MenuItem("Checklist templates", AdminChecklistPage.class, MenuHighlightAdminChecklist.class),
|
||||
new MenuItem("Non work days", NonWorkDaysPage.class),
|
||||
new MenuItem("Survey", AdminSurveyPage.class),
|
||||
new MenuItem("Reviewer targets", AdminReviewerCapacityManagementPage.class)
|
||||
new MenuItem("Reviewer targets", AdminReviewerCapacityManagementPage.class),
|
||||
new MenuItem("Grading templates", AdminGradingTemplatesOverviewPage.class, MenuHighlightGradingTemplates.class)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<h1>Create a new grading report template</h1>
|
||||
|
||||
<div wicket:id="feedback"></div>
|
||||
|
||||
<form wicket:id="form" class="line-length-limit">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" wicket:for="project_type">Project type</label>
|
||||
<select wicket:id="project_type" class="form-select">
|
||||
<option value="bachelor">Bachelor</option>
|
||||
<option value="master">Master</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div wicket:id="grading_template_component_panel"></div>
|
||||
|
||||
<div class="position-sticky bottom-0 bg-white p-3 border line-length-limit">
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,80 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
|
||||
import se.su.dsv.scipro.components.AjaxDropDownChoice;
|
||||
import se.su.dsv.scipro.data.DetachableServiceModel;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||
import se.su.dsv.scipro.report.DuplicateDateException;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
import se.su.dsv.scipro.report.ValidDateMustBeInTheFutureException;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeService;
|
||||
|
||||
public class AdminGradingTemplateCreationPage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
|
||||
|
||||
@Inject
|
||||
GradingReportTemplateService gradingReportTemplateService;
|
||||
@Inject
|
||||
ProjectTypeService projectTypeService;
|
||||
|
||||
private final IModel<ProjectType> projectTypeModel;
|
||||
private EditingGradingTemplate editingGradingTemplateModel;
|
||||
|
||||
public AdminGradingTemplateCreationPage() {
|
||||
projectTypeModel = new DetachableServiceModel<>(projectTypeService);
|
||||
|
||||
add(new FeedbackPanel("feedback"));
|
||||
|
||||
Form<?> form = new Form<>("form") {
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
super.onSubmit();
|
||||
GradingReportTemplateUpdate update = AdminGradingTemplateEditPage.toUpdate(editingGradingTemplateModel);
|
||||
try {
|
||||
GradingReportTemplate gradingReportTemplate = gradingReportTemplateService.create(
|
||||
projectTypeModel.getObject(),
|
||||
update);
|
||||
getSession().success(getString("template_created", projectTypeModel));
|
||||
PageParameters pageParameters = AdminGradingTemplateEditPage.getPageParameters(gradingReportTemplate);
|
||||
throw new RestartResponseException(AdminGradingTemplateEditPage.class, pageParameters);
|
||||
} catch (ValidDateMustBeInTheFutureException e) {
|
||||
error(getString("valid_from_must_be_in_the_future", () -> e));
|
||||
} catch (DuplicateDateException e) {
|
||||
error(getString("another_template_exists_for_the_given_date_date", () -> e));
|
||||
}
|
||||
}
|
||||
};
|
||||
form.setOutputMarkupId(true);
|
||||
add(form);
|
||||
|
||||
form.add(new AjaxDropDownChoice<>(
|
||||
"project_type",
|
||||
projectTypeModel,
|
||||
gradingReportTemplateService.getProjectTypes(),
|
||||
new LambdaChoiceRenderer<>(ProjectType::getName, ProjectType::getId)) {
|
||||
@Override
|
||||
public void onNewSelection(AjaxRequestTarget target, ProjectType objectSelected) {
|
||||
target.add(form);
|
||||
}
|
||||
});
|
||||
|
||||
editingGradingTemplateModel = new EditingGradingTemplate();
|
||||
form.add(new EditingGradingTemplateComponentPanel("grading_template_component_panel", Model.of(editingGradingTemplateModel)) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(projectTypeModel.getObject() != null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<div wicket:id="feedback"></div>
|
||||
<form wicket:id="form">
|
||||
<div wicket:id="editing" class="mb-3"></div>
|
||||
<div class="position-sticky bottom-0 bg-white p-3 border line-length-limit">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,130 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
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.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
|
||||
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.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 {
|
||||
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
|
||||
GradingReportTemplateService gradingReportTemplateService;
|
||||
|
||||
private EditingGradingTemplate editingGradingTemplate;
|
||||
|
||||
public AdminGradingTemplateEditPage(PageParameters pageParameters) {
|
||||
long id = pageParameters.get(GRADING_REPORT_TEMPLATE_ID_PARAMETER).toLong();
|
||||
|
||||
GradingReportTemplate template = gradingReportTemplateService.getTemplate(id);
|
||||
|
||||
editingGradingTemplate = new EditingGradingTemplate(template);
|
||||
|
||||
add(new FeedbackPanel("feedback"));
|
||||
|
||||
Form<EditingGradingTemplate> form = new Form<>("form") {
|
||||
@Override
|
||||
protected void 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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
form.add(new EditingGradingTemplateComponentPanel("editing", Model.of(editingGradingTemplate)));
|
||||
add(form);
|
||||
}
|
||||
|
||||
static GradingReportTemplateUpdate toUpdate(EditingGradingTemplate editingGradingTemplate) {
|
||||
List<GradingReportTemplateUpdate.GradeLimit> gradeLimits = editingGradingTemplate
|
||||
.getGradeLimits()
|
||||
.getGradeLimits()
|
||||
.stream()
|
||||
.map(AdminGradingTemplateEditPage::toGrade)
|
||||
.toList();
|
||||
List<GradingReportTemplateUpdate.Criteria> criteria = editingGradingTemplate
|
||||
.getCriteria()
|
||||
.stream()
|
||||
.map(AdminGradingTemplateEditPage::toCriteria)
|
||||
.toList();
|
||||
return new GradingReportTemplateUpdate(
|
||||
editingGradingTemplate.getValidFrom(),
|
||||
editingGradingTemplate.getNote(),
|
||||
editingGradingTemplate.getGradeLimits().getFailingGrade(),
|
||||
gradeLimits,
|
||||
criteria);
|
||||
}
|
||||
|
||||
private static 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()),
|
||||
switch (criteria.getType()) {
|
||||
case PROJECT -> GradingReportTemplateUpdate.Criteria.Type.THESIS;
|
||||
case INDIVIDUAL -> GradingReportTemplateUpdate.Criteria.Type.INDIVIDUAL;
|
||||
},
|
||||
criteria.getPointsRequiredToPass(),
|
||||
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 static GradingReportTemplateUpdate.GradeLimit toGrade(GradeLimits.GradeLimit gradeLimit) {
|
||||
return new GradingReportTemplateUpdate.GradeLimit(
|
||||
gradeLimit.getGrade(),
|
||||
gradeLimit.getLowerLimit());
|
||||
}
|
||||
|
||||
public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
|
||||
PageParameters pageParameters = new PageParameters();
|
||||
pageParameters.add(GRADING_REPORT_TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
|
||||
return pageParameters;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<head>
|
||||
<title>View a specific grading report template</title>
|
||||
<wicket:remove>
|
||||
<link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
</wicket:remove>
|
||||
</head>
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<div class="line-length-limit" wicket:id="details">
|
||||
<h1>
|
||||
Grading report template for
|
||||
<wicket:container wicket:id="project_type"/>
|
||||
<small>
|
||||
(<span wicket:id="valid_period"></span>)
|
||||
</small>
|
||||
</h1>
|
||||
<h2>Criteria</h2>
|
||||
<ol class="list-unstyled">
|
||||
<li wicket:id="criteria" class="mb-3">
|
||||
<h3 wicket:id="title"></h3>
|
||||
<ol class="list-unstyled">
|
||||
<li wicket:id="points">
|
||||
<span wicket:id="point"></span> - <span wicket:id="description"></span>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,95 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.markup.html.GenericWebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||
import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
|
||||
import se.su.dsv.scipro.report.GradingCriterionTemplate;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class AdminGradingTemplatePage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
|
||||
private static final String TEMPLATE_ID_PARAMETER = "id";
|
||||
|
||||
@Inject
|
||||
private GradingReportTemplateService gradingReportTemplateService;
|
||||
|
||||
public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
|
||||
PageParameters pageParameters = new PageParameters();
|
||||
pageParameters.add(TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
|
||||
return pageParameters;
|
||||
}
|
||||
|
||||
public AdminGradingTemplatePage(PageParameters parameters) {
|
||||
long templateId = parameters.get(TEMPLATE_ID_PARAMETER).toLong();
|
||||
|
||||
IModel<GradingReportTemplate> template = LoadableDetachableModel.of(() ->
|
||||
gradingReportTemplateService.getTemplate(templateId));
|
||||
|
||||
if (template.getObject() == null) {
|
||||
throw new RestartResponseException(AdminGradingTemplatesOverviewPage.class);
|
||||
}
|
||||
|
||||
add(new TemplateDetailsPanel("details", template));
|
||||
}
|
||||
|
||||
private class TemplateDetailsPanel extends GenericWebMarkupContainer<GradingReportTemplate> {
|
||||
public TemplateDetailsPanel(String id, IModel<GradingReportTemplate> model) {
|
||||
super(id, model);
|
||||
|
||||
add(new Label("project_type", model.map(GradingReportTemplate::getProjectType).map(ProjectType::getName)));
|
||||
|
||||
IModel<String> validPeriod = () -> {
|
||||
GradingReportTemplate template = model.getObject();
|
||||
LocalDate validFrom = template.getValidFrom();
|
||||
LocalDate endDate = gradingReportTemplateService.getEndDate(template);
|
||||
if (endDate == null) {
|
||||
return "from " + validFrom + " and onwards";
|
||||
} else {
|
||||
return "from " + validFrom + " to " + endDate;
|
||||
}
|
||||
};
|
||||
|
||||
add(new Label("valid_period", validPeriod));
|
||||
|
||||
add(new ListView<>("criteria", model.map(this::getCriteria)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<GradingCriterionTemplate> item) {
|
||||
item.add(new Label("title", item.getModel().map(GradingCriterionTemplate::getTitle)));
|
||||
item.add(new ListView<>(
|
||||
"points",
|
||||
item.getModel().map(GradingCriterionTemplate::getGradingCriterionPointTemplates))
|
||||
{
|
||||
@Override
|
||||
protected void populateItem(ListItem<GradingCriterionPointTemplate> item) {
|
||||
item.add(new Label("point", item.getModel().map(GradingCriterionPointTemplate::getPoint)));
|
||||
item.add(new Label(
|
||||
"description",
|
||||
item.getModel().map(GradingCriterionPointTemplate::getDescription)));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<GradingCriterionTemplate> getCriteria(GradingReportTemplate gradingReportTemplate) {
|
||||
ArrayList<GradingCriterionTemplate> criteria = new ArrayList<>(gradingReportTemplate.getCriteria());
|
||||
criteria.sort(Comparator.comparing(GradingCriterionTemplate::getSortOrder));
|
||||
return Collections.unmodifiableList(criteria);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<head>
|
||||
<wicket:remove>
|
||||
<link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
</wicket:remove>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<wicket:extend>
|
||||
<div class="line-length-limit">
|
||||
<h1>Grading report template management</h1>
|
||||
<p>
|
||||
Here you can manage the grading templates that are used to grade projects.
|
||||
</p>
|
||||
<p>
|
||||
A template consists of multiple criteria, each with a description and the maximum points that can be scored on it.
|
||||
|
||||
The point sum total conversion to grade letters is also defined in the template.
|
||||
</p>
|
||||
<p>
|
||||
Each template has a period during which it is in use. The period is defined by a start date,
|
||||
and it remains current until a new template takes over. There will only ever be one template
|
||||
current for any given date.
|
||||
|
||||
The template that is selected to be used for grading a project is the one that was current the date
|
||||
the project started.
|
||||
</p>
|
||||
<p>
|
||||
Once a template has become current it is <strong><em>no longer possible</em></strong> to edit it.
|
||||
</p>
|
||||
|
||||
<div class="btn-group mb-3" role="group">
|
||||
<wicket:link>
|
||||
<a href="AdminGradingTemplateCreationPage.html" class="btn btn-outline-primary">Create new template</a>
|
||||
</wicket:link>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" wicket:id="project_types">
|
||||
<h2 wicket:id="name">Bachelor</h2>
|
||||
|
||||
<div class="hstack gap-3 flex-wrap align-items-start" wicket:id="templates">
|
||||
<div class="card" wicket:id="upcoming_templates">
|
||||
<div class="card-header bg-info-subtle">
|
||||
Upcoming template
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl>
|
||||
<dt>Valid from</dt>
|
||||
<dd wicket:id="valid_from">2024-10-31</dd>
|
||||
|
||||
<wicket:enclosure>
|
||||
<dt>Note</dt>
|
||||
<dd wicket:id="note">Added a new criteria U14</dd>
|
||||
</wicket:enclosure>
|
||||
</dl>
|
||||
<span class="card-text">
|
||||
<a href="#" wicket:id="edit_template">View / Edit</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-success-subtle">
|
||||
Current template
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl>
|
||||
<dt>Valid from</dt>
|
||||
<dd wicket:id="valid_from">2024-01-01</dd>
|
||||
|
||||
<wicket:enclosure>
|
||||
<dt>Note</dt>
|
||||
<dd wicket:id="note">Change ethics criteria to include AI</dd>
|
||||
</wicket:enclosure>
|
||||
</dl>
|
||||
<span class="card-text">
|
||||
<a href="#" wicket:id="view_template">View</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<wicket:remove>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Older templates
|
||||
</div>
|
||||
<div class="card-body hstack gap-2">
|
||||
<div class="mw-100">
|
||||
<dl>
|
||||
<dt>Valid</dt>
|
||||
<dd>2023-01-01 to 2023-12-41</dd>
|
||||
|
||||
<dt>Note</dt>
|
||||
<dd>Change ethics criteria to include AI</dd>
|
||||
</dl>
|
||||
<span class="card-text">
|
||||
<a href="#">View</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="vr"></div>
|
||||
<div>
|
||||
<dl>
|
||||
<dt>Valid</dt>
|
||||
<dd>2001-01-01 to 2022-12-31</dd>
|
||||
|
||||
<dt>Note</dt>
|
||||
<dd>Change ethics criteria to include AI</dd>
|
||||
</dl>
|
||||
<span class="card-text">
|
||||
<a href="#">View</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:remove>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,57 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.markup.html.GenericWebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
|
||||
import se.su.dsv.scipro.components.NonEmptyLabel;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AdminGradingTemplatesOverviewPage extends AbstractAdminProjectPage {
|
||||
@Inject
|
||||
private GradingReportTemplateService gradingReportTemplateService;
|
||||
|
||||
public AdminGradingTemplatesOverviewPage() {
|
||||
LoadableDetachableModel<List<ProjectType>> projectTypes =
|
||||
LoadableDetachableModel.of(gradingReportTemplateService::getProjectTypes);
|
||||
|
||||
add(new ListView<>("project_types", projectTypes) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ProjectType> item) {
|
||||
item.add(new Label("name", item.getModel().map(ProjectType::getName)));
|
||||
item.add(new TemplatesPanel("templates", item.getModel()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class TemplatesPanel extends GenericWebMarkupContainer<ProjectType> {
|
||||
public TemplatesPanel(String id, IModel<ProjectType> model) {
|
||||
super(id, model);
|
||||
|
||||
IModel<GradingReportTemplate> currentTemplate = model.map(gradingReportTemplateService::getCurrentTemplate);
|
||||
|
||||
add(new Label("valid_from", currentTemplate.map(GradingReportTemplate::getValidFrom)));
|
||||
add(new NonEmptyLabel("note", currentTemplate.map(GradingReportTemplate::getNote)));
|
||||
add(new BookmarkablePageLink<>("view_template", AdminGradingTemplatePage.class, AdminGradingTemplatePage.getPageParameters(currentTemplate.getObject())));
|
||||
|
||||
IModel<List<GradingReportTemplate>> upcomingTemplates = model.map(gradingReportTemplateService::getUpcomingTemplates);
|
||||
add(new ListView<>("upcoming_templates", upcomingTemplates) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<GradingReportTemplate> item) {
|
||||
item.add(new Label("valid_from", item.getModel().map(GradingReportTemplate::getValidFrom)));
|
||||
item.add(new NonEmptyLabel("note", item.getModel().map(GradingReportTemplate::getNote)));
|
||||
item.add(new BookmarkablePageLink<>("edit_template", AdminGradingTemplateEditPage.class, AdminGradingTemplateEditPage.getPageParameters(item.getModelObject())));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
|
||||
import se.su.dsv.scipro.report.GradingCriterionTemplate;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class EditingGradingTemplate implements Serializable {
|
||||
private String note;
|
||||
private LocalDate validFrom;
|
||||
private List<Criteria> criteria;
|
||||
private GradeLimits gradeLimits;
|
||||
|
||||
public EditingGradingTemplate() {
|
||||
this.gradeLimits = new GradeLimits();
|
||||
this.criteria = new ArrayList<>();
|
||||
}
|
||||
|
||||
EditingGradingTemplate(GradingReportTemplate template) {
|
||||
this.note = template.getNote();
|
||||
this.validFrom = template.getValidFrom();
|
||||
this.gradeLimits = new GradeLimits(template);
|
||||
this.criteria = new ArrayList<>();
|
||||
for (var criteria : template.getCriteria()) {
|
||||
Criteria editingCriteria = new Criteria(criteria);
|
||||
this.criteria.add(editingCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
public String getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
public void setNote(String note) {
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
public LocalDate getValidFrom() {
|
||||
return validFrom;
|
||||
}
|
||||
|
||||
public void setValidFrom(LocalDate validFrom) {
|
||||
this.validFrom = validFrom;
|
||||
}
|
||||
|
||||
public GradeLimits getGradeLimits() {
|
||||
return gradeLimits;
|
||||
}
|
||||
|
||||
public List<Criteria> getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public int getMaxPointsAvailable() {
|
||||
return criteria.stream()
|
||||
.mapToInt(Criteria::getMaxPoints)
|
||||
.sum();
|
||||
}
|
||||
|
||||
public void addCriteria() {
|
||||
this.criteria.add(new Criteria());
|
||||
}
|
||||
|
||||
class Criteria implements Serializable {
|
||||
enum Flag {
|
||||
OPPOSITION, REFLECTION
|
||||
}
|
||||
|
||||
enum Type {
|
||||
PROJECT, INDIVIDUAL
|
||||
}
|
||||
|
||||
private String titleSv;
|
||||
private String titleEn;
|
||||
private List<Point> points = new ArrayList<>();
|
||||
private Flag flag;
|
||||
private Type type = Type.PROJECT;
|
||||
private int pointsRequiredToPass;
|
||||
|
||||
Criteria(GradingCriterionTemplate criteria) {
|
||||
this.titleSv = criteria.getTitle();
|
||||
this.titleEn = criteria.getTitleEn();
|
||||
this.pointsRequiredToPass = criteria.getPointsRequiredToPass();
|
||||
for (var point : criteria.getGradingCriterionPointTemplates()) {
|
||||
if (point.getPoint() == 0) continue;
|
||||
Point editingPoint = new Point(point);
|
||||
this.points.add(editingPoint);
|
||||
}
|
||||
this.flag = criteria.getFlag() == null ? null : switch (criteria.getFlag()) {
|
||||
case OPPOSITION -> Flag.OPPOSITION;
|
||||
case REFLECTION -> Flag.REFLECTION;
|
||||
};
|
||||
}
|
||||
|
||||
private Criteria() {}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getTitleSv() {
|
||||
return titleSv;
|
||||
}
|
||||
|
||||
public void setTitleSv(String titleSv) {
|
||||
this.titleSv = titleSv;
|
||||
}
|
||||
|
||||
public String getTitleEn() {
|
||||
return titleEn;
|
||||
}
|
||||
|
||||
public void setTitleEn(String titleEn) {
|
||||
this.titleEn = titleEn;
|
||||
}
|
||||
|
||||
public Flag getFlag() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
public void setFlag(Flag flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
public int getMaxPoints() {
|
||||
return points.size();
|
||||
}
|
||||
|
||||
public List<Point> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void setPoints(List<Point> points) {
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
public int getPointsRequiredToPass() {
|
||||
return pointsRequiredToPass;
|
||||
}
|
||||
|
||||
public void setPointsRequiredToPass(int pointsRequiredToPass) {
|
||||
this.pointsRequiredToPass = pointsRequiredToPass;
|
||||
}
|
||||
|
||||
class Point implements Serializable {
|
||||
private String requirementEn;
|
||||
private String requirementSv;
|
||||
|
||||
Point() {
|
||||
this.requirementEn = "";
|
||||
this.requirementSv = "";
|
||||
}
|
||||
|
||||
Point(GradingCriterionPointTemplate point) {
|
||||
this.requirementEn = point.getDescriptionEn();
|
||||
this.requirementSv = point.getDescription();
|
||||
}
|
||||
|
||||
public String getRequirementEn() {
|
||||
return requirementEn;
|
||||
}
|
||||
|
||||
public void setRequirementEn(String requirement) {
|
||||
this.requirementEn = requirement;
|
||||
}
|
||||
|
||||
public String getRequirementSv() {
|
||||
return requirementSv;
|
||||
}
|
||||
|
||||
public void setRequirementSv(String requirementSv) {
|
||||
this.requirementSv = requirementSv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<head>
|
||||
<wicket:remove>
|
||||
<link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
</wicket:remove>
|
||||
</head>
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="mb-3 line-length-limit">
|
||||
<label class="form-label" wicket:for="valid_from">
|
||||
Valid from
|
||||
</label>
|
||||
<input required type="text" class="form-control" wicket:id="valid_from">
|
||||
<small class="text-muted">
|
||||
When does this grading template take effect.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 line-length-limit">
|
||||
<label class="form-label" wicket:for="note">
|
||||
Note
|
||||
</label>
|
||||
<textarea class="form-control" wicket:id="note" rows="3"></textarea>
|
||||
<small class="text-muted">
|
||||
This note can be used to describe the template, for example, what changes have been made from the previous version.
|
||||
It is purely an aid for administrators if necessary.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<fieldset wicket:id="grade_limits" class="line-length-limit">
|
||||
<legend>Grade limits</legend>
|
||||
<p>
|
||||
A grade limit is a point requirement to get another grade, e.g. at least 30 points to receive an A.
|
||||
The order in which you specify the grade limits does not matter.
|
||||
The authors will receive the grade associated with the highest minimum point requirement they met.
|
||||
If they do not meet any of the requirements, they will receive the failing grade.
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" wicket:for="failing_grade">Failing grade</label>
|
||||
<input required type="text" class="form-control" wicket:id="failing_grade">
|
||||
<small class="text-muted">The grade they receive if they do not meet the minimum points for any other grade.</small>
|
||||
</div>
|
||||
|
||||
<wicket:container wicket:id="grade_limits">
|
||||
<div class="row align-items-center mb-3" wicket:id="grade_limit">
|
||||
<div class="col"><label class="form-label" wicket:for="minimum">Minimum points</label></div>
|
||||
<div class="col"><input type="number" required class="form-control" min="1" wicket:id="minimum"></div>
|
||||
<div class="col"><label class="form-label">Grade</label></div>
|
||||
<div class="col"><input type="text" required class="form-control" wicket:id="grade"></div>
|
||||
<div class="col-auto"><button class="btn btn-sm btn-outline-danger" wicket:id="remove">Remove</button></div>
|
||||
</div>
|
||||
</wicket:container>
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-sm btn-outline-success" wicket:id="add">Add new grade limit</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<p class="mb-3">
|
||||
<wicket:message key="max_points_available">
|
||||
The maximum number of points available is <strong wicket:id="max_points_available">8</strong> with the below criteria.
|
||||
</wicket:message>
|
||||
</p>
|
||||
|
||||
<div class="mb-3 line-length-limit card" wicket:id="criteria">
|
||||
<div class="card-header text-bg-info text-white hstack justify-content-between">
|
||||
<h3 class="text-white mb-0">Criterion</h3>
|
||||
<button class="btn btn-sm btn-outline-danger" wicket:id="remove">Remove</button>
|
||||
</div>
|
||||
<div wicket:id="criteria" class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<label class="form-label" wicket:for="title_en">
|
||||
Title (english)
|
||||
</label>
|
||||
<input required class="form-control" wicket:id="title_en" type="text">
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label" wicket:for="title_sv">
|
||||
Title (swedish)
|
||||
</label>
|
||||
<input required class="form-control" wicket:id="title_sv" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" wicket:for="type">Type</label>
|
||||
<select wicket:id="type" class="form-control"></select>
|
||||
<small class="text-muted">Whether this criterion is assessed once for the thesis or per author.</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" wicket:for="flag">Special flag</label>
|
||||
<select class="form-control" wicket:id="flag"></select>
|
||||
<small class="text-muted">You can flag a criteria if it is connected to some other part of the system</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" wicket:for="points_required_to_pass">
|
||||
Points required to pass
|
||||
</label>
|
||||
<input required type="number" class="form-control" wicket:id="points_required_to_pass">
|
||||
<small class="text-muted">
|
||||
If the author does not get at least this many points on this criterion,
|
||||
they get the default grade.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Points</legend>
|
||||
|
||||
<ol class="list-unstyled">
|
||||
<li wicket:id="points">
|
||||
<fieldset wicket:id="point" class="card mb-3">
|
||||
<legend class="card-header hstack justify-content-between">
|
||||
<span>Requirement for <span wicket:id="point"></span> point(s)</span>
|
||||
<button class="btn btn-sm btn-outline-danger" wicket:id="remove">Remove</button>
|
||||
</legend>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label wicket:for="requirement_en">
|
||||
Requirement (english)
|
||||
</label>
|
||||
<textarea required rows="4" class="form-control" wicket:id="requirement_en"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label wicket:for="requirement_sv">
|
||||
Requirement (swedish)
|
||||
</label>
|
||||
<textarea required rows="4" class="form-control" wicket:id="requirement_sv"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</li>
|
||||
<li>
|
||||
<div class="row align-items-center">
|
||||
<label class="col-auto">
|
||||
<wicket:container wicket:id="new_point"/>
|
||||
</label>
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-success" wicket:id="add_new_point">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-sm btn-outline-success" wicket:id="add_criteria">Add new criteria</button>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,297 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.markup.html.GenericWebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.form.NumberTextField;
|
||||
import org.apache.wicket.markup.html.form.RequiredTextField;
|
||||
import org.apache.wicket.markup.html.form.TextArea;
|
||||
import org.apache.wicket.markup.html.form.TextField;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import se.su.dsv.scipro.components.AjaxDropDownChoice;
|
||||
import se.su.dsv.scipro.components.BootstrapDatePicker;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
class EditingGradingTemplateComponentPanel extends GenericPanel<EditingGradingTemplate> {
|
||||
EditingGradingTemplateComponentPanel(
|
||||
String id,
|
||||
IModel<EditingGradingTemplate> editingGradingTemplateModel)
|
||||
{
|
||||
super(id, editingGradingTemplateModel);
|
||||
|
||||
setOutputMarkupId(true);
|
||||
|
||||
TextField<LocalDate> validFromField = new RequiredTextField<>("valid_from",
|
||||
LambdaModel.of(editingGradingTemplateModel,
|
||||
EditingGradingTemplate::getValidFrom,
|
||||
EditingGradingTemplate::setValidFrom),
|
||||
LocalDate.class);
|
||||
validFromField.add(new BootstrapDatePicker());
|
||||
validFromField.add(new AutoSave());
|
||||
add(validFromField);
|
||||
|
||||
add(new TextArea<>("note", LambdaModel.of(
|
||||
editingGradingTemplateModel,
|
||||
EditingGradingTemplate::getNote,
|
||||
EditingGradingTemplate::setNote)) {
|
||||
{
|
||||
add(new AutoSave());
|
||||
}
|
||||
});
|
||||
|
||||
add(new GradeLimitsPanel("grade_limits", editingGradingTemplateModel.map(EditingGradingTemplate::getGradeLimits)));
|
||||
|
||||
add(new Label("max_points_available", editingGradingTemplateModel.map(EditingGradingTemplate::getMaxPointsAvailable)));
|
||||
|
||||
add(new ListView<>("criteria", editingGradingTemplateModel.map(EditingGradingTemplate::getCriteria)) {
|
||||
{
|
||||
setReuseItems(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(ListItem<EditingGradingTemplate.Criteria> item) {
|
||||
item.add(new AjaxLink<>("remove") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
editingGradingTemplateModel.getObject().getCriteria().remove(item.getModelObject());
|
||||
target.add(EditingGradingTemplateComponentPanel.this);
|
||||
}
|
||||
});
|
||||
item.add(new CriteriaEditingPanel("criteria", item.getModel()));
|
||||
}
|
||||
});
|
||||
|
||||
add(new AjaxLink<>("add_criteria") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
editingGradingTemplateModel.getObject().addCriteria();
|
||||
target.add(EditingGradingTemplateComponentPanel.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class CriteriaEditingPanel extends GenericWebMarkupContainer<EditingGradingTemplate.Criteria> {
|
||||
CriteriaEditingPanel(String id, IModel<EditingGradingTemplate.Criteria> model) {
|
||||
super(id, model);
|
||||
|
||||
setOutputMarkupId(true);
|
||||
|
||||
add(new RequiredTextField<>("title_sv", LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria::getTitleSv,
|
||||
EditingGradingTemplate.Criteria::setTitleSv)) {
|
||||
{
|
||||
add(new AutoSave());
|
||||
}
|
||||
});
|
||||
add(new RequiredTextField<>("title_en", LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria::getTitleEn,
|
||||
EditingGradingTemplate.Criteria::setTitleEn)) {
|
||||
{
|
||||
add(new AutoSave());
|
||||
}
|
||||
});
|
||||
|
||||
AjaxDropDownChoice<EditingGradingTemplate.Criteria.Type> typeChoice = new AjaxDropDownChoice<>(
|
||||
"type",
|
||||
LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria::getType,
|
||||
EditingGradingTemplate.Criteria::setType),
|
||||
List.of(EditingGradingTemplate.Criteria.Type.values()),
|
||||
new EnumChoiceRenderer<>(this))
|
||||
{
|
||||
@Override
|
||||
public void onNewSelection(
|
||||
AjaxRequestTarget target,
|
||||
EditingGradingTemplate.Criteria.Type objectSelected)
|
||||
{
|
||||
// auto save
|
||||
}
|
||||
};
|
||||
typeChoice.setRequired(true);
|
||||
add(typeChoice);
|
||||
|
||||
NumberTextField<Integer> pointsRequiredToPass = new NumberTextField<>(
|
||||
"points_required_to_pass",
|
||||
LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria::getPointsRequiredToPass,
|
||||
EditingGradingTemplate.Criteria::setPointsRequiredToPass),
|
||||
Integer.class);
|
||||
pointsRequiredToPass.setMinimum(0);
|
||||
pointsRequiredToPass.setRequired(true);
|
||||
pointsRequiredToPass.add(new AutoSave());
|
||||
add(pointsRequiredToPass);
|
||||
|
||||
AjaxDropDownChoice<EditingGradingTemplate.Criteria.Flag> flagChoice = new AjaxDropDownChoice<>(
|
||||
"flag",
|
||||
LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria::getFlag,
|
||||
EditingGradingTemplate.Criteria::setFlag),
|
||||
List.of(EditingGradingTemplate.Criteria.Flag.values()),
|
||||
new EnumChoiceRenderer<>(this))
|
||||
{
|
||||
@Override
|
||||
public void onNewSelection(
|
||||
AjaxRequestTarget target,
|
||||
EditingGradingTemplate.Criteria.Flag objectSelected)
|
||||
{
|
||||
// auto save
|
||||
}
|
||||
};
|
||||
flagChoice.setNullValid(true);
|
||||
add(flagChoice);
|
||||
|
||||
add(new ListView<>("points", model.map(EditingGradingTemplate.Criteria::getPoints)) {
|
||||
{
|
||||
setReuseItems(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(ListItem<EditingGradingTemplate.Criteria.Point> item) {
|
||||
item.add(new PointEditingPanel("point", item));
|
||||
}
|
||||
});
|
||||
|
||||
add(new Label("new_point", model
|
||||
.map(criteria -> criteria.getPoints().size())
|
||||
.map(size -> "Requirement for " + (size + 1) + " points")));
|
||||
|
||||
add(new AjaxLink<>("add_new_point") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
EditingGradingTemplate.Criteria criteria = model.getObject();
|
||||
EditingGradingTemplate.Criteria.Point newPoint = criteria.new Point();
|
||||
criteria.getPoints().add(newPoint);
|
||||
target.add(CriteriaEditingPanel.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class PointEditingPanel extends GenericWebMarkupContainer<EditingGradingTemplate.Criteria.Point> {
|
||||
public PointEditingPanel(String id, ListItem<EditingGradingTemplate.Criteria.Point> item) {
|
||||
super(id, item.getModel());
|
||||
|
||||
IModel<EditingGradingTemplate.Criteria.Point> model = item.getModel();
|
||||
|
||||
add(new Label("point", () -> item.getIndex() + 1));
|
||||
|
||||
TextArea<String> englishRequirement = new TextArea<>("requirement_en", LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria.Point::getRequirementEn,
|
||||
EditingGradingTemplate.Criteria.Point::setRequirementEn));
|
||||
englishRequirement.setRequired(true);
|
||||
englishRequirement.add(new AutoSave());
|
||||
add(englishRequirement);
|
||||
|
||||
TextArea<String> swedishRequirement = new TextArea<>("requirement_sv", LambdaModel.of(
|
||||
model,
|
||||
EditingGradingTemplate.Criteria.Point::getRequirementSv,
|
||||
EditingGradingTemplate.Criteria.Point::setRequirementSv));
|
||||
swedishRequirement.setRequired(true);
|
||||
swedishRequirement.add(new AutoSave());
|
||||
add(swedishRequirement);
|
||||
|
||||
add(new AjaxLink<>("remove") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
EditingGradingTemplate.Criteria criteria = CriteriaEditingPanel.this.getModelObject();
|
||||
criteria.getPoints().remove(model.getObject());
|
||||
target.add(CriteriaEditingPanel.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class GradeLimitsPanel extends GenericWebMarkupContainer<GradeLimits> {
|
||||
public GradeLimitsPanel(String id, IModel<GradeLimits> model) {
|
||||
super(id, model);
|
||||
|
||||
setOutputMarkupId(true);
|
||||
|
||||
add(new RequiredTextField<>("failing_grade", LambdaModel.of(
|
||||
model,
|
||||
GradeLimits::getFailingGrade,
|
||||
GradeLimits::setFailingGrade)) {
|
||||
{
|
||||
add(new AutoSave());
|
||||
}
|
||||
});
|
||||
|
||||
add(new ListView<>("grade_limits", model.map(GradeLimits::getGradeLimits)) {
|
||||
{
|
||||
setReuseItems(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(ListItem<GradeLimits.GradeLimit> item) {
|
||||
item.add(new GradeLimitEditingPanel("grade_limit", item.getModel()));
|
||||
}
|
||||
});
|
||||
|
||||
add(new AjaxLink<>("add") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
GradeLimits gradeLimits = GradeLimitsPanel.this.getModelObject();
|
||||
gradeLimits.addNewLimit();
|
||||
target.add(GradeLimitsPanel.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class GradeLimitEditingPanel extends GenericWebMarkupContainer<GradeLimits.GradeLimit> {
|
||||
public GradeLimitEditingPanel(String id, IModel<GradeLimits.GradeLimit> model) {
|
||||
super(id, model);
|
||||
|
||||
NumberTextField<Integer> minimum = new NumberTextField<>("minimum", LambdaModel.of(
|
||||
model,
|
||||
GradeLimits.GradeLimit::getLowerLimit,
|
||||
GradeLimits.GradeLimit::setLowerLimit), Integer.class);
|
||||
minimum.setRequired(true);
|
||||
minimum.add(new AutoSave());
|
||||
add(minimum);
|
||||
|
||||
TextField<String> grade = new TextField<>("grade", LambdaModel.of(
|
||||
model,
|
||||
GradeLimits.GradeLimit::getGrade,
|
||||
GradeLimits.GradeLimit::setGrade));
|
||||
grade.setRequired(true);
|
||||
grade.add(new AutoSave());
|
||||
add(grade);
|
||||
|
||||
add(new AjaxLink<>("remove") {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
GradeLimits gradeLimits = GradeLimitsPanel.this.getModelObject();
|
||||
gradeLimits.getGradeLimits().remove(model.getObject());
|
||||
target.add(GradeLimitsPanel.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AutoSave extends AjaxFormComponentUpdatingBehavior {
|
||||
public AutoSave() {
|
||||
super("input");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdate(AjaxRequestTarget target) {
|
||||
// just trigger the ajax call is enough to update the model object
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
Flag.OPPOSITION=Final seminar opposition
|
||||
Flag.REFLECTION=Reflection
|
||||
max_points_available=The maximum number of points available is ${max_points_available} with the below criteria.
|
||||
failing_grade.Required=You must set the failing grade.
|
||||
title_sv.Required=You must set the swedish title for all criteria
|
||||
title_en.Required=You must set the english title for all criteria
|
||||
requirement_sv.Required=You must set the swedish requirement for every criteria point
|
||||
requirement_en.Required=You must set the english requirement for every criteria point
|
||||
Type.PROJECT=Once per thesis
|
||||
Type.INDIVIDUAL=Individually for each author
|
||||
minimum.Required=You must set the minimum number of points for each grade
|
||||
grade.Required=You must set the letter for each grade
|
||||
flag.nullValid=None
|
|
@ -0,0 +1,64 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class GradeLimits implements Serializable {
|
||||
private List<GradeLimit> gradeLimits;
|
||||
private String failingGrade;
|
||||
|
||||
public GradeLimits() {
|
||||
this.gradeLimits = new ArrayList<>();
|
||||
}
|
||||
|
||||
GradeLimits(GradingReportTemplate template) {
|
||||
this.gradeLimits = new ArrayList<>();
|
||||
for (var gradeLimit : template.getGradeLimits()) {
|
||||
GradeLimit editableGradeLimit = new GradeLimit();
|
||||
editableGradeLimit.setGrade(gradeLimit.getGrade());
|
||||
editableGradeLimit.setLowerLimit(gradeLimit.getLowerLimit());
|
||||
gradeLimits.add(editableGradeLimit);
|
||||
}
|
||||
this.failingGrade = template.getFailingGrade();
|
||||
}
|
||||
|
||||
void addNewLimit() {
|
||||
getGradeLimits().add(new GradeLimit());
|
||||
}
|
||||
|
||||
public String getFailingGrade() {
|
||||
return failingGrade;
|
||||
}
|
||||
|
||||
public void setFailingGrade(String failingGrade) {
|
||||
this.failingGrade = failingGrade;
|
||||
}
|
||||
|
||||
public List<GradeLimit> getGradeLimits() {
|
||||
return gradeLimits;
|
||||
}
|
||||
|
||||
class GradeLimit implements Serializable {
|
||||
private String grade;
|
||||
private int lowerLimit;
|
||||
|
||||
public String getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public void setGrade(String grade) {
|
||||
this.grade = grade;
|
||||
}
|
||||
|
||||
public int getLowerLimit() {
|
||||
return lowerLimit;
|
||||
}
|
||||
|
||||
public void setLowerLimit(int lowerLimit) {
|
||||
this.lowerLimit = lowerLimit;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package se.su.dsv.scipro.admin.pages.grading;
|
||||
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlight;
|
||||
|
||||
public interface MenuHighlightGradingTemplates extends MenuHighlight {
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
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}.
|
||||
template_created=New template for ${name} created.
|
|
@ -0,0 +1,19 @@
|
|||
package se.su.dsv.scipro.components;
|
||||
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.model.IModel;
|
||||
|
||||
/**
|
||||
* A basic {@link Label} that hides itself if the rendered model object {@link String#isBlank() is blank}
|
||||
*/
|
||||
public class NonEmptyLabel extends Label {
|
||||
public NonEmptyLabel(String id, IModel<?> model) {
|
||||
super(id, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(!getDefaultModelObjectAsString().isBlank());
|
||||
}
|
||||
}
|
|
@ -121,7 +121,7 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
|
|||
grading.add(pf);
|
||||
grading.add(new Label("pfCriteria", item.getModel().combineWith(language, (gc, lang) -> gc.getGradingCriterionPoints().get(1).getDescription(lang))));
|
||||
|
||||
if (item.getModelObject().getTitle().startsWith("Ö1 ")) {
|
||||
if (item.getModelObject().getFlag() == AbstractGradingCriterion.Flag.OPPOSITION) {
|
||||
grading.setEnabled(false);
|
||||
}
|
||||
item.add(new AjaxConfirmationLink<>("enable", "Are you sure you want to grade this criteria? It is usually filled in by the seminar supervisor") {
|
||||
|
@ -194,7 +194,7 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
|
|||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
final boolean isOppositionCriteria = gradingCriterion.getObject().getTitle().startsWith("Ö1 ");
|
||||
final boolean isOppositionCriteria = gradingCriterion.getObject().getFlag() == AbstractGradingCriterion.Flag.OPPOSITION;
|
||||
setVisibilityAllowed(isOppositionCriteria && isEnabledInHierarchy());
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
|
|||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
boolean isReflectionCriteria = gradingCriterion.getObject().getTitle().startsWith("Ö6 ");
|
||||
boolean isReflectionCriteria = gradingCriterion.getObject().getFlag() == AbstractGradingCriterion.Flag.REFLECTION;
|
||||
setVisibilityAllowed(isReflectionCriteria);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public class GradingReportPointsPanel extends Panel {
|
|||
return gradingReportIModel.getObject().getGrade(gradeCalculator);
|
||||
}
|
||||
};
|
||||
final Label grade = new Label(GRADE, gradeModel) {
|
||||
final Label grade = new Label(GRADE, gradeModel.map(GradingReport.Grade::name)) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
|
|
|
@ -598,3 +598,6 @@ th.wicket_orderUp, th.sorting_asc {
|
|||
.bg-su-primary {
|
||||
background-color: #002f5f;
|
||||
}
|
||||
.line-length-limit {
|
||||
max-width: 80em;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import se.su.dsv.scipro.test.ObjectMother;
|
|||
import se.su.dsv.scipro.test.SeminarBuilder;
|
||||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -76,7 +78,8 @@ public class DownloadPdfReportPanelTest extends SciProTest {
|
|||
}
|
||||
|
||||
private void prepareReports() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
.point(0)
|
||||
|
|
|
@ -29,6 +29,7 @@ import se.su.dsv.scipro.test.UserBuilder;
|
|||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -152,7 +153,8 @@ public class OppositionReportPageTest extends SciProTest {
|
|||
}
|
||||
|
||||
private GradingReportTemplate createTemplate(ProjectType bachelor) {
|
||||
GradingReportTemplate reportTemplate = new GradingReportTemplate(bachelor, 30);
|
||||
GradingReportTemplate reportTemplate = new GradingReportTemplate(bachelor,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
List<GradingCriterionPointTemplate> gradingCriterionPointTemplates = new ArrayList<>();
|
||||
gradingCriterionPointTemplates.add(new GradingCriterionPointTemplate.Builder()
|
||||
.point(1)
|
||||
|
|
|
@ -17,6 +17,7 @@ import se.su.dsv.scipro.system.User;
|
|||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.*;
|
||||
|
||||
import static se.su.dsv.scipro.grading.CriteriaPanel.*;
|
||||
|
@ -110,7 +111,8 @@ public class CriteriaPanelTest extends SciProTest {
|
|||
}
|
||||
|
||||
private void prepareReports() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
gradingReportTemplate.addIndividualCriterion("Projektkriterium", "Project criterion", 1, getPointTemplates(2));
|
||||
gradingReportTemplate.addIndividualCriterion("Individuellt kriterium", "Individual criterion", 0, getPointTemplates(2));
|
||||
gradingReport = gradingReportTemplate.createSupervisorReport(SOME_PROJECT, SOME_USER);
|
||||
|
|
|
@ -20,6 +20,7 @@ import se.su.dsv.scipro.system.User;
|
|||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
@ -44,7 +45,8 @@ public class FillOutReportPanelTest extends SciProTest {
|
|||
User headSupervisorUser = new UserBuilder().create();
|
||||
Project project = Project.builder().title("title").projectType(projectType).startDate(LocalDate.now()).headSupervisor(headSupervisorUser).build();
|
||||
finalSeminar.setProject(project);
|
||||
GradingReportTemplate template = new GradingReportTemplate(projectType, 30);
|
||||
GradingReportTemplate template = new GradingReportTemplate(projectType,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
template.addProjectCriterion("U1", "U1", 1, getPointTemplates(1));
|
||||
FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
|
||||
finalSeminarOpposition.setFinalSeminar(finalSeminar);
|
||||
|
|
|
@ -13,6 +13,7 @@ import se.su.dsv.scipro.system.User;
|
|||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -44,7 +45,7 @@ public class GradingReportPointsPanelTest extends SciProTest {
|
|||
when(gradeCalculator.getGrade(any(GradingReport.class))).thenReturn(grade);
|
||||
startPanel();
|
||||
|
||||
tester.assertLabel(path(panel, GRADE), grade.toString());
|
||||
tester.assertLabel(path(panel, GRADE), grade.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -61,7 +62,8 @@ public class GradingReportPointsPanelTest extends SciProTest {
|
|||
}
|
||||
|
||||
private void prepareReports() {
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR, 30);
|
||||
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(BACHELOR,
|
||||
LocalDate.of(2024, Month.JANUARY, 1));
|
||||
gradingReportTemplate.addProjectCriterion("title", "titleEn", 0, getPointTemplates(2));
|
||||
gradingReport = gradingReportTemplate.createSupervisorReport(SOME_PROJECT, SOME_USER);
|
||||
for (GradingCriterion gradingCriterion : gradingReport.getGradingCriteria()) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import se.su.dsv.scipro.system.DegreeType;
|
|||
import se.su.dsv.scipro.system.ProjectType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.Date;
|
||||
|
||||
public class ThesisApprovedPanelTest extends SciProTest {
|
||||
|
@ -31,7 +32,8 @@ public class ThesisApprovedPanelTest extends SciProTest {
|
|||
ProjectType projectType = new ProjectType(ProjectType.MASTER, "Master", "Master");
|
||||
projectType.setId(1L);
|
||||
|
||||
Mockito.when(gradingReportService.getTemplate(SOME_PROJECT)).thenReturn(new GradingReportTemplate(projectType, 30));
|
||||
Mockito.when(gradingReportService.getTemplate(SOME_PROJECT)).thenReturn(new GradingReportTemplate(projectType,
|
||||
LocalDate.of(2024, Month.JANUARY, 1)));
|
||||
panel = tester.startComponentInPage(new ThesisApprovedPanel("panel", Model.of(SOME_PROJECT)));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user
Shouldn't we be able to delete faulty or unwanted templates? Maybe we should introduce that in the future?