Skip to content
25 changes: 25 additions & 0 deletions src/main/java/com/somemore/recruitboard/domain/RecruitBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;
import lombok.Builder;
Expand Down Expand Up @@ -84,6 +85,13 @@ public void updateWith(String region) {
recruitmentInfo.updateWith(region);
}

public void changeRecruitStatus(RecruitStatus newStatus, LocalDateTime currentDateTime) {
validateStatusChange(newStatus);
validateChangeDeadline(currentDateTime);

this.recruitStatus = newStatus;
}

private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) {
recruitmentInfo.updateWith(
dto.recruitmentCount(),
Expand All @@ -93,4 +101,21 @@ private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) {
dto.admitted()
);
}

private void validateStatusChange(RecruitStatus newStatus) {
if (newStatus.isChangeable()) {
return;
}
throw new IllegalArgumentException("상태는 '모집중' 또는 '마감'으로만 변경할 수 있습니다.");
}

private void validateChangeDeadline(LocalDateTime currentDateTime) {
LocalDateTime volunteerStartDateTime = recruitmentInfo.getVolunteerStartDateTime();
LocalDateTime deadline = volunteerStartDateTime.toLocalDate().atStartOfDay();

if (!currentDateTime.isBefore(deadline)) {
throw new IllegalStateException("봉사 시작 일시 자정 전까지만 상태를 변경할 수 있습니다.");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
@Getter
@RequiredArgsConstructor
public enum RecruitStatus {
RECRUITING("모집중"),
CLOSED("마감"),
COMPLETED("종료"),
RECRUITING("모집중", true),
CLOSED("마감", true),
COMPLETED("종료", false),

;
private final String text;

private final boolean changeable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.somemore.recruitboard.service.command;

import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD;
import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD;

import com.somemore.global.exception.BadRequestException;
import com.somemore.global.exception.ExceptionMessage;
import com.somemore.recruitboard.domain.RecruitBoard;
import com.somemore.recruitboard.repository.RecruitBoardRepository;
import com.somemore.recruitboard.usecase.command.DeleteRecruitBoardUseCase;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional
@Service
public class DeleteRecruitBoardService implements DeleteRecruitBoardUseCase {

private final RecruitBoardRepository recruitBoardRepository;

@Override
public void deleteRecruitBoard(UUID centerId, Long recruitBoardId) {
RecruitBoard recruitBoard = recruitBoardRepository.findById(recruitBoardId).orElseThrow(
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
);

validateWriter(recruitBoard, centerId);

recruitBoard.markAsDeleted();
recruitBoardRepository.save(recruitBoard);
}

private void validateWriter(RecruitBoard recruitBoard, UUID centerId) {
if (recruitBoard.isWriter(centerId)) {
return;
}

throw new BadRequestException(UNAUTHORIZED_RECRUIT_BOARD.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
import com.somemore.global.exception.BadRequestException;
import com.somemore.location.usecase.command.UpdateLocationUseCase;
import com.somemore.recruitboard.domain.RecruitBoard;
import com.somemore.recruitboard.domain.RecruitStatus;
import com.somemore.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto;
import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
import com.somemore.recruitboard.repository.RecruitBoardRepository;
import com.somemore.recruitboard.usecase.command.UpdateRecruitBoardUseCase;
import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -23,7 +24,6 @@
public class UpdateRecruitBoardService implements UpdateRecruitBoardUseCase {

private final RecruitBoardRepository recruitBoardRepository;
private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
private final UpdateLocationUseCase updateLocationUseCase;

@Override
Expand All @@ -33,9 +33,7 @@ public void updateRecruitBoard(
UUID centerId,
String imgUrl) {

RecruitBoard recruitBoard = recruitBoardQueryUseCase.findById(recruitBoardId).orElseThrow(
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
);
RecruitBoard recruitBoard = getRecruitBoard(recruitBoardId);
validateWriter(recruitBoard, centerId);
recruitBoard.updateWith(requestDto, imgUrl);

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

RecruitBoard recruitBoard = recruitBoardQueryUseCase.findById(recruitBoardId).orElseThrow(
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
);
RecruitBoard recruitBoard = getRecruitBoard(recruitBoardId);
validateWriter(recruitBoard, centerId);

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

@Override
public void updateRecruitBoardStatus(RecruitStatus status, Long recruitBoardId, UUID centerId,
LocalDateTime currentDateTime) {
RecruitBoard recruitBoard = getRecruitBoard(recruitBoardId);
validateWriter(recruitBoard, centerId);

recruitBoard.changeRecruitStatus(status, currentDateTime);
recruitBoardRepository.save(recruitBoard);
}

private RecruitBoard getRecruitBoard(Long recruitBoardId) {
return recruitBoardRepository.findById(recruitBoardId).orElseThrow(
() -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage())
);
}

private void validateWriter(RecruitBoard recruitBoard, UUID centerId) {
if (recruitBoard.isWriter(centerId)) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.somemore.recruitboard.usecase.command;

import java.util.UUID;

public interface DeleteRecruitBoardUseCase {

void deleteRecruitBoard(UUID centerId, Long recruitBoardId);
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package com.somemore.recruitboard.usecase.command;

import com.somemore.recruitboard.domain.RecruitStatus;
import com.somemore.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto;
import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
import java.time.LocalDateTime;
import java.util.UUID;

public interface UpdateRecruitBoardUseCase {

public void updateRecruitBoard(
void updateRecruitBoard(
RecruitBoardUpdateRequestDto requestDto,
Long recruitBoardId,
UUID centerId,
String imgUrl
);

public void updateRecruitBoardLocation(
void updateRecruitBoardLocation(
RecruitBoardLocationUpdateRequestDto requestDto,
Long recruitBoardId,
UUID centerId
);

void updateRecruitBoardStatus(
RecruitStatus status,
Long recruitBoardId,
UUID centerId,
LocalDateTime currentDateTime
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public static LocalDateTime createUpdateStartDateTime() {
return LocalDateTime.of(2024, 11, 25, 16, 0);
}

public static LocalDateTime createCurrentDateTime() {
// 2024-11-24 T:18:00:00
return LocalDateTime.of(2024, 11, 24, 18, 0);
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package com.somemore.recruitboard.domain;

import static com.somemore.common.fixture.LocalDateTimeFixture.createCurrentDateTime;
import static com.somemore.common.fixture.LocalDateTimeFixture.createStartDateTime;
import static com.somemore.common.fixture.LocalDateTimeFixture.createUpdateStartDateTime;
import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED;
import static com.somemore.recruitboard.domain.RecruitStatus.COMPLETED;
import static com.somemore.recruitboard.domain.RecruitStatus.RECRUITING;
import static com.somemore.recruitboard.domain.VolunteerType.OTHER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class RecruitBoardTest {

Expand Down Expand Up @@ -120,6 +126,70 @@ void isNotWriterWithWrongCenterId() {
assertThat(isWriter).isFalse();
}

@DisplayName("모집글 상태를 모집중에서 모집 마감으로 변경할 수 있다")
@Test
void changeStatusFromRecruitingToClose() {
// given
UUID centerId = UUID.randomUUID();
RecruitBoard recruitBoard = createRecruitBoard(centerId);
RecruitStatus newStatus = CLOSED;
LocalDateTime currentDateTime = createCurrentDateTime();

// when
recruitBoard.changeRecruitStatus(newStatus, currentDateTime);

// then
assertThat(recruitBoard.getRecruitStatus()).isEqualTo(newStatus);
}

@DisplayName("모집글 상태를 모집마감에서 모집중으로 변경할 수 있다")
@Test
void changeStatusFromCloseToRecruiting() {
// given
UUID centerId = UUID.randomUUID();
RecruitBoard recruitBoard = createRecruitBoard(centerId);
LocalDateTime currentDateTime = createCurrentDateTime();
recruitBoard.changeRecruitStatus(CLOSED, currentDateTime);
RecruitStatus newStatus = RECRUITING;

// when
recruitBoard.changeRecruitStatus(newStatus, currentDateTime);

// then
assertThat(recruitBoard.getRecruitStatus()).isEqualTo(newStatus);
}

@DisplayName("모집글 상태는 마감으로 변경할 수 없다")
@Test
void changeStatusWhenInvalidStatus() {
// given
UUID centerId = UUID.randomUUID();
RecruitBoard recruitBoard = createRecruitBoard(centerId);
LocalDateTime currentDateTime = createCurrentDateTime();

// when & then
assertThatThrownBy(() -> recruitBoard.changeRecruitStatus(COMPLETED, currentDateTime))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("봉사 시작일 자정 이후 모집 상태를 변경할 경우 에러가 발생한다")
@ParameterizedTest
@ValueSource(longs = {0, 1})
void changeStatusWhenDeadLineAfter(Long secondsOffset) {
// given
UUID centerId = UUID.randomUUID();
RecruitBoard recruitBoard = createRecruitBoard(centerId);
LocalDateTime deadLineDateTime = recruitBoard.getRecruitmentInfo()
.getVolunteerStartDateTime().toLocalDate().atStartOfDay();
LocalDateTime currentDateTime = deadLineDateTime.plusSeconds(secondsOffset);

// when
// then
assertThatThrownBy(
() -> recruitBoard.changeRecruitStatus(CLOSED, currentDateTime)
).isInstanceOf(IllegalStateException.class);

}

private static RecruitBoard createRecruitBoard(UUID centerId) {
LocalDateTime startDateTime = createStartDateTime();
Expand Down Expand Up @@ -149,4 +219,5 @@ private static RecruitBoard createRecruitBoard(UUID centerId, LocalDateTime star
.recruitmentInfo(recruitmentInfo)
.build();
}

}
Loading