Skip to content

Commit 405cba9

Browse files
authored
feat: 봉사 모집글 삭제, 상태 변경 기능 (#48)
* feat(recruit-board): 봉사 모집글 삭제 기능 * test(recruit-board): 봉사 모집글 삭제 기능 테스트 * refactor: 불필요한 접근 제어자 제거 * feat(recruit-board): 모집글 상태 변경 기능 * test(recruit-board): 모집글 상태 변경 기능 테스트 * refactor: 중복 코드 제거 * refactor(recruit-board): 메서드명 변경 * refactor(recruit-board): RecruitBoard 상태 변경 검증 내부 로직 수정
1 parent 493d688 commit 405cba9

File tree

10 files changed

+302
-16
lines changed

10 files changed

+302
-16
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import jakarta.persistence.Id;
1616
import jakarta.persistence.Lob;
1717
import jakarta.persistence.Table;
18+
import java.time.LocalDateTime;
1819
import java.time.LocalTime;
1920
import java.util.UUID;
2021
import lombok.Builder;
@@ -84,6 +85,13 @@ public void updateWith(String region) {
8485
recruitmentInfo.updateWith(region);
8586
}
8687

88+
public void changeRecruitStatus(RecruitStatus newStatus, LocalDateTime currentDateTime) {
89+
validateStatusChange(newStatus);
90+
validateChangeDeadline(currentDateTime);
91+
92+
this.recruitStatus = newStatus;
93+
}
94+
8795
private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) {
8896
recruitmentInfo.updateWith(
8997
dto.recruitmentCount(),
@@ -93,4 +101,21 @@ private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) {
93101
dto.admitted()
94102
);
95103
}
104+
105+
private void validateStatusChange(RecruitStatus newStatus) {
106+
if (newStatus.isChangeable()) {
107+
return;
108+
}
109+
throw new IllegalArgumentException("상태는 '모집중' 또는 '마감'으로만 변경할 수 있습니다.");
110+
}
111+
112+
private void validateChangeDeadline(LocalDateTime currentDateTime) {
113+
LocalDateTime volunteerStartDateTime = recruitmentInfo.getVolunteerStartDateTime();
114+
LocalDateTime deadline = volunteerStartDateTime.toLocalDate().atStartOfDay();
115+
116+
if (!currentDateTime.isBefore(deadline)) {
117+
throw new IllegalStateException("봉사 시작 일시 자정 전까지만 상태를 변경할 수 있습니다.");
118+
}
119+
}
120+
96121
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
@Getter
77
@RequiredArgsConstructor
88
public enum RecruitStatus {
9-
RECRUITING("모집중"),
10-
CLOSED("마감"),
11-
COMPLETED("종료"),
9+
RECRUITING("모집중", true),
10+
CLOSED("마감", true),
11+
COMPLETED("종료", false),
1212

1313
;
1414
private final String text;
15-
15+
private final boolean changeable;
1616
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.somemore.recruitboard.service.command;
2+
3+
import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD;
4+
import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD;
5+
6+
import com.somemore.global.exception.BadRequestException;
7+
import com.somemore.global.exception.ExceptionMessage;
8+
import com.somemore.recruitboard.domain.RecruitBoard;
9+
import com.somemore.recruitboard.repository.RecruitBoardRepository;
10+
import com.somemore.recruitboard.usecase.command.DeleteRecruitBoardUseCase;
11+
import java.util.UUID;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
15+
16+
@RequiredArgsConstructor
17+
@Transactional
18+
@Service
19+
public class DeleteRecruitBoardService implements DeleteRecruitBoardUseCase {
20+
21+
private final RecruitBoardRepository recruitBoardRepository;
22+
23+
@Override
24+
public void deleteRecruitBoard(UUID centerId, Long recruitBoardId) {
25+
RecruitBoard recruitBoard = recruitBoardRepository.findById(recruitBoardId).orElseThrow(
26+
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
27+
);
28+
29+
validateWriter(recruitBoard, centerId);
30+
31+
recruitBoard.markAsDeleted();
32+
recruitBoardRepository.save(recruitBoard);
33+
}
34+
35+
private void validateWriter(RecruitBoard recruitBoard, UUID centerId) {
36+
if (recruitBoard.isWriter(centerId)) {
37+
return;
38+
}
39+
40+
throw new BadRequestException(UNAUTHORIZED_RECRUIT_BOARD.getMessage());
41+
}
42+
}

src/main/java/com/somemore/recruitboard/service/command/UpdateRecruitBoardService.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
import com.somemore.global.exception.BadRequestException;
77
import com.somemore.location.usecase.command.UpdateLocationUseCase;
88
import com.somemore.recruitboard.domain.RecruitBoard;
9+
import com.somemore.recruitboard.domain.RecruitStatus;
910
import com.somemore.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto;
1011
import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
1112
import com.somemore.recruitboard.repository.RecruitBoardRepository;
1213
import com.somemore.recruitboard.usecase.command.UpdateRecruitBoardUseCase;
13-
import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase;
14+
import java.time.LocalDateTime;
1415
import java.util.UUID;
1516
import lombok.RequiredArgsConstructor;
1617
import org.springframework.stereotype.Service;
@@ -23,7 +24,6 @@
2324
public class UpdateRecruitBoardService implements UpdateRecruitBoardUseCase {
2425

2526
private final RecruitBoardRepository recruitBoardRepository;
26-
private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
2727
private final UpdateLocationUseCase updateLocationUseCase;
2828

2929
@Override
@@ -33,9 +33,7 @@ public void updateRecruitBoard(
3333
UUID centerId,
3434
String imgUrl) {
3535

36-
RecruitBoard recruitBoard = recruitBoardQueryUseCase.findById(recruitBoardId).orElseThrow(
37-
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
38-
);
36+
RecruitBoard recruitBoard = getRecruitBoard(recruitBoardId);
3937
validateWriter(recruitBoard, centerId);
4038
recruitBoard.updateWith(requestDto, imgUrl);
4139

@@ -46,9 +44,7 @@ public void updateRecruitBoard(
4644
public void updateRecruitBoardLocation(RecruitBoardLocationUpdateRequestDto requestDto,
4745
Long recruitBoardId, UUID centerId) {
4846

49-
RecruitBoard recruitBoard = recruitBoardQueryUseCase.findById(recruitBoardId).orElseThrow(
50-
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
51-
);
47+
RecruitBoard recruitBoard = getRecruitBoard(recruitBoardId);
5248
validateWriter(recruitBoard, centerId);
5349

5450
updateLocationUseCase.updateLocation(requestDto.toLocationUpdateRequestDto(),
@@ -58,6 +54,22 @@ public void updateRecruitBoardLocation(RecruitBoardLocationUpdateRequestDto requ
5854
recruitBoardRepository.save(recruitBoard);
5955
}
6056

57+
@Override
58+
public void updateRecruitBoardStatus(RecruitStatus status, Long recruitBoardId, UUID centerId,
59+
LocalDateTime currentDateTime) {
60+
RecruitBoard recruitBoard = getRecruitBoard(recruitBoardId);
61+
validateWriter(recruitBoard, centerId);
62+
63+
recruitBoard.changeRecruitStatus(status, currentDateTime);
64+
recruitBoardRepository.save(recruitBoard);
65+
}
66+
67+
private RecruitBoard getRecruitBoard(Long recruitBoardId) {
68+
return recruitBoardRepository.findById(recruitBoardId).orElseThrow(
69+
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
70+
);
71+
}
72+
6173
private void validateWriter(RecruitBoard recruitBoard, UUID centerId) {
6274
if (recruitBoard.isWriter(centerId)) {
6375
return;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.somemore.recruitboard.usecase.command;
2+
3+
import java.util.UUID;
4+
5+
public interface DeleteRecruitBoardUseCase {
6+
7+
void deleteRecruitBoard(UUID centerId, Long recruitBoardId);
8+
}
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
11
package com.somemore.recruitboard.usecase.command;
22

3+
import com.somemore.recruitboard.domain.RecruitStatus;
34
import com.somemore.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto;
45
import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
6+
import java.time.LocalDateTime;
57
import java.util.UUID;
68

79
public interface UpdateRecruitBoardUseCase {
810

9-
public void updateRecruitBoard(
11+
void updateRecruitBoard(
1012
RecruitBoardUpdateRequestDto requestDto,
1113
Long recruitBoardId,
1214
UUID centerId,
1315
String imgUrl
1416
);
1517

16-
public void updateRecruitBoardLocation(
18+
void updateRecruitBoardLocation(
1719
RecruitBoardLocationUpdateRequestDto requestDto,
1820
Long recruitBoardId,
1921
UUID centerId
2022
);
2123

24+
void updateRecruitBoardStatus(
25+
RecruitStatus status,
26+
Long recruitBoardId,
27+
UUID centerId,
28+
LocalDateTime currentDateTime
29+
);
30+
2231
}

src/test/java/com/somemore/common/fixture/LocalDateTimeFixture.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ public static LocalDateTime createUpdateStartDateTime() {
1717
return LocalDateTime.of(2024, 11, 25, 16, 0);
1818
}
1919

20+
public static LocalDateTime createCurrentDateTime() {
21+
// 2024-11-24 T:18:00:00
22+
return LocalDateTime.of(2024, 11, 24, 18, 0);
23+
}
24+
2025
}

src/test/java/com/somemore/recruitboard/domain/RecruitBoardTest.java

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

3+
import static com.somemore.common.fixture.LocalDateTimeFixture.createCurrentDateTime;
34
import static com.somemore.common.fixture.LocalDateTimeFixture.createStartDateTime;
45
import static com.somemore.common.fixture.LocalDateTimeFixture.createUpdateStartDateTime;
6+
import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED;
7+
import static com.somemore.recruitboard.domain.RecruitStatus.COMPLETED;
58
import static com.somemore.recruitboard.domain.RecruitStatus.RECRUITING;
69
import static com.somemore.recruitboard.domain.VolunteerType.OTHER;
710
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
812

913
import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
1014
import java.time.LocalDateTime;
1115
import java.time.LocalTime;
1216
import java.util.UUID;
1317
import org.junit.jupiter.api.DisplayName;
1418
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.params.ParameterizedTest;
20+
import org.junit.jupiter.params.provider.ValueSource;
1521

1622
class RecruitBoardTest {
1723

@@ -120,6 +126,70 @@ void isNotWriterWithWrongCenterId() {
120126
assertThat(isWriter).isFalse();
121127
}
122128

129+
@DisplayName("모집글 상태를 모집중에서 모집 마감으로 변경할 수 있다")
130+
@Test
131+
void changeStatusFromRecruitingToClose() {
132+
// given
133+
UUID centerId = UUID.randomUUID();
134+
RecruitBoard recruitBoard = createRecruitBoard(centerId);
135+
RecruitStatus newStatus = CLOSED;
136+
LocalDateTime currentDateTime = createCurrentDateTime();
137+
138+
// when
139+
recruitBoard.changeRecruitStatus(newStatus, currentDateTime);
140+
141+
// then
142+
assertThat(recruitBoard.getRecruitStatus()).isEqualTo(newStatus);
143+
}
144+
145+
@DisplayName("모집글 상태를 모집마감에서 모집중으로 변경할 수 있다")
146+
@Test
147+
void changeStatusFromCloseToRecruiting() {
148+
// given
149+
UUID centerId = UUID.randomUUID();
150+
RecruitBoard recruitBoard = createRecruitBoard(centerId);
151+
LocalDateTime currentDateTime = createCurrentDateTime();
152+
recruitBoard.changeRecruitStatus(CLOSED, currentDateTime);
153+
RecruitStatus newStatus = RECRUITING;
154+
155+
// when
156+
recruitBoard.changeRecruitStatus(newStatus, currentDateTime);
157+
158+
// then
159+
assertThat(recruitBoard.getRecruitStatus()).isEqualTo(newStatus);
160+
}
161+
162+
@DisplayName("모집글 상태는 마감으로 변경할 수 없다")
163+
@Test
164+
void changeStatusWhenInvalidStatus() {
165+
// given
166+
UUID centerId = UUID.randomUUID();
167+
RecruitBoard recruitBoard = createRecruitBoard(centerId);
168+
LocalDateTime currentDateTime = createCurrentDateTime();
169+
170+
// when & then
171+
assertThatThrownBy(() -> recruitBoard.changeRecruitStatus(COMPLETED, currentDateTime))
172+
.isInstanceOf(IllegalArgumentException.class);
173+
}
174+
175+
@DisplayName("봉사 시작일 자정 이후 모집 상태를 변경할 경우 에러가 발생한다")
176+
@ParameterizedTest
177+
@ValueSource(longs = {0, 1})
178+
void changeStatusWhenDeadLineAfter(Long secondsOffset) {
179+
// given
180+
UUID centerId = UUID.randomUUID();
181+
RecruitBoard recruitBoard = createRecruitBoard(centerId);
182+
LocalDateTime deadLineDateTime = recruitBoard.getRecruitmentInfo()
183+
.getVolunteerStartDateTime().toLocalDate().atStartOfDay();
184+
LocalDateTime currentDateTime = deadLineDateTime.plusSeconds(secondsOffset);
185+
186+
// when
187+
// then
188+
assertThatThrownBy(
189+
() -> recruitBoard.changeRecruitStatus(CLOSED, currentDateTime)
190+
).isInstanceOf(IllegalStateException.class);
191+
192+
}
123193

124194
private static RecruitBoard createRecruitBoard(UUID centerId) {
125195
LocalDateTime startDateTime = createStartDateTime();
@@ -149,4 +219,5 @@ private static RecruitBoard createRecruitBoard(UUID centerId, LocalDateTime star
149219
.recruitmentInfo(recruitmentInfo)
150220
.build();
151221
}
222+
152223
}

0 commit comments

Comments
 (0)