diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java index 50453ee3b2..fb3bf8b4f9 100644 --- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java +++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java @@ -20,7 +20,11 @@ import se.su.dsv.scipro.file.FileUpload; import se.su.dsv.scipro.finalseminar.FinalSeminar; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; 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.Target; +import se.su.dsv.scipro.match.TholanderBox; import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate; import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate; 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.security.auth.roles.Roles; import se.su.dsv.scipro.system.*; +import se.su.dsv.scipro.util.Pair; public class DataInitializer implements Lifecycle { @@ -51,6 +56,9 @@ public class DataInitializer implements Lifecycle { @Inject private PasswordService passwordService; + @Inject + private IdeaService ideaService; + @Inject private MilestoneActivityTemplateService milestoneActivityTemplateService; @@ -94,14 +102,19 @@ public class DataInitializer implements Lifecycle { private User stina_student; private User sid_student; private User simon_student; - private ProjectType bachelorClass; + private User sofia_student; private Set<ResearchArea> researchAreas; 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 researchArea2; + private Keyword keyword1; + private Keyword keyword2; + private ProjectType bachelorClass; private ProjectType masterClass; private ProjectType magisterClass; + private ApplicationPeriod applicationPeriod; private Project project1; private Project project2; @@ -111,13 +124,17 @@ public class DataInitializer implements Lifecycle { if (profile.getCurrentProfile() == Profiles.DEV && noUsers()) { createDefaultProjectTypesIfNotDone(); createDefaultChecklistCategoriesIfNotDone(); + createProgram(); createApplicationPeriodIfNotDone(); createGradingCriterionTemplateIfNotDone(); createResearchAreasForDemo(); createKeywordsIfNotDone(); createMilestonesIfNotDone(); createUsers(); + createMatchedIdea(); createProjects(); + createTarget(); + createStudentIdea(); createRoughDraftApproval(); createPastFinalSeminar(); setUpNotifications(); @@ -175,27 +192,36 @@ public class DataInitializer implements Lifecycle { 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() { - ApplicationPeriod applicationPeriod = new ApplicationPeriod("HT 2014"); + applicationPeriod = new ApplicationPeriod("HT 2014"); applicationPeriod.setStartDate(LocalDate.now().minusDays(APPLICATION_PERIOD_START_MINUS_DAYS)); applicationPeriod.setEndDate(LocalDate.now().plusDays(APPLICATION_PERIOD_END_PLUS_DAYS)); applicationPeriod.setCourseStartDate(LocalDate.now().plusDays(APPLICATION_PERIOD_COURSE_START_PLUS_DAYS)); applicationPeriod.setCourseStartTime(LocalTime.of(8, 0)); applicationPeriod = save(applicationPeriod); - applicationPeriod.setProjectTypes(new HashSet<>(Collections.singletonList(bachelorClass))); + applicationPeriod.setProjectTypes(new HashSet<>(Set.of(bachelorClass, masterClass))); save(applicationPeriod); } private void createKeywordsIfNotDone() { - Keyword keyword1 = new Keyword("IT"); + keyword1 = new Keyword("IT"); keyword1.addResearchArea(researchArea1); keyword1.addResearchArea(researchArea2); - save(keyword1); + keyword1 = save(keyword1); - Keyword keyword2 = new Keyword("Computers"); + keyword2 = new Keyword("Computers"); keyword2.addResearchArea(researchArea1); keyword2.addResearchArea(researchArea2); - save(keyword2); + keyword2 = save(keyword2); } 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 project 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) { @@ -269,8 +300,11 @@ public class DataInitializer implements Lifecycle { user.setUnit(u); user.setResearchAreas(researchAreas); user.setLanguages(languages); + user.setActiveAsSupervisor(true); + createBeta(user); - return user; + + return save(user); } private User createUser(String firstName, String lastName) { @@ -322,12 +356,54 @@ public class DataInitializer implements Lifecycle { 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() { save(getBachelorTemplate()); save(getMasterTemplate()); 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() { GradingReportTemplate gradingReportTemplate = new GradingReportTemplate( bachelorClass, diff --git a/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java b/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java index c7b62f64cd..3158294d42 100644 --- a/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java @@ -169,7 +169,11 @@ public class FirstMeetingPanel extends GenericPanel<Idea> { } 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(); String date = dateService.format(getModelObject().getFirstMeetingDate(), DateStyle.DATETIME); String room = getModelObject().getRoom(); diff --git a/view/src/main/java/se/su/dsv/scipro/match/AbstractAdminIdeaPanel.java b/view/src/main/java/se/su/dsv/scipro/match/AbstractAdminIdeaPanel.java index e7f1301dcf..fa794256ce 100644 --- a/view/src/main/java/se/su/dsv/scipro/match/AbstractAdminIdeaPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/match/AbstractAdminIdeaPanel.java @@ -205,17 +205,34 @@ public abstract class AbstractAdminIdeaPanel extends Panel { ideaService.adminUnmatchIdea(idea, SciProSession.get().getUser()); info("Unmatched idea: " + idea.getTitle()); } else { - if ( - targetService.hasTargetsLeft( - idea.getApplicationPeriod(), - newSelection, - idea.getProjectType() - ) - ) { - ideaService.changeSupervisor(idea, newSelection, SciProSession.get().getUser()); - info("Supervisor changed"); + ideaService.changeSupervisor(idea, newSelection, SciProSession.get().getUser()); + + Target currentTarget = targetService.findOne( + idea.getApplicationPeriod(), + newSelection, + idea.getProjectType() + ); + Long countMatched = ideaService.countMatched( + 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 { - error("The supervisor have reached current target numbers"); + info(msg); } } target.addListener(new AjaxFeedbackPanelUpdater()); diff --git a/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java b/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java index cd59b023bf..f59bd4ac49 100755 --- a/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java @@ -337,29 +337,26 @@ public class SupervisorMyIdeasPanel extends Panel { if (idea.getMatch() == null) { return "-"; } - switch (idea.getMatchStatus()) { - case UNMATCHED: - return getString("status.unmatched"); - case COMPLETED: - return getString("status.completed"); - case INACTIVE: - return getString("status.inactive"); - case MATCHED: + return switch (idea.getMatchStatus()) { + case UNMATCHED -> getString("status.unmatched"); + case COMPLETED -> getString("status.completed"); + case INACTIVE -> getString("status.inactive"); + case MATCHED -> { if (applicationPeriodService.courseStartHasPassed(idea.getApplicationPeriod())) { if (idea.isExported()) { if (idea.wasExportSuccessful()) { - return getString("status.project.created"); + yield getString("status.project.created"); } else { - return getString("status.export.failed"); + yield getString("status.export.failed"); } } else { - return getString("status.awaiting.project.creation"); + yield getString("status.awaiting.project.creation"); } } 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 + } + }; } }; } diff --git a/view/src/main/webapp/css/scipro_m.css b/view/src/main/webapp/css/scipro_m.css index 5c4184a9c0..7ce4954a00 100755 --- a/view/src/main/webapp/css/scipro_m.css +++ b/view/src/main/webapp/css/scipro_m.css @@ -129,6 +129,12 @@ footer a:hover { color: #d95e00;} border-color: #EBCCD1; } +.feedbackPanelWARNING { + color: #000000; + background-color: #FFD105; + border-color: #EBCCD1; +} + .feedbackPanelINFO { color: #3C763D; background-color: #DFF0D8; diff --git a/war/src/main/resources/application.properties b/war/src/main/resources/application.properties index bb89287006..0df6b375e6 100644 --- a/war/src/main/resources/application.properties +++ b/war/src/main/resources/application.properties @@ -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 # join tables (idea_Keyword vs idea_keyword) spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl +spring.jpa.show-sql=false spring.mvc.servlet.path=/api