Skip to content

Commit 70c1bdd

Browse files
authored
지원자 합격 처리(구성원 승인) API 개발 (#311)
* feat: 지원자 합격 처리 (구성원 승인) API 개발 * test: 지원자 합격 처리 (구성원 승인) 서비스 로직 단위 테스트 * clean: 줄바꿈 추가 * refactor: 무분별한 어드민 승격 방지를 위해 메서드 파라미터 제거 및 내부 주입으로 변경 * refactor: applyIds로 지원 정보 리스트 조회 시 N+1 문제가 발생하지 않도록 개선
1 parent b8a98cc commit 70c1bdd

8 files changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.ject.support.domain.admin.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import org.ject.support.domain.admin.dto.ApplyPassRequest;
6+
import org.springframework.web.bind.annotation.RequestBody;
7+
8+
@Tag(name = "Apply Pass", description = "지원자 합격 API")
9+
public interface ApplyPassApiSpec {
10+
11+
@Operation(
12+
summary = "지원자 합격",
13+
description = "지원자를 합격 처리하여 구성원으로 승인합니다."
14+
)
15+
int passApply(@RequestBody ApplyPassRequest request);
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.ject.support.domain.admin.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.ject.support.domain.admin.dto.ApplyPassRequest;
5+
import org.ject.support.domain.admin.service.ApplyPassService;
6+
import org.springframework.security.access.prepost.PreAuthorize;
7+
import org.springframework.web.bind.annotation.PostMapping;
8+
import org.springframework.web.bind.annotation.RequestBody;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
@RequiredArgsConstructor
14+
@RequestMapping("/admin/apply/pass")
15+
public class ApplyPassController implements ApplyPassApiSpec {
16+
17+
private final ApplyPassService applyPassService;
18+
19+
@Override
20+
@PostMapping
21+
@PreAuthorize("hasRole('ROLE_ADMIN')")
22+
public int passApply(@RequestBody ApplyPassRequest request) {
23+
return applyPassService.passApply(request.applyIds());
24+
}
25+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.ject.support.domain.admin.dto;
2+
3+
import java.util.List;
4+
5+
public record ApplyPassRequest(List<Long> applyIds) {
6+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.ject.support.domain.admin.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.ject.support.domain.apply.domain.Apply;
5+
import org.ject.support.domain.apply.exception.ApplyException;
6+
import org.ject.support.domain.apply.repository.ApplyRepository;
7+
import org.ject.support.domain.member.entity.Member;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
import java.util.List;
12+
13+
import static org.ject.support.domain.apply.exception.ApplyErrorCode.NOT_SUBMITTED;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
public class ApplyPassService {
18+
19+
private final ApplyRepository applyRepository;
20+
21+
@Transactional
22+
public int passApply(List<Long> applyIds) {
23+
// 지원 정보 조회
24+
List<Apply> applies = applyRepository.findAllByIdWithMember(applyIds);
25+
26+
// 지원자 합격 처리
27+
applies.forEach(apply -> {
28+
// 지원서 제출 완료 여부 검증
29+
if (apply.isNotSubmitted()) {
30+
throw new ApplyException(NOT_SUBMITTED);
31+
}
32+
33+
// 지원자 role 승격
34+
Member member = apply.getMember();
35+
member.promoteToSemester();
36+
});
37+
38+
// 승인한 구성원 수 반환
39+
return applies.size();
40+
}
41+
}

src/main/java/org/ject/support/domain/apply/exception/ApplyErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public enum ApplyErrorCode implements ErrorCode {
1515
ALREADY_SUBMITTED(CONFLICT, 2, "이미 지원서를 제출한 상태입니다."),
1616
NOT_FOUND_TEMP_APPLICATION_FORM(NOT_FOUND, 3, "임시 저장한 지원서가 존재하지 않습니다."),
1717
NOT_FOUND_SUBMITTED_APPLICATION_FORM(NOT_FOUND, 4, "제출된 지원서가 존재하지 않습니다."),
18+
NOT_SUBMITTED(CONFLICT, 5, "제출 완료된 지원서가 아닙니다.")
1819
;
1920

2021
private final HttpStatus httpStatus;

src/main/java/org/ject/support/domain/apply/repository/ApplyRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ public interface ApplyRepository extends JpaRepository<Apply, Long>, ApplyQueryR
2424

2525
@Query("select count(a) from Apply a where a.status = :status")
2626
Long countByStatus(@Param("status") Apply.Status status);
27+
28+
@Query("SELECT a FROM Apply a JOIN FETCH a.member WHERE a.id IN :ids")
29+
List<Apply> findAllByIdWithMember(@Param("ids") List<Long> ids);
2730
}

src/main/java/org/ject/support/domain/member/entity/Member.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,8 @@ public void deleteProfile() {
141141
this.experiencePeriod = null;
142142
this.interestedDomains.clear();
143143
}
144+
145+
public void promoteToSemester() {
146+
this.role = Role.SEMESTER;
147+
}
144148
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.ject.support.domain.admin.service;
2+
3+
import org.ject.support.base.UnitTestSupport;
4+
import org.ject.support.domain.apply.domain.ApplicationForm;
5+
import org.ject.support.domain.apply.domain.Apply;
6+
import org.ject.support.domain.apply.exception.ApplyException;
7+
import org.ject.support.domain.apply.repository.ApplyRepository;
8+
import org.ject.support.domain.member.JobFamily;
9+
import org.ject.support.domain.member.MemberStatus;
10+
import org.ject.support.domain.member.Role;
11+
import org.ject.support.domain.member.entity.Member;
12+
import org.ject.support.domain.recruit.domain.Question;
13+
import org.ject.support.domain.recruit.domain.Recruit;
14+
import org.ject.support.domain.recruit.domain.Semester;
15+
import org.junit.jupiter.api.Test;
16+
import org.mockito.InjectMocks;
17+
import org.mockito.Mock;
18+
19+
import java.time.LocalDateTime;
20+
import java.util.List;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
24+
import static org.mockito.Mockito.when;
25+
26+
class ApplyPassServiceTest extends UnitTestSupport {
27+
28+
@InjectMocks
29+
ApplyPassService applyPassService;
30+
31+
@Mock
32+
ApplyRepository applyRepository;
33+
34+
@Test
35+
void 지원자_합격_처리() {
36+
// given
37+
Recruit recruit = getActiveRecruit(1L, JobFamily.BE, List.of());
38+
39+
Member applicant1 = getApplicant(1L, "applicant1@test.com");
40+
Member applicant2 = getApplicant(2L, "applicant2@test.com");
41+
Member applicant3 = getApplicant(3L, "applicant3@test.com");
42+
43+
Apply apply1 = getApply(1L, recruit, applicant1, getApplicationForm("content"), Apply.Status.SUBMITTED);
44+
Apply apply2 = getApply(2L, recruit, applicant2, getApplicationForm("content"), Apply.Status.SUBMITTED);
45+
Apply apply3 = getApply(3L, recruit, applicant3, getApplicationForm("content"), Apply.Status.SUBMITTED);
46+
47+
when(applyRepository.findAllByIdWithMember(List.of(1L, 2L, 3L))).thenReturn(List.of(apply1, apply2, apply3));
48+
49+
// when
50+
int result = applyPassService.passApply(List.of(1L, 2L, 3L));
51+
52+
// then
53+
assertThat(result).isEqualTo(3);
54+
assertThat(applicant1.getRole()).isEqualTo(Role.SEMESTER);
55+
assertThat(applicant2.getRole()).isEqualTo(Role.SEMESTER);
56+
assertThat(applicant3.getRole()).isEqualTo(Role.SEMESTER);
57+
}
58+
59+
@Test
60+
void 제출되지_않은_지원서_승인_실패() {
61+
Recruit recruit = getActiveRecruit(1L, JobFamily.BE, List.of());
62+
63+
Member applicant1 = getApplicant(1L, "applicant1@test.com");
64+
Member applicant2 = getApplicant(2L, "applicant2@test.com");
65+
Member applicant3 = getApplicant(3L, "applicant3@test.com");
66+
67+
Apply apply1 = getApply(1L, recruit, applicant1, getApplicationForm("content"), Apply.Status.SUBMITTED);
68+
Apply apply2 = getApply(2L, recruit, applicant2, getApplicationForm("content"), Apply.Status.TEMP_SAVED);
69+
Apply apply3 = getApply(3L, recruit, applicant3, getApplicationForm("content"), Apply.Status.SUBMITTED);
70+
71+
when(applyRepository.findAllByIdWithMember(List.of(1L, 2L, 3L))).thenReturn(List.of(apply1, apply2, apply3));
72+
73+
// when, then
74+
assertThatThrownBy(() -> applyPassService.passApply(List.of(1L, 2L, 3L)))
75+
.isInstanceOf(ApplyException.class);
76+
}
77+
78+
private Recruit getActiveRecruit(Long id, JobFamily jobFamily, List<Question> questions) {
79+
return Recruit.builder()
80+
.id(id)
81+
.semester(Semester.builder().id(1L).name("1기").build())
82+
.startDate(LocalDateTime.now().minusDays(1))
83+
.endDate(LocalDateTime.now().plusDays(1))
84+
.jobFamily(jobFamily)
85+
.questions(questions)
86+
.build();
87+
}
88+
89+
private Member getApplicant(Long id, String email) {
90+
return Member.builder()
91+
.id(id)
92+
.email(email)
93+
.pin("111111")
94+
.role(Role.APPLY)
95+
.status(MemberStatus.ACTIVE)
96+
.build();
97+
}
98+
99+
private Apply getApply(Long id, Recruit recruit, Member applicant, ApplicationForm applicationForm, Apply.Status status) {
100+
return Apply.builder()
101+
.id(id)
102+
.recruit(recruit)
103+
.member(applicant)
104+
.applicationForm(applicationForm)
105+
.status(status)
106+
.build();
107+
}
108+
109+
private ApplicationForm getApplicationForm(String content) {
110+
return ApplicationForm.builder()
111+
.id(1L)
112+
.content(content)
113+
.build();
114+
}
115+
}

0 commit comments

Comments
 (0)