Skip to content

Commit 9c921f4

Browse files
MoSchmidtaz108
andauthored
Development: Initial server and client logic to accept/reject application (#236)
* initial server structure to review applications * adapt client to initial review of applications * add default Accept Message * generate code for new DTOs * add default reject message * add researchGroupWebsite to ProfessorDTO * adapt Review Dialog to translation and Accept/Reject DTOs * adapt server to reject Application and add documentation * add client teste cases and comments * fix i18n naming * fix client and server style errors * fix default message white spaces and padding of review dialog * add cancel button to review dialog and remove X-button * re-add padding of dialog * add CurrentUSer functionality * Revert "add CurrentUSer functionality" This reverts commit 41af431. * create breakpoints.ts as shared constants * review dialog fix buttons --------- Co-authored-by: Aniruddh Zaveri <92953467+az108@users.noreply.github.com>
1 parent c2a9b73 commit 9c921f4

34 files changed

Lines changed: 1074 additions & 75 deletions

openapi/openapi.yaml

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,74 @@
314314
}
315315
}
316316
},
317+
"/api/evaluation/applications({applicationId}/reject": {
318+
"post": {
319+
"tags": [
320+
"application-evaluation-resource"
321+
],
322+
"operationId": "rejectApplication",
323+
"parameters": [
324+
{
325+
"name": "applicationId",
326+
"in": "path",
327+
"required": true,
328+
"schema": {
329+
"type": "string",
330+
"format": "uuid"
331+
}
332+
}
333+
],
334+
"requestBody": {
335+
"content": {
336+
"application/json": {
337+
"schema": {
338+
"$ref": "#/components/schemas/RejectDTO"
339+
}
340+
}
341+
},
342+
"required": true
343+
},
344+
"responses": {
345+
"200": {
346+
"description": "OK"
347+
}
348+
}
349+
}
350+
},
351+
"/api/evaluation/applications({applicationId}/accept": {
352+
"post": {
353+
"tags": [
354+
"application-evaluation-resource"
355+
],
356+
"operationId": "acceptApplication",
357+
"parameters": [
358+
{
359+
"name": "applicationId",
360+
"in": "path",
361+
"required": true,
362+
"schema": {
363+
"type": "string",
364+
"format": "uuid"
365+
}
366+
}
367+
],
368+
"requestBody": {
369+
"content": {
370+
"application/json": {
371+
"schema": {
372+
"$ref": "#/components/schemas/AcceptDTO"
373+
}
374+
}
375+
},
376+
"required": true
377+
},
378+
"responses": {
379+
"200": {
380+
"description": "OK"
381+
}
382+
}
383+
}
384+
},
317385
"/api/applications/upload-documents/{applicationId}/{documentType}": {
318386
"post": {
319387
"tags": [
@@ -2190,6 +2258,42 @@
21902258
"title"
21912259
]
21922260
},
2261+
"RejectDTO": {
2262+
"type": "object",
2263+
"properties": {
2264+
"reason": {
2265+
"type": "string",
2266+
"enum": [
2267+
"JOB_FILLED",
2268+
"JOB_OUTDATED",
2269+
"FAILED_REQUIREMENTS",
2270+
"OTHER_REASON"
2271+
]
2272+
},
2273+
"notifyApplicant": {
2274+
"type": "boolean"
2275+
}
2276+
},
2277+
"required": [
2278+
"reason"
2279+
]
2280+
},
2281+
"AcceptDTO": {
2282+
"type": "object",
2283+
"properties": {
2284+
"message": {
2285+
"type": "string",
2286+
"maxLength": 1000,
2287+
"minLength": 0
2288+
},
2289+
"notifyApplicant": {
2290+
"type": "boolean"
2291+
},
2292+
"closeJob": {
2293+
"type": "boolean"
2294+
}
2295+
}
2296+
},
21932297
"CreateApplicationDTO": {
21942298
"type": "object",
21952299
"properties": {
@@ -3317,6 +3421,9 @@
33173421
"applicationDetailDTO": {
33183422
"$ref": "#/components/schemas/ApplicationDetailDTO"
33193423
},
3424+
"professor": {
3425+
"$ref": "#/components/schemas/ProfessorDTO"
3426+
},
33203427
"rating": {
33213428
"type": "integer",
33223429
"format": "int32"
@@ -3353,6 +3460,26 @@
33533460
}
33543461
}
33553462
},
3463+
"ProfessorDTO": {
3464+
"type": "object",
3465+
"properties": {
3466+
"firstName": {
3467+
"type": "string"
3468+
},
3469+
"lastName": {
3470+
"type": "string"
3471+
},
3472+
"email": {
3473+
"type": "string"
3474+
},
3475+
"researchGroupName": {
3476+
"type": "string"
3477+
},
3478+
"researchGroupWebsite": {
3479+
"type": "string"
3480+
}
3481+
}
3482+
},
33563483
"UserForApplicationDetailDTO": {
33573484
"type": "object",
33583485
"properties": {
@@ -3461,4 +3588,4 @@
34613588
}
34623589
}
34633590
}
3464-
}
3591+
}

src/main/java/de/tum/cit/aet/application/domain/dto/ApplicationDetailDTO.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ public record ApplicationDetailDTO(
2626
* @param application
2727
* @return
2828
*/
29-
public static ApplicationDetailDTO getFromEntity(Application application) {
29+
public static ApplicationDetailDTO getFromEntity(Application application, Job job) {
3030
if (application == null) {
3131
throw new EntityNotFoundException("Application Entity should not be null");
3232
}
3333
Applicant applicant = application.getApplicant();
34-
Job job = application.getJob();
3534

3635
return new ApplicationDetailDTO(
3736
application.getApplicationId(),

src/main/java/de/tum/cit/aet/application/service/ApplicationService.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22

33
import de.tum.cit.aet.application.constants.ApplicationState;
44
import de.tum.cit.aet.application.domain.Application;
5-
import de.tum.cit.aet.application.domain.dto.ApplicationDetailDTO;
6-
import de.tum.cit.aet.application.domain.dto.ApplicationDocumentIdsDTO;
7-
import de.tum.cit.aet.application.domain.dto.ApplicationForApplicantDTO;
8-
import de.tum.cit.aet.application.domain.dto.ApplicationOverviewDTO;
9-
import de.tum.cit.aet.application.domain.dto.CreateApplicationDTO;
10-
import de.tum.cit.aet.application.domain.dto.UpdateApplicationDTO;
5+
import de.tum.cit.aet.application.domain.dto.*;
116
import de.tum.cit.aet.application.repository.ApplicationRepository;
127
import de.tum.cit.aet.core.constants.DocumentType;
138
import de.tum.cit.aet.core.domain.Document;
@@ -360,6 +355,6 @@ public ApplicationDetailDTO getApplicationDetail(UUID applicationId) {
360355
}
361356
Application application = applicationRepository.findById(applicationId).orElseThrow();
362357

363-
return ApplicationDetailDTO.getFromEntity(application);
358+
return ApplicationDetailDTO.getFromEntity(application, application.getJob());
364359
}
365360
}

src/main/java/de/tum/cit/aet/evaluation/constants/RejectReason.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,13 @@
33
import lombok.AllArgsConstructor;
44
import lombok.Getter;
55

6-
/**
7-
* The client will ask the server for a list of possible reject reasons.
8-
* The supervisor can use and adapt those to their needs.
9-
* This method is chosen because lateron instead of an enum a database can be used
10-
* and each chair can implement custom Reject Reason Texts.
11-
*/
12-
136
@Getter
147
@AllArgsConstructor
158
public enum RejectReason {
16-
FAILED_APPLICANT_REQUIREMENTS("FAILED_APPLICANT_REQUIREMENTS"),
17-
POSITION_ALREADY_FILLED("POSITION_ALREADY_FILLED"),
18-
POSITION_OUTDATED("POSITION_OUTDATED"),
19-
NOT_SUFFICIENT_GRADES("NOT_SUFFICIENT_GRADES"),
20-
STRONGER_CANDIDATES_SELECTED("STRONGER_CANDIDATES_SELECTED"),
21-
APPLICATION_NOT_INTERESTING_ENOUGH("APPLICATION_NOT_INTERESTING_ENOUGH"),
22-
LACK_OF_RESEARCH_EXPERIENCE("LACK_OF_RESEARCH_EXPERIENCE");
9+
JOB_FILLED("JOB_FILLED"),
10+
JOB_OUTDATED("JOB_OUTDATED"),
11+
FAILED_REQUIREMENTS("FAILED_REQUIREMENTS"),
12+
OTHER_REASON("OTHER_REASON");
2313

2414
private final String value;
25-
26-
public String getStandardText(String languageKey) {
27-
return "Not yet implemented";
28-
}
2915
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package de.tum.cit.aet.evaluation.dto;
2+
3+
import jakarta.validation.constraints.Size;
4+
5+
public record AcceptDTO(
6+
@Size(max = 3000, message = "Message can not exceed 3000 characters") String message,
7+
boolean notifyApplicant,
8+
boolean closeJob
9+
) {}

src/main/java/de/tum/cit/aet/evaluation/dto/ApplicationEvaluationDetailDTO.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,29 @@
33
import com.fasterxml.jackson.annotation.JsonInclude;
44
import de.tum.cit.aet.application.domain.Application;
55
import de.tum.cit.aet.application.domain.dto.ApplicationDetailDTO;
6+
import de.tum.cit.aet.job.domain.Job;
7+
import de.tum.cit.aet.usermanagement.dto.ProfessorDTO;
68
import jakarta.validation.constraints.NotNull;
79
import java.time.LocalDateTime;
810

911
@JsonInclude(JsonInclude.Include.NON_EMPTY)
10-
public record ApplicationEvaluationDetailDTO(@NotNull ApplicationDetailDTO applicationDetailDTO, Integer rating, LocalDateTime appliedAt) {
12+
public record ApplicationEvaluationDetailDTO(
13+
@NotNull ApplicationDetailDTO applicationDetailDTO,
14+
ProfessorDTO professor,
15+
Integer rating,
16+
LocalDateTime appliedAt
17+
) {
1118
/**
1219
* Creates an {@link ApplicationEvaluationDetailDTO} from the given {@link Application} entity.
1320
*
1421
* @param application the {@link Application} entity
1522
* @return a new {@link ApplicationEvaluationDetailDTO} populated from the application data
1623
*/
1724
public static ApplicationEvaluationDetailDTO fromApplication(Application application) {
25+
Job job = application.getJob();
1826
return new ApplicationEvaluationDetailDTO(
19-
ApplicationDetailDTO.getFromEntity(application),
27+
ApplicationDetailDTO.getFromEntity(application, job),
28+
ProfessorDTO.fromEntity(job.getSupervisingProfessor()),
2029
application.getRating(),
2130
application.getCreatedAt()
2231
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package de.tum.cit.aet.evaluation.dto;
2+
3+
import de.tum.cit.aet.evaluation.constants.RejectReason;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
public record RejectDTO(@NotNull RejectReason reason, boolean notifyApplicant) {}

src/main/java/de/tum/cit/aet/evaluation/service/ApplicationEvaluationService.java

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,110 @@
44
import de.tum.cit.aet.application.domain.Application;
55
import de.tum.cit.aet.core.dto.OffsetPageDTO;
66
import de.tum.cit.aet.core.dto.SortDTO;
7+
import de.tum.cit.aet.core.exception.EntityNotFoundException;
78
import de.tum.cit.aet.core.util.OffsetPageRequest;
8-
import de.tum.cit.aet.evaluation.dto.ApplicationEvaluationDetailListDTO;
9-
import de.tum.cit.aet.evaluation.dto.ApplicationEvaluationOverviewListDTO;
10-
import de.tum.cit.aet.evaluation.dto.EvaluationFilterDTO;
11-
import de.tum.cit.aet.evaluation.dto.JobFilterOptionDTO;
9+
import de.tum.cit.aet.evaluation.domain.ApplicationReview;
10+
import de.tum.cit.aet.evaluation.dto.*;
1211
import de.tum.cit.aet.evaluation.repository.ApplicationEvaluationRepository;
1312
import de.tum.cit.aet.evaluation.repository.JobEvaluationRepository;
1413
import de.tum.cit.aet.usermanagement.domain.ResearchGroup;
14+
import de.tum.cit.aet.usermanagement.domain.User;
1515
import java.util.List;
1616
import java.util.Map;
1717
import java.util.Set;
1818
import java.util.UUID;
1919
import lombok.AllArgsConstructor;
20+
import lombok.NonNull;
2021
import org.springframework.data.domain.Pageable;
2122
import org.springframework.stereotype.Service;
2223

2324
@Service
2425
@AllArgsConstructor
2526
public class ApplicationEvaluationService {
2627

28+
private final ApplicationEvaluationRepository applicationEvaluationRepository;
29+
private final JobEvaluationRepository jobEvaluationRepository;
30+
2731
private static final Set<ApplicationState> VIEWABLE_STATES = Set.of(
2832
ApplicationState.SENT,
2933
ApplicationState.IN_REVIEW,
3034
ApplicationState.ACCEPTED,
3135
ApplicationState.REJECTED
3236
);
3337

38+
private static final Set<ApplicationState> REVIEW_STATES = Set.of(ApplicationState.SENT, ApplicationState.IN_REVIEW);
39+
3440
private static final Set<String> SORTABLE_FIELDS = Set.of("rating", "createdAt", "applicant.lastName");
3541

36-
private final ApplicationEvaluationRepository applicationEvaluationRepository;
37-
private final JobEvaluationRepository jobEvaluationRepository;
42+
/**
43+
* Accepts the specified application and updates its state.
44+
*
45+
* @param applicationId the ID of the application to accept
46+
* @param acceptDTO the acceptance details
47+
* @param reviewingUser the user performing the review
48+
*/
49+
public void acceptApplication(@NonNull UUID applicationId, @NonNull AcceptDTO acceptDTO, @NonNull User reviewingUser) {
50+
Application application = applicationEvaluationRepository
51+
.findById(applicationId)
52+
.orElseThrow(() -> new EntityNotFoundException("Application not found"));
53+
54+
//TODO add authorization
55+
56+
if (!REVIEW_STATES.contains(application.getState())) {
57+
throw new IllegalArgumentException("Application can not be reviewed");
58+
}
59+
60+
application.setState(ApplicationState.ACCEPTED);
61+
setApplicationReview(application, reviewingUser, acceptDTO.message());
62+
applicationEvaluationRepository.save(application);
63+
64+
if (acceptDTO.closeJob()) {
65+
System.out.println("Should close Job and reject all");
66+
//TODO integrate close job and reject all
67+
}
68+
//TODO add notification
69+
70+
}
71+
72+
/**
73+
* Rejects the specified application and updates its state.
74+
*
75+
* @param applicationId the ID of the application to reject
76+
* @param rejectDTO the rejection details
77+
* @param reviewingUser the user performing the review
78+
*/
79+
public void rejectApplication(@NonNull UUID applicationId, @NonNull RejectDTO rejectDTO, @NonNull User reviewingUser) {
80+
Application application = applicationEvaluationRepository
81+
.findById(applicationId)
82+
.orElseThrow(() -> new EntityNotFoundException("Application not found"));
83+
84+
//TODO add authorization
85+
86+
if (!REVIEW_STATES.contains(application.getState())) {
87+
throw new IllegalArgumentException("Application can not be reviewed");
88+
}
89+
90+
application.setState(ApplicationState.REJECTED);
91+
setApplicationReview(application, reviewingUser, rejectDTO.reason().getValue());
92+
applicationEvaluationRepository.save(application);
93+
//TODO add notification
94+
}
95+
96+
/**
97+
* Sets the review details for the given application.
98+
*
99+
* @param application the application to review
100+
* @param reviewingUser the user performing the review
101+
* @param reason the review reason or message
102+
*/
103+
private void setApplicationReview(Application application, User reviewingUser, String reason) {
104+
ApplicationReview applicationReview = new ApplicationReview();
105+
applicationReview.setReviewedBy(reviewingUser);
106+
applicationReview.setReason(reason);
107+
108+
application.setApplicationReview(applicationReview);
109+
applicationReview.setApplication(application);
110+
}
38111

39112
/**
40113
* Retrieves a paginated and optionally sorted list of applications for a given research group.

0 commit comments

Comments
 (0)