Allow supervisors to request improvements from final seminar opponents #78

Merged
niat8586 merged 41 commits from opponent-completion into develop 2025-03-05 10:05:38 +01:00
6 changed files with 135 additions and 34 deletions
Showing only changes of commit 970f6ee61b - Show all commits

View File

@ -20,7 +20,11 @@ import se.su.dsv.scipro.file.FileUpload;
import se.su.dsv.scipro.finalseminar.FinalSeminar; import se.su.dsv.scipro.finalseminar.FinalSeminar;
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
import se.su.dsv.scipro.match.ApplicationPeriod; import se.su.dsv.scipro.match.ApplicationPeriod;
import se.su.dsv.scipro.match.Idea;
import se.su.dsv.scipro.match.IdeaService;
import se.su.dsv.scipro.match.Keyword; import se.su.dsv.scipro.match.Keyword;
import se.su.dsv.scipro.match.Target;
import se.su.dsv.scipro.match.TholanderBox;
import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate; import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate;
import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate; import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate;
import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService; import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService;
@ -37,6 +41,7 @@ import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
import se.su.dsv.scipro.reviewing.RoughDraftApprovalService; import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
import se.su.dsv.scipro.security.auth.roles.Roles; import se.su.dsv.scipro.security.auth.roles.Roles;
import se.su.dsv.scipro.system.*; import se.su.dsv.scipro.system.*;
import se.su.dsv.scipro.util.Pair;
public class DataInitializer implements Lifecycle { public class DataInitializer implements Lifecycle {
@ -51,6 +56,9 @@ public class DataInitializer implements Lifecycle {
@Inject @Inject
private PasswordService passwordService; private PasswordService passwordService;
@Inject
private IdeaService ideaService;
@Inject @Inject
private MilestoneActivityTemplateService milestoneActivityTemplateService; private MilestoneActivityTemplateService milestoneActivityTemplateService;
@ -94,14 +102,19 @@ public class DataInitializer implements Lifecycle {
private User stina_student; private User stina_student;
private User sid_student; private User sid_student;
private User simon_student; private User simon_student;
private ProjectType bachelorClass; private User sofia_student;
private Set<ResearchArea> researchAreas; private Set<ResearchArea> researchAreas;
private Long researchAreaId = RESEARCH_AREA_ID; private Long researchAreaId = RESEARCH_AREA_ID;
private Set<Language> languages; private Set<Language> languages = Set.of(Language.SWEDISH, Language.ENGLISH);
private Program program;
private ResearchArea researchArea1; private ResearchArea researchArea1;
private ResearchArea researchArea2; private ResearchArea researchArea2;
private Keyword keyword1;
private Keyword keyword2;
private ProjectType bachelorClass;
private ProjectType masterClass; private ProjectType masterClass;
private ProjectType magisterClass; private ProjectType magisterClass;
private ApplicationPeriod applicationPeriod;
private Project project1; private Project project1;
private Project project2; private Project project2;
@ -111,13 +124,17 @@ public class DataInitializer implements Lifecycle {
if (profile.getCurrentProfile() == Profiles.DEV && noUsers()) { if (profile.getCurrentProfile() == Profiles.DEV && noUsers()) {
createDefaultProjectTypesIfNotDone(); createDefaultProjectTypesIfNotDone();
createDefaultChecklistCategoriesIfNotDone(); createDefaultChecklistCategoriesIfNotDone();
createProgram();
createApplicationPeriodIfNotDone(); createApplicationPeriodIfNotDone();
createGradingCriterionTemplateIfNotDone(); createGradingCriterionTemplateIfNotDone();
createResearchAreasForDemo(); createResearchAreasForDemo();
createKeywordsIfNotDone(); createKeywordsIfNotDone();
createMilestonesIfNotDone(); createMilestonesIfNotDone();
createUsers(); createUsers();
createMatchedIdea();
createProjects(); createProjects();
createTarget();
createStudentIdea();
createRoughDraftApproval(); createRoughDraftApproval();
createPastFinalSeminar(); createPastFinalSeminar();
setUpNotifications(); setUpNotifications();
@ -175,27 +192,36 @@ public class DataInitializer implements Lifecycle {
return userService.findByUsername(ADMIN + MAIL) == null; return userService.findByUsername(ADMIN + MAIL) == null;
} }
private void createProgram() {
program = new Program();
program.setCode("AppCompSci");
program.setName("Tillämpad Datavetenskap");
program.setExternalId(123);
program.setNameEn("Applied Computer Science");
program = save(program);
}
private void createApplicationPeriodIfNotDone() { private void createApplicationPeriodIfNotDone() {
ApplicationPeriod applicationPeriod = new ApplicationPeriod("HT 2014"); applicationPeriod = new ApplicationPeriod("HT 2014");
applicationPeriod.setStartDate(LocalDate.now().minusDays(APPLICATION_PERIOD_START_MINUS_DAYS)); applicationPeriod.setStartDate(LocalDate.now().minusDays(APPLICATION_PERIOD_START_MINUS_DAYS));
applicationPeriod.setEndDate(LocalDate.now().plusDays(APPLICATION_PERIOD_END_PLUS_DAYS)); applicationPeriod.setEndDate(LocalDate.now().plusDays(APPLICATION_PERIOD_END_PLUS_DAYS));
applicationPeriod.setCourseStartDate(LocalDate.now().plusDays(APPLICATION_PERIOD_COURSE_START_PLUS_DAYS)); applicationPeriod.setCourseStartDate(LocalDate.now().plusDays(APPLICATION_PERIOD_COURSE_START_PLUS_DAYS));
applicationPeriod.setCourseStartTime(LocalTime.of(8, 0)); applicationPeriod.setCourseStartTime(LocalTime.of(8, 0));
applicationPeriod = save(applicationPeriod); applicationPeriod = save(applicationPeriod);
applicationPeriod.setProjectTypes(new HashSet<>(Collections.singletonList(bachelorClass))); applicationPeriod.setProjectTypes(new HashSet<>(Set.of(bachelorClass, masterClass)));
save(applicationPeriod); save(applicationPeriod);
} }
private void createKeywordsIfNotDone() { private void createKeywordsIfNotDone() {
Keyword keyword1 = new Keyword("IT"); keyword1 = new Keyword("IT");
keyword1.addResearchArea(researchArea1); keyword1.addResearchArea(researchArea1);
keyword1.addResearchArea(researchArea2); keyword1.addResearchArea(researchArea2);
save(keyword1); keyword1 = save(keyword1);
Keyword keyword2 = new Keyword("Computers"); keyword2 = new Keyword("Computers");
keyword2.addResearchArea(researchArea1); keyword2.addResearchArea(researchArea1);
keyword2.addResearchArea(researchArea2); keyword2.addResearchArea(researchArea2);
save(keyword2); keyword2 = save(keyword2);
} }
private void createResearchAreasForDemo() { private void createResearchAreasForDemo() {
@ -251,6 +277,11 @@ public class DataInitializer implements Lifecycle {
// can not be used as author on any idea // can not be used as author on any idea
// can not be used as author on any project // can not be used as author on any project
createStudent("Stig"); createStudent("Stig");
// Used to test assign supervisor to student idea
// this student has a submitted idea, which has not assigned supervisor
// don't use this student for anything else
sofia_student = createStudent("Sofia");
} }
private User createStudent(String firstName) { private User createStudent(String firstName) {
@ -269,8 +300,11 @@ public class DataInitializer implements Lifecycle {
user.setUnit(u); user.setUnit(u);
user.setResearchAreas(researchAreas); user.setResearchAreas(researchAreas);
user.setLanguages(languages); user.setLanguages(languages);
user.setActiveAsSupervisor(true);
createBeta(user); createBeta(user);
return user;
return save(user);
} }
private User createUser(String firstName, String lastName) { private User createUser(String firstName, String lastName) {
@ -322,12 +356,54 @@ public class DataInitializer implements Lifecycle {
return u; return u;
} }
private void createMatchedIdea() {
Idea idea = new Idea();
idea.setApplicationPeriod(applicationPeriod);
idea.setType(Idea.Type.SUPERVISOR);
idea.setProjectType(masterClass);
idea.setTitle("Idea without first meeting");
idea.setDescription("Explore the deep sea");
idea.setPrerequisites("Diving experience");
idea.setResearchArea(researchArea1);
idea.setPublished(true);
Idea saved = ideaService.saveSupervisorIdea(idea, eve_employee, new ArrayList<>(Set.of(keyword1)), true);
Pair<Boolean, String> validated = ideaService.validateAdminAddAuthors(saved, Set.of(sid_student));
assert validated.getHead();
ideaService.setAuthors(saved, Set.of(sid_student), eve_employee);
}
private void createGradingCriterionTemplateIfNotDone() { private void createGradingCriterionTemplateIfNotDone() {
save(getBachelorTemplate()); save(getBachelorTemplate());
save(getMasterTemplate()); save(getMasterTemplate());
save(getMagisterTemplate()); save(getMagisterTemplate());
} }
private void createTarget() {
Target target = new Target(eric_employee, applicationPeriod, bachelorClass);
target.setTarget(10);
save(target);
}
private void createStudentIdea() {
Idea idea = new Idea();
idea.setTitle("Fundamental Math Concepts of AI");
idea.setType(Idea.Type.STUDENT);
TholanderBox box = new TholanderBox();
box.setLiterature("Math AI Literature");
box.setBackground("Math AI Background");
box.setProblem("Math AI Problem");
box.setMethod("Math AI Method");
box.setInterests("Math AI Interests");
idea.setTholanderBox(box);
idea.setProjectType(bachelorClass);
idea.setApplicationPeriod(applicationPeriod);
List<Keyword> keywords = List.of(keyword1, keyword2);
ideaService.saveStudentIdea(idea, sofia_student, program, new HashSet<User>(), keywords, true);
}
private GradingReportTemplate getBachelorTemplate() { private GradingReportTemplate getBachelorTemplate() {
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate( GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(
bachelorClass, bachelorClass,

View File

@ -169,7 +169,11 @@ public class FirstMeetingPanel extends GenericPanel<Idea> {
} }
private void saveAndNotify() { private void saveAndNotify() {
firstMeetingRepository.save(getModelObject()); FirstMeeting saved = firstMeetingRepository.save(getModelObject());
// After saving the first meeting we have to populate it on the already loaded idea to
// make sure that other places that want to render the first meeting get the correct data.
// An alternative would be to detach the idea model to force a database refresh.
FirstMeetingPanel.this.getModelObject().setFirstMeeting(saved);
NotificationSource source = new NotificationSource(); NotificationSource source = new NotificationSource();
String date = dateService.format(getModelObject().getFirstMeetingDate(), DateStyle.DATETIME); String date = dateService.format(getModelObject().getFirstMeetingDate(), DateStyle.DATETIME);
String room = getModelObject().getRoom(); String room = getModelObject().getRoom();

View File

@ -205,17 +205,34 @@ public abstract class AbstractAdminIdeaPanel extends Panel {
ideaService.adminUnmatchIdea(idea, SciProSession.get().getUser()); ideaService.adminUnmatchIdea(idea, SciProSession.get().getUser());
info("Unmatched idea: " + idea.getTitle()); info("Unmatched idea: " + idea.getTitle());
} else { } else {
if ( ideaService.changeSupervisor(idea, newSelection, SciProSession.get().getUser());
targetService.hasTargetsLeft(
idea.getApplicationPeriod(), Target currentTarget = targetService.findOne(
newSelection, idea.getApplicationPeriod(),
idea.getProjectType() newSelection,
) idea.getProjectType()
) { );
ideaService.changeSupervisor(idea, newSelection, SciProSession.get().getUser()); Long countMatched = ideaService.countMatched(
info("Supervisor changed"); idea.getApplicationPeriod(),
newSelection,
idea.getProjectType()
);
int targetCounter = (currentTarget == null) ? 0 : currentTarget.getTarget();
String msg =
"Supervisor changed: matched/target -> " +
countMatched +
" / " +
targetCounter +
" (" +
idea.getProjectType().getName() +
")";
if (countMatched > targetCounter) {
warn(msg);
} else { } else {
error("The supervisor have reached current target numbers"); info(msg);
} }
} }
target.addListener(new AjaxFeedbackPanelUpdater()); target.addListener(new AjaxFeedbackPanelUpdater());

View File

@ -337,29 +337,26 @@ public class SupervisorMyIdeasPanel extends Panel {
if (idea.getMatch() == null) { if (idea.getMatch() == null) {
return "-"; return "-";
} }
switch (idea.getMatchStatus()) { return switch (idea.getMatchStatus()) {
case UNMATCHED: case UNMATCHED -> getString("status.unmatched");
return getString("status.unmatched"); case COMPLETED -> getString("status.completed");
case COMPLETED: case INACTIVE -> getString("status.inactive");
return getString("status.completed"); case MATCHED -> {
case INACTIVE:
return getString("status.inactive");
case MATCHED:
if (applicationPeriodService.courseStartHasPassed(idea.getApplicationPeriod())) { if (applicationPeriodService.courseStartHasPassed(idea.getApplicationPeriod())) {
if (idea.isExported()) { if (idea.isExported()) {
if (idea.wasExportSuccessful()) { if (idea.wasExportSuccessful()) {
return getString("status.project.created"); yield getString("status.project.created");
} else { } else {
return getString("status.export.failed"); yield getString("status.export.failed");
} }
} else { } else {
return getString("status.awaiting.project.creation"); yield getString("status.awaiting.project.creation");
} }
} else { } else {
return getString("status.awaiting.course.start", Model.of(idea.getApplicationPeriod())); yield getString("status.awaiting.course.start", Model.of(idea.getApplicationPeriod()));
} }
} }
return "-"; // can't happen };
} }
}; };
} }

View File

@ -129,6 +129,12 @@ footer a:hover { color: #d95e00;}
border-color: #EBCCD1; border-color: #EBCCD1;
} }
.feedbackPanelWARNING {
color: #000000;
background-color: #FFD105;
border-color: #EBCCD1;
}
.feedbackPanelINFO { .feedbackPanelINFO {
color: #3C763D; color: #3C763D;
background-color: #DFF0D8; background-color: #DFF0D8;

View File

@ -9,6 +9,7 @@ spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.Ph
# We also need to set the implicit strategy to be JPA compliant, as we rely on this naming strategy for certain # We also need to set the implicit strategy to be JPA compliant, as we rely on this naming strategy for certain
# join tables (idea_Keyword vs idea_keyword) # join tables (idea_Keyword vs idea_keyword)
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
spring.jpa.show-sql=false
spring.mvc.servlet.path=/api spring.mvc.servlet.path=/api