diff --git a/CHANGELOG.md b/CHANGELOG.md
index 86c7b039685dfbc27d474f5439013515e02eff15..f680e74be9e41f8e46bcb09c71f645e5dd856ed1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+- Show salary scale on search people page @dsavvidi
### Changed
- Queue feedback rating link replaced with actual rating. @dsavvidi
@@ -20,10 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Job offers are now created as draft (hidden) by default and can be published later @dsavvidi
- Ability for coordinators to search for students in the system. @dsavvidi
- Alert if rejecting people with empty/not edited rejection messsage. @dsavvidi
-- Exporting contract requests now has batches. @rwbackx
+- Add global default rejection text. @dsavvidi
+
+### Changed
### Fixed
+- Exporting contract requests now has batches. @rwbackx
- Toggling state now saved on Job Offers Page for Students @dsavvidi
+- Retracting a contract request gave a constraint violation exception @dsavvidi
## [2.1.6]
diff --git a/src/main/java/nl/tudelft/tam/controller/PersonController.java b/src/main/java/nl/tudelft/tam/controller/PersonController.java
index da25e1a6ddf4faf18c02f65738293c10105ef5e2..f8ccdc0387c12dd87fcbc32c465d866ba789d549 100644
--- a/src/main/java/nl/tudelft/tam/controller/PersonController.java
+++ b/src/main/java/nl/tudelft/tam/controller/PersonController.java
@@ -153,7 +153,7 @@ public class PersonController {
@GetMapping("search")
@PreAuthorize("@authorisationService.isCoordinatorOfAny()")
public String searchForPeople(@RequestParam(required = false) String q, Model model) {
- List<PersonSummaryDTO> people = personService.searchForPeople(q);
+ Map<PersonSummaryDTO, Profile> people = personService.findPersonProfilePairs(q);
model.addAttribute("people", people);
return "person/search";
}
diff --git a/src/main/java/nl/tudelft/tam/model/Declaration.java b/src/main/java/nl/tudelft/tam/model/Declaration.java
index da0d9dbe197ee352bcadfaea6331cba99f1a96fd..b6a1bbc5f57a507011513fee787a890e4796a9ed 100644
--- a/src/main/java/nl/tudelft/tam/model/Declaration.java
+++ b/src/main/java/nl/tudelft/tam/model/Declaration.java
@@ -63,7 +63,7 @@ public class Declaration {
@Builder.Default
@ToString.Exclude
@EqualsAndHashCode.Exclude
- @OneToMany(mappedBy = "declaration")
+ @OneToMany(mappedBy = "declaration", cascade = CascadeType.REMOVE)
private List<DeclarationChangeEvent> events = new ArrayList<>();
@NotNull
diff --git a/src/main/java/nl/tudelft/tam/service/ApplicationService.java b/src/main/java/nl/tudelft/tam/service/ApplicationService.java
index a865762576f02a4cc4717ebeebba95f2541736cf..268c17f7b976d07a961d8ff29c122862891748d6 100644
--- a/src/main/java/nl/tudelft/tam/service/ApplicationService.java
+++ b/src/main/java/nl/tudelft/tam/service/ApplicationService.java
@@ -192,12 +192,15 @@ public class ApplicationService {
roleService.removeRole(personId, offerId);
- // Send notification of offer to student
+ // Send notification of rejection to student
EditionDetailsDTO edition = editionService.getEditionById(app.getJobOffer().getEditionId());
+ CourseDetailsDTO course = courseService.getCourseById(edition.getCourse().getId());
+ String courseStaff = String.join(", ",
+ course.getManagers().stream().map(PersonSummaryDTO::getDisplayName).toList());
String rejectionMessage = app.getJobOffer().getRejectMessage();
sendApplicationRejectedNotification(personId, app.getJobOffer().getName(),
edition.getCourse().getName(), edition.getCourse().getCode(), edition.getName(),
- rejectionMessage);
+ rejectionMessage, courseStaff);
}
/**
@@ -210,6 +213,10 @@ public class ApplicationService {
public void rejectAllOpenInJobOffer(Long id, Long handler) {
JobOffer jobOffer = jobOfferService.findByIdOrThrow(id);
EditionDetailsDTO edition = editionService.getEditionById(jobOffer.getEditionId());
+ CourseDetailsDTO course = courseService.getCourseById(edition.getCourse().getId());
+ String courseStaff = String.join(", ",
+ course.getManagers().stream().map(PersonSummaryDTO::getDisplayName).toList());
+ String rejectionMessage = jobOffer.getRejectMessage();
jobOffer.getApplications().forEach(a -> {
if (a.getStatus() == Status.SUBMITTED) {
@@ -218,10 +225,9 @@ public class ApplicationService {
roleService.removeRole(a.getId().getPersonId(), a.getId().getJobOfferId());
// Send notification to the student
- String rejectionMessage = jobOffer.getRejectMessage();
sendApplicationRejectedNotification(a.getId().getPersonId(), jobOffer.getName(),
edition.getCourse().getName(), edition.getCourse().getCode(), edition.getName(),
- rejectionMessage);
+ rejectionMessage, courseStaff);
}
});
}
@@ -958,20 +964,21 @@ public class ApplicationService {
* @param course The course that the application was for
* @param courseCode The course code of the course that the application was for
* @param year The name of the edition (eg. Q1 22/23)
+ * @param courseStaff The names of the course staff (managers)
* @param rejectionMessage The rejection message from the course staff (can be null)
*/
public void sendApplicationRejectedNotification(Long personId, String position, String course,
String courseCode, String year,
- String rejectionMessage) {
+ String rejectionMessage, String courseStaff) {
Profile profile = profileService.findByIdOrThrow(personId);
PersonDetailsDTO person = personService.getPersonById(personId);
Map<String, Object> contextVars = new HashMap<>(
Map.of("name", person.getDisplayName(), "position", position, "course", course,
- "courseCode", courseCode, "year", year));
+ "courseCode", courseCode, "year", year, "courseStaff", courseStaff));
EmailCreateDTO email = EmailCreateDTO.builder()
- .subject("Application (" + courseCode + ") Not Selected")
+ .subject("Application (" + courseCode + ") Was Not Selected")
.template("html/application_rejected")
.contextVariables(contextVars)
.build();
diff --git a/src/main/java/nl/tudelft/tam/service/CourseService.java b/src/main/java/nl/tudelft/tam/service/CourseService.java
index 0446a22fec79ace5ddb16c90e785a40194b44162..8e4acc15c0fd4bfc1df09d1f9d4adf00a01024c4 100644
--- a/src/main/java/nl/tudelft/tam/service/CourseService.java
+++ b/src/main/java/nl/tudelft/tam/service/CourseService.java
@@ -73,6 +73,16 @@ public class CourseService {
return courseApi.getAllCoursesById(ids).collectList().block();
}
+ /**
+ * Gets a course by id.
+ *
+ * @param id the course id
+ * @return The course details
+ */
+ public CourseDetailsDTO getCourseById(Long id) {
+ return courseApi.getCourseById(id).block();
+ }
+
/**
* Gets all courses.
*
diff --git a/src/main/resources/email/html/application_rejected.html b/src/main/resources/email/html/application_rejected.html
index 0fdece97bad851464b659f66965e41bfcf5890c0..1f2482bcde87dec3d2b78db944a91c102051d96c 100644
--- a/src/main/resources/email/html/application_rejected.html
+++ b/src/main/resources/email/html/application_rejected.html
@@ -42,14 +42,20 @@
<div style="padding: 0.5em 0">
<!-- <h2 th:text="#{email.application.rejected.title}"></h2>-->
<p th:text="#{email.greeting(${name})}"></p>
+ <br />
<p
- th:text="#{email.application.rejected.message(${position}, ${course}, ${year})}"></p>
- <p
- th:if="${rejectionMessage != null}"
- th:text="#{email.application.rejected.rejectionMessage}"></p>
- <p th:if="${rejectionMessage != null}" th:text="${rejectionMessage}"></p>
- <p th:text="#{email.closing}"></p>
- <p th:text="#{email.closing.person}"></p>
+ style="white-space: pre-line"
+ th:if="${rejectionMessage == null}"
+ th:text="#{email.application.rejected.message(${position}, ${course}, ${year}, ${courseStaff})}"></p>
+
+ <div th:if="${rejectionMessage != null}">
+ <p th:text="#{email.application.rejected.rejectionMessage}"></p>
+ <br />
+ <p th:text="${rejectionMessage}"></p>
+ <br />
+ <p th:text="#{email.closing}"></p>
+ <p th:text="#{email.closing.person}"></p>
+ </div>
</div>
<div>
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 5fdf0a499b8cba629ca2ea7d3626d656726894c5..9343b9d3db22e0f195ea00c0dfa70a7a8f07a057 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -424,24 +424,34 @@ coordinator.defaults.description = Job Information
coordinator.defaults.hiringMessage = Hiring Message
coordinator.defaults.rejectMessage = Rejection Message
-email.greeting = Hi {0},
+email.greeting = Dear {0},
email.closing = Best Regards,
email.closing.person = TAM
-email.notificationPreferences = To change your email notification preferences, please visit
+email.notificationPreferences = To change your email notification preferences, please visit
email.notificationPreferences.here = here.
email.application.submitted.title = Application Successfully Submitted!
email.application.submitted.message = Thank you for applying for the {0} position in the {1} ({2}) course! We have received your application in good order.
email.application.offered.title = Action Required: Offered {0} Position!
-email.application.offered.message = You have been offered the {0} position in the {1} ({2}) course! Please accept or reject the offer as soon as possible.
-email.application.offered.action = You can accept or reject the offer
+email.application.offered.message = You have been offered the {0} position in the {1} ({2}) course! Please accept or reject the offer as soon as possible.
+email.application.offered.action = You can accept or reject the offer
email.application.offered.hiringMessage = The following message was provided by the course staff:
-email.application.rejected.title = Application Not Selected
-email.application.rejected.message = Sadly, your application submitted for the {0} position in the {1} ({2}) course was not selected.
+email.application.rejected.title = Application Was Not Selected
+email.application.rejected.message = Thanks for applying as a {0} for {1} ({2}). \
+ We have carefully considered all applications and regret to inform you that we did not select you. \
+ \n \n We understand that this can be a bit of a disappointment, so we would like to give a bit of context. \
+ We of course looked at your grade for this course, but would like to stress that we did not just sort on grade to determine who to hire. \
+ For instance, we also looked at which other activities you did in this course. \
+ And there is another very important factor: team composition. \
+ We want a certain amount of more "veteran" TAs and a number of new TAs. \
+ \n \n But let us stress that you should not get discouraged by this! \
+ Other courses, other quarters: a no now does not mean we will not select you for another course, \
+ and does not mean you are also rejected for other applications you did. \
+ \n \n Thank you for your interest, and we hope to see you again in the next application round! \
+ \n \n Best regards,\n {3}.
email.application.rejected.rejectionMessage = The following message was provided by the course staff:
email.declaration.rejected.title = Contract Request Rejected
-email.declaration.rejected.message = Sadly, your contract request of {0} hours for {1} in the {2} ({3}) course was rejected. For more information please contact the course staff.
+email.declaration.rejected.message = Sadly, your contract request of {0} hours for {1} in the {2} ({3}) course was rejected. For more information, please contact the course staff.
email.declaration.approved.title = Contract Request Approved
-email.declaration.approved.message = Your contract request of {0} hours for {1} in the {2} ({3}) course was approved! You will be able to declare these hours in FlexDelft soon. For more information please contact the course staff.
-
-search.people = Search people
+email.declaration.approved.message = Your contract request of {0} hours for {1} in the {2} ({3}) course was approved! You will be able to declare these hours in FlexDelft soon. For more information, please contact the course staff.
+search.people = Search people
\ No newline at end of file
diff --git a/src/main/resources/templates/person/search.html b/src/main/resources/templates/person/search.html
index ee96947e910d05923a4c752f411fcab89e04777b..9a363b1cc4851c3f9a072ecbffb8b26669421790 100644
--- a/src/main/resources/templates/person/search.html
+++ b/src/main/resources/templates/person/search.html
@@ -52,6 +52,7 @@
<th th:text="#{person.number}"></th>
<th th:text="#{person.username}"></th>
<th th:text="#{person.email}"></th>
+ <th th:text="#{payscale}"></th>
<th th:text="#{jobOffer.queueFeedback}"></th>
<th></th>
</tr>
@@ -61,13 +62,14 @@
<a
class="link"
target="_blank"
- th:href="@{|/profile/${person.id}|}"
- th:text="${person.displayName}"></a>
+ th:href="@{|/profile/${person.getKey().id}|}"
+ th:text="${person.getKey().displayName}"></a>
</div>
</td>
- <td th:text="${person.number}"></td>
- <td th:text="${person.username}"></td>
- <td th:text="${person.email}"></td>
+ <td th:text="${person.getKey().number}"></td>
+ <td th:text="${person.getKey().username}"></td>
+ <td th:text="${person.getKey().email}"></td>
+ <td th:text="${person.getValue().payScale}"></td>
<td>
<a
class="link"
diff --git a/src/test/java/nl/tudelft/tam/controller/PersonControllerTest.java b/src/test/java/nl/tudelft/tam/controller/PersonControllerTest.java
index edf3bbe44d2b178d6f7c2656449a81ffa93d3660..69e9902a7f1a49d2dbc0cefc470da89f8ba3d8fe 100644
--- a/src/test/java/nl/tudelft/tam/controller/PersonControllerTest.java
+++ b/src/test/java/nl/tudelft/tam/controller/PersonControllerTest.java
@@ -24,6 +24,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,6 +39,7 @@ import org.springframework.transaction.annotation.Transactional;
import application.test.TestTAMApplication;
import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
+import nl.tudelft.tam.model.Profile;
import nl.tudelft.tam.security.AuthorisationService;
import nl.tudelft.tam.service.ApplicationService;
import nl.tudelft.tam.service.PersonService;
@@ -115,19 +117,21 @@ public class PersonControllerTest {
@Test
@WithUserDetails("admin")
void searchForPeopleTest() throws Exception {
+ Map<PersonSummaryDTO, Profile> entrymap = Map.ofEntries(Map.entry(person1, new Profile()),
+ Map.entry(person2, new Profile()));
when(authService.isCoordinatorOfAny()).thenReturn(true);
- when(personService.searchForPeople(anyString())).thenReturn(List.of(person1));
+ when(personService.findPersonProfilePairs(anyString())).thenReturn(entrymap);
String user = "test";
mvc.perform(get("/person/search")
- .param("q", user))
+ .param("q", user))
.andExpect(status().isOk()) // Expect HTTP 200 status
- .andExpect(model().attribute("people", List.of(person1))) // Expect the "people" attribute in the model
+ .andExpect(model().attribute("people", entrymap)) // Expect the "people" attribute in the model
.andExpect(view().name("person/search"));
verify(authService).isCoordinatorOfAny();
- verify(personService).searchForPeople(user);
+ verify(personService).findPersonProfilePairs(user);
}
@Test
@@ -137,12 +141,12 @@ public class PersonControllerTest {
mvc.perform(MockMvcRequestBuilders.get("/person/search"))
.andExpect(status().isOk())
- .andExpect(model().attribute("people", List.of()))
+ .andExpect(model().attribute("people", Map.of()))
.andExpect(view().name("person/search"));
verify(authService).isCoordinatorOfAny();
- verify(personService, times(0)).searchForPeople(anyString());
- verify(personService).searchForPeople(null);
+ verify(personService, times(0)).findPersonProfilePairs(anyString());
+ verify(personService).findPersonProfilePairs(null);
}
@Test
@@ -154,7 +158,7 @@ public class PersonControllerTest {
.andExpect(status().isForbidden());
verify(authService).isCoordinatorOfAny();
- verify(personService, times(0)).searchForPeople(anyString());
+ verify(personService, times(0)).findPersonProfilePairs(anyString());
}
@Test
diff --git a/src/test/java/nl/tudelft/tam/service/ApplicationServiceMockTest.java b/src/test/java/nl/tudelft/tam/service/ApplicationServiceMockTest.java
index 68b0172232dfc2510edbbb156bd49ac86567559f..feb76b7808d5702dfc6066bc236910b024ed4c27 100644
--- a/src/test/java/nl/tudelft/tam/service/ApplicationServiceMockTest.java
+++ b/src/test/java/nl/tudelft/tam/service/ApplicationServiceMockTest.java
@@ -154,13 +154,15 @@ class ApplicationServiceMockTest {
.status(Status.ACCEPTED).build();
app2a = Application.builder().id(new Application.AppId(PERSON_ID_1, JOFFER_ID_2)).jobOffer(offer2)
.status(Status.REJECTED_BY_STUDENT).build();
+ offer1.setApplications(List.of(app1a, app1b));
personSummary1 = new PersonSummaryDTO().id(PERSON_ID_1).username("Person1").displayName("Person 1")
.email("Person1@email.com").number(1);
personSummary2 = new PersonSummaryDTO().id(PERSON_ID_2).username("Person2").displayName("Person 2")
.email("Person2@email.com").number(2);
- courseDetails = new CourseDetailsDTO().id(COURSE_ID).name("Course").code("Test");
+ courseDetails = new CourseDetailsDTO().id(COURSE_ID).name("Course").code("Test")
+ .managers(List.of(personSummary1, personSummary2));
courseSummary = new CourseSummaryDTO().id(COURSE_ID).name("Course").code("Test");
programSummary = new ProgramSummaryDTO().id(PROGRAM_ID).name("Program");
cohortDetails = new CohortDetailsDTO().id(COHORT_ID).name("Cohort").program(programSummary);
@@ -484,6 +486,7 @@ class ApplicationServiceMockTest {
.thenReturn(Profile.builder().id(ADMIN_ID).build());
doNothing().when(roleService).removeRole(anyLong(), anyLong());
+ when(courseService.getCourseById(anyLong())).thenReturn(courseDetails);
when(editionService.getEditionById(anyLong())).thenReturn(editionDetails);
when(personService.getPersonById(eq(PERSON_ID_1)))
.thenReturn(new PersonDetailsDTO().id(PERSON_ID_1).email("test@test.com")
@@ -531,6 +534,7 @@ class ApplicationServiceMockTest {
.id(new Application.AppId(PERSON_ID_1, JOFFER_ID_1)).build()));
doNothing().when(roleService).removeRole(anyLong(), anyLong());
+ when(courseService.getCourseById(anyLong())).thenReturn(courseDetails);
when(editionService.getEditionById(anyLong())).thenReturn(editionDetails);
when(personService.getPersonById(eq(PERSON_ID_1)))
.thenReturn(new PersonDetailsDTO().id(PERSON_ID_1).email("test@test.com")
diff --git a/src/test/java/nl/tudelft/tam/service/CourseServiceTest.java b/src/test/java/nl/tudelft/tam/service/CourseServiceTest.java
index 38d21f7dee803022994d45f667cf7ca46e4674ac..9d61ebbd2265ba4d77a0354ccfd70bb718254d94 100644
--- a/src/test/java/nl/tudelft/tam/service/CourseServiceTest.java
+++ b/src/test/java/nl/tudelft/tam/service/CourseServiceTest.java
@@ -18,10 +18,10 @@
package nl.tudelft.tam.service;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
import java.util.List;
@@ -38,6 +38,7 @@ import nl.tudelft.labracore.api.dto.CourseSummaryDTO;
import nl.tudelft.labracore.api.dto.PersonIdDTO;
import nl.tudelft.tam.cache.CourseCacheManager;
import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
@SpringBootTest(classes = TestTAMApplication.class)
@Transactional
@@ -67,6 +68,14 @@ public class CourseServiceTest {
verify(courseControllerApi).getAllCoursesByManager(PERSON_ID);
}
+ @Test
+ public void getCourseByIdTest() {
+ CourseDetailsDTO course = new CourseDetailsDTO().id(1L);
+ when(courseControllerApi.getCourseById(anyLong())).thenReturn(Mono.just(course));
+ assertEquals(courseService.getCourseById(1L), course);
+ verify(courseControllerApi, times(1)).getCourseById(1L);
+ }
+
@Test
void getOrThrowTest() {
CourseDetailsDTO course = new CourseDetailsDTO().id(1L);