Skip to content

Commit 4b3335b

Browse files
authored
feat: 봉사 지원 승인/거절 (#132)
* feat(recruit-board): 모집 게시글 도메인 로직 * feat(recruit-board): 봉사 활동 모집글 조회 기능 * feat(volunteer-apply): 봉사 활동 지원 승인 기능 * test(volunteer-apply): 봉사 활동 지원 승인 기능 테스트 * feat(volunteer-apply): 봉사 활동 지원 승인 API * test(volunteer-apply): 봉사 활동 지원 승인 API 테스트 * chore: 디렉토리 위치 변경 * feat(volunteer-apply): 봉사 활동 지원 거절 기능 * test(volunteer-apply): 봉사 활동 지원 거절 기능 테스트 * feat(volunteer-apply): 봉사 활동 지원 거절 API * test(volunteer-apply): 봉사 활동 지원 거절 API 테스트 * refactor(volunteer-apply): 메서드 이름 수정 * chore: 불필요한 import 제거
1 parent cf679de commit 4b3335b

File tree

11 files changed

+473
-3
lines changed

11 files changed

+473
-3
lines changed

src/main/java/com/somemore/global/exception/ExceptionMessage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public enum ExceptionMessage {
3232
RECRUITMENT_NOT_OPEN("현재 모집 진행 중이 아닙니다."),
3333
DUPLICATE_APPLICATION("이미 신청한 봉사 모집 공고입니다."),
3434
UNAUTHORIZED_VOLUNTEER_APPLY("해당 지원에 권한이 없습니다."),
35+
RECRUIT_BOARD_ALREADY_COMPLETED("이미 종료된 봉사 활동입니다."),
3536

3637
;
3738
private final String message;

src/main/java/com/somemore/recruitboard/domain/RecruitBoard.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.somemore.recruitboard.domain;
22

3+
import static com.somemore.recruitboard.domain.RecruitStatus.COMPLETED;
34
import static com.somemore.recruitboard.domain.RecruitStatus.RECRUITING;
45
import static jakarta.persistence.EnumType.STRING;
56
import static jakarta.persistence.GenerationType.IDENTITY;
@@ -106,6 +107,10 @@ private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) {
106107
);
107108
}
108109

110+
public boolean isCompleted() {
111+
return this.recruitStatus == COMPLETED;
112+
}
113+
109114
private void validateStatusChange(RecruitStatus newStatus) {
110115
if (newStatus.isChangeable()) {
111116
return;

src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
import com.somemore.recruitboard.dto.response.RecruitBoardResponseDto;
88
import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto;
99
import com.somemore.recruitboard.dto.response.RecruitBoardWithLocationResponseDto;
10-
import org.springframework.data.domain.Page;
11-
1210
import java.util.List;
1311
import java.util.UUID;
12+
import org.springframework.data.domain.Page;
1413

1514
public interface RecruitBoardQueryUseCase {
1615

1716
RecruitBoard getById(Long id);
17+
1818
RecruitBoardResponseDto getRecruitBoardById(Long id);
1919

2020
RecruitBoardWithLocationResponseDto getWithLocationById(Long id);
@@ -25,7 +25,7 @@ Page<RecruitBoardDetailResponseDto> getRecruitBoardsNearby(
2525
RecruitBoardNearByCondition condition);
2626

2727
Page<RecruitBoardResponseDto> getRecruitBoardsByCenterId(UUID centerId,
28-
RecruitBoardSearchCondition condition);
28+
RecruitBoardSearchCondition condition);
2929

3030
List<Long> getNotCompletedIdsByCenterIds(UUID centerId);
3131

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.somemore.volunteerapply.controller;
2+
3+
import com.somemore.auth.annotation.CurrentUser;
4+
import com.somemore.global.common.response.ApiResponse;
5+
import com.somemore.volunteerapply.usecase.ApproveVolunteerApplyUseCase;
6+
import com.somemore.volunteerapply.usecase.RejectVolunteerApplyUseCase;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import java.util.UUID;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.security.access.annotation.Secured;
12+
import org.springframework.web.bind.annotation.PatchMapping;
13+
import org.springframework.web.bind.annotation.PathVariable;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
@Tag(name = "Center Volunteer Apply Command API", description = "봉사 활동 지원 승인, 거절, 정산 API")
18+
@RequiredArgsConstructor
19+
@RequestMapping("/api")
20+
@RestController
21+
public class CenterVolunteerApplyCommandApiController {
22+
23+
private final ApproveVolunteerApplyUseCase approveVolunteerApplyUseCase;
24+
private final RejectVolunteerApplyUseCase rejectVolunteerApplyUseCase;
25+
26+
@Secured("ROLE_CENTER")
27+
@Operation(summary = "봉사 활동 지원 승인", description = "봉사 활동 지원을 승인합니다.")
28+
@PatchMapping("/volunteer-apply/{id}/approve")
29+
public ApiResponse<String> approve(
30+
@CurrentUser UUID centerId,
31+
@PathVariable Long id
32+
) {
33+
34+
approveVolunteerApplyUseCase.approve(id, centerId);
35+
return ApiResponse.ok("봉사 활동 지원 승인 성공");
36+
}
37+
38+
@Secured("ROLE_CENTER")
39+
@Operation(summary = "봉사 활동 지원 거절", description = "봉사 활동 지원을 거절합니다.")
40+
@PatchMapping("/volunteer-apply/{id}/reject")
41+
public ApiResponse<String> reject(
42+
@CurrentUser UUID centerId,
43+
@PathVariable Long id
44+
) {
45+
46+
rejectVolunteerApplyUseCase.reject(id, centerId);
47+
return ApiResponse.ok("봉사 활동 지원 거절 성공");
48+
}
49+
50+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.somemore.volunteerapply.service;
2+
3+
import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER_APPLY;
4+
import static com.somemore.global.exception.ExceptionMessage.RECRUIT_BOARD_ALREADY_COMPLETED;
5+
import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD;
6+
import static com.somemore.volunteerapply.domain.ApplyStatus.APPROVED;
7+
8+
import com.somemore.global.exception.BadRequestException;
9+
import com.somemore.recruitboard.domain.RecruitBoard;
10+
import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase;
11+
import com.somemore.volunteerapply.domain.VolunteerApply;
12+
import com.somemore.volunteerapply.repository.VolunteerApplyRepository;
13+
import com.somemore.volunteerapply.usecase.ApproveVolunteerApplyUseCase;
14+
import java.util.UUID;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.stereotype.Service;
17+
import org.springframework.transaction.annotation.Transactional;
18+
19+
@RequiredArgsConstructor
20+
@Transactional
21+
@Service
22+
public class ApproveVolunteerApplyService implements ApproveVolunteerApplyUseCase {
23+
24+
private final VolunteerApplyRepository volunteerApplyRepository;
25+
26+
private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
27+
28+
@Override
29+
public void approve(Long id, UUID centerId) {
30+
VolunteerApply apply = getVolunteerApply(id);
31+
RecruitBoard recruitBoard = recruitBoardQueryUseCase.getById(apply.getRecruitBoardId());
32+
33+
validateWriter(recruitBoard, centerId);
34+
validateBoardStatus(recruitBoard);
35+
36+
apply.changeStatus(APPROVED);
37+
volunteerApplyRepository.save(apply);
38+
}
39+
40+
private VolunteerApply getVolunteerApply(Long id) {
41+
return volunteerApplyRepository.findById(id).orElseThrow(
42+
() -> new BadRequestException(NOT_EXISTS_VOLUNTEER_APPLY)
43+
);
44+
}
45+
46+
private void validateWriter(RecruitBoard recruitBoard, UUID centerId) {
47+
if (recruitBoard.isWriter(centerId)) {
48+
return;
49+
}
50+
throw new BadRequestException(UNAUTHORIZED_RECRUIT_BOARD);
51+
}
52+
53+
private void validateBoardStatus(RecruitBoard recruitBoard) {
54+
if (recruitBoard.isCompleted()) {
55+
throw new BadRequestException(RECRUIT_BOARD_ALREADY_COMPLETED);
56+
}
57+
}
58+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.somemore.volunteerapply.service;
2+
3+
import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER_APPLY;
4+
import static com.somemore.global.exception.ExceptionMessage.RECRUIT_BOARD_ALREADY_COMPLETED;
5+
import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD;
6+
import static com.somemore.volunteerapply.domain.ApplyStatus.REJECTED;
7+
8+
import com.somemore.global.exception.BadRequestException;
9+
import com.somemore.recruitboard.domain.RecruitBoard;
10+
import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase;
11+
import com.somemore.volunteerapply.domain.VolunteerApply;
12+
import com.somemore.volunteerapply.repository.VolunteerApplyRepository;
13+
import com.somemore.volunteerapply.usecase.RejectVolunteerApplyUseCase;
14+
import java.util.UUID;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.stereotype.Service;
17+
import org.springframework.transaction.annotation.Transactional;
18+
19+
@RequiredArgsConstructor
20+
@Transactional
21+
@Service
22+
public class RejectVolunteerApplyService implements RejectVolunteerApplyUseCase {
23+
24+
private final VolunteerApplyRepository volunteerApplyRepository;
25+
private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
26+
27+
@Override
28+
public void reject(Long id, UUID centerId) {
29+
VolunteerApply apply = getApply(id);
30+
RecruitBoard recruitBoard = recruitBoardQueryUseCase.getById(apply.getRecruitBoardId());
31+
32+
validateWriter(recruitBoard, centerId);
33+
validateBoardStatus(recruitBoard);
34+
35+
apply.changeStatus(REJECTED);
36+
volunteerApplyRepository.save(apply);
37+
}
38+
39+
private VolunteerApply getApply(Long id) {
40+
return volunteerApplyRepository.findById(id).orElseThrow(
41+
() -> new BadRequestException(NOT_EXISTS_VOLUNTEER_APPLY)
42+
);
43+
}
44+
45+
private void validateWriter(RecruitBoard recruitBoard, UUID centerId) {
46+
if (recruitBoard.isWriter(centerId)) {
47+
return;
48+
}
49+
throw new BadRequestException(UNAUTHORIZED_RECRUIT_BOARD);
50+
}
51+
52+
private void validateBoardStatus(RecruitBoard recruitBoard) {
53+
if (recruitBoard.isCompleted()) {
54+
throw new BadRequestException(RECRUIT_BOARD_ALREADY_COMPLETED);
55+
}
56+
}
57+
58+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.somemore.volunteerapply.usecase;
2+
3+
4+
import java.util.UUID;
5+
6+
public interface ApproveVolunteerApplyUseCase {
7+
8+
void approve(Long id, UUID centerId);
9+
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.somemore.volunteerapply.usecase;
2+
3+
import java.util.UUID;
4+
5+
public interface RejectVolunteerApplyUseCase {
6+
7+
void reject(Long id, UUID centerId);
8+
9+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.somemore.volunteerapply.controller;
2+
3+
import static org.mockito.ArgumentMatchers.any;
4+
import static org.mockito.BDDMockito.willDoNothing;
5+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
6+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
7+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
8+
9+
import com.somemore.ControllerTestSupport;
10+
import com.somemore.WithMockCustomUser;
11+
import com.somemore.volunteerapply.usecase.ApproveVolunteerApplyUseCase;
12+
import com.somemore.volunteerapply.usecase.RejectVolunteerApplyUseCase;
13+
import java.util.UUID;
14+
import org.junit.jupiter.api.DisplayName;
15+
import org.junit.jupiter.api.Test;
16+
import org.springframework.beans.factory.annotation.Autowired;
17+
import org.springframework.boot.test.mock.mockito.MockBean;
18+
import org.springframework.test.web.servlet.MockMvc;
19+
20+
class CenterVolunteerApplyCommandApiControllerTest extends ControllerTestSupport {
21+
22+
@Autowired
23+
private MockMvc mockMvc;
24+
25+
@MockBean
26+
private ApproveVolunteerApplyUseCase approveVolunteerApplyUseCase;
27+
28+
@MockBean
29+
private RejectVolunteerApplyUseCase rejectVolunteerApplyUseCase;
30+
31+
@Test
32+
@DisplayName("봉사 활동 지원 승인 성공 테스트")
33+
@WithMockCustomUser(role = "CENTER")
34+
void approve() throws Exception {
35+
// given
36+
Long id = 1L;
37+
38+
willDoNothing().given(approveVolunteerApplyUseCase)
39+
.approve(any(), any(UUID.class));
40+
// when
41+
mockMvc.perform(patch("/api/volunteer-apply/{id}/approve", id)
42+
.header("Authorization", "Bearer access-token"))
43+
// then
44+
.andExpect(status().isOk())
45+
.andExpect(jsonPath("$.code").value(200))
46+
.andExpect(jsonPath("$.data").value(""))
47+
.andExpect(jsonPath("$.message").value("봉사 활동 지원 승인 성공"));
48+
}
49+
50+
@Test
51+
@DisplayName("봉사 활동 지원 거절 성공 테스트")
52+
@WithMockCustomUser(role = "CENTER")
53+
void reject() throws Exception {
54+
// given
55+
Long id = 1L;
56+
57+
willDoNothing().given(rejectVolunteerApplyUseCase)
58+
.reject(any(), any(UUID.class));
59+
// when
60+
mockMvc.perform(patch("/api/volunteer-apply/{id}/reject", id)
61+
.header("Authorization", "Bearer access-token"))
62+
// then
63+
.andExpect(status().isOk())
64+
.andExpect(jsonPath("$.code").value(200))
65+
.andExpect(jsonPath("$.data").value(""))
66+
.andExpect(jsonPath("$.message").value("봉사 활동 지원 거절 성공"));
67+
}
68+
}

0 commit comments

Comments
 (0)