Allows admins to manage grading report templates #14

Merged
niat8586 merged 41 commits from 3482-new-grading-criteria into develop 2024-10-30 10:05:23 +01:00
69 changed files with 2208 additions and 111 deletions

View File

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

View File

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

View File

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

View File

@ -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 {
Review

Shouldn't we be able to delete faulty or unwanted templates? Maybe we should introduce that in the future?

Shouldn't we be able to delete faulty or unwanted templates? Maybe we should introduce that in the future?
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;
}

View File

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

View File

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

View File

@ -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
niat8586 marked this conversation as resolved Outdated

"Criterion marked with this flag will extra functionality related ..." should it say "Criterion marked with this flag will add extra functionality related

Same with the other comments i'm guessing.

"Criterion marked with this flag will extra functionality related ..." should it say "Criterion marked with this flag will **add** extra functionality related Same with the other comments i'm guessing.
* 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;

View File

@ -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 {
ansv7779 marked this conversation as resolved Outdated

Should extend Exception instead of Throwable

Should extend Exception instead of Throwable
private final LocalDate validFrom;
private final ProjectType projectType;
public DuplicateDateException(LocalDate validFrom, ProjectType projectType) {
this.validFrom = validFrom;
this.projectType = projectType;
}
public LocalDate validFrom() {
return validFrom;
}
public ProjectType projectType() {
return projectType;
}
}

View File

@ -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) {

View 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;
}
}

View File

@ -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(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
package se.su.dsv.scipro.report;
public class NoSuchTemplateException extends Exception {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
ALTER TABLE `grading_report_template`
ADD COLUMN `note` TEXT,
ADD COLUMN `failing_grade` VARCHAR(32);

View File

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

View File

@ -0,0 +1,4 @@
ALTER TABLE `grading_criterion_template`
ADD COLUMN `flag` VARCHAR(64);
ALTER TABLE `GradingCriterion`
ADD COLUMN `flag` VARCHAR(64);

View File

@ -0,0 +1,2 @@
UPDATE `GradingCriterion` SET `flag` = 'OPPOSITION' WHERE title like 'Ö1 %';
UPDATE `grading_criterion_template` SET `flag` = 'OPPOSITION' WHERE title like 'Ö1 %';

View File

@ -0,0 +1,2 @@
UPDATE `GradingCriterion` SET `flag` = 'REFLECTION' WHERE title like 'Ö6 %';
UPDATE `grading_criterion_template` SET `flag` = 'REFLECTION' WHERE title like 'Ö6 %';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

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

View File

@ -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>

View File

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

View File

@ -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

View File

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

View File

@ -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 {
}

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -598,3 +598,6 @@ th.wicket_orderUp, th.sorting_asc {
.bg-su-primary {
background-color: #002f5f;
}
.line-length-limit {
max-width: 80em;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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()) {

View File

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