From f33956068def43c3b98a806407b622ca2103ffe5 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 20 Mar 2025 09:18:39 +0100
Subject: [PATCH 01/49] 87: Add initial view for splitting project

---
 .../admin/pages/AdminEditProjectPage.html     |  3 +
 .../admin/pages/AdminEditProjectPage.java     | 36 +++++++--
 .../admin/pages/AdminSplitProjectPanel.html   | 15 ++++
 .../admin/pages/AdminSplitProjectPanel.java   | 80 +++++++++++++++++++
 .../AdminSplitProjectPanel.utf8.properties    |  1 +
 5 files changed, 129 insertions(+), 6 deletions(-)
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.html
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties

diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.html
index 4f18134263..ef96d30cfa 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.html
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.html
@@ -49,6 +49,9 @@
             </form>
         </div>
     </div>
+
+    <wicket:container wicket:id="splitPanel"/>
+
 </wicket:extend>
 </body>
 </html>
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java
index d34c954c7f..6e93e74617 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java
@@ -2,15 +2,24 @@ package se.su.dsv.scipro.admin.pages;
 
 import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
-import java.time.LocalDate;
-import java.util.*;
 import org.apache.wicket.RestartResponseException;
-import org.apache.wicket.markup.html.form.*;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.FormComponent;
+import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
+import org.apache.wicket.markup.html.form.RequiredTextField;
+import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LambdaModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
-import se.su.dsv.scipro.components.*;
+import se.su.dsv.scipro.components.AutoCompleteRoleProvider;
+import se.su.dsv.scipro.components.BootstrapDatePicker;
+import se.su.dsv.scipro.components.DatesValidator;
+import se.su.dsv.scipro.components.DefaultSelect2MultiChoice;
+import se.su.dsv.scipro.components.ResearchAreaChoiceRenderer;
+import se.su.dsv.scipro.components.ResearchAreasModel;
+import se.su.dsv.scipro.components.SupervisorAutoComplete;
 import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
 import se.su.dsv.scipro.data.DetachableServiceModel;
 import se.su.dsv.scipro.data.DetachableServiceModelCollection;
@@ -26,9 +35,21 @@ import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.ReviewerAssignedEvent;
 import se.su.dsv.scipro.security.auth.Authorization;
 import se.su.dsv.scipro.security.auth.roles.Roles;
-import se.su.dsv.scipro.system.*;
+import se.su.dsv.scipro.system.ProjectType;
+import se.su.dsv.scipro.system.ProjectTypeService;
+import se.su.dsv.scipro.system.ResearchArea;
+import se.su.dsv.scipro.system.ResearchAreaService;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.system.UserSearchService;
+import se.su.dsv.scipro.system.UserService;
 import se.su.dsv.scipro.util.PageParameterKeys;
 
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.TreeSet;
+
 @Authorization(authorizedRoles = { Roles.SYSADMIN })
 public class AdminEditProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
 
@@ -74,7 +95,10 @@ public class AdminEditProjectPage extends AbstractAdminProjectPage implements Me
         if (project == null) {
             throw new RestartResponseException(AdminCreateProjectPage.class);
         }
-        add(new ProjectForm(FORM, new DetachableServiceModel<>(projectService, project)));
+        DetachableServiceModel<Project> dsModel = new DetachableServiceModel<>(projectService, project);
+
+        add(new ProjectForm(FORM, dsModel));
+        add(new AdminSplitProjectPanel("splitPanel", dsModel));
     }
 
     private class ProjectForm extends Form<Project> {
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.html
new file mode 100644
index 0000000000..0aedf51684
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.html
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<body>
+<wicket:panel>
+    <div class="row mt-5">
+        <div class="col-md-12 col-lg-8 col-xl-5">
+            <p>
+                <a class="btn" wicket:id="splitProjectLink"><wicket:message key="splitButton" /></a>
+            </p>
+            <p wicket:id="splitInfo"></p>
+        </div>
+    </div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
new file mode 100644
index 0000000000..b8bcaf413b
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -0,0 +1,80 @@
+package se.su.dsv.scipro.admin.pages;
+
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.ProjectStatus;
+import se.su.dsv.scipro.security.auth.roles.Roles;
+import se.su.dsv.scipro.session.SciProSession;
+
+public class AdminSplitProjectPanel extends Panel {
+    private enum SplittableStatus {
+        NOT_ACTIVE, NOT_TWO_PARTICIPANTS, FINAL_SEMINAR_PHASE_STARTED, OK
+    }
+
+    public AdminSplitProjectPanel(String id, final IModel<Project> projectModel) {
+        super(id, projectModel);
+
+        Project project = projectModel.getObject();
+        SplittableStatus splittableStatus = getSplitStatus(project);
+
+        AbstractLink splitProjectLink = new BookmarkablePageLink<Void>("splitProjectLink", AdminCreateProjectPage.class) {
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+
+                if (splittableStatus == SplittableStatus.OK) {
+                    setEnabled(true);
+                    this.add(new AttributeAppender("class", Model.of(" btn-success")));
+                } else {
+                    setEnabled(false);
+                    this.add(new AttributeAppender("class", Model.of(" btn-secondary disabled")));
+                }
+            }
+        };
+        add(splitProjectLink);
+
+        if (splittableStatus == SplittableStatus.NOT_TWO_PARTICIPANTS) {
+            add(new Label("splitInfo", Model.of("To be able to split a project, it needs to have 2 participants.")));
+        } else if (splittableStatus == SplittableStatus.NOT_ACTIVE) {
+            add(new Label("splitInfo", Model.of("Only active project can be split.")));
+        } else {
+            add(new Label("splitInfo", Model.of("")));
+        }
+
+        /*
+        Label splitInfoLabel = new Label("splitInfoLabel", dsModel) {
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+
+                Project attachedProject = dsModel.getObject();
+                if (attachedProject.getProjectParticipants().size() < 2) {
+                    this
+                }
+            }
+        };
+        */
+    }
+
+    @Override
+    protected void onConfigure() {
+        super.onConfigure();
+        setVisibilityAllowed(SciProSession.get().authorizedForRole(Roles.ADMIN));
+    }
+
+    private SplittableStatus getSplitStatus(final Project project) {
+       if (project.getProjectParticipants().size() != 2) {
+           return SplittableStatus.NOT_TWO_PARTICIPANTS;
+       } else if (project.getProjectStatus() != ProjectStatus.ACTIVE) {
+           return SplittableStatus.NOT_ACTIVE;
+       } else {
+           return SplittableStatus.OK;
+       }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
new file mode 100644
index 0000000000..dbea6689ec
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
@@ -0,0 +1 @@
+splitButton = Split ProjectX
\ No newline at end of file
-- 
2.39.5


From 739bf1c0bd90bf595f957901fabe72384e818d4a Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 24 Mar 2025 14:31:58 +0100
Subject: [PATCH 02/49] 87: Add support for split project confirmation page

---
 .../se/su/dsv/scipro/SciProApplication.java   |  1 +
 .../admin/pages/AdminSplitProjectPage.html    | 30 ++++++++
 .../admin/pages/AdminSplitProjectPage.java    | 75 +++++++++++++++++++
 .../admin/pages/AdminSplitProjectPanel.java   |  8 +-
 .../AdminSplitProjectPanel.utf8.properties    |  2 +-
 5 files changed, 114 insertions(+), 2 deletions(-)
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java

diff --git a/view/src/main/java/se/su/dsv/scipro/SciProApplication.java b/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
index 37f93c29e7..35b9c3b3e0 100755
--- a/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
+++ b/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
@@ -294,6 +294,7 @@ public class SciProApplication extends LifecycleManagedWebApplication {
         mountPage("admin/maintenance", SystemMaintenancePage.class);
         mountPage("admin/project", ProjectManagementPage.class);
         mountPage("admin/project/create", AdminCreateProjectPage.class);
+        mountPage("admin/project/split", AdminSplitProjectPage.class);
         mountPage("admin/project/survey", AdminSurveyPage.class);
         mountPage("admin/project/reviewer", AdminAssignReviewerPage.class);
         mountPage("admin/project/reviewer/capacity", AdminReviewerCapacityManagementPage.class);
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html
new file mode 100644
index 0000000000..f49fc10ae1
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org" lang="en">
+<body>
+<wicket:extend>
+    <div class="row">
+        <div class="col-lg-5">
+            <h4 wicket:id="projectTitle"></h4>
+
+            <p wicket:id="errorInfo"></p>
+
+            <form class="form-horizontal" wicket:id="splitProjectForm">
+                <p>This project will be split between following authors:</p>
+
+                <div class="mb-3">
+                    <ul wicket:id="authorList">
+                        <li wicket:id="author"></li>
+                    </ul>
+                </div>
+                <button class="btn btn-success" type="submit">Split Project</button>
+            </form>
+
+            <div class="mt-5">
+                <a class="btn btn-success" wicket:id="cancelLink">Cancel</a>
+            </div>
+        </div>
+    </div>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
new file mode 100644
index 0000000000..bf7aac2f5a
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -0,0 +1,75 @@
+package se.su.dsv.scipro.admin.pages;
+
+import jakarta.inject.Inject;
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.markup.html.form.Form;
+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.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
+import se.su.dsv.scipro.data.DetachableServiceModel;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.ProjectService;
+import se.su.dsv.scipro.security.auth.Authorization;
+import se.su.dsv.scipro.security.auth.roles.Roles;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.util.PageParameterKeys;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.AbstractLink;
+
+import java.util.ArrayList;
+
+@Authorization(authorizedRoles = { Roles.SYSADMIN })
+public class AdminSplitProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
+
+    @Inject
+    private ProjectService projectService;
+
+    public AdminSplitProjectPage(PageParameters pp) {
+        final long id = pp.get(PageParameterKeys.MAP.get(Project.class)).toLong(0);
+        final Project project = projectService.findOne(id);
+        if (project == null) {
+            throw new RestartResponseException(AdminCreateProjectPage.class);
+        }
+        DetachableServiceModel<Project> dsModel = new DetachableServiceModel<>(projectService, project);
+
+        add(new Label("projectTitle", dsModel.map(Project::getTitle)));
+
+        add(new Label("errorInfo", Model.of("error Info!!")) {
+                @Override
+                protected void onConfigure() {
+                    super.onConfigure();
+                    setVisibilityAllowed(false);
+                }
+            }
+        );
+
+        add(new SplitProjectForm("splitProjectForm", dsModel));
+
+        add(new BookmarkablePageLink<Void>("cancelLink", ProjectManagementPage.class));
+    }
+
+    private class SplitProjectForm extends Form<Project> {
+        public SplitProjectForm(String id, final IModel<Project> model) {
+            super(id, model);
+
+            add(new ListView<>("authorList", model.map(Project::getProjectParticipants).map(ArrayList::new)) {
+                @Override
+                protected void populateItem(ListItem<User> item) {
+                    item.add(new Label("author", item.getModel().map(User::getFullName)));
+                }
+            });
+        }
+
+        @Override
+        protected void onSubmit() {
+            Long projectId = getModel().getObject().getId();
+
+
+        }
+    }
+}
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index b8bcaf413b..77811428df 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -7,10 +7,12 @@ import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.panel.Panel;
 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.project.Project;
 import se.su.dsv.scipro.project.ProjectStatus;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.session.SciProSession;
+import se.su.dsv.scipro.util.PageParameterKeys;
 
 public class AdminSplitProjectPanel extends Panel {
     private enum SplittableStatus {
@@ -23,7 +25,10 @@ public class AdminSplitProjectPanel extends Panel {
         Project project = projectModel.getObject();
         SplittableStatus splittableStatus = getSplitStatus(project);
 
-        AbstractLink splitProjectLink = new BookmarkablePageLink<Void>("splitProjectLink", AdminCreateProjectPage.class) {
+        final PageParameters pp = new PageParameters();
+        pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
+
+        AbstractLink splitProjectLink = new BookmarkablePageLink<Void>("splitProjectLink", AdminSplitProjectPage.class, pp) {
             @Override
             protected void onConfigure() {
                 super.onConfigure();
@@ -37,6 +42,7 @@ public class AdminSplitProjectPanel extends Panel {
                 }
             }
         };
+
         add(splitProjectLink);
 
         if (splittableStatus == SplittableStatus.NOT_TWO_PARTICIPANTS) {
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
index dbea6689ec..2683bf299a 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
@@ -1 +1 @@
-splitButton = Split ProjectX
\ No newline at end of file
+splitButton = Split Project
\ No newline at end of file
-- 
2.39.5


From e59178a54049b6cfc4927bdea00140f634c45ae0 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 09:10:31 +0100
Subject: [PATCH 03/49] 87: Add initial version of SplitOrRestartProjectService

---
 .../java/se/su/dsv/scipro/CoreConfig.java     |  9 ++
 .../split/SplitOrRestartProjectService.java   | 29 +++++++
 .../SplitOrRestartProjectServiceImpl.java     | 86 +++++++++++++++++++
 3 files changed, 124 insertions(+)
 create mode 100644 core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
 create mode 100644 core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index 352c43456a..30bd0bd214 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -145,6 +145,7 @@ import se.su.dsv.scipro.project.ProjectPeopleStatisticsServiceImpl;
 import se.su.dsv.scipro.project.ProjectRepo;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.ProjectServiceImpl;
+import se.su.dsv.scipro.project.split.SplitOrRestartProjectServiceImpl;
 import se.su.dsv.scipro.projectpartner.ProjectPartnerServiceImpl;
 import se.su.dsv.scipro.reflection.ReflectionService;
 import se.su.dsv.scipro.reflection.ReflectionServiceImpl;
@@ -812,6 +813,14 @@ public class CoreConfig {
         return new ProjectServiceImpl(projectRepo, clock, eventBus, em);
     }
 
+    @Bean
+    public SplitOrRestartProjectServiceImpl SplitOrRestartProjectService(
+            ProjectService projectService,
+            FinalSeminarService finalSeminarService
+    ) {
+        return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService);
+    }
+
     @Bean
     public ProjectTypeServiceImpl projectTypeService(Provider<EntityManager> em) {
         return new ProjectTypeServiceImpl(em);
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
new file mode 100644
index 0000000000..42c0a74b65
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
@@ -0,0 +1,29 @@
+package se.su.dsv.scipro.project.split;
+
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.util.Pair;
+
+public interface SplitOrRestartProjectService {
+
+    enum SplittableStatus {
+        NOT_EXIST("Project does not exist."),
+        NOT_ACTIVE("Only active project can be split."),
+        NOT_TWO_PARTICIPANTS("To be able to split a project, it needs to have 2 participants."),
+        FINAL_SEMINAR_PHASE_STARTED("Final seminar phase has been started, too late to split."),
+        OK("Ok to split.");
+
+        private final String message;
+
+        SplittableStatus(String message) {
+            this.message = message;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+
+    Pair<SplittableStatus, Project> getSplittableStatus(int projectId);
+
+    void splitProject(int projectId);
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
new file mode 100644
index 0000000000..f750faa778
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -0,0 +1,86 @@
+package se.su.dsv.scipro.project.split;
+
+import jakarta.inject.Inject;
+import se.su.dsv.scipro.finalseminar.FinalSeminarService;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.ProjectService;
+import se.su.dsv.scipro.project.ProjectStatus;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.util.Pair;
+
+import java.util.List;
+
+public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
+
+    private final ProjectService projectService;
+    private final FinalSeminarService finalSeminarService;
+
+    @Inject
+    public SplitOrRestartProjectServiceImpl(ProjectService projectService, FinalSeminarService finalSeminarService) {
+        this.projectService = projectService;
+        this.finalSeminarService = finalSeminarService;
+    }
+
+    @Override
+    public Pair<SplittableStatus, Project> getSplittableStatus(int projectId) {
+        Project project = projectService.findOne(Long.valueOf(projectId));
+        if (project == null)
+            return new Pair<>(SplittableStatus.NOT_EXIST, null);
+
+        if (project.getProjectStatus() != ProjectStatus.ACTIVE) {
+            return new Pair<>(SplittableStatus.NOT_ACTIVE, project);
+        }
+
+        if (project.getProjectParticipants().size() != 2) {
+            return new Pair<>(SplittableStatus.NOT_TWO_PARTICIPANTS, project);
+        }
+
+        if (finalSeminarService.findByProject(project) != null) {
+            return new Pair<>(SplittableStatus.FINAL_SEMINAR_PHASE_STARTED, project);
+        } else {
+            return new Pair<>(SplittableStatus.OK, project);
+        }
+    }
+
+    @Override
+    public void splitProject(int projectId) {
+        Pair<SplittableStatus, Project> result = getSplittableStatus(projectId);
+        if (result.getHead() != SplittableStatus.OK) {
+            throw new IllegalStateException("Project must to be verified to be able to split " +
+                    "before this method can be called.");
+        }
+
+        Project project = result.getTail();
+
+        for (User author : project.getProjectParticipants()) {
+            Project childProject = new Project();
+            childProject.setTitle(project.getTitle());
+            childProject.setProjectType(project.getProjectType());
+
+            childProject.setStartDate(project.getStartDate());
+            childProject.setExpectedEndDate(project.getExpectedEndDate());
+
+            // Copy supervisor
+            childProject.setProjectParticipants(List.of(author));
+            // Copy reviewer
+            // Copy cosupervisor
+            // Copy research area
+
+            childProject.setProjectStatus(ProjectStatus.ACTIVE);
+
+            // childProject.setParentProjectId
+            // childProject.setRootProjectId
+
+            childProject = projectService.save(childProject);
+
+            // Send event to eventBus to synchronize eventual Phase Two Approval
+        }
+
+        // Parent project will set as inactive
+        project.setProjectStatus(ProjectStatus.INACTIVE);
+        projectService.save(project);
+
+
+        System.out.println("Hello, split project -> " + projectId);
+    }
+}
-- 
2.39.5


From f2e19b686d012a1ce9525979c587bd3a64ebb811 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 10:04:40 +0100
Subject: [PATCH 04/49] 87: Use service method in AdminSplitProjectPanel

---
 .../split/SplitOrRestartProjectService.java   |  4 +-
 .../SplitOrRestartProjectServiceImpl.java     |  6 +-
 .../admin/pages/AdminSplitProjectPanel.java   | 57 +++++++------------
 3 files changed, 24 insertions(+), 43 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
index 42c0a74b65..8231532181 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
@@ -23,7 +23,7 @@ public interface SplitOrRestartProjectService {
         }
     }
 
-    Pair<SplittableStatus, Project> getSplittableStatus(int projectId);
+    Pair<SplittableStatus, Project> getSplittableStatus(long projectId);
 
-    void splitProject(int projectId);
+    void splitProject(long projectId);
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index f750faa778..cd8fa7a87a 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -22,8 +22,8 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     }
 
     @Override
-    public Pair<SplittableStatus, Project> getSplittableStatus(int projectId) {
-        Project project = projectService.findOne(Long.valueOf(projectId));
+    public Pair<SplittableStatus, Project> getSplittableStatus(long projectId) {
+        Project project = projectService.findOne(projectId);
         if (project == null)
             return new Pair<>(SplittableStatus.NOT_EXIST, null);
 
@@ -43,7 +43,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     }
 
     @Override
-    public void splitProject(int projectId) {
+    public void splitProject(long projectId) {
         Pair<SplittableStatus, Project> result = getSplittableStatus(projectId);
         if (result.getHead() != SplittableStatus.OK) {
             throw new IllegalStateException("Project must to be verified to be able to split " +
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index 77811428df..15d15e457d 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -1,29 +1,40 @@
 package se.su.dsv.scipro.admin.pages;
 
+import jakarta.inject.Inject;
 import org.apache.wicket.behavior.AttributeAppender;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import se.su.dsv.scipro.project.Project;
-import se.su.dsv.scipro.project.ProjectStatus;
+import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.session.SciProSession;
 import se.su.dsv.scipro.util.PageParameterKeys;
+import se.su.dsv.scipro.util.Pair;
+
+import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
 
 public class AdminSplitProjectPanel extends Panel {
-    private enum SplittableStatus {
-        NOT_ACTIVE, NOT_TWO_PARTICIPANTS, FINAL_SEMINAR_PHASE_STARTED, OK
-    }
+
+    @Inject
+    private SplitOrRestartProjectService splitOrRestartProjectService;
 
     public AdminSplitProjectPanel(String id, final IModel<Project> projectModel) {
         super(id, projectModel);
 
         Project project = projectModel.getObject();
-        SplittableStatus splittableStatus = getSplitStatus(project);
+
+        LoadableDetachableModel<SplittableStatus> ldModel = LoadableDetachableModel.of(
+                () -> {
+                    Pair<SplittableStatus, Project > pair = splitOrRestartProjectService.getSplittableStatus(project.getId());
+                    return pair.getHead();
+                }
+        );
 
         final PageParameters pp = new PageParameters();
         pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
@@ -33,7 +44,7 @@ public class AdminSplitProjectPanel extends Panel {
             protected void onConfigure() {
                 super.onConfigure();
 
-                if (splittableStatus == SplittableStatus.OK) {
+                if (ldModel.getObject() == SplittableStatus.OK) {
                     setEnabled(true);
                     this.add(new AttributeAppender("class", Model.of(" btn-success")));
                 } else {
@@ -42,30 +53,10 @@ public class AdminSplitProjectPanel extends Panel {
                 }
             }
         };
-
         add(splitProjectLink);
 
-        if (splittableStatus == SplittableStatus.NOT_TWO_PARTICIPANTS) {
-            add(new Label("splitInfo", Model.of("To be able to split a project, it needs to have 2 participants.")));
-        } else if (splittableStatus == SplittableStatus.NOT_ACTIVE) {
-            add(new Label("splitInfo", Model.of("Only active project can be split.")));
-        } else {
-            add(new Label("splitInfo", Model.of("")));
-        }
-
-        /*
-        Label splitInfoLabel = new Label("splitInfoLabel", dsModel) {
-            @Override
-            protected void onConfigure() {
-                super.onConfigure();
-
-                Project attachedProject = dsModel.getObject();
-                if (attachedProject.getProjectParticipants().size() < 2) {
-                    this
-                }
-            }
-        };
-        */
+        add(new Label("splitInfo", Model.of(ldModel.getObject() == SplittableStatus.OK ? "" :
+                ldModel.getObject().getMessage())));
     }
 
     @Override
@@ -73,14 +64,4 @@ public class AdminSplitProjectPanel extends Panel {
         super.onConfigure();
         setVisibilityAllowed(SciProSession.get().authorizedForRole(Roles.ADMIN));
     }
-
-    private SplittableStatus getSplitStatus(final Project project) {
-       if (project.getProjectParticipants().size() != 2) {
-           return SplittableStatus.NOT_TWO_PARTICIPANTS;
-       } else if (project.getProjectStatus() != ProjectStatus.ACTIVE) {
-           return SplittableStatus.NOT_ACTIVE;
-       } else {
-           return SplittableStatus.OK;
-       }
-    }
 }
-- 
2.39.5


From 02dc0629625cec6c6196709d278e9a283848d025 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 10:26:28 +0100
Subject: [PATCH 05/49] 87: Add initial support of AdminViewParentProjectPage

---
 .../se/su/dsv/scipro/SciProApplication.java   |  1 +
 .../admin/pages/AdminSplitProjectPage.java    | 10 ++++---
 .../pages/AdminViewParentProjectPage.html     | 26 +++++++++++++++++++
 .../pages/AdminViewParentProjectPage.java     | 18 +++++++++++++
 4 files changed, 52 insertions(+), 3 deletions(-)
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html
 create mode 100644 view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java

diff --git a/view/src/main/java/se/su/dsv/scipro/SciProApplication.java b/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
index 35b9c3b3e0..5e3c9cb577 100755
--- a/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
+++ b/view/src/main/java/se/su/dsv/scipro/SciProApplication.java
@@ -295,6 +295,7 @@ public class SciProApplication extends LifecycleManagedWebApplication {
         mountPage("admin/project", ProjectManagementPage.class);
         mountPage("admin/project/create", AdminCreateProjectPage.class);
         mountPage("admin/project/split", AdminSplitProjectPage.class);
+        mountPage("admin/project/viewparentproject", AdminViewParentProjectPage.class);
         mountPage("admin/project/survey", AdminSurveyPage.class);
         mountPage("admin/project/reviewer", AdminAssignReviewerPage.class);
         mountPage("admin/project/reviewer/capacity", AdminReviewerCapacityManagementPage.class);
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
index bf7aac2f5a..a91635ff5c 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -2,6 +2,7 @@ package se.su.dsv.scipro.admin.pages;
 
 import jakarta.inject.Inject;
 import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.list.ListItem;
@@ -18,9 +19,6 @@ import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.PageParameterKeys;
 
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.AbstractLink;
-
 import java.util.ArrayList;
 
 @Authorization(authorizedRoles = { Roles.SYSADMIN })
@@ -69,7 +67,13 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
         protected void onSubmit() {
             Long projectId = getModel().getObject().getId();
 
+            System.out.println("Project ID: " + projectId);
 
+
+            final PageParameters pp = new PageParameters();
+            pp.set(PageParameterKeys.MAP.get(Project.class), projectId);
+
+            setResponsePage(AdminViewParentProjectPage.class, pp);
         }
     }
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html
new file mode 100644
index 0000000000..7cdd694718
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org" lang="en">
+<body>
+<wicket:extend>
+    <div class="row">
+        <div class="col-lg-5">
+            <h4>Hahahaha</h4>
+
+            <p>The project has following children projects:</p>
+
+            <div class="mb-3">
+                <ul>
+                    <li>Abcd</li>
+                    <li>efbud</li>
+                </ul>
+            </div>
+
+            <div class="mt-5">
+                <a class="btn btn-success" wicket:id="link">Projects</a>
+            </div>
+        </div>
+    </div>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
new file mode 100644
index 0000000000..4b5d46e624
--- /dev/null
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
@@ -0,0 +1,18 @@
+package se.su.dsv.scipro.admin.pages;
+
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
+import se.su.dsv.scipro.security.auth.Authorization;
+import se.su.dsv.scipro.security.auth.roles.Roles;
+
+@Authorization(authorizedRoles = { Roles.SYSADMIN })
+public class AdminViewParentProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
+
+    public AdminViewParentProjectPage(PageParameters pp) {
+
+        add(new BookmarkablePageLink<Void>("link", ProjectManagementPage.class));
+    }
+
+
+}
-- 
2.39.5


From 6526dc1fd915a9915692ae3b7aeec42d70887b88 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 12:57:18 +0100
Subject: [PATCH 06/49] 87: Add initial support of parent-child project

---
 .../se/su/dsv/scipro/project/Project.java     | 24 +++++++++++++++++++
 .../SplitOrRestartProjectServiceImpl.java     | 24 ++++++++++++-------
 .../v7__project_parent_phase2_review.sql      | 16 +++++++++++++
 .../admin/pages/AdminSplitProjectPage.java    |  5 ++++
 4 files changed, 60 insertions(+), 9 deletions(-)
 create mode 100644 core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql

diff --git a/core/src/main/java/se/su/dsv/scipro/project/Project.java b/core/src/main/java/se/su/dsv/scipro/project/Project.java
index 82f8400f9c..9a9e469738 100755
--- a/core/src/main/java/se/su/dsv/scipro/project/Project.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/Project.java
@@ -113,6 +113,14 @@ public class Project extends DomainObject {
     @Column(name = "daisy_identifier", unique = true)
     private Integer identifier;
 
+    @Basic
+    @Column(name = "parent_project_id")
+    private Long parentProjectId;
+
+    @Basic
+    @Column(name = "root_project_id")
+    private Long rootProjectId;
+
     // ----------------------------------------------------------------------------------
     // Embedded JPA-mapping
     // ----------------------------------------------------------------------------------
@@ -365,6 +373,22 @@ public class Project extends DomainObject {
         this.userNotes = userNotes;
     }
 
+    public Long getParentProjectId() {
+        return parentProjectId;
+    }
+
+    public void setParentProjectId(Long parentProjectId) {
+        this.parentProjectId = parentProjectId;
+    }
+
+    public Long getRootProjectId() {
+        return rootProjectId;
+    }
+
+    public void setRootProjectId(Long rootProjectId) {
+        this.rootProjectId = rootProjectId;
+    }
+
     // ----------------------------------------------------------------------------------
     // Methods Common To All Objects
     // ----------------------------------------------------------------------------------
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index cd8fa7a87a..f0d5532afe 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -1,6 +1,7 @@
 package se.su.dsv.scipro.project.split;
 
 import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
@@ -22,6 +23,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     }
 
     @Override
+    @Transactional
     public Pair<SplittableStatus, Project> getSplittableStatus(long projectId) {
         Project project = projectService.findOne(projectId);
         if (project == null)
@@ -43,6 +45,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     }
 
     @Override
+    @Transactional
     public void splitProject(long projectId) {
         Pair<SplittableStatus, Project> result = getSplittableStatus(projectId);
         if (result.getHead() != SplittableStatus.OK) {
@@ -52,35 +55,38 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
 
         Project project = result.getTail();
 
+        // Todo: Get ev. Phase Two Approval
+
         for (User author : project.getProjectParticipants()) {
             Project childProject = new Project();
+
             childProject.setTitle(project.getTitle());
             childProject.setProjectType(project.getProjectType());
+            childProject.setProjectStatus(ProjectStatus.ACTIVE);
+            childProject.setResearchArea(project.getResearchArea());
 
             childProject.setStartDate(project.getStartDate());
             childProject.setExpectedEndDate(project.getExpectedEndDate());
 
-            // Copy supervisor
+            childProject.setHeadSupervisor(project.getHeadSupervisor());
             childProject.setProjectParticipants(List.of(author));
-            // Copy reviewer
-            // Copy cosupervisor
-            // Copy research area
+            childProject.setCoSupervisors(project.getCoSupervisors());
 
-            childProject.setProjectStatus(ProjectStatus.ACTIVE);
+            childProject.setReviewers(project.getReviewers());
 
-            // childProject.setParentProjectId
-            // childProject.setRootProjectId
+            childProject.setParentProjectId(project.getId());
+            childProject.setRootProjectId(project.getRootProjectId() != null ? project.getRootProjectId() :
+                    project.getId());
 
             childProject = projectService.save(childProject);
 
-            // Send event to eventBus to synchronize eventual Phase Two Approval
+            // Todo: Send event to eventBus to synchronize eventual Phase Two Approval
         }
 
         // Parent project will set as inactive
         project.setProjectStatus(ProjectStatus.INACTIVE);
         projectService.save(project);
 
-
         System.out.println("Hello, split project -> " + projectId);
     }
 }
diff --git a/core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql
new file mode 100644
index 0000000000..2fa8341982
--- /dev/null
+++ b/core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql
@@ -0,0 +1,16 @@
+
+alter table `project`
+    add column `parent_project_id` bigint(20) null default null;
+
+alter table `project`
+    add column `root_project_id` bigint(20) null default null;
+
+alter table `project`
+    add constraint fk_project_parent_project_id_project_id
+        foreign key (parent_project_id) references project(id)
+            on delete cascade on update cascade;
+
+alter table `project`
+    add constraint fk_project_root_project_id_project_id
+        foreign key (root_project_id) references project(id)
+            on delete cascade on update cascade;
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
index a91635ff5c..b3f5221637 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -14,6 +14,7 @@ import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectMan
 import se.su.dsv.scipro.data.DetachableServiceModel;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
+import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
 import se.su.dsv.scipro.security.auth.Authorization;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.User;
@@ -27,6 +28,9 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
     @Inject
     private ProjectService projectService;
 
+    @Inject
+    private SplitOrRestartProjectService splitOrRestartProjectService;
+
     public AdminSplitProjectPage(PageParameters pp) {
         final long id = pp.get(PageParameterKeys.MAP.get(Project.class)).toLong(0);
         final Project project = projectService.findOne(id);
@@ -69,6 +73,7 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
 
             System.out.println("Project ID: " + projectId);
 
+            splitOrRestartProjectService.splitProject(projectId);
 
             final PageParameters pp = new PageParameters();
             pp.set(PageParameterKeys.MAP.get(Project.class), projectId);
-- 
2.39.5


From 0e261fc85eda5c05a20584db001a768559a5300f Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 13:00:11 +0100
Subject: [PATCH 07/49] 87: Reformat code

---
 .../java/se/su/dsv/scipro/CoreConfig.java     |  4 +--
 .../split/SplitOrRestartProjectService.java   |  1 -
 .../SplitOrRestartProjectServiceImpl.java     | 17 +++++------
 .../admin/pages/AdminEditProjectPage.java     | 11 ++++---
 .../admin/pages/AdminSplitProjectPage.java    | 20 +++++++------
 .../admin/pages/AdminSplitProjectPanel.java   | 29 +++++++++++--------
 .../pages/AdminViewParentProjectPage.java     |  7 ++---
 7 files changed, 46 insertions(+), 43 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index 30bd0bd214..6de4da3244 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -815,8 +815,8 @@ public class CoreConfig {
 
     @Bean
     public SplitOrRestartProjectServiceImpl SplitOrRestartProjectService(
-            ProjectService projectService,
-            FinalSeminarService finalSeminarService
+        ProjectService projectService,
+        FinalSeminarService finalSeminarService
     ) {
         return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService);
     }
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
index 8231532181..e2fc88005c 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
@@ -4,7 +4,6 @@ import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.util.Pair;
 
 public interface SplitOrRestartProjectService {
-
     enum SplittableStatus {
         NOT_EXIST("Project does not exist."),
         NOT_ACTIVE("Only active project can be split."),
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index f0d5532afe..7937045a58 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -2,6 +2,7 @@ package se.su.dsv.scipro.project.split;
 
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
+import java.util.List;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
@@ -9,8 +10,6 @@ import se.su.dsv.scipro.project.ProjectStatus;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.Pair;
 
-import java.util.List;
-
 public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
 
     private final ProjectService projectService;
@@ -26,8 +25,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     @Transactional
     public Pair<SplittableStatus, Project> getSplittableStatus(long projectId) {
         Project project = projectService.findOne(projectId);
-        if (project == null)
-            return new Pair<>(SplittableStatus.NOT_EXIST, null);
+        if (project == null) return new Pair<>(SplittableStatus.NOT_EXIST, null);
 
         if (project.getProjectStatus() != ProjectStatus.ACTIVE) {
             return new Pair<>(SplittableStatus.NOT_ACTIVE, project);
@@ -49,8 +47,9 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     public void splitProject(long projectId) {
         Pair<SplittableStatus, Project> result = getSplittableStatus(projectId);
         if (result.getHead() != SplittableStatus.OK) {
-            throw new IllegalStateException("Project must to be verified to be able to split " +
-                    "before this method can be called.");
+            throw new IllegalStateException(
+                "Project must to be verified to be able to split " + "before this method can be called."
+            );
         }
 
         Project project = result.getTail();
@@ -75,11 +74,11 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
             childProject.setReviewers(project.getReviewers());
 
             childProject.setParentProjectId(project.getId());
-            childProject.setRootProjectId(project.getRootProjectId() != null ? project.getRootProjectId() :
-                    project.getId());
+            childProject.setRootProjectId(
+                project.getRootProjectId() != null ? project.getRootProjectId() : project.getId()
+            );
 
             childProject = projectService.save(childProject);
-
             // Todo: Send event to eventBus to synchronize eventual Phase Two Approval
         }
 
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java
index 6e93e74617..ae6ac05b4a 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPage.java
@@ -2,6 +2,11 @@ package se.su.dsv.scipro.admin.pages;
 
 import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.TreeSet;
 import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.markup.html.form.DropDownChoice;
 import org.apache.wicket.markup.html.form.Form;
@@ -44,12 +49,6 @@ import se.su.dsv.scipro.system.UserSearchService;
 import se.su.dsv.scipro.system.UserService;
 import se.su.dsv.scipro.util.PageParameterKeys;
 
-import java.time.LocalDate;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.TreeSet;
-
 @Authorization(authorizedRoles = { Roles.SYSADMIN })
 public class AdminEditProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
 
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
index b3f5221637..6a0d779fbb 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -1,6 +1,7 @@
 package se.su.dsv.scipro.admin.pages;
 
 import jakarta.inject.Inject;
+import java.util.ArrayList;
 import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Form;
@@ -20,8 +21,6 @@ import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.PageParameterKeys;
 
-import java.util.ArrayList;
-
 @Authorization(authorizedRoles = { Roles.SYSADMIN })
 public class AdminSplitProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
 
@@ -41,7 +40,8 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
 
         add(new Label("projectTitle", dsModel.map(Project::getTitle)));
 
-        add(new Label("errorInfo", Model.of("error Info!!")) {
+        add(
+            new Label("errorInfo", Model.of("error Info!!")) {
                 @Override
                 protected void onConfigure() {
                     super.onConfigure();
@@ -56,15 +56,17 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
     }
 
     private class SplitProjectForm extends Form<Project> {
+
         public SplitProjectForm(String id, final IModel<Project> model) {
             super(id, model);
-
-            add(new ListView<>("authorList", model.map(Project::getProjectParticipants).map(ArrayList::new)) {
-                @Override
-                protected void populateItem(ListItem<User> item) {
-                    item.add(new Label("author", item.getModel().map(User::getFullName)));
+            add(
+                new ListView<>("authorList", model.map(Project::getProjectParticipants).map(ArrayList::new)) {
+                    @Override
+                    protected void populateItem(ListItem<User> item) {
+                        item.add(new Label("author", item.getModel().map(User::getFullName)));
+                    }
                 }
-            });
+            );
         }
 
         @Override
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index 15d15e457d..2585786797 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -1,5 +1,7 @@
 package se.su.dsv.scipro.admin.pages;
 
+import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
+
 import jakarta.inject.Inject;
 import org.apache.wicket.behavior.AttributeAppender;
 import org.apache.wicket.markup.html.basic.Label;
@@ -17,8 +19,6 @@ import se.su.dsv.scipro.session.SciProSession;
 import se.su.dsv.scipro.util.PageParameterKeys;
 import se.su.dsv.scipro.util.Pair;
 
-import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
-
 public class AdminSplitProjectPanel extends Panel {
 
     @Inject
@@ -26,20 +26,21 @@ public class AdminSplitProjectPanel extends Panel {
 
     public AdminSplitProjectPanel(String id, final IModel<Project> projectModel) {
         super(id, projectModel);
-
         Project project = projectModel.getObject();
 
-        LoadableDetachableModel<SplittableStatus> ldModel = LoadableDetachableModel.of(
-                () -> {
-                    Pair<SplittableStatus, Project > pair = splitOrRestartProjectService.getSplittableStatus(project.getId());
-                    return pair.getHead();
-                }
-        );
+        LoadableDetachableModel<SplittableStatus> ldModel = LoadableDetachableModel.of(() -> {
+            Pair<SplittableStatus, Project> pair = splitOrRestartProjectService.getSplittableStatus(project.getId());
+            return pair.getHead();
+        });
 
         final PageParameters pp = new PageParameters();
         pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
 
-        AbstractLink splitProjectLink = new BookmarkablePageLink<Void>("splitProjectLink", AdminSplitProjectPage.class, pp) {
+        AbstractLink splitProjectLink = new BookmarkablePageLink<Void>(
+            "splitProjectLink",
+            AdminSplitProjectPage.class,
+            pp
+        ) {
             @Override
             protected void onConfigure() {
                 super.onConfigure();
@@ -55,8 +56,12 @@ public class AdminSplitProjectPanel extends Panel {
         };
         add(splitProjectLink);
 
-        add(new Label("splitInfo", Model.of(ldModel.getObject() == SplittableStatus.OK ? "" :
-                ldModel.getObject().getMessage())));
+        add(
+            new Label(
+                "splitInfo",
+                Model.of(ldModel.getObject() == SplittableStatus.OK ? "" : ldModel.getObject().getMessage())
+            )
+        );
     }
 
     @Override
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
index 4b5d46e624..56c1ed8e4e 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
@@ -7,12 +7,11 @@ import se.su.dsv.scipro.security.auth.Authorization;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 
 @Authorization(authorizedRoles = { Roles.SYSADMIN })
-public class AdminViewParentProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
+public class AdminViewParentProjectPage
+    extends AbstractAdminProjectPage
+    implements MenuHighlightAdminProjectManagement {
 
     public AdminViewParentProjectPage(PageParameters pp) {
-
         add(new BookmarkablePageLink<Void>("link", ProjectManagementPage.class));
     }
-
-
 }
-- 
2.39.5


From 38830f3856e02623ebb246bdc7c0cb471a5840eb Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 13:23:45 +0100
Subject: [PATCH 08/49] 87: Remove SQL-file with wrong naming convention

---
 .../v7__project_parent_phase2_review.sql         | 16 ----------------
 1 file changed, 16 deletions(-)
 delete mode 100644 core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql

diff --git a/core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql
deleted file mode 100644
index 2fa8341982..0000000000
--- a/core/src/main/resources/db/migration/v7__project_parent_phase2_review.sql
+++ /dev/null
@@ -1,16 +0,0 @@
-
-alter table `project`
-    add column `parent_project_id` bigint(20) null default null;
-
-alter table `project`
-    add column `root_project_id` bigint(20) null default null;
-
-alter table `project`
-    add constraint fk_project_parent_project_id_project_id
-        foreign key (parent_project_id) references project(id)
-            on delete cascade on update cascade;
-
-alter table `project`
-    add constraint fk_project_root_project_id_project_id
-        foreign key (root_project_id) references project(id)
-            on delete cascade on update cascade;
-- 
2.39.5


From 7d92dc4ddbccfe7ff284253acd8c830d19cdfb41 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 13:24:32 +0100
Subject: [PATCH 09/49] 87: Rename sql with with capital V

---
 .../V7__project_parent_phase2_review.sql         | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql

diff --git a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
new file mode 100644
index 0000000000..2fa8341982
--- /dev/null
+++ b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
@@ -0,0 +1,16 @@
+
+alter table `project`
+    add column `parent_project_id` bigint(20) null default null;
+
+alter table `project`
+    add column `root_project_id` bigint(20) null default null;
+
+alter table `project`
+    add constraint fk_project_parent_project_id_project_id
+        foreign key (parent_project_id) references project(id)
+            on delete cascade on update cascade;
+
+alter table `project`
+    add constraint fk_project_root_project_id_project_id
+        foreign key (root_project_id) references project(id)
+            on delete cascade on update cascade;
-- 
2.39.5


From d1936635907d39326192b78027d4a86c7134b214 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 26 Mar 2025 13:59:13 +0100
Subject: [PATCH 10/49] 87: Fix test & Reformat code

---
 view/src/test/java/se/su/dsv/scipro/SciProTest.java         | 4 ++++
 .../su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java | 6 ++++++
 2 files changed, 10 insertions(+)

diff --git a/view/src/test/java/se/su/dsv/scipro/SciProTest.java b/view/src/test/java/se/su/dsv/scipro/SciProTest.java
index d83e739cb0..78c2b93168 100755
--- a/view/src/test/java/se/su/dsv/scipro/SciProTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/SciProTest.java
@@ -97,6 +97,7 @@ import se.su.dsv.scipro.project.ProjectNoteService;
 import se.su.dsv.scipro.project.ProjectPeopleStatisticsService;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.pages.ProjectStartPage;
+import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
 import se.su.dsv.scipro.projectpartner.ProjectPartnerService;
 import se.su.dsv.scipro.reflection.ReflectionService;
 import se.su.dsv.scipro.report.GradeCalculatorService;
@@ -252,6 +253,9 @@ public abstract class SciProTest {
     @Mock
     protected ProjectService projectService;
 
+    @Mock
+    protected SplitOrRestartProjectService splitOrRestartProjectService;
+
     @Mock
     protected ResearchAreaService researchAreaService;
 
diff --git a/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java b/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java
index fca1614838..65ceb1bb27 100644
--- a/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java
@@ -20,11 +20,13 @@ import se.su.dsv.scipro.mail.MailEvent;
 import se.su.dsv.scipro.notifications.dataobject.NotificationSource;
 import se.su.dsv.scipro.notifications.dataobject.ProjectEvent;
 import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.DegreeType;
 import se.su.dsv.scipro.system.ProjectType;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.PageParameterKeys;
+import se.su.dsv.scipro.util.Pair;
 
 public class AdminEditProjectPageTest extends SciProTest {
 
@@ -269,6 +271,10 @@ public class AdminEditProjectPageTest extends SciProTest {
 
     private void startPage(Project project) {
         if (project.getId() != null) when(projectService.findOne(project.getId())).thenReturn(project);
+        lenient()
+            .when(splitOrRestartProjectService.getSplittableStatus(project.getId() != null ? project.getId() : 0L))
+            .thenReturn(new Pair<>(SplitOrRestartProjectService.SplittableStatus.OK, project));
+
         PageParameters pp = new PageParameters();
         pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
         tester.startPage(AdminEditProjectPage.class, pp);
-- 
2.39.5


From 8266b1f1fe0d403282d5c51f3d2d9c8b2f0e02ee Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 27 Mar 2025 11:48:34 +0100
Subject: [PATCH 11/49] 87: Improve result page (AdminViewParentProjectPage)
 after split

---
 .../split/SplitOrRestartProjectService.java   |  3 ++
 .../SplitOrRestartProjectServiceImpl.java     | 12 ++++-
 .../admin/pages/AdminSplitProjectPage.java    |  2 -
 .../pages/AdminViewParentProjectPage.html     |  7 ++-
 .../pages/AdminViewParentProjectPage.java     | 51 +++++++++++++++++++
 5 files changed, 67 insertions(+), 8 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
index e2fc88005c..d11626a0a0 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
@@ -1,5 +1,6 @@
 package se.su.dsv.scipro.project.split;
 
+import java.util.List;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.util.Pair;
 
@@ -25,4 +26,6 @@ public interface SplitOrRestartProjectService {
     Pair<SplittableStatus, Project> getSplittableStatus(long projectId);
 
     void splitProject(long projectId);
+
+    List<Project> getChildProjects(long parentProjectId);
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 7937045a58..0b5bc53f82 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -1,5 +1,6 @@
 package se.su.dsv.scipro.project.split;
 
+import com.querydsl.core.types.dsl.BooleanExpression;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
 import java.util.List;
@@ -7,6 +8,7 @@ import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.ProjectStatus;
+import se.su.dsv.scipro.project.QProject;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.Pair;
 
@@ -78,14 +80,20 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
                 project.getRootProjectId() != null ? project.getRootProjectId() : project.getId()
             );
 
+            // Todo: add RoughDraftApproval if it's 'APPROVED'
+
             childProject = projectService.save(childProject);
-            // Todo: Send event to eventBus to synchronize eventual Phase Two Approval
+            // Todo: Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
         }
 
         // Parent project will set as inactive
         project.setProjectStatus(ProjectStatus.INACTIVE);
         projectService.save(project);
+    }
 
-        System.out.println("Hello, split project -> " + projectId);
+    @Override
+    @Transactional
+    public List<Project> getChildProjects(long parentProjectId) {
+        return projectService.findAll(QProject.project.rootProjectId.eq(parentProjectId));
     }
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
index 6a0d779fbb..5ec65d6b52 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -73,8 +73,6 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
         protected void onSubmit() {
             Long projectId = getModel().getObject().getId();
 
-            System.out.println("Project ID: " + projectId);
-
             splitOrRestartProjectService.splitProject(projectId);
 
             final PageParameters pp = new PageParameters();
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html
index 7cdd694718..c7f6ecaa59 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.html
@@ -4,14 +4,13 @@
 <wicket:extend>
     <div class="row">
         <div class="col-lg-5">
-            <h4>Hahahaha</h4>
+            <h4 wicket:id="projectTitle"></h4>
 
             <p>The project has following children projects:</p>
 
             <div class="mb-3">
-                <ul>
-                    <li>Abcd</li>
-                    <li>efbud</li>
+                <ul wicket:id="projectList">
+                    <li><a wicket:id="editLink"></a></li>
                 </ul>
             </div>
 
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
index 56c1ed8e4e..36ec634056 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
@@ -1,17 +1,68 @@
 package se.su.dsv.scipro.admin.pages;
 
+import jakarta.inject.Inject;
+import java.util.List;
+import org.apache.wicket.RestartResponseException;
+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.LoadableDetachableModel;
+import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.ProjectService;
+import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
 import se.su.dsv.scipro.security.auth.Authorization;
 import se.su.dsv.scipro.security.auth.roles.Roles;
+import se.su.dsv.scipro.util.PageParameterKeys;
 
 @Authorization(authorizedRoles = { Roles.SYSADMIN })
 public class AdminViewParentProjectPage
     extends AbstractAdminProjectPage
     implements MenuHighlightAdminProjectManagement {
 
+    @Inject
+    private ProjectService projectService;
+
+    @Inject
+    private SplitOrRestartProjectService splitOrRestartProjectService;
+
     public AdminViewParentProjectPage(PageParameters pp) {
+        final long id = pp.get(PageParameterKeys.MAP.get(Project.class)).toLong(0);
+        final Project project = projectService.findOne(id);
+        if (project == null) {
+            throw new RestartResponseException(AdminCreateProjectPage.class);
+        }
+
+        add(new Label("projectTitle", Model.of(project.getTitle())));
+
+        LoadableDetachableModel<List<Project>> ldModel = LoadableDetachableModel.of(() ->
+            splitOrRestartProjectService.getChildProjects(id)
+        );
+
+        add(
+            new ListView<>("projectList", ldModel) {
+                @Override
+                protected void populateItem(ListItem<Project> item) {
+                    Project project = item.getModelObject();
+
+                    final PageParameters pp = new PageParameters();
+                    pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
+
+                    BookmarkablePageLink<Void> link = new BookmarkablePageLink<Void>(
+                        "editLink",
+                        AdminEditProjectPage.class,
+                        pp
+                    );
+                    link.setBody(Model.of(project.getTitle() + " - " + project.getAuthorNames()));
+
+                    item.add(link);
+                }
+            }
+        );
+
         add(new BookmarkablePageLink<Void>("link", ProjectManagementPage.class));
     }
 }
-- 
2.39.5


From 1ebf21f14bf03ac466b490437d6e2b35611dcc1a Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 2 Apr 2025 09:41:27 +0200
Subject: [PATCH 12/49] 87: Refactor SplittableStatusRecord using Java record

---
 .../java/se/su/dsv/scipro/CoreConfig.java     |  5 ++-
 .../split/SplitOrRestartProjectService.java   | 11 ++++-
 .../SplitOrRestartProjectServiceImpl.java     | 41 +++++++++++++------
 .../admin/pages/AdminSplitProjectPanel.java   |  6 +--
 .../admin/pages/AdminEditProjectPageTest.java |  8 +++-
 5 files changed, 51 insertions(+), 20 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index 6de4da3244..b0f924f458 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -816,9 +816,10 @@ public class CoreConfig {
     @Bean
     public SplitOrRestartProjectServiceImpl SplitOrRestartProjectService(
         ProjectService projectService,
-        FinalSeminarService finalSeminarService
+        FinalSeminarService finalSeminarService,
+        RoughDraftApprovalService roughDraftApprovalService
     ) {
-        return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService);
+        return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService, roughDraftApprovalService);
     }
 
     @Bean
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
index d11626a0a0..0ae111bc44 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
@@ -2,13 +2,14 @@ package se.su.dsv.scipro.project.split;
 
 import java.util.List;
 import se.su.dsv.scipro.project.Project;
-import se.su.dsv.scipro.util.Pair;
+import se.su.dsv.scipro.reviewing.RoughDraftApproval;
 
 public interface SplitOrRestartProjectService {
     enum SplittableStatus {
         NOT_EXIST("Project does not exist."),
         NOT_ACTIVE("Only active project can be split."),
         NOT_TWO_PARTICIPANTS("To be able to split a project, it needs to have 2 participants."),
+        PHASE_TWO_STARTED("Phase 2 (Review) is already started, can't split right now."),
         FINAL_SEMINAR_PHASE_STARTED("Final seminar phase has been started, too late to split."),
         OK("Ok to split.");
 
@@ -23,7 +24,13 @@ public interface SplitOrRestartProjectService {
         }
     }
 
-    Pair<SplittableStatus, Project> getSplittableStatus(long projectId);
+    record SplittableStatusRecord(
+        SplittableStatus splittableStatus,
+        Project project,
+        RoughDraftApproval roughDraftApproval
+    ) {}
+
+    SplittableStatusRecord getSplittableStatus(long projectId);
 
     void splitProject(long projectId);
 
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 0b5bc53f82..5f52b113c3 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -4,11 +4,14 @@ import com.querydsl.core.types.dsl.BooleanExpression;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
 import java.util.List;
+import java.util.Optional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.ProjectStatus;
 import se.su.dsv.scipro.project.QProject;
+import se.su.dsv.scipro.reviewing.RoughDraftApproval;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.util.Pair;
 
@@ -16,47 +19,60 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
 
     private final ProjectService projectService;
     private final FinalSeminarService finalSeminarService;
+    private final RoughDraftApprovalService roughDraftApprovalService;
 
     @Inject
-    public SplitOrRestartProjectServiceImpl(ProjectService projectService, FinalSeminarService finalSeminarService) {
+    public SplitOrRestartProjectServiceImpl(
+        ProjectService projectService,
+        FinalSeminarService finalSeminarService,
+        RoughDraftApprovalService roughDraftApprovalService
+    ) {
         this.projectService = projectService;
         this.finalSeminarService = finalSeminarService;
+        this.roughDraftApprovalService = roughDraftApprovalService;
     }
 
     @Override
     @Transactional
-    public Pair<SplittableStatus, Project> getSplittableStatus(long projectId) {
+    public SplittableStatusRecord getSplittableStatus(long projectId) {
         Project project = projectService.findOne(projectId);
-        if (project == null) return new Pair<>(SplittableStatus.NOT_EXIST, null);
+        if (project == null) return new SplittableStatusRecord(SplittableStatus.NOT_EXIST, null, null);
 
         if (project.getProjectStatus() != ProjectStatus.ACTIVE) {
-            return new Pair<>(SplittableStatus.NOT_ACTIVE, project);
+            return new SplittableStatusRecord(SplittableStatus.NOT_ACTIVE, project, null);
         }
 
         if (project.getProjectParticipants().size() != 2) {
-            return new Pair<>(SplittableStatus.NOT_TWO_PARTICIPANTS, project);
+            return new SplittableStatusRecord(SplittableStatus.NOT_TWO_PARTICIPANTS, project, null);
         }
 
         if (finalSeminarService.findByProject(project) != null) {
-            return new Pair<>(SplittableStatus.FINAL_SEMINAR_PHASE_STARTED, project);
+            return new SplittableStatusRecord(SplittableStatus.FINAL_SEMINAR_PHASE_STARTED, project, null);
+        }
+
+        Optional<RoughDraftApproval> o = roughDraftApprovalService.findBy(project);
+        if (o.isPresent() && !o.get().isDecided()) {
+            return new SplittableStatusRecord(SplittableStatus.PHASE_TWO_STARTED, project, null);
+        }
+
+        if (o.isPresent() && o.get().isApproved()) {
+            return new SplittableStatusRecord(SplittableStatus.OK, project, o.get());
         } else {
-            return new Pair<>(SplittableStatus.OK, project);
+            return new SplittableStatusRecord(SplittableStatus.OK, project, null);
         }
     }
 
     @Override
     @Transactional
     public void splitProject(long projectId) {
-        Pair<SplittableStatus, Project> result = getSplittableStatus(projectId);
-        if (result.getHead() != SplittableStatus.OK) {
+        SplittableStatusRecord result = getSplittableStatus(projectId);
+        if (result.splittableStatus() != SplittableStatus.OK) {
             throw new IllegalStateException(
                 "Project must to be verified to be able to split " + "before this method can be called."
             );
         }
 
-        Project project = result.getTail();
-
-        // Todo: Get ev. Phase Two Approval
+        Project project = result.project();
 
         for (User author : project.getProjectParticipants()) {
             Project childProject = new Project();
@@ -81,6 +97,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
             );
 
             // Todo: add RoughDraftApproval if it's 'APPROVED'
+            if (result.roughDraftApproval() != null) {}
 
             childProject = projectService.save(childProject);
             // Todo: Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index 2585786797..9d4a095e41 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -17,7 +17,6 @@ import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.session.SciProSession;
 import se.su.dsv.scipro.util.PageParameterKeys;
-import se.su.dsv.scipro.util.Pair;
 
 public class AdminSplitProjectPanel extends Panel {
 
@@ -29,8 +28,9 @@ public class AdminSplitProjectPanel extends Panel {
         Project project = projectModel.getObject();
 
         LoadableDetachableModel<SplittableStatus> ldModel = LoadableDetachableModel.of(() -> {
-            Pair<SplittableStatus, Project> pair = splitOrRestartProjectService.getSplittableStatus(project.getId());
-            return pair.getHead();
+            SplitOrRestartProjectService.SplittableStatusRecord status =
+                splitOrRestartProjectService.getSplittableStatus(project.getId());
+            return status.splittableStatus();
         });
 
         final PageParameters pp = new PageParameters();
diff --git a/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java b/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java
index 65ceb1bb27..f9fb3c2bc8 100644
--- a/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/admin/pages/AdminEditProjectPageTest.java
@@ -273,7 +273,13 @@ public class AdminEditProjectPageTest extends SciProTest {
         if (project.getId() != null) when(projectService.findOne(project.getId())).thenReturn(project);
         lenient()
             .when(splitOrRestartProjectService.getSplittableStatus(project.getId() != null ? project.getId() : 0L))
-            .thenReturn(new Pair<>(SplitOrRestartProjectService.SplittableStatus.OK, project));
+            .thenReturn(
+                new SplitOrRestartProjectService.SplittableStatusRecord(
+                    SplitOrRestartProjectService.SplittableStatus.OK,
+                    project,
+                    null
+                )
+            );
 
         PageParameters pp = new PageParameters();
         pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
-- 
2.39.5


From 8a050491f98e0938ccf060ac31f19114b6c84ba2 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 2 Apr 2025 12:44:24 +0200
Subject: [PATCH 13/49] 87: Add clone functionality

---
 .../se/su/dsv/scipro/project/Project.java     | 16 ++++++++++
 .../se/su/dsv/scipro/reviewing/Decision.java  | 19 ++++++++++++
 .../scipro/reviewing/ReviewerApproval.java    | 30 +++++++++++++++++++
 .../scipro/reviewing/RoughDraftApproval.java  | 15 +++++++++-
 .../V7__project_parent_phase2_review.sql      | 12 ++++++++
 5 files changed, 91 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/Project.java b/core/src/main/java/se/su/dsv/scipro/project/Project.java
index 9a9e469738..7e2a9dc6ad 100755
--- a/core/src/main/java/se/su/dsv/scipro/project/Project.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/Project.java
@@ -36,6 +36,9 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
+
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
 import se.su.dsv.scipro.data.dataobjects.Member;
 import se.su.dsv.scipro.reusable.SciProUtilities;
 import se.su.dsv.scipro.system.DegreeType;
@@ -121,6 +124,11 @@ public class Project extends DomainObject {
     @Column(name = "root_project_id")
     private Long rootProjectId;
 
+    @Basic
+    @Column(name = "clone_timestamp")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date cloneTimestamp;
+
     // ----------------------------------------------------------------------------------
     // Embedded JPA-mapping
     // ----------------------------------------------------------------------------------
@@ -389,6 +397,14 @@ public class Project extends DomainObject {
         this.rootProjectId = rootProjectId;
     }
 
+    public Date getCloneTimestamp() {
+        return cloneTimestamp;
+    }
+
+    public void setCloneTimestamp(Date cloneTimestamp) {
+        this.cloneTimestamp = cloneTimestamp;
+    }
+
     // ----------------------------------------------------------------------------------
     // Methods Common To All Objects
     // ----------------------------------------------------------------------------------
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/Decision.java b/core/src/main/java/se/su/dsv/scipro/reviewing/Decision.java
index 67d701cdc9..b3b4ebc635 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/Decision.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/Decision.java
@@ -196,6 +196,25 @@ public class Decision {
         decideWithDecisionDate(status, reason, attachment, Instant.now());
     }
 
+    Decision cloneToReviewerApproval(RoughDraftApproval roughDraftApproval) {
+        Decision clonedDecision = new Decision();
+        clonedDecision.reviewerApproval = roughDraftApproval;
+
+        clonedDecision.status = this.status;
+        clonedDecision.reason = this.reason;
+        clonedDecision.comment = this.comment;
+        clonedDecision.requested = this.requested;
+        clonedDecision.decisionDate = this.decisionDate;
+        clonedDecision.deadline = this.deadline;
+        clonedDecision.reviewerAssignedAt = this.reviewerAssignedAt;
+
+        clonedDecision.assignedReviewer = this.assignedReviewer;
+        clonedDecision.attachment = this.attachment;
+        clonedDecision.thesis = this.thesis;
+
+        return clonedDecision;
+    }
+
     private void decideWithDecisionDate(
         final Status status,
         final String reason,
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
index 9e91d2fca1..0f25dd4aa4 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
@@ -1,6 +1,8 @@
 package se.su.dsv.scipro.reviewing;
 
+import jakarta.persistence.Basic;
 import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
 import jakarta.persistence.DiscriminatorColumn;
 import jakarta.persistence.Entity;
 import jakarta.persistence.GeneratedValue;
@@ -16,6 +18,9 @@ import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
+
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
 import se.su.dsv.scipro.file.FileReference;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.DomainObject;
@@ -32,6 +37,15 @@ public abstract class ReviewerApproval extends DomainObject {
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
 
+    @Basic
+    @Column(name = "is_cloned")
+    protected Boolean isCloned = false;
+
+    @Basic
+    @Column(name = "clone_timestamp")
+    @Temporal(TemporalType.TIMESTAMP)
+    protected Date cloneTimestamp;
+
     // ----------------------------------------------------------------------------------
     // JPA-mappings of foreign keys in this table (reviewer_approval) referencing other
     // tables.
@@ -59,6 +73,22 @@ public abstract class ReviewerApproval extends DomainObject {
         return this.project;
     }
 
+    public Boolean getCloned() {
+        return isCloned;
+    }
+
+    public void setCloned(Boolean cloned) {
+        isCloned = cloned;
+    }
+
+    public Date getCloneTimestamp() {
+        return cloneTimestamp;
+    }
+
+    public void setCloneTimestamp(Date cloneDate) {
+        this.cloneTimestamp = cloneDate;
+    }
+
     // ----------------------------------------------------------------------------------
     // Other methods
     // ----------------------------------------------------------------------------------
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
index bd8854b67b..1e9ca0b8a3 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
@@ -1,7 +1,9 @@
 package se.su.dsv.scipro.reviewing;
 
 import jakarta.persistence.Entity;
-import java.util.*;
+
+import java.time.Instant;
+import java.util.Date;
 import se.su.dsv.scipro.file.FileReference;
 import se.su.dsv.scipro.project.Project;
 
@@ -24,4 +26,15 @@ public class RoughDraftApproval extends ReviewerApproval {
     public Step getStep() {
         return Step.ROUGH_DRAFT_APPROVAL;
     }
+
+    public RoughDraftApproval cloneToProject(final Project newProject) {
+        RoughDraftApproval rda = new RoughDraftApproval();
+        rda.project = newProject;
+        this.decisions.forEach(decision -> rda.decisions.add(decision.cloneToReviewerApproval(rda)));
+
+        rda.isCloned = true;
+        rda.cloneTimestamp = Date.from(Instant.now());
+
+        return rda;
+    }
 }
diff --git a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
index 2fa8341982..dd5e3e588c 100644
--- a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
+++ b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
@@ -5,6 +5,9 @@ alter table `project`
 alter table `project`
     add column `root_project_id` bigint(20) null default null;
 
+alter table `project`
+    add column `clone_timestamp` datetime not null default null;
+
 alter table `project`
     add constraint fk_project_parent_project_id_project_id
         foreign key (parent_project_id) references project(id)
@@ -14,3 +17,12 @@ alter table `project`
     add constraint fk_project_root_project_id_project_id
         foreign key (root_project_id) references project(id)
             on delete cascade on update cascade;
+
+
+alter table `reviewer_approval`
+    add column `is_cloned` bit(1) not null default false;
+
+alter table `reviewer_approval`
+    add column `clone_timestamp` datetime not null default null;
+
+update table `reviewer_approval` set is_cloned = false;
\ No newline at end of file
-- 
2.39.5


From 57774b1c3260102176fbf5dc9076e13f0cfb399f Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 2 Apr 2025 13:18:06 +0200
Subject: [PATCH 14/49] 87: Save cloned RoughDraftApproval

---
 .../java/se/su/dsv/scipro/CoreConfig.java     |  5 ++--
 .../SplitOrRestartProjectServiceImpl.java     | 30 +++++++++++++------
 .../reviewing/RoughDraftApprovalService.java  |  4 ++-
 .../RoughDraftApprovalServiceImpl.java        |  6 ++++
 4 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index b0f924f458..16a400668c 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -817,9 +817,10 @@ public class CoreConfig {
     public SplitOrRestartProjectServiceImpl SplitOrRestartProjectService(
         ProjectService projectService,
         FinalSeminarService finalSeminarService,
-        RoughDraftApprovalService roughDraftApprovalService
+        RoughDraftApprovalService roughDraftApprovalService,
+        EventBus eventBus
     ) {
-        return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService, roughDraftApprovalService);
+        return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService, roughDraftApprovalService, eventBus);
     }
 
     @Bean
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 5f52b113c3..877274f46e 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -1,35 +1,41 @@
 package se.su.dsv.scipro.project.split;
 
-import com.querydsl.core.types.dsl.BooleanExpression;
+import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
-import java.util.List;
-import java.util.Optional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.ProjectStatus;
 import se.su.dsv.scipro.project.QProject;
 import se.su.dsv.scipro.reviewing.RoughDraftApproval;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedEvent;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
-import se.su.dsv.scipro.util.Pair;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
 
 public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
 
     private final ProjectService projectService;
     private final FinalSeminarService finalSeminarService;
     private final RoughDraftApprovalService roughDraftApprovalService;
+    private final EventBus eventBus;
 
     @Inject
     public SplitOrRestartProjectServiceImpl(
         ProjectService projectService,
         FinalSeminarService finalSeminarService,
-        RoughDraftApprovalService roughDraftApprovalService
+        RoughDraftApprovalService roughDraftApprovalService,
+        EventBus eventBus
     ) {
         this.projectService = projectService;
         this.finalSeminarService = finalSeminarService;
         this.roughDraftApprovalService = roughDraftApprovalService;
+        this.eventBus = eventBus;
     }
 
     @Override
@@ -95,12 +101,18 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
             childProject.setRootProjectId(
                 project.getRootProjectId() != null ? project.getRootProjectId() : project.getId()
             );
-
-            // Todo: add RoughDraftApproval if it's 'APPROVED'
-            if (result.roughDraftApproval() != null) {}
+            childProject.setCloneTimestamp(Date.from(Instant.now()));
 
             childProject = projectService.save(childProject);
-            // Todo: Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
+
+            // Add cloned RoughDraftApproval if it's 'APPROVED'
+            RoughDraftApproval rda = result.roughDraftApproval();
+            if (rda != null && rda.isApproved()) {
+                RoughDraftApproval clonedRda = roughDraftApprovalService.saveCloned(rda.cloneToProject(childProject));
+
+                // Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
+                eventBus.post(new RoughDraftApprovalApprovedEvent(clonedRda));
+            }
         }
 
         // Parent project will set as inactive
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalService.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalService.java
index a4e41dfa40..308ff71d00 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalService.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalService.java
@@ -1,3 +1,5 @@
 package se.su.dsv.scipro.reviewing;
 
-public interface RoughDraftApprovalService extends ReviewerApprovalService<RoughDraftApproval> {}
+public interface RoughDraftApprovalService extends ReviewerApprovalService<RoughDraftApproval> {
+    RoughDraftApproval saveCloned(RoughDraftApproval approval);
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalServiceImpl.java
index e0bb356aad..413444cd07 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalServiceImpl.java
@@ -71,4 +71,10 @@ public class RoughDraftApprovalServiceImpl
     public Optional<RoughDraftApproval> findBy(Project project) {
         return Optional.ofNullable(findOne(QRoughDraftApproval.roughDraftApproval.project.eq(project)));
     }
+
+    @Override
+    @Transactional
+    public RoughDraftApproval saveCloned(RoughDraftApproval approval) {
+        return save(approval);
+    }
 }
-- 
2.39.5


From b04c5d439e0eeb782f961f62ed6d4a0e70ad3b17 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 2 Apr 2025 13:19:56 +0200
Subject: [PATCH 15/49] 87: reformat code

---
 core/src/main/java/se/su/dsv/scipro/CoreConfig.java      | 7 ++++++-
 core/src/main/java/se/su/dsv/scipro/project/Project.java | 5 ++---
 .../project/split/SplitOrRestartProjectServiceImpl.java  | 9 ++++-----
 .../se/su/dsv/scipro/reviewing/ReviewerApproval.java     | 5 ++---
 .../se/su/dsv/scipro/reviewing/RoughDraftApproval.java   | 1 -
 5 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index 16a400668c..03e3b29123 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -820,7 +820,12 @@ public class CoreConfig {
         RoughDraftApprovalService roughDraftApprovalService,
         EventBus eventBus
     ) {
-        return new SplitOrRestartProjectServiceImpl(projectService, finalSeminarService, roughDraftApprovalService, eventBus);
+        return new SplitOrRestartProjectServiceImpl(
+            projectService,
+            finalSeminarService,
+            roughDraftApprovalService,
+            eventBus
+        );
     }
 
     @Bean
diff --git a/core/src/main/java/se/su/dsv/scipro/project/Project.java b/core/src/main/java/se/su/dsv/scipro/project/Project.java
index 7e2a9dc6ad..178eed3051 100755
--- a/core/src/main/java/se/su/dsv/scipro/project/Project.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/Project.java
@@ -23,6 +23,8 @@ import jakarta.persistence.MapKeyJoinColumn;
 import jakarta.persistence.PrePersist;
 import jakarta.persistence.PreUpdate;
 import jakarta.persistence.Table;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -36,9 +38,6 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
-
-import jakarta.persistence.Temporal;
-import jakarta.persistence.TemporalType;
 import se.su.dsv.scipro.data.dataobjects.Member;
 import se.su.dsv.scipro.reusable.SciProUtilities;
 import se.su.dsv.scipro.system.DegreeType;
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 877274f46e..2f727f4471 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -3,6 +3,10 @@ package se.su.dsv.scipro.project.split;
 import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
@@ -13,11 +17,6 @@ import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedEvent;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
 
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-
 public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
 
     private final ProjectService projectService;
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
index 0f25dd4aa4..db77e685be 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
@@ -13,14 +13,13 @@ import jakarta.persistence.OneToMany;
 import jakarta.persistence.OneToOne;
 import jakarta.persistence.OrderBy;
 import jakarta.persistence.Table;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
 import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
-
-import jakarta.persistence.Temporal;
-import jakarta.persistence.TemporalType;
 import se.su.dsv.scipro.file.FileReference;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.system.DomainObject;
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
index 1e9ca0b8a3..1ddc5906fb 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
@@ -1,7 +1,6 @@
 package se.su.dsv.scipro.reviewing;
 
 import jakarta.persistence.Entity;
-
 import java.time.Instant;
 import java.util.Date;
 import se.su.dsv.scipro.file.FileReference;
-- 
2.39.5


From 8465d4c6873301335dca8055a9174bf4f6a532bd Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 2 Apr 2025 15:18:46 +0200
Subject: [PATCH 16/49] 87: Loose up some SQL-constraints

---
 .../db/migration/V7__project_parent_phase2_review.sql     | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
index dd5e3e588c..394207522f 100644
--- a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
+++ b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
@@ -6,7 +6,7 @@ alter table `project`
     add column `root_project_id` bigint(20) null default null;
 
 alter table `project`
-    add column `clone_timestamp` datetime not null default null;
+    add column `clone_timestamp` datetime null default null;
 
 alter table `project`
     add constraint fk_project_parent_project_id_project_id
@@ -20,9 +20,7 @@ alter table `project`
 
 
 alter table `reviewer_approval`
-    add column `is_cloned` bit(1) not null default false;
+    add column `is_cloned` bit(1) null default null;
 
 alter table `reviewer_approval`
-    add column `clone_timestamp` datetime not null default null;
-
-update table `reviewer_approval` set is_cloned = false;
\ No newline at end of file
+    add column `clone_timestamp` datetime null default null;
-- 
2.39.5


From e963bee932917da2d21a0680bb3996b736896e37 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 3 Apr 2025 12:56:20 +0200
Subject: [PATCH 17/49] 87: Use other event so no notification will go

---
 .../milestones/service/MilestoneActivator.java     |  6 ++++++
 .../split/SplitOrRestartProjectServiceImpl.java    | 13 +++++++------
 .../RoughDraftApprovalApprovedClonedEvent.java     | 14 ++++++++++++++
 3 files changed, 27 insertions(+), 6 deletions(-)
 create mode 100644 core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalApprovedClonedEvent.java

diff --git a/core/src/main/java/se/su/dsv/scipro/milestones/service/MilestoneActivator.java b/core/src/main/java/se/su/dsv/scipro/milestones/service/MilestoneActivator.java
index 145ee9d096..7a518492fc 100644
--- a/core/src/main/java/se/su/dsv/scipro/milestones/service/MilestoneActivator.java
+++ b/core/src/main/java/se/su/dsv/scipro/milestones/service/MilestoneActivator.java
@@ -21,6 +21,7 @@ import se.su.dsv.scipro.peer.SecondPeerReviewCompletedEvent;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.report.SupervisorGradingReportSubmittedEvent;
 import se.su.dsv.scipro.reviewing.FinalSeminarApprovalApprovedEvent;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedClonedEvent;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedEvent;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalRequestedEvent;
 import se.su.dsv.scipro.system.User;
@@ -136,6 +137,11 @@ public class MilestoneActivator {
         activateProjectMilestone(Set.of(event.getName()), event.getProject());
     }
 
+    @Subscribe
+    public void reviewerApprovalApprovedClone(RoughDraftApprovalApprovedClonedEvent event) {
+        activateProjectMilestone(Set.of(event.getName()), event.getProject());
+    }
+
     @Subscribe
     public void finalSeminarThesisDeleted(FinalSeminarThesisDeletedEvent event) {
         deactivateProjectMilestone(Set.of("FinalSeminarThesisUploaded"), event.getFinalSeminar().getProject());
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 2f727f4471..9bddb9433a 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -3,20 +3,21 @@ package se.su.dsv.scipro.project.split;
 import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.ProjectStatus;
 import se.su.dsv.scipro.project.QProject;
 import se.su.dsv.scipro.reviewing.RoughDraftApproval;
-import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedEvent;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedClonedEvent;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
 
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
 public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
 
     private final ProjectService projectService;
@@ -110,7 +111,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
                 RoughDraftApproval clonedRda = roughDraftApprovalService.saveCloned(rda.cloneToProject(childProject));
 
                 // Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
-                eventBus.post(new RoughDraftApprovalApprovedEvent(clonedRda));
+                eventBus.post(new RoughDraftApprovalApprovedClonedEvent(clonedRda));
             }
         }
 
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalApprovedClonedEvent.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalApprovedClonedEvent.java
new file mode 100644
index 0000000000..d403a44d07
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalApprovedClonedEvent.java
@@ -0,0 +1,14 @@
+package se.su.dsv.scipro.reviewing;
+
+import se.su.dsv.scipro.project.Project;
+
+public record RoughDraftApprovalApprovedClonedEvent(ReviewerApproval process) {
+    public Project getProject() {
+        return process.getProject();
+    }
+
+    public String getName() {
+        ReviewerApproval.Step step = process.getStep();
+        return step.getDeclaringClass().getSimpleName() + "." + step.name();
+    }
+}
-- 
2.39.5


From 671d05e0e16e37d2ea5bd7e32d0c24521f0cccb3 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 3 Apr 2025 15:02:04 +0200
Subject: [PATCH 18/49] 87: Adjust sql script

---
 .../db/migration/V7__project_parent_phase2_review.sql         | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
index 394207522f..dda4ed8e2c 100644
--- a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
+++ b/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
@@ -20,7 +20,9 @@ alter table `project`
 
 
 alter table `reviewer_approval`
-    add column `is_cloned` bit(1) null default null;
+    add column `is_cloned` boolean not null default false;
+
+update `reviewer_approval` set is_cloned = false;
 
 alter table `reviewer_approval`
     add column `clone_timestamp` datetime null default null;
-- 
2.39.5


From d90688648974690ab99ac6ce5e3574e882f526e5 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 3 Apr 2025 15:04:05 +0200
Subject: [PATCH 19/49] 87: reformat code

---
 .../project/split/SplitOrRestartProjectServiceImpl.java  | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 9bddb9433a..020e649758 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -3,6 +3,10 @@ package se.su.dsv.scipro.project.split;
 import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
 import se.su.dsv.scipro.finalseminar.FinalSeminarService;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
@@ -13,11 +17,6 @@ import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedClonedEvent;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
 
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-
 public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
 
     private final ProjectService projectService;
-- 
2.39.5


From 13fa678ac570bdadb7d60ca9c67dbc6869302abf Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 7 Apr 2025 09:58:39 +0200
Subject: [PATCH 20/49] 87: Don't count cloned reviews.

---
 .../java/se/su/dsv/scipro/reviewing/DecisionRepositoryImpl.java  | 1 +
 1 file changed, 1 insertion(+)

diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/DecisionRepositoryImpl.java b/core/src/main/java/se/su/dsv/scipro/reviewing/DecisionRepositoryImpl.java
index 5843cf3335..97d55c2b84 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/DecisionRepositoryImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/DecisionRepositoryImpl.java
@@ -25,6 +25,7 @@ public class DecisionRepositoryImpl extends AbstractRepository implements Decisi
                     .eq(reviewer)
                     .and(QDecision.decision.reviewerAssignedAt.goe(fromDate))
                     .and(QDecision.decision.reviewerAssignedAt.loe(toDate))
+                    .and(QDecision.decision.reviewerApproval.isCloned.isFalse())
             )
             .distinct()
             .fetchCount();
-- 
2.39.5


From 3f604ad926524179a3696d410b050fd210a661f9 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 7 Apr 2025 12:50:34 +0200
Subject: [PATCH 21/49] 87: Refactor out status message to view layer

---
 .../split/SplitOrRestartProjectService.java   | 22 +++++--------------
 .../admin/pages/AdminSplitProjectPanel.java   | 18 ++++++++++-----
 .../AdminSplitProjectPanel.utf8.properties    |  7 +++++-
 3 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
index 0ae111bc44..43b69e7780 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectService.java
@@ -6,22 +6,12 @@ import se.su.dsv.scipro.reviewing.RoughDraftApproval;
 
 public interface SplitOrRestartProjectService {
     enum SplittableStatus {
-        NOT_EXIST("Project does not exist."),
-        NOT_ACTIVE("Only active project can be split."),
-        NOT_TWO_PARTICIPANTS("To be able to split a project, it needs to have 2 participants."),
-        PHASE_TWO_STARTED("Phase 2 (Review) is already started, can't split right now."),
-        FINAL_SEMINAR_PHASE_STARTED("Final seminar phase has been started, too late to split."),
-        OK("Ok to split.");
-
-        private final String message;
-
-        SplittableStatus(String message) {
-            this.message = message;
-        }
-
-        public String getMessage() {
-            return message;
-        }
+        NOT_EXIST,
+        NOT_ACTIVE,
+        NOT_TWO_PARTICIPANTS,
+        PHASE_TWO_STARTED,
+        FINAL_SEMINAR_PHASE_STARTED,
+        OK,
     }
 
     record SplittableStatusRecord(
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index 9d4a095e41..ba322bf238 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -56,12 +56,7 @@ public class AdminSplitProjectPanel extends Panel {
         };
         add(splitProjectLink);
 
-        add(
-            new Label(
-                "splitInfo",
-                Model.of(ldModel.getObject() == SplittableStatus.OK ? "" : ldModel.getObject().getMessage())
-            )
-        );
+        add(new Label("splitInfo", Model.of(getStatusMessage(ldModel.getObject()))));
     }
 
     @Override
@@ -69,4 +64,15 @@ public class AdminSplitProjectPanel extends Panel {
         super.onConfigure();
         setVisibilityAllowed(SciProSession.get().authorizedForRole(Roles.ADMIN));
     }
+
+    private String getStatusMessage(SplittableStatus status) {
+        return switch (status) {
+            case OK -> "";
+            case NOT_EXIST -> getString("status_not_exist");
+            case NOT_ACTIVE -> getString("status_not_active");
+            case NOT_TWO_PARTICIPANTS -> getString("status_not_two_participants");
+            case PHASE_TWO_STARTED -> getString("status_phase_two_started");
+            case FINAL_SEMINAR_PHASE_STARTED -> getString("status_final_seminar_phase_started");
+        };
+    }
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
index 2683bf299a..b3ec52b2aa 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
@@ -1 +1,6 @@
-splitButton = Split Project
\ No newline at end of file
+splitButton = Split Project
+status_not_exit = Project does not exist.
+status_not_active = Only active project can be split.
+status_not_two_participants = To be able to split a project, it needs to have 2 participants.
+status_phase_two_started = Phase 2 (Review) is already started, can't split right now.
+status_final_seminar_phase_started = Final seminar phase has been started, too late to split.
-- 
2.39.5


From 8d5a082a6b017a911482f4faaf8e0a82f68292e5 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 7 Apr 2025 13:31:14 +0200
Subject: [PATCH 22/49] 87: Move cancel link to same row as submit button

---
 .../scipro/admin/pages/AdminSplitProjectPage.html | 11 ++++-------
 .../scipro/admin/pages/AdminSplitProjectPage.java | 15 ++-------------
 2 files changed, 6 insertions(+), 20 deletions(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html
index f49fc10ae1..f05d53317e 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.html
@@ -6,8 +6,6 @@
         <div class="col-lg-5">
             <h4 wicket:id="projectTitle"></h4>
 
-            <p wicket:id="errorInfo"></p>
-
             <form class="form-horizontal" wicket:id="splitProjectForm">
                 <p>This project will be split between following authors:</p>
 
@@ -16,12 +14,11 @@
                         <li wicket:id="author"></li>
                     </ul>
                 </div>
-                <button class="btn btn-success" type="submit">Split Project</button>
-            </form>
 
-            <div class="mt-5">
-                <a class="btn btn-success" wicket:id="cancelLink">Cancel</a>
-            </div>
+                <button class="btn btn-success" type="submit">Split Project</button>
+                &nbsp;&nbsp;
+                <a wicket:id="cancelLink">Cancel</a>
+            </form>
         </div>
     </div>
 
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
index 5ec65d6b52..4145fdd056 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -9,7 +9,6 @@ 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.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
 import se.su.dsv.scipro.data.DetachableServiceModel;
@@ -40,19 +39,7 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
 
         add(new Label("projectTitle", dsModel.map(Project::getTitle)));
 
-        add(
-            new Label("errorInfo", Model.of("error Info!!")) {
-                @Override
-                protected void onConfigure() {
-                    super.onConfigure();
-                    setVisibilityAllowed(false);
-                }
-            }
-        );
-
         add(new SplitProjectForm("splitProjectForm", dsModel));
-
-        add(new BookmarkablePageLink<Void>("cancelLink", ProjectManagementPage.class));
     }
 
     private class SplitProjectForm extends Form<Project> {
@@ -67,6 +54,8 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
                     }
                 }
             );
+
+            add(new BookmarkablePageLink<Void>("cancelLink", ProjectManagementPage.class));
         }
 
         @Override
-- 
2.39.5


From 185785582a035e0f64b42edd2f6cbd44701fc65d Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 7 Apr 2025 15:48:37 +0200
Subject: [PATCH 23/49] 87: First version of integration test

---
 ...rRestartProjectServiceIntegrationTest.java | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
new file mode 100644
index 0000000000..a7dec54c80
--- /dev/null
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -0,0 +1,88 @@
+package se.su.dsv.scipro.project.split;
+
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.ProjectStatus;
+import se.su.dsv.scipro.system.DegreeType;
+import se.su.dsv.scipro.system.ProjectType;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.test.IntegrationTest;
+
+import java.time.LocalDate;
+
+import static org.junit.Assert.assertTrue;
+import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
+import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatusRecord;
+
+public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
+    @Inject
+    private SplitOrRestartProjectService sorpService;
+
+    private Project parentProject;
+    private User supervisor;
+    private User reviwer;
+    private User author1;
+    private User author2;
+
+    @BeforeEach
+    public void setUp() {
+        ProjectType bachelor = createProjectType();
+        this.parentProject = createProject(bachelor, ProjectStatus.ACTIVE);
+        /*
+        user = createUser();
+        Unit unit = new Unit();
+        unit.setTitle("DSV IT");
+        user.setUnit(save(unit));
+
+         */
+    }
+
+    @Test
+    public void project_must_exist() {
+        SplittableStatusRecord record = sorpService.getSplittableStatus(0);
+        SplittableStatus status = record.splittableStatus();
+
+        assertTrue(status == SplittableStatus.NOT_EXIST);
+    }
+
+    @Test
+    public void project_must_be_active() {
+        parentProject.setProjectStatus(ProjectStatus.INACTIVE);
+        parentProject = save(parentProject);
+
+        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatus status = record.splittableStatus();
+
+        assertTrue(status == SplittableStatus.NOT_ACTIVE);
+    }
+
+
+
+    private ProjectType createProjectType() {
+        ProjectType projectType = new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor");
+        save(projectType);
+        return projectType;
+    }
+
+    private Project createProject(ProjectType projectType, ProjectStatus active) {
+        User headSupervisor = save(
+                User.builder().firstName("John").lastName("Doe").emailAddress("john@example.com").build()
+        );
+        Project project = new Project();
+        project.setTitle("Some title");
+        project.setProjectType(projectType);
+        project.setProjectStatus(active);
+        project.setHeadSupervisor(headSupervisor);
+        project.setStartDate(LocalDate.now());
+        project = save(project);
+        System.out.println(project);
+        return project;
+    }
+
+    private User createUser() {
+        User user = User.builder().firstName("Bob").lastName("Sponge").emailAddress("bob@example.com").build();
+        return save(user);
+    }
+}
-- 
2.39.5


From 195e7f3bfb436877a22138ad9559c6ce2fab2b2b Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 9 Apr 2025 13:56:35 +0200
Subject: [PATCH 24/49] 87: Integration test in progress

---
 ...rRestartProjectServiceIntegrationTest.java | 156 +++++++++++++++---
 1 file changed, 135 insertions(+), 21 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index a7dec54c80..f9866d2efb 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -1,16 +1,28 @@
 package se.su.dsv.scipro.project.split;
 
 import jakarta.inject.Inject;
-import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import se.su.dsv.scipro.file.FileUpload;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectStatus;
+import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
+import se.su.dsv.scipro.reviewing.RoughDraftApproval;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
+import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.DegreeType;
 import se.su.dsv.scipro.system.ProjectType;
+import se.su.dsv.scipro.system.ResearchArea;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.IntegrationTest;
 
+import java.io.InputStream;
 import java.time.LocalDate;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
 
 import static org.junit.Assert.assertTrue;
 import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
@@ -20,6 +32,13 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     @Inject
     private SplitOrRestartProjectService sorpService;
 
+    @Inject
+    private RoughDraftApprovalService rdaService;
+
+    @Inject
+    private ReviewerAssignmentService raService;
+
+    private ResearchArea researchArea;
     private Project parentProject;
     private User supervisor;
     private User reviwer;
@@ -28,15 +47,13 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
     @BeforeEach
     public void setUp() {
-        ProjectType bachelor = createProjectType();
-        this.parentProject = createProject(bachelor, ProjectStatus.ACTIVE);
-        /*
-        user = createUser();
-        Unit unit = new Unit();
-        unit.setTitle("DSV IT");
-        user.setUnit(save(unit));
+        ProjectType bachelor = save (new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor"));
 
-         */
+        researchArea = new ResearchArea();
+        researchArea.setTitle("Computer Science");
+        researchArea = save(researchArea);
+
+        parentProject = createProject(bachelor, ProjectStatus.ACTIVE);
     }
 
     @Test
@@ -58,31 +75,128 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         assertTrue(status == SplittableStatus.NOT_ACTIVE);
     }
 
+    @Test
+    public void project_must_have_two_participants() {
+        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatus status = record.splittableStatus();
 
-
-    private ProjectType createProjectType() {
-        ProjectType projectType = new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor");
-        save(projectType);
-        return projectType;
+        assertTrue(status == SplittableStatus.NOT_TWO_PARTICIPANTS);
     }
 
+    @Test
+    public void project_phase_two_started() {
+        setUpBeforePhaseTwo();
+
+        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatus status = record.splittableStatus();
+
+        assertTrue(status == SplittableStatus.PHASE_TWO_STARTED);
+    }
+
+    @Test
+    public void split_on_failed_phase_two() {
+        setUpBeforePhaseTwo();
+
+        Optional<RoughDraftApproval> optional = this.rdaService.findBy(parentProject);
+        optional.ifPresent(rda -> rda.reject("Fail", Optional.empty()));
+
+        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatus status = record.splittableStatus();
+
+        assertTrue(status == SplittableStatus.OK);
+
+        sorpService.splitProject(parentProject.getId());
+        List<Project> childProjects = sorpService.getChildProjects(parentProject.getId());
+
+        assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
+        assertTrue(childProjects.size() == 2);
+        childProjects.forEach(project -> {
+            assertTrue(project.getProjectParticipants().size() == 1);
+            assertTrue(project.getHeadSupervisor().equals(supervisor));
+            assertTrue(project.getProjectStatus() == ProjectStatus.ACTIVE);
+        });
+    }
+
+    /*
+        PHASE_TWO_STARTED,
+        FINAL_SEMINAR_PHASE_STARTED,
+        OK,
+     */
+
     private Project createProject(ProjectType projectType, ProjectStatus active) {
-        User headSupervisor = save(
-                User.builder().firstName("John").lastName("Doe").emailAddress("john@example.com").build()
-        );
+        supervisor = createEmployee("Eric", "Employee", "eric@example.com", false);
+
         Project project = new Project();
         project.setTitle("Some title");
         project.setProjectType(projectType);
         project.setProjectStatus(active);
-        project.setHeadSupervisor(headSupervisor);
+        project.setHeadSupervisor(supervisor);
         project.setStartDate(LocalDate.now());
+
+        author1 = createUser("Adam", "Student", "adam.student@example.com");
+        project.addProjectParticipant(author1);
+
         project = save(project);
-        System.out.println(project);
         return project;
     }
 
-    private User createUser() {
-        User user = User.builder().firstName("Bob").lastName("Sponge").emailAddress("bob@example.com").build();
+    private User createEmployee(String firstName, String lastName, String email, boolean isReviewer) {
+        User employee = createUser(firstName, lastName, email);
+
+        Set<ResearchArea> researchAreas = new HashSet<>();
+        researchAreas.add(researchArea);
+        employee.setResearchAreas(researchAreas);
+
+        if (isReviewer) {
+            employee.addRole(Roles.REVIEWER);
+        }
+
+        save(employee);
+        return employee;
+    }
+
+    private User createUser(String firstName, String lastName, String email) {
+        User user = User.builder().firstName(firstName).lastName(lastName).emailAddress(email).build();
         return save(user);
     }
+
+    private void setUpBeforePhaseTwo() {
+        author2 = createUser("Bertil", "Student", "bertil.student@example.com");
+        parentProject.addProjectParticipant(author2);
+        save(parentProject);
+
+        rdaService.requestApproval(parentProject, dummyFile(), "comment");
+
+        reviwer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
+        ReviewerAssignmentService.ReviewerAssignment reviewerAssignment = raService.assignReviewer(parentProject, reviwer);
+    }
+
+    private FileUpload dummyFile() {
+        return new FileUpload() {
+            @Override
+            public String getFileName() {
+                return "dummy.tmp";
+            }
+
+            @Override
+            public String getContentType() {
+                return "text/plain";
+            }
+
+            @Override
+            public User getUploader() {
+                return null;
+            }
+
+            @Override
+            public long getSize() {
+                return 0;
+            }
+
+            @Override
+            public <T> T handleData(Function<InputStream, T> handler) {
+                return handler.apply(InputStream.nullInputStream());
+            }
+        };
+    }
 }
-- 
2.39.5


From d875d9ce7c6704ab4b577faedd1cb2a0a6da6155 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 9 Apr 2025 15:17:21 +0200
Subject: [PATCH 25/49] 87: Update test

---
 ...rRestartProjectServiceIntegrationTest.java | 42 +++++++++++++------
 1 file changed, 29 insertions(+), 13 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index f9866d2efb..1dc9429e5b 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -7,6 +7,7 @@ import se.su.dsv.scipro.file.FileUpload;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectStatus;
 import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
+import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
 import se.su.dsv.scipro.reviewing.RoughDraftApproval;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.security.auth.roles.Roles;
@@ -30,13 +31,16 @@ import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.Splitt
 
 public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
     @Inject
-    private SplitOrRestartProjectService sorpService;
+    private SplitOrRestartProjectService splitOrRestartProjectService;
 
     @Inject
-    private RoughDraftApprovalService rdaService;
+    private RoughDraftApprovalService roughDraftApprovalService;
 
     @Inject
-    private ReviewerAssignmentService raService;
+    private ReviewerAssignmentService reviewerAssignmentService;
+
+    @Inject
+    private ReviewerCapacityService reviewerCapacityService;
 
     private ResearchArea researchArea;
     private Project parentProject;
@@ -58,7 +62,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
     @Test
     public void project_must_exist() {
-        SplittableStatusRecord record = sorpService.getSplittableStatus(0);
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(0);
         SplittableStatus status = record.splittableStatus();
 
         assertTrue(status == SplittableStatus.NOT_EXIST);
@@ -69,7 +73,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         parentProject.setProjectStatus(ProjectStatus.INACTIVE);
         parentProject = save(parentProject);
 
-        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
         assertTrue(status == SplittableStatus.NOT_ACTIVE);
@@ -77,7 +81,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
     @Test
     public void project_must_have_two_participants() {
-        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
         assertTrue(status == SplittableStatus.NOT_TWO_PARTICIPANTS);
@@ -87,7 +91,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     public void project_phase_two_started() {
         setUpBeforePhaseTwo();
 
-        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
         assertTrue(status == SplittableStatus.PHASE_TWO_STARTED);
@@ -97,26 +101,38 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     public void split_on_failed_phase_two() {
         setUpBeforePhaseTwo();
 
-        Optional<RoughDraftApproval> optional = this.rdaService.findBy(parentProject);
+        Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
         optional.ifPresent(rda -> rda.reject("Fail", Optional.empty()));
 
-        SplittableStatusRecord record = sorpService.getSplittableStatus(parentProject.getId());
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
         assertTrue(status == SplittableStatus.OK);
 
-        sorpService.splitProject(parentProject.getId());
-        List<Project> childProjects = sorpService.getChildProjects(parentProject.getId());
+        splitOrRestartProjectService.splitProject(parentProject.getId());
+        List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
         assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
         assertTrue(childProjects.size() == 2);
+
         childProjects.forEach(project -> {
             assertTrue(project.getProjectParticipants().size() == 1);
             assertTrue(project.getHeadSupervisor().equals(supervisor));
             assertTrue(project.getProjectStatus() == ProjectStatus.ACTIVE);
+            assertTrue(roughDraftApprovalService.findBy(project).isEmpty());
         });
     }
 
+    @Test
+    public void split_on_approved_phase_two() {
+        setUpBeforePhaseTwo();
+
+        Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
+        optional.ifPresent(rda -> rda.approve("Approve", Optional.empty()));
+
+
+    }
+
     /*
         PHASE_TWO_STARTED,
         FINAL_SEMINAR_PHASE_STARTED,
@@ -165,10 +181,10 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         parentProject.addProjectParticipant(author2);
         save(parentProject);
 
-        rdaService.requestApproval(parentProject, dummyFile(), "comment");
+        roughDraftApprovalService.requestApproval(parentProject, dummyFile(), "comment");
 
         reviwer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
-        ReviewerAssignmentService.ReviewerAssignment reviewerAssignment = raService.assignReviewer(parentProject, reviwer);
+        ReviewerAssignmentService.ReviewerAssignment reviewerAssignment = reviewerAssignmentService.assignReviewer(parentProject, reviwer);
     }
 
     private FileUpload dummyFile() {
-- 
2.39.5


From 4be16af51c2580fa5cfe31750ff7b09990eef832 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 10 Apr 2025 09:59:48 +0200
Subject: [PATCH 26/49] 87: Improve test

---
 .../split/SplitOrRestartProjectServiceIntegrationTest.java      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index 1dc9429e5b..3e9ae50f4f 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -112,7 +112,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         splitOrRestartProjectService.splitProject(parentProject.getId());
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
-        assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
+        assertTrue(parentProject.getProjectStatus() ==ProjectStatus.INACTIVE);
         assertTrue(childProjects.size() == 2);
 
         childProjects.forEach(project -> {
-- 
2.39.5


From 80694e2c14b683bdef059393658fa6d15125565d Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 10 Apr 2025 14:18:59 +0200
Subject: [PATCH 27/49] 87: Improve test

---
 ...itOrRestartProjectServiceIntegrationTest.java | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index 3e9ae50f4f..2c3f957976 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -19,6 +19,7 @@ import se.su.dsv.scipro.test.IntegrationTest;
 
 import java.io.InputStream;
 import java.time.LocalDate;
+import java.time.Year;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -30,6 +31,9 @@ import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.Splitt
 import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatusRecord;
 
 public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
+    private static int TARGET = 3;
+    private static int REMAINING_TARGET = 2;
+
     @Inject
     private SplitOrRestartProjectService splitOrRestartProjectService;
 
@@ -45,7 +49,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     private ResearchArea researchArea;
     private Project parentProject;
     private User supervisor;
-    private User reviwer;
+    private User reviewer;
     private User author1;
     private User author2;
 
@@ -110,6 +114,11 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         assertTrue(status == SplittableStatus.OK);
 
         splitOrRestartProjectService.splitProject(parentProject.getId());
+
+        ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(reviewer, Year.now());
+        assertTrue(remainingTargets.spring() == REMAINING_TARGET ||
+                remainingTargets.autumn() == REMAINING_TARGET);
+
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
         assertTrue(parentProject.getProjectStatus() ==ProjectStatus.INACTIVE);
@@ -183,8 +192,9 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         roughDraftApprovalService.requestApproval(parentProject, dummyFile(), "comment");
 
-        reviwer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
-        ReviewerAssignmentService.ReviewerAssignment reviewerAssignment = reviewerAssignmentService.assignReviewer(parentProject, reviwer);
+        reviewer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
+        reviewerCapacityService.assignTarget(reviewer, new ReviewerCapacityService.Target(Year.now(), TARGET, TARGET, ""));
+        reviewerAssignmentService.assignReviewer(parentProject, reviewer);
     }
 
     private FileUpload dummyFile() {
-- 
2.39.5


From 96eb62178d3eedebb6c15a4eaced168f40ab8aff Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 10 Apr 2025 14:20:53 +0200
Subject: [PATCH 28/49] 87: Reformat code

---
 ...rRestartProjectServiceIntegrationTest.java | 45 ++++++++++---------
 1 file changed, 24 insertions(+), 21 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index 2c3f957976..5aaaf6f789 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -1,6 +1,18 @@
 package se.su.dsv.scipro.project.split;
 
+import static org.junit.Assert.assertTrue;
+import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
+import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatusRecord;
+
 import jakarta.inject.Inject;
+import java.io.InputStream;
+import java.time.LocalDate;
+import java.time.Year;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import se.su.dsv.scipro.file.FileUpload;
@@ -17,20 +29,8 @@ import se.su.dsv.scipro.system.ResearchArea;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.IntegrationTest;
 
-import java.io.InputStream;
-import java.time.LocalDate;
-import java.time.Year;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-
-import static org.junit.Assert.assertTrue;
-import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
-import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatusRecord;
-
 public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
+
     private static int TARGET = 3;
     private static int REMAINING_TARGET = 2;
 
@@ -55,7 +55,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
     @BeforeEach
     public void setUp() {
-        ProjectType bachelor = save (new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor"));
+        ProjectType bachelor = save(new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor"));
 
         researchArea = new ResearchArea();
         researchArea.setTitle("Computer Science");
@@ -115,13 +115,15 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         splitOrRestartProjectService.splitProject(parentProject.getId());
 
-        ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(reviewer, Year.now());
-        assertTrue(remainingTargets.spring() == REMAINING_TARGET ||
-                remainingTargets.autumn() == REMAINING_TARGET);
+        ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(
+            reviewer,
+            Year.now()
+        );
+        assertTrue(remainingTargets.spring() == REMAINING_TARGET || remainingTargets.autumn() == REMAINING_TARGET);
 
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
-        assertTrue(parentProject.getProjectStatus() ==ProjectStatus.INACTIVE);
+        assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
         assertTrue(childProjects.size() == 2);
 
         childProjects.forEach(project -> {
@@ -138,8 +140,6 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
         optional.ifPresent(rda -> rda.approve("Approve", Optional.empty()));
-
-
     }
 
     /*
@@ -193,7 +193,10 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         roughDraftApprovalService.requestApproval(parentProject, dummyFile(), "comment");
 
         reviewer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
-        reviewerCapacityService.assignTarget(reviewer, new ReviewerCapacityService.Target(Year.now(), TARGET, TARGET, ""));
+        reviewerCapacityService.assignTarget(
+            reviewer,
+            new ReviewerCapacityService.Target(Year.now(), TARGET, TARGET, "")
+        );
         reviewerAssignmentService.assignReviewer(parentProject, reviewer);
     }
 
-- 
2.39.5


From f956ba1af8af5f538660017296257232e83c3925 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 10 Apr 2025 14:30:05 +0200
Subject: [PATCH 29/49] 87: Improve test

---
 ...rRestartProjectServiceIntegrationTest.java | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index 5aaaf6f789..465ac9db7a 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -140,6 +140,34 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
         optional.ifPresent(rda -> rda.approve("Approve", Optional.empty()));
+
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
+        SplittableStatus status = record.splittableStatus();
+
+        assertTrue(status == SplittableStatus.OK);
+
+        splitOrRestartProjectService.splitProject(parentProject.getId());
+
+        ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(
+            reviewer,
+            Year.now()
+        );
+        assertTrue(remainingTargets.spring() == REMAINING_TARGET || remainingTargets.autumn() == REMAINING_TARGET);
+
+        List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
+
+        assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
+        assertTrue(childProjects.size() == 2);
+
+        childProjects.forEach(project -> {
+            assertTrue(project.getProjectParticipants().size() == 1);
+            assertTrue(project.getHeadSupervisor().equals(supervisor));
+            assertTrue(project.getProjectStatus() == ProjectStatus.ACTIVE);
+
+            Optional<RoughDraftApproval> optionalRda = roughDraftApprovalService.findBy(project);
+            assertTrue(optionalRda.isPresent());
+            assertTrue(optional.get().isApproved());
+        });
     }
 
     /*
-- 
2.39.5


From 6ccf72d4ab7585e0af5d56f009a7760a721cb497 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 14 Apr 2025 12:56:01 +0200
Subject: [PATCH 30/49] 87: Improve integration test

---
 ...rRestartProjectServiceIntegrationTest.java | 46 ++++++++++++-------
 1 file changed, 29 insertions(+), 17 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index 465ac9db7a..6b874bd28a 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -8,25 +8,18 @@ import jakarta.inject.Inject;
 import java.io.InputStream;
 import java.time.LocalDate;
 import java.time.Year;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.time.ZonedDateTime;
+import java.util.*;
 import java.util.function.Function;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import se.su.dsv.scipro.file.FileUpload;
+import se.su.dsv.scipro.finalseminar.FinalSeminar;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectStatus;
-import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
-import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
-import se.su.dsv.scipro.reviewing.RoughDraftApproval;
-import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
+import se.su.dsv.scipro.reviewing.*;
 import se.su.dsv.scipro.security.auth.roles.Roles;
-import se.su.dsv.scipro.system.DegreeType;
-import se.su.dsv.scipro.system.ProjectType;
-import se.su.dsv.scipro.system.ResearchArea;
-import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.system.*;
 import se.su.dsv.scipro.test.IntegrationTest;
 
 public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
@@ -40,6 +33,9 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     @Inject
     private RoughDraftApprovalService roughDraftApprovalService;
 
+    @Inject
+    private FinalSeminarApprovalService finalSeminarApprovalService;
+
     @Inject
     private ReviewerAssignmentService reviewerAssignmentService;
 
@@ -170,11 +166,27 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         });
     }
 
-    /*
-        PHASE_TWO_STARTED,
-        FINAL_SEMINAR_PHASE_STARTED,
-        OK,
-     */
+    @Test
+    public void split_on_final_seminar_phase_started() {
+        setUpBeforePhaseTwo();
+
+        Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
+        optional.ifPresent(rda -> rda.approve("Approve", Optional.empty()));
+
+        FinalSeminar finalSeminar = new FinalSeminar();
+        finalSeminar.setProject(parentProject);
+        finalSeminar.setStartDate(Date.from(ZonedDateTime.now().plusDays(10).toInstant()));
+        finalSeminar.setRoom("room");
+        finalSeminar.setPresentationLanguage(Language.ENGLISH);
+        parentProject.setLanguage(Language.ENGLISH);
+
+        save(finalSeminar);
+
+        SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
+        SplittableStatus status = record.splittableStatus();
+
+        assertTrue(status == SplittableStatus.FINAL_SEMINAR_PHASE_STARTED);
+    }
 
     private Project createProject(ProjectType projectType, ProjectStatus active) {
         supervisor = createEmployee("Eric", "Employee", "eric@example.com", false);
-- 
2.39.5


From 2bb6fae1887956b73db24a9d44c518ad6e5b176b Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 14 Apr 2025 14:20:34 +0200
Subject: [PATCH 31/49] 87: Add more milestones & events to test data

---
 .../dsv/scipro/testdata/DataInitializer.java  | 63 +++++++++++--------
 1 file changed, 37 insertions(+), 26 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index c12cb602b2..89742860d1 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -73,6 +73,9 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
     @Inject
     private MilestoneActivityTemplateService milestoneActivityTemplateService;
 
+    @Inject
+    private EventService eventService;
+
     @Inject
     private FileService fileService;
 
@@ -2017,36 +2020,42 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "First meeting held",
             "First meeting with supervisor.",
             milestonePhaseTemplate1,
-            null
+            null,
+                null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Project plan approved",
             "Project plan approved by supervisor.",
             milestonePhaseTemplate1,
-            null
+            null, null
+        );
+
+        List<Event> events = eventService.findAll();
+
+        createMileStone(
+            MilestoneActivityTemplate.Type.PROJECT,
+            "Rough draft sent to reviewer for approval (Auto)",
+            "Rough draft sent to the reviewer for the first time.",
+            milestonePhaseTemplate2,
+            null, events.stream().filter(event -> event.getName().equals("RoughDraftApprovalRequested")).findFirst().get()
         );
 
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
-            "Rough draft sent to reviewer for approval",
-            "Rough draft approved by reviewer.",
-            milestonePhaseTemplate2,
-            null
-        );
-        createMileStone(
-            MilestoneActivityTemplate.Type.PROJECT,
-            "Rough draft approved by reviewer",
+            "Rough draft approved by reviewer (Auto)",
             "Rough draft approved.",
             milestonePhaseTemplate2,
-            null
+            null, events.stream().filter(event -> event.getName().equals("Step.ROUGH_DRAFT_APPROVAL")).findFirst().get()
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
-            "Peer review 1",
+            "Peer review 1 (Auto)",
             "This is a recommendation of when to perform peer review 1.",
             milestonePhaseTemplate2,
-            MilestoneActivityTemplate.PEER_REVIEW_ONE
+            MilestoneActivityTemplate.PEER_REVIEW_ONE,
+                null
         );
 
         createMileStone(
@@ -2054,14 +2063,14 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Result and discussion completed and approved",
             "Result and discussion.",
             milestonePhaseTemplate3,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Peer review 2",
             "This is a recommendation of when to perform peer review 2.",
             milestonePhaseTemplate3,
-            MilestoneActivityTemplate.PEER_REVIEW_TWO
+            MilestoneActivityTemplate.PEER_REVIEW_TWO, null
         );
 
         createMileStone(
@@ -2069,42 +2078,42 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Thesis approved for final seminar presentation",
             "Thesis approved for final seminar.",
             milestonePhaseTemplate4,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Final seminar created",
             "Creation of final seminar.",
             milestonePhaseTemplate4,
-            MilestoneActivityTemplate.CREATE_SEMINAR
+            MilestoneActivityTemplate.CREATE_SEMINAR, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Final seminar thesis uploaded",
             "Final seminar thesis uploaded.",
             milestonePhaseTemplate4,
-            MilestoneActivityTemplate.THESIS_UPLOADED
+            MilestoneActivityTemplate.THESIS_UPLOADED, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Perform an oral and written opposition",
             "Opposition.",
             milestonePhaseTemplate4,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Active participation in a final seminar",
             "Active participation.",
             milestonePhaseTemplate4,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Defend the thesis in a final seminar",
             "Defence of final thesis.",
             milestonePhaseTemplate4,
-            null
+            null, null
         );
 
         createMileStone(
@@ -2112,28 +2121,28 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Revised final thesis of the submitted thesis",
             "Revised final thesis.",
             milestonePhaseTemplate5,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Originality report approved",
             "Originality report.",
             milestonePhaseTemplate5,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Supervisor and reviewer final grading report submitted",
             "Final grading report.",
             milestonePhaseTemplate5,
-            null
+            null, null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Grading completed",
             "Grading completed by examiner.",
             milestonePhaseTemplate5,
-            null
+            null, null
         );
     }
 
@@ -2142,7 +2151,8 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
         String title,
         String description,
         MilestonePhaseTemplate milestonePhaseTemplate,
-        String code
+        String code,
+        Event event
     ) {
         MilestoneActivityTemplate milestoneActivityTemplate = new MilestoneActivityTemplate(type, title, description);
         milestoneActivityTemplate.addProjectType(bachelorClass);
@@ -2150,6 +2160,7 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
         milestoneActivityTemplate.addProjectType(magisterClass);
         milestoneActivityTemplate.setMilestonePhaseTemplate(milestonePhaseTemplate);
         milestoneActivityTemplate.setCode(code);
+        milestoneActivityTemplate.setActivatedBy(event);
         milestoneActivityTemplateService.save(milestoneActivityTemplate, milestonePhaseTemplate);
     }
 
-- 
2.39.5


From ae3755af5b50429f7fbb2c83e3a3b8a8abc4851f Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 14 Apr 2025 14:21:35 +0200
Subject: [PATCH 32/49] 87: Reformat code

---
 .../dsv/scipro/testdata/DataInitializer.java  | 49 ++++++++++++-------
 1 file changed, 32 insertions(+), 17 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index 89742860d1..c496d20c0c 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -2021,14 +2021,15 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "First meeting with supervisor.",
             milestonePhaseTemplate1,
             null,
-                null
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Project plan approved",
             "Project plan approved by supervisor.",
             milestonePhaseTemplate1,
-            null, null
+            null,
+            null
         );
 
         List<Event> events = eventService.findAll();
@@ -2038,7 +2039,8 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Rough draft sent to reviewer for approval (Auto)",
             "Rough draft sent to the reviewer for the first time.",
             milestonePhaseTemplate2,
-            null, events.stream().filter(event -> event.getName().equals("RoughDraftApprovalRequested")).findFirst().get()
+            null,
+            events.stream().filter(event -> event.getName().equals("RoughDraftApprovalRequested")).findFirst().get()
         );
 
         createMileStone(
@@ -2046,7 +2048,8 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Rough draft approved by reviewer (Auto)",
             "Rough draft approved.",
             milestonePhaseTemplate2,
-            null, events.stream().filter(event -> event.getName().equals("Step.ROUGH_DRAFT_APPROVAL")).findFirst().get()
+            null,
+            events.stream().filter(event -> event.getName().equals("Step.ROUGH_DRAFT_APPROVAL")).findFirst().get()
         );
 
         createMileStone(
@@ -2055,7 +2058,7 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "This is a recommendation of when to perform peer review 1.",
             milestonePhaseTemplate2,
             MilestoneActivityTemplate.PEER_REVIEW_ONE,
-                null
+            null
         );
 
         createMileStone(
@@ -2063,14 +2066,16 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Result and discussion completed and approved",
             "Result and discussion.",
             milestonePhaseTemplate3,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Peer review 2",
             "This is a recommendation of when to perform peer review 2.",
             milestonePhaseTemplate3,
-            MilestoneActivityTemplate.PEER_REVIEW_TWO, null
+            MilestoneActivityTemplate.PEER_REVIEW_TWO,
+            null
         );
 
         createMileStone(
@@ -2078,42 +2083,48 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Thesis approved for final seminar presentation",
             "Thesis approved for final seminar.",
             milestonePhaseTemplate4,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Final seminar created",
             "Creation of final seminar.",
             milestonePhaseTemplate4,
-            MilestoneActivityTemplate.CREATE_SEMINAR, null
+            MilestoneActivityTemplate.CREATE_SEMINAR,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Final seminar thesis uploaded",
             "Final seminar thesis uploaded.",
             milestonePhaseTemplate4,
-            MilestoneActivityTemplate.THESIS_UPLOADED, null
+            MilestoneActivityTemplate.THESIS_UPLOADED,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Perform an oral and written opposition",
             "Opposition.",
             milestonePhaseTemplate4,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Active participation in a final seminar",
             "Active participation.",
             milestonePhaseTemplate4,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Defend the thesis in a final seminar",
             "Defence of final thesis.",
             milestonePhaseTemplate4,
-            null, null
+            null,
+            null
         );
 
         createMileStone(
@@ -2121,28 +2132,32 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Revised final thesis of the submitted thesis",
             "Revised final thesis.",
             milestonePhaseTemplate5,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Originality report approved",
             "Originality report.",
             milestonePhaseTemplate5,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Supervisor and reviewer final grading report submitted",
             "Final grading report.",
             milestonePhaseTemplate5,
-            null, null
+            null,
+            null
         );
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Grading completed",
             "Grading completed by examiner.",
             milestonePhaseTemplate5,
-            null, null
+            null,
+            null
         );
     }
 
-- 
2.39.5


From 299c67e8dd7173935beb25c2d35b4023598e3832 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 14 Apr 2025 16:41:16 +0200
Subject: [PATCH 33/49] 87: Add SplitProjectPopulator

---
 .../populators/SplitProjectPopulator.java     | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
new file mode 100644
index 0000000000..5c640a0ece
--- /dev/null
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
@@ -0,0 +1,45 @@
+package se.su.dsv.scipro.testdata.populators;
+
+import jakarta.inject.Inject;
+import java.time.LocalDate;
+import java.util.Set;
+import org.springframework.stereotype.Service;
+import se.su.dsv.scipro.project.Project;
+import se.su.dsv.scipro.project.ProjectService;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.testdata.BaseData;
+import se.su.dsv.scipro.testdata.Factory;
+import se.su.dsv.scipro.testdata.TestDataPopulator;
+
+@Service
+public class SplitProjectPopulator implements TestDataPopulator {
+
+    private final ProjectService projectService;
+
+    @Inject
+    public SplitProjectPopulator(ProjectService projectService) {
+        this.projectService = projectService;
+    }
+
+    @Override
+    public void populate(BaseData baseData, Factory factory) {
+        System.out.println("How do I do?");
+        User supervisor = factory.createSupervisor("Emil");
+
+        User author1 = factory.createAuthor("Scott");
+        User author2 = factory.createAuthor("Scarlett");
+
+        User reviewer = factory.createReviewer("Elias");
+
+        Project project = Project.builder()
+            .title("Operating System Boot Time Security")
+            .projectType(baseData.bachelor())
+            .startDate(LocalDate.now())
+            .headSupervisor(supervisor)
+            .reviewers(Set.of(reviewer))
+            .projectParticipants(Set.of(author1, author2))
+            .build();
+
+        projectService.save(project);
+    }
+}
-- 
2.39.5


From 25b87579eb6acd65f0b98240b8b9369272a44e96 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 16 Apr 2025 10:28:42 +0200
Subject: [PATCH 34/49] 87: Add test data

---
 .../populators/SplitProjectPopulator.java     | 61 ++++++++++++++++++-
 1 file changed, 59 insertions(+), 2 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
index 5c640a0ece..b170b217ca 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
@@ -1,11 +1,20 @@
 package se.su.dsv.scipro.testdata.populators;
 
 import jakarta.inject.Inject;
+import java.io.InputStream;
 import java.time.LocalDate;
+import java.time.Year;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
 import org.springframework.stereotype.Service;
+import se.su.dsv.scipro.file.FileUpload;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
+import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
+import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
+import se.su.dsv.scipro.reviewing.RoughDraftApproval;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.testdata.BaseData;
 import se.su.dsv.scipro.testdata.Factory;
@@ -15,15 +24,25 @@ import se.su.dsv.scipro.testdata.TestDataPopulator;
 public class SplitProjectPopulator implements TestDataPopulator {
 
     private final ProjectService projectService;
+    private final ReviewerCapacityService reviewerCapacityService;
+    private final RoughDraftApprovalService roughDraftApprovalService;
+    private final ReviewerAssignmentService reviewerAssignmentService;
 
     @Inject
-    public SplitProjectPopulator(ProjectService projectService) {
+    public SplitProjectPopulator(
+        ProjectService projectService,
+        ReviewerCapacityService reviewerCapacityService,
+        RoughDraftApprovalService roughDraftApprovalService,
+        ReviewerAssignmentService reviewerAssignmentService
+    ) {
         this.projectService = projectService;
+        this.reviewerCapacityService = reviewerCapacityService;
+        this.roughDraftApprovalService = roughDraftApprovalService;
+        this.reviewerAssignmentService = reviewerAssignmentService;
     }
 
     @Override
     public void populate(BaseData baseData, Factory factory) {
-        System.out.println("How do I do?");
         User supervisor = factory.createSupervisor("Emil");
 
         User author1 = factory.createAuthor("Scott");
@@ -31,6 +50,8 @@ public class SplitProjectPopulator implements TestDataPopulator {
 
         User reviewer = factory.createReviewer("Elias");
 
+        reviewerCapacityService.assignTarget(reviewer, new ReviewerCapacityService.Target(Year.now(), 3, 3, ""));
+
         Project project = Project.builder()
             .title("Operating System Boot Time Security")
             .projectType(baseData.bachelor())
@@ -41,5 +62,41 @@ public class SplitProjectPopulator implements TestDataPopulator {
             .build();
 
         projectService.save(project);
+
+        roughDraftApprovalService.requestApproval(project, dummyFile(), "Request review.");
+
+        reviewerAssignmentService.assignReviewer(project, reviewer);
+
+        Optional<RoughDraftApproval> optional = roughDraftApprovalService.findBy(project);
+        optional.ifPresent(rda -> rda.approve("Approve! Good Work!", Optional.empty()));
+    }
+
+    private FileUpload dummyFile() {
+        return new FileUpload() {
+            @Override
+            public String getFileName() {
+                return "dummy.tmp";
+            }
+
+            @Override
+            public String getContentType() {
+                return "text/plain";
+            }
+
+            @Override
+            public User getUploader() {
+                return null;
+            }
+
+            @Override
+            public long getSize() {
+                return 0;
+            }
+
+            @Override
+            public <T> T handleData(Function<InputStream, T> handler) {
+                return handler.apply(InputStream.nullInputStream());
+            }
+        };
     }
 }
-- 
2.39.5


From 5c36cd345d664ef123c987b30f8b4486ac23f2d2 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 23 Apr 2025 10:39:51 +0200
Subject: [PATCH 35/49] 87: Use Instant instead of Date & Remove deprecated
 @Temporal

---
 .../main/java/se/su/dsv/scipro/project/Project.java   | 11 +++++------
 .../split/SplitOrRestartProjectServiceImpl.java       |  2 +-
 .../se/su/dsv/scipro/reviewing/ReviewerApproval.java  | 11 +++++------
 .../su/dsv/scipro/reviewing/RoughDraftApproval.java   |  2 +-
 4 files changed, 12 insertions(+), 14 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/Project.java b/core/src/main/java/se/su/dsv/scipro/project/Project.java
index 178eed3051..d90a52ffcb 100755
--- a/core/src/main/java/se/su/dsv/scipro/project/Project.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/Project.java
@@ -23,8 +23,8 @@ import jakarta.persistence.MapKeyJoinColumn;
 import jakarta.persistence.PrePersist;
 import jakarta.persistence.PreUpdate;
 import jakarta.persistence.Table;
-import jakarta.persistence.Temporal;
-import jakarta.persistence.TemporalType;
+
+import java.time.Instant;
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -125,8 +125,7 @@ public class Project extends DomainObject {
 
     @Basic
     @Column(name = "clone_timestamp")
-    @Temporal(TemporalType.TIMESTAMP)
-    private Date cloneTimestamp;
+    private Instant cloneTimestamp;
 
     // ----------------------------------------------------------------------------------
     // Embedded JPA-mapping
@@ -396,11 +395,11 @@ public class Project extends DomainObject {
         this.rootProjectId = rootProjectId;
     }
 
-    public Date getCloneTimestamp() {
+    public Instant getCloneTimestamp() {
         return cloneTimestamp;
     }
 
-    public void setCloneTimestamp(Date cloneTimestamp) {
+    public void setCloneTimestamp(Instant cloneTimestamp) {
         this.cloneTimestamp = cloneTimestamp;
     }
 
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 020e649758..1947b51c9b 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -100,7 +100,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
             childProject.setRootProjectId(
                 project.getRootProjectId() != null ? project.getRootProjectId() : project.getId()
             );
-            childProject.setCloneTimestamp(Date.from(Instant.now()));
+            childProject.setCloneTimestamp(Instant.now());
 
             childProject = projectService.save(childProject);
 
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
index db77e685be..f07b2fd48f 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
@@ -13,8 +13,8 @@ import jakarta.persistence.OneToMany;
 import jakarta.persistence.OneToOne;
 import jakarta.persistence.OrderBy;
 import jakarta.persistence.Table;
-import jakarta.persistence.Temporal;
-import jakarta.persistence.TemporalType;
+
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedList;
@@ -42,8 +42,7 @@ public abstract class ReviewerApproval extends DomainObject {
 
     @Basic
     @Column(name = "clone_timestamp")
-    @Temporal(TemporalType.TIMESTAMP)
-    protected Date cloneTimestamp;
+    protected Instant cloneTimestamp;
 
     // ----------------------------------------------------------------------------------
     // JPA-mappings of foreign keys in this table (reviewer_approval) referencing other
@@ -80,11 +79,11 @@ public abstract class ReviewerApproval extends DomainObject {
         isCloned = cloned;
     }
 
-    public Date getCloneTimestamp() {
+    public Instant getCloneTimestamp() {
         return cloneTimestamp;
     }
 
-    public void setCloneTimestamp(Date cloneDate) {
+    public void setCloneTimestamp(Instant cloneDate) {
         this.cloneTimestamp = cloneDate;
     }
 
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
index 1ddc5906fb..e22ec0af44 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
@@ -32,7 +32,7 @@ public class RoughDraftApproval extends ReviewerApproval {
         this.decisions.forEach(decision -> rda.decisions.add(decision.cloneToReviewerApproval(rda)));
 
         rda.isCloned = true;
-        rda.cloneTimestamp = Date.from(Instant.now());
+        rda.cloneTimestamp = Instant.now();
 
         return rda;
     }
-- 
2.39.5


From 25ba68f929249639e88b36f9492412c06311dd67 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 23 Apr 2025 14:03:13 +0200
Subject: [PATCH 36/49] 87: Replace assertTrue with assertEquals

---
 ...rRestartProjectServiceIntegrationTest.java | 40 ++++++++++---------
 1 file changed, 22 insertions(+), 18 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index 6b874bd28a..c2a82bb300 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -1,6 +1,7 @@
 package se.su.dsv.scipro.project.split;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
 import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatusRecord;
 
@@ -11,6 +12,7 @@ import java.time.Year;
 import java.time.ZonedDateTime;
 import java.util.*;
 import java.util.function.Function;
+
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import se.su.dsv.scipro.file.FileUpload;
@@ -65,7 +67,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(0);
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.NOT_EXIST);
+        assertEquals(SplittableStatus.NOT_EXIST, status);
     }
 
     @Test
@@ -76,7 +78,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.NOT_ACTIVE);
+        assertEquals(SplittableStatus.NOT_ACTIVE, status);
     }
 
     @Test
@@ -84,7 +86,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.NOT_TWO_PARTICIPANTS);
+        assertEquals(SplittableStatus.NOT_TWO_PARTICIPANTS, status);
     }
 
     @Test
@@ -94,7 +96,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.PHASE_TWO_STARTED);
+        assertEquals(SplittableStatus.PHASE_TWO_STARTED, status);
     }
 
     @Test
@@ -107,7 +109,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.OK);
+        assertEquals(SplittableStatus.OK, status);
 
         splitOrRestartProjectService.splitProject(parentProject.getId());
 
@@ -115,17 +117,18 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
             reviewer,
             Year.now()
         );
+        // Todo: improve this with higher precision by using MutabledFixedClock
         assertTrue(remainingTargets.spring() == REMAINING_TARGET || remainingTargets.autumn() == REMAINING_TARGET);
 
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
-        assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
-        assertTrue(childProjects.size() == 2);
+        assertEquals(ProjectStatus.INACTIVE, parentProject.getProjectStatus());
+        assertEquals(2, childProjects.size());
 
         childProjects.forEach(project -> {
-            assertTrue(project.getProjectParticipants().size() == 1);
-            assertTrue(project.getHeadSupervisor().equals(supervisor));
-            assertTrue(project.getProjectStatus() == ProjectStatus.ACTIVE);
+            assertEquals(1, project.getProjectParticipants().size());
+            assertEquals(supervisor, project.getHeadSupervisor());
+            assertEquals(ProjectStatus.ACTIVE, project.getProjectStatus());
             assertTrue(roughDraftApprovalService.findBy(project).isEmpty());
         });
     }
@@ -140,7 +143,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.OK);
+        assertEquals(SplittableStatus.OK, status);
 
         splitOrRestartProjectService.splitProject(parentProject.getId());
 
@@ -148,17 +151,18 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
             reviewer,
             Year.now()
         );
+        // todo: improve this by using MutableFixedClock
         assertTrue(remainingTargets.spring() == REMAINING_TARGET || remainingTargets.autumn() == REMAINING_TARGET);
 
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
-        assertTrue(parentProject.getProjectStatus() == ProjectStatus.INACTIVE);
-        assertTrue(childProjects.size() == 2);
+        assertEquals(ProjectStatus.INACTIVE, parentProject.getProjectStatus());
+        assertEquals(2, childProjects.size());
 
         childProjects.forEach(project -> {
-            assertTrue(project.getProjectParticipants().size() == 1);
-            assertTrue(project.getHeadSupervisor().equals(supervisor));
-            assertTrue(project.getProjectStatus() == ProjectStatus.ACTIVE);
+            assertEquals(1, project.getProjectParticipants().size());
+            assertEquals(supervisor, project.getHeadSupervisor());
+            assertEquals(ProjectStatus.ACTIVE, project.getProjectStatus());
 
             Optional<RoughDraftApproval> optionalRda = roughDraftApprovalService.findBy(project);
             assertTrue(optionalRda.isPresent());
@@ -185,7 +189,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
         SplittableStatus status = record.splittableStatus();
 
-        assertTrue(status == SplittableStatus.FINAL_SEMINAR_PHASE_STARTED);
+        assertEquals(SplittableStatus.FINAL_SEMINAR_PHASE_STARTED, status);
     }
 
     private Project createProject(ProjectType projectType, ProjectStatus active) {
-- 
2.39.5


From 60d73e18e40a62242fcb2022adfc910163ce720a Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 23 Apr 2025 14:37:06 +0200
Subject: [PATCH 37/49] 87: Improve code readability in DataInitializer &
 Reformat code

---
 .../se/su/dsv/scipro/project/Project.java     |  1 -
 .../scipro/reviewing/ReviewerApproval.java    |  1 -
 ...rRestartProjectServiceIntegrationTest.java |  1 -
 .../dsv/scipro/testdata/DataInitializer.java  | 97 +++++++++++--------
 4 files changed, 54 insertions(+), 46 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/project/Project.java b/core/src/main/java/se/su/dsv/scipro/project/Project.java
index d90a52ffcb..21fceed91d 100755
--- a/core/src/main/java/se/su/dsv/scipro/project/Project.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/Project.java
@@ -23,7 +23,6 @@ import jakarta.persistence.MapKeyJoinColumn;
 import jakarta.persistence.PrePersist;
 import jakarta.persistence.PreUpdate;
 import jakarta.persistence.Table;
-
 import java.time.Instant;
 import java.time.LocalDate;
 import java.util.ArrayList;
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
index f07b2fd48f..56e1decee9 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/ReviewerApproval.java
@@ -13,7 +13,6 @@ import jakarta.persistence.OneToMany;
 import jakarta.persistence.OneToOne;
 import jakarta.persistence.OrderBy;
 import jakarta.persistence.Table;
-
 import java.time.Instant;
 import java.util.Collections;
 import java.util.Date;
diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index c2a82bb300..cfaa8455af 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -12,7 +12,6 @@ import java.time.Year;
 import java.time.ZonedDateTime;
 import java.util.*;
 import java.util.function.Function;
-
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import se.su.dsv.scipro.file.FileUpload;
diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index c496d20c0c..541ed95fac 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -2019,17 +2019,14 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             MilestoneActivityTemplate.Type.STUDENT,
             "First meeting held",
             "First meeting with supervisor.",
-            milestonePhaseTemplate1,
-            null,
-            null
+            milestonePhaseTemplate1
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Project plan approved",
             "Project plan approved by supervisor.",
-            milestonePhaseTemplate1,
-            null,
-            null
+            milestonePhaseTemplate1
         );
 
         List<Event> events = eventService.findAll();
@@ -2039,7 +2036,6 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Rough draft sent to reviewer for approval (Auto)",
             "Rough draft sent to the reviewer for the first time.",
             milestonePhaseTemplate2,
-            null,
             events.stream().filter(event -> event.getName().equals("RoughDraftApprovalRequested")).findFirst().get()
         );
 
@@ -2048,7 +2044,6 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Rough draft approved by reviewer (Auto)",
             "Rough draft approved.",
             milestonePhaseTemplate2,
-            null,
             events.stream().filter(event -> event.getName().equals("Step.ROUGH_DRAFT_APPROVAL")).findFirst().get()
         );
 
@@ -2057,110 +2052,126 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Peer review 1 (Auto)",
             "This is a recommendation of when to perform peer review 1.",
             milestonePhaseTemplate2,
-            MilestoneActivityTemplate.PEER_REVIEW_ONE,
-            null
+            MilestoneActivityTemplate.PEER_REVIEW_ONE
         );
 
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Result and discussion completed and approved",
             "Result and discussion.",
-            milestonePhaseTemplate3,
-            null,
-            null
+            milestonePhaseTemplate3
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Peer review 2",
             "This is a recommendation of when to perform peer review 2.",
             milestonePhaseTemplate3,
-            MilestoneActivityTemplate.PEER_REVIEW_TWO,
-            null
+            MilestoneActivityTemplate.PEER_REVIEW_TWO
         );
 
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Thesis approved for final seminar presentation",
             "Thesis approved for final seminar.",
-            milestonePhaseTemplate4,
-            null,
-            null
+            milestonePhaseTemplate4
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Final seminar created",
             "Creation of final seminar.",
             milestonePhaseTemplate4,
-            MilestoneActivityTemplate.CREATE_SEMINAR,
-            null
+            MilestoneActivityTemplate.CREATE_SEMINAR
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Final seminar thesis uploaded",
             "Final seminar thesis uploaded.",
             milestonePhaseTemplate4,
-            MilestoneActivityTemplate.THESIS_UPLOADED,
-            null
+            MilestoneActivityTemplate.THESIS_UPLOADED
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Perform an oral and written opposition",
             "Opposition.",
-            milestonePhaseTemplate4,
-            null,
-            null
+            milestonePhaseTemplate4
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Active participation in a final seminar",
             "Active participation.",
-            milestonePhaseTemplate4,
-            null,
-            null
+            milestonePhaseTemplate4
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Defend the thesis in a final seminar",
             "Defence of final thesis.",
-            milestonePhaseTemplate4,
-            null,
-            null
+            milestonePhaseTemplate4
         );
 
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Revised final thesis of the submitted thesis",
             "Revised final thesis.",
-            milestonePhaseTemplate5,
-            null,
-            null
+            milestonePhaseTemplate5
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Originality report approved",
             "Originality report.",
-            milestonePhaseTemplate5,
-            null,
-            null
+            milestonePhaseTemplate5
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.PROJECT,
             "Supervisor and reviewer final grading report submitted",
             "Final grading report.",
-            milestonePhaseTemplate5,
-            null,
-            null
+            milestonePhaseTemplate5
         );
+
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
             "Grading completed",
             "Grading completed by examiner.",
-            milestonePhaseTemplate5,
-            null,
-            null
+            milestonePhaseTemplate5
         );
     }
 
+    private void createMileStone(
+        MilestoneActivityTemplate.Type type,
+        String title,
+        String description,
+        MilestonePhaseTemplate milestonePhaseTemplate
+    ) {
+        createMileStone(type, title, description, milestonePhaseTemplate, null, null);
+    }
+
+    private void createMileStone(
+        MilestoneActivityTemplate.Type type,
+        String title,
+        String description,
+        MilestonePhaseTemplate milestonePhaseTemplate,
+        Event event
+    ) {
+        createMileStone(type, title, description, milestonePhaseTemplate, null, event);
+    }
+
+    private void createMileStone(
+        MilestoneActivityTemplate.Type type,
+        String title,
+        String description,
+        MilestonePhaseTemplate milestonePhaseTemplate,
+        String code
+    ) {
+        createMileStone(type, title, description, milestonePhaseTemplate, code, null);
+    }
+
     private void createMileStone(
         MilestoneActivityTemplate.Type type,
         String title,
-- 
2.39.5


From 142c8355895b053170b9c221aab0227fed235c66 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 24 Apr 2025 11:01:16 +0200
Subject: [PATCH 38/49] 87: Set Milestone Activity Template creation date
 earlier to activate them

---
 .../java/se/su/dsv/scipro/testdata/DataInitializer.java    | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index 541ed95fac..3c8d7a78d5 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -6,10 +6,7 @@ import jakarta.persistence.EntityManager;
 import jakarta.transaction.Transactional;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.time.Month;
-import java.time.ZonedDateTime;
+import java.time.*;
 import java.util.*;
 import java.util.function.Function;
 import se.su.dsv.scipro.checklist.ChecklistCategory;
@@ -2181,6 +2178,8 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
         Event event
     ) {
         MilestoneActivityTemplate milestoneActivityTemplate = new MilestoneActivityTemplate(type, title, description);
+        LocalDate ld = LocalDate.now().minusYears(1);
+        milestoneActivityTemplate.setDateCreated(Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()));
         milestoneActivityTemplate.addProjectType(bachelorClass);
         milestoneActivityTemplate.addProjectType(masterClass);
         milestoneActivityTemplate.addProjectType(magisterClass);
-- 
2.39.5


From 18689dca2430548bfde57c372028bffa0da0da7c Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Thu, 24 Apr 2025 16:10:09 +0200
Subject: [PATCH 39/49] 87: Improve test code, milestones work now correctly.

---
 .../se/su/dsv/scipro/testdata/DataInitializer.java     | 10 ++++++++--
 .../testdata/populators/SplitProjectPopulator.java     |  8 ++++++--
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index 3c8d7a78d5..318054f0c1 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -25,6 +25,7 @@ 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;
+import se.su.dsv.scipro.milestones.service.MilestonePhaseTemplateService;
 import se.su.dsv.scipro.notifications.dataobject.CustomEvent;
 import se.su.dsv.scipro.notifications.dataobject.GroupEvent;
 import se.su.dsv.scipro.notifications.dataobject.IdeaEvent;
@@ -70,6 +71,9 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
     @Inject
     private MilestoneActivityTemplateService milestoneActivityTemplateService;
 
+    @Inject
+    private MilestonePhaseTemplateService milestonePhaseTemplateService;
+
     @Inject
     private EventService eventService;
 
@@ -2190,8 +2194,10 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
     }
 
     private MilestonePhaseTemplate createMileStonePhase(String title, String description) {
-        MilestonePhaseTemplate milestonePhaseTemplate1 = new MilestonePhaseTemplate(title, description);
-        return save(milestonePhaseTemplate1);
+        MilestonePhaseTemplate milestonePhaseTemplate = new MilestonePhaseTemplate(title, description);
+        LocalDate ld = LocalDate.now().minusYears(1);
+        milestonePhaseTemplate.setDateCreated(Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()));
+        return milestonePhaseTemplateService.save(milestonePhaseTemplate);
     }
 
     private <T> T save(T entity) {
diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
index b170b217ca..7aa4cc629e 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
@@ -13,6 +13,7 @@ import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
 import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
+import se.su.dsv.scipro.reviewing.ReviewerDecisionService;
 import se.su.dsv.scipro.reviewing.RoughDraftApproval;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.system.User;
@@ -27,18 +28,21 @@ public class SplitProjectPopulator implements TestDataPopulator {
     private final ReviewerCapacityService reviewerCapacityService;
     private final RoughDraftApprovalService roughDraftApprovalService;
     private final ReviewerAssignmentService reviewerAssignmentService;
+    private final ReviewerDecisionService reviewerDecisionService;
 
     @Inject
     public SplitProjectPopulator(
         ProjectService projectService,
         ReviewerCapacityService reviewerCapacityService,
         RoughDraftApprovalService roughDraftApprovalService,
-        ReviewerAssignmentService reviewerAssignmentService
+        ReviewerAssignmentService reviewerAssignmentService,
+        ReviewerDecisionService reviewerDecisionService
     ) {
         this.projectService = projectService;
         this.reviewerCapacityService = reviewerCapacityService;
         this.roughDraftApprovalService = roughDraftApprovalService;
         this.reviewerAssignmentService = reviewerAssignmentService;
+        this.reviewerDecisionService = reviewerDecisionService;
     }
 
     @Override
@@ -68,7 +72,7 @@ public class SplitProjectPopulator implements TestDataPopulator {
         reviewerAssignmentService.assignReviewer(project, reviewer);
 
         Optional<RoughDraftApproval> optional = roughDraftApprovalService.findBy(project);
-        optional.ifPresent(rda -> rda.approve("Approve! Good Work!", Optional.empty()));
+        optional.ifPresent(rda -> reviewerDecisionService.approve(rda, "Approved! Good Work!", Optional.empty()));
     }
 
     private FileUpload dummyFile() {
-- 
2.39.5


From d0ee2fc98b55155000dfc25e3d6c1668e515e436 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 28 Apr 2025 10:43:37 +0200
Subject: [PATCH 40/49] 87: Improve model handling

---
 .../se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java   | 2 +-
 .../su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java  | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index ba322bf238..bdd6c0bffa 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -56,7 +56,7 @@ public class AdminSplitProjectPanel extends Panel {
         };
         add(splitProjectLink);
 
-        add(new Label("splitInfo", Model.of(getStatusMessage(ldModel.getObject()))));
+        add(new Label("splitInfo", ldModel.map(this::getStatusMessage)));
     }
 
     @Override
diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
index 36ec634056..3b1dacca7e 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminViewParentProjectPage.java
@@ -56,8 +56,7 @@ public class AdminViewParentProjectPage
                         AdminEditProjectPage.class,
                         pp
                     );
-                    link.setBody(Model.of(project.getTitle() + " - " + project.getAuthorNames()));
-
+                    link.setBody(item.getModel().map(p -> p.getTitle() + " - " + p.getAuthorNames()));
                     item.add(link);
                 }
             }
-- 
2.39.5


From ec5c7f8b1db054c8690789a153106e4e8a6d3c15 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 28 Apr 2025 12:05:44 +0200
Subject: [PATCH 41/49] 87: Use injected Clock in
 SplitOrRestartProjectServiceImpl instead of hardwired clock

---
 core/src/main/java/se/su/dsv/scipro/CoreConfig.java    |  2 ++
 .../split/SplitOrRestartProjectServiceImpl.java        | 10 ++++++++--
 .../se/su/dsv/scipro/reviewing/RoughDraftApproval.java |  4 ++--
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
index 134606ed7f..14f17f7ec9 100644
--- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
+++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java
@@ -820,12 +820,14 @@ public class CoreConfig {
         ProjectService projectService,
         FinalSeminarService finalSeminarService,
         RoughDraftApprovalService roughDraftApprovalService,
+        Clock clock,
         EventBus eventBus
     ) {
         return new SplitOrRestartProjectServiceImpl(
             projectService,
             finalSeminarService,
             roughDraftApprovalService,
+            clock,
             eventBus
         );
     }
diff --git a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
index 1947b51c9b..a720f76827 100644
--- a/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
+++ b/core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
@@ -3,6 +3,7 @@ package se.su.dsv.scipro.project.split;
 import com.google.common.eventbus.EventBus;
 import jakarta.inject.Inject;
 import jakarta.transaction.Transactional;
+import java.time.Clock;
 import java.time.Instant;
 import java.util.Date;
 import java.util.List;
@@ -22,6 +23,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
     private final ProjectService projectService;
     private final FinalSeminarService finalSeminarService;
     private final RoughDraftApprovalService roughDraftApprovalService;
+    private final Clock clock;
     private final EventBus eventBus;
 
     @Inject
@@ -29,11 +31,13 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
         ProjectService projectService,
         FinalSeminarService finalSeminarService,
         RoughDraftApprovalService roughDraftApprovalService,
+        Clock clock,
         EventBus eventBus
     ) {
         this.projectService = projectService;
         this.finalSeminarService = finalSeminarService;
         this.roughDraftApprovalService = roughDraftApprovalService;
+        this.clock = clock;
         this.eventBus = eventBus;
     }
 
@@ -73,7 +77,7 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
         SplittableStatusRecord result = getSplittableStatus(projectId);
         if (result.splittableStatus() != SplittableStatus.OK) {
             throw new IllegalStateException(
-                "Project must to be verified to be able to split " + "before this method can be called."
+                "Project must to be verified to be able to split before this method can be called."
             );
         }
 
@@ -107,7 +111,9 @@ public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectSe
             // Add cloned RoughDraftApproval if it's 'APPROVED'
             RoughDraftApproval rda = result.roughDraftApproval();
             if (rda != null && rda.isApproved()) {
-                RoughDraftApproval clonedRda = roughDraftApprovalService.saveCloned(rda.cloneToProject(childProject));
+                RoughDraftApproval clonedRda = roughDraftApprovalService.saveCloned(
+                    rda.cloneToProject(childProject, clock.instant())
+                );
 
                 // Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
                 eventBus.post(new RoughDraftApprovalApprovedClonedEvent(clonedRda));
diff --git a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
index e22ec0af44..d31d847cc5 100644
--- a/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
+++ b/core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApproval.java
@@ -26,13 +26,13 @@ public class RoughDraftApproval extends ReviewerApproval {
         return Step.ROUGH_DRAFT_APPROVAL;
     }
 
-    public RoughDraftApproval cloneToProject(final Project newProject) {
+    public RoughDraftApproval cloneToProject(final Project newProject, Instant instant) {
         RoughDraftApproval rda = new RoughDraftApproval();
         rda.project = newProject;
         this.decisions.forEach(decision -> rda.decisions.add(decision.cloneToReviewerApproval(rda)));
 
         rda.isCloned = true;
-        rda.cloneTimestamp = Instant.now();
+        rda.cloneTimestamp = instant;
 
         return rda;
     }
-- 
2.39.5


From 7ea749e4f3b0afe17ae32873f05e55cdbc2f82b1 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 28 Apr 2025 13:15:34 +0200
Subject: [PATCH 42/49] 87: Improve test precision with MutableFixedClock

---
 ...rRestartProjectServiceIntegrationTest.java | 42 ++++++++++++++-----
 1 file changed, 32 insertions(+), 10 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index cfaa8455af..c90ba5eaaf 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -9,8 +9,12 @@ import jakarta.inject.Inject;
 import java.io.InputStream;
 import java.time.LocalDate;
 import java.time.Year;
-import java.time.ZonedDateTime;
-import java.util.*;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -18,10 +22,19 @@ import se.su.dsv.scipro.file.FileUpload;
 import se.su.dsv.scipro.finalseminar.FinalSeminar;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectStatus;
-import se.su.dsv.scipro.reviewing.*;
+import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
+import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
+import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
+import se.su.dsv.scipro.reviewing.RoughDraftApproval;
+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.system.DegreeType;
+import se.su.dsv.scipro.system.Language;
+import se.su.dsv.scipro.system.ProjectType;
+import se.su.dsv.scipro.system.ResearchArea;
+import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.test.IntegrationTest;
+import se.su.dsv.scipro.test.MutableFixedClock;
 
 public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
 
@@ -37,6 +50,9 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     @Inject
     private FinalSeminarApprovalService finalSeminarApprovalService;
 
+    @Inject
+    MutableFixedClock clock;
+
     @Inject
     private ReviewerAssignmentService reviewerAssignmentService;
 
@@ -116,8 +132,9 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
             reviewer,
             Year.now()
         );
-        // Todo: improve this with higher precision by using MutabledFixedClock
-        assertTrue(remainingTargets.spring() == REMAINING_TARGET || remainingTargets.autumn() == REMAINING_TARGET);
+
+        assertEquals(REMAINING_TARGET, remainingTargets.spring());
+        assertEquals(TARGET, remainingTargets.autumn());
 
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
@@ -150,8 +167,9 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
             reviewer,
             Year.now()
         );
-        // todo: improve this by using MutableFixedClock
-        assertTrue(remainingTargets.spring() == REMAINING_TARGET || remainingTargets.autumn() == REMAINING_TARGET);
+
+        assertEquals(REMAINING_TARGET, remainingTargets.spring());
+        assertEquals(TARGET, remainingTargets.autumn());
 
         List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
 
@@ -178,7 +196,9 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         FinalSeminar finalSeminar = new FinalSeminar();
         finalSeminar.setProject(parentProject);
-        finalSeminar.setStartDate(Date.from(ZonedDateTime.now().plusDays(10).toInstant()));
+
+        LocalDate plus30Days = LocalDate.now(clock).plusDays(30);
+        finalSeminar.setStartDate(Date.from(plus30Days.atStartOfDay(ZoneId.systemDefault()).toInstant()));
         finalSeminar.setRoom("room");
         finalSeminar.setPresentationLanguage(Language.ENGLISH);
         parentProject.setLanguage(Language.ENGLISH);
@@ -199,7 +219,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         project.setProjectType(projectType);
         project.setProjectStatus(active);
         project.setHeadSupervisor(supervisor);
-        project.setStartDate(LocalDate.now());
+        project.setStartDate(LocalDate.now().withMonth(1).withDayOfMonth(1));
 
         author1 = createUser("Adam", "Student", "adam.student@example.com");
         project.addProjectParticipant(author1);
@@ -233,6 +253,8 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         parentProject.addProjectParticipant(author2);
         save(parentProject);
 
+        clock.setDate(LocalDate.now().withMonth(3).withDayOfMonth(1));
+
         roughDraftApprovalService.requestApproval(parentProject, dummyFile(), "comment");
 
         reviewer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
-- 
2.39.5


From 2b5da3fee1ad7b9ee513b52208f6aa28f432fd16 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 28 Apr 2025 13:19:13 +0200
Subject: [PATCH 43/49] 87: Update migration db-script to higher version

---
 ...ent_phase2_review.sql => V8__project_parent_phase2_review.sql} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename core/src/main/resources/db/migration/{V7__project_parent_phase2_review.sql => V8__project_parent_phase2_review.sql} (100%)

diff --git a/core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql b/core/src/main/resources/db/migration/V8__project_parent_phase2_review.sql
similarity index 100%
rename from core/src/main/resources/db/migration/V7__project_parent_phase2_review.sql
rename to core/src/main/resources/db/migration/V8__project_parent_phase2_review.sql
-- 
2.39.5


From 0f1d58686347f530803bda71a7a8307cb98557cf Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 28 Apr 2025 15:13:23 +0200
Subject: [PATCH 44/49] 87: Add info about if RoughDraftApproval is cloned

---
 .../se/su/dsv/scipro/reviewer/ApprovalReviewerPanel.java | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/reviewer/ApprovalReviewerPanel.java b/view/src/main/java/se/su/dsv/scipro/reviewer/ApprovalReviewerPanel.java
index 37e981c569..05ae98907e 100644
--- a/view/src/main/java/se/su/dsv/scipro/reviewer/ApprovalReviewerPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/reviewer/ApprovalReviewerPanel.java
@@ -83,10 +83,11 @@ public abstract class ApprovalReviewerPanel extends Panel {
                         LinkWrapper.apply(componentId, id -> {
                             AbstractLink link = newDecisionLink(id, rowModel.map(Decision::getReviewerApproval));
                             link.setBody(
-                                rowModel
-                                    .map(Decision::getReviewerApproval)
-                                    .map(ReviewerApproval::getProject)
-                                    .map(Project::getTitle)
+                                rowModel.map(dec -> {
+                                    ReviewerApproval ra = dec.getReviewerApproval();
+                                    Project p = ra.getProject();
+                                    return ra.getCloned() ? p.getTitle() + " (Cloned)" : p.getTitle();
+                                })
                             );
                             return link;
                         })
-- 
2.39.5


From 8d7a0f6eb1670b9bb6dd9ab048e531f964277e3b Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 5 May 2025 14:44:23 +0200
Subject: [PATCH 45/49] 87: Improve test by using fixed year (2025)

---
 .../SplitOrRestartProjectServiceIntegrationTest.java   | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
index c90ba5eaaf..f8551c5596 100644
--- a/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
+++ b/core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
@@ -41,6 +41,8 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
     private static int TARGET = 3;
     private static int REMAINING_TARGET = 2;
 
+    private static int FIXED_YEAR = 2025;
+
     @Inject
     private SplitOrRestartProjectService splitOrRestartProjectService;
 
@@ -130,7 +132,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(
             reviewer,
-            Year.now()
+            Year.of(FIXED_YEAR)
         );
 
         assertEquals(REMAINING_TARGET, remainingTargets.spring());
@@ -165,7 +167,7 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
 
         ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(
             reviewer,
-            Year.now()
+            Year.of(FIXED_YEAR)
         );
 
         assertEquals(REMAINING_TARGET, remainingTargets.spring());
@@ -253,14 +255,14 @@ public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest
         parentProject.addProjectParticipant(author2);
         save(parentProject);
 
-        clock.setDate(LocalDate.now().withMonth(3).withDayOfMonth(1));
+        clock.setDate(LocalDate.now().withYear(FIXED_YEAR).withMonth(3).withDayOfMonth(1));
 
         roughDraftApprovalService.requestApproval(parentProject, dummyFile(), "comment");
 
         reviewer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
         reviewerCapacityService.assignTarget(
             reviewer,
-            new ReviewerCapacityService.Target(Year.now(), TARGET, TARGET, "")
+            new ReviewerCapacityService.Target(Year.of(FIXED_YEAR), TARGET, TARGET, "")
         );
         reviewerAssignmentService.assignReviewer(parentProject, reviewer);
     }
-- 
2.39.5


From 821b53476320e04d74b68c942223f78ae85559a1 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 5 May 2025 15:49:17 +0200
Subject: [PATCH 46/49] 87: Remove unnecessary authorization check

---
 .../su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
index bdd6c0bffa..83a50b64c2 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.java
@@ -14,8 +14,6 @@ import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
-import se.su.dsv.scipro.security.auth.roles.Roles;
-import se.su.dsv.scipro.session.SciProSession;
 import se.su.dsv.scipro.util.PageParameterKeys;
 
 public class AdminSplitProjectPanel extends Panel {
@@ -59,12 +57,6 @@ public class AdminSplitProjectPanel extends Panel {
         add(new Label("splitInfo", ldModel.map(this::getStatusMessage)));
     }
 
-    @Override
-    protected void onConfigure() {
-        super.onConfigure();
-        setVisibilityAllowed(SciProSession.get().authorizedForRole(Roles.ADMIN));
-    }
-
     private String getStatusMessage(SplittableStatus status) {
         return switch (status) {
             case OK -> "";
-- 
2.39.5


From ca6f580006154f354a429bdceca000d8ad9c6370 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Mon, 5 May 2025 15:57:43 +0200
Subject: [PATCH 47/49] 87: Use UserLabel component

---
 .../se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
index 4145fdd056..8a02d457aa 100644
--- a/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
+++ b/view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPage.java
@@ -12,6 +12,7 @@ import org.apache.wicket.model.IModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
 import se.su.dsv.scipro.data.DetachableServiceModel;
+import se.su.dsv.scipro.profile.UserLabel;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
@@ -50,7 +51,7 @@ public class AdminSplitProjectPage extends AbstractAdminProjectPage implements M
                 new ListView<>("authorList", model.map(Project::getProjectParticipants).map(ArrayList::new)) {
                     @Override
                     protected void populateItem(ListItem<User> item) {
-                        item.add(new Label("author", item.getModel().map(User::getFullName)));
+                        item.add(new UserLabel("author", item.getModel()));
                     }
                 }
             );
-- 
2.39.5


From 8f4d2620be94d15382d0573c62c905e04d4ff516 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Wed, 7 May 2025 13:31:16 +0200
Subject: [PATCH 48/49] 87: Improve testdata, it's now possible to follow up
 individual milestones as peer review

---
 .../dsv/scipro/testdata/DataInitializer.java  |  8 ++-
 .../populators/SplitProjectPopulator.java     | 65 +++++++++++++++++--
 2 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index 318054f0c1..8a4fca0ffe 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -2053,7 +2053,8 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
             "Peer review 1 (Auto)",
             "This is a recommendation of when to perform peer review 1.",
             milestonePhaseTemplate2,
-            MilestoneActivityTemplate.PEER_REVIEW_ONE
+            MilestoneActivityTemplate.PEER_REVIEW_ONE,
+            events.stream().filter(event -> event.getName().equals("FirstPeerReviewCompleted")).findFirst().get()
         );
 
         createMileStone(
@@ -2065,10 +2066,11 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
 
         createMileStone(
             MilestoneActivityTemplate.Type.STUDENT,
-            "Peer review 2",
+            "Peer review 2 (Auto)",
             "This is a recommendation of when to perform peer review 2.",
             milestonePhaseTemplate3,
-            MilestoneActivityTemplate.PEER_REVIEW_TWO
+            MilestoneActivityTemplate.PEER_REVIEW_TWO,
+            events.stream().filter(event -> event.getName().equals("SecondPeerReviewCompleted")).findFirst().get()
         );
 
         createMileStone(
diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
index 7aa4cc629e..33f0fd7e2a 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
@@ -9,6 +9,8 @@ import java.util.Set;
 import java.util.function.Function;
 import org.springframework.stereotype.Service;
 import se.su.dsv.scipro.file.FileUpload;
+import se.su.dsv.scipro.peer.PeerPortal;
+import se.su.dsv.scipro.peer.PeerRequest;
 import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.project.ProjectService;
 import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
@@ -16,6 +18,7 @@ import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
 import se.su.dsv.scipro.reviewing.ReviewerDecisionService;
 import se.su.dsv.scipro.reviewing.RoughDraftApproval;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
+import se.su.dsv.scipro.system.Language;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.testdata.BaseData;
 import se.su.dsv.scipro.testdata.Factory;
@@ -29,6 +32,9 @@ public class SplitProjectPopulator implements TestDataPopulator {
     private final RoughDraftApprovalService roughDraftApprovalService;
     private final ReviewerAssignmentService reviewerAssignmentService;
     private final ReviewerDecisionService reviewerDecisionService;
+    private final PeerPortal peerPortal;
+
+    private User otherSupervisorAndReviewer;
 
     @Inject
     public SplitProjectPopulator(
@@ -36,45 +42,92 @@ public class SplitProjectPopulator implements TestDataPopulator {
         ReviewerCapacityService reviewerCapacityService,
         RoughDraftApprovalService roughDraftApprovalService,
         ReviewerAssignmentService reviewerAssignmentService,
-        ReviewerDecisionService reviewerDecisionService
+        ReviewerDecisionService reviewerDecisionService,
+        PeerPortal peerPortal
     ) {
         this.projectService = projectService;
         this.reviewerCapacityService = reviewerCapacityService;
         this.roughDraftApprovalService = roughDraftApprovalService;
         this.reviewerAssignmentService = reviewerAssignmentService;
         this.reviewerDecisionService = reviewerDecisionService;
+        this.peerPortal = peerPortal;
     }
 
     @Override
     public void populate(BaseData baseData, Factory factory) {
+        setUpOtherProjects(baseData, factory);
+
         User supervisor = factory.createSupervisor("Emil");
 
         User author1 = factory.createAuthor("Scott");
         User author2 = factory.createAuthor("Scarlett");
 
-        User reviewer = factory.createReviewer("Elias");
-
-        reviewerCapacityService.assignTarget(reviewer, new ReviewerCapacityService.Target(Year.now(), 3, 3, ""));
+        reviewerCapacityService.assignTarget(
+            otherSupervisorAndReviewer,
+            new ReviewerCapacityService.Target(Year.now(), 3, 3, "")
+        );
 
         Project project = Project.builder()
             .title("Operating System Boot Time Security")
             .projectType(baseData.bachelor())
             .startDate(LocalDate.now())
             .headSupervisor(supervisor)
-            .reviewers(Set.of(reviewer))
+            .reviewers(Set.of(otherSupervisorAndReviewer))
             .projectParticipants(Set.of(author1, author2))
             .build();
 
         projectService.save(project);
 
+        setUpPeerRequest(project, author2, "Please checkout the OS Boot Time Security. Draft 1");
+        setUpPeerRequest(project, author2, "Please checkout the OS Boot Time Security. Draft 2");
+
         roughDraftApprovalService.requestApproval(project, dummyFile(), "Request review.");
 
-        reviewerAssignmentService.assignReviewer(project, reviewer);
+        reviewerAssignmentService.assignReviewer(project, otherSupervisorAndReviewer);
 
         Optional<RoughDraftApproval> optional = roughDraftApprovalService.findBy(project);
         optional.ifPresent(rda -> reviewerDecisionService.approve(rda, "Approved! Good Work!", Optional.empty()));
     }
 
+    private void setUpOtherProjects(BaseData baseData, Factory factory) {
+        otherSupervisorAndReviewer = factory.createReviewer("Elias");
+
+        User author1 = factory.createAuthor("Sebastian");
+
+        Project otherProject1 = Project.builder()
+            .title("The CISC Architecture")
+            .projectType(baseData.bachelor())
+            .startDate(LocalDate.now())
+            .headSupervisor(otherSupervisorAndReviewer)
+            .projectParticipants(Set.of(author1))
+            .build();
+
+        projectService.save(otherProject1);
+
+        setUpPeerRequest(otherProject1, author1, "Please checkout the CISC Architecture.");
+
+        User author2 = factory.createAuthor("Sven");
+        Project otherProject2 = Project.builder()
+            .title("The RISC Architecture")
+            .projectType(baseData.bachelor())
+            .startDate(LocalDate.now())
+            .headSupervisor(otherSupervisorAndReviewer)
+            .projectParticipants(Set.of(author2))
+            .build();
+        projectService.save(otherProject2);
+
+        setUpPeerRequest(otherProject2, author2, "Please checkout the RISC Architecture.");
+    }
+
+    private void setUpPeerRequest(Project project, User requester, String comment) {
+        PeerRequest peerRequest = new PeerRequest();
+        peerRequest.setProject(project);
+        peerRequest.setRequester(requester);
+        peerRequest.setComment(comment);
+        peerRequest.setLanguage(Language.ENGLISH);
+        peerPortal.storePeerRequest(dummyFile(), peerRequest);
+    }
+
     private FileUpload dummyFile() {
         return new FileUpload() {
             @Override
-- 
2.39.5


From f87b0d70e83f5ffe915b96badb17abeebe576f94 Mon Sep 17 00:00:00 2001
From: Tom Zhao <tom.zhao@dsv.su.se>
Date: Fri, 9 May 2025 14:00:37 +0200
Subject: [PATCH 49/49] 87: Use single import

---
 .../dsv/scipro/testdata/DataInitializer.java  | 24 +++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
index 99139fde92..e61f794393 100644
--- a/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
+++ b/test-data/src/main/java/se/su/dsv/scipro/testdata/DataInitializer.java
@@ -9,7 +9,14 @@ import java.time.LocalTime;
 import java.time.Month;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import se.su.dsv.scipro.checklist.ChecklistCategory;
 import se.su.dsv.scipro.data.dataobjects.Member;
 import se.su.dsv.scipro.file.FileReference;
@@ -46,7 +53,20 @@ import se.su.dsv.scipro.report.GradingReportTemplate;
 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.system.Event;
+import se.su.dsv.scipro.system.EventService;
+import se.su.dsv.scipro.system.Language;
+import se.su.dsv.scipro.system.Lifecycle;
+import se.su.dsv.scipro.system.Password;
+import se.su.dsv.scipro.system.PasswordHandler;
+import se.su.dsv.scipro.system.PasswordService;
+import se.su.dsv.scipro.system.Program;
+import se.su.dsv.scipro.system.ProjectType;
+import se.su.dsv.scipro.system.ResearchArea;
+import se.su.dsv.scipro.system.Unit;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.system.UserService;
+import se.su.dsv.scipro.system.Username;
 import se.su.dsv.scipro.util.Pair;
 
 public class DataInitializer implements Lifecycle, BaseData, Factory {
-- 
2.39.5