From ffbbc67ad8eaa42006c9a1ceb8ac806558ae23ee Mon Sep 17 00:00:00 2001 From: sangrae Date: Fri, 16 Oct 2026 19:23:17 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=82=AC=EB=AC=BC=ED=95=A8=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=AA=A8=EC=A7=91=20=EA=B8=B0=EA=B0=84=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stream/api/app/event/locker/AppLockerApi.java | 8 +++++--- .../domain/locker/domain/LockerErrorCode.java | 1 + .../event/domain/locker/domain/LockerPeriod.java | 10 ++++++++++ .../service/impl/LockerApplicationServiceImpl.java | 12 ++++++++++++ .../impl/LockerApplicationServiceImplTest.java | 14 ++++++++------ 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/api/app-api/src/main/java/kr/ac/kookmin/stream/api/app/event/locker/AppLockerApi.java b/api/app-api/src/main/java/kr/ac/kookmin/stream/api/app/event/locker/AppLockerApi.java index 8663bd51..cf75b921 100644 --- a/api/app-api/src/main/java/kr/ac/kookmin/stream/api/app/event/locker/AppLockerApi.java +++ b/api/app-api/src/main/java/kr/ac/kookmin/stream/api/app/event/locker/AppLockerApi.java @@ -51,10 +51,12 @@ ApiResponse getSectionLockers( /** 고른 사물함을 신청하고 즉시 배정한다. */ @Operation(summary = "사물함 신청", - description = "게시된 운영 회차에서 고른 사물함을 신청하고 즉시 배정한다. 운영 회차당 한 개만 신청할 수 있고 " - + "신청 후에는 취소하거나 변경할 수 없다. 같은 사물함에 동시에 신청하면 먼저 완료된 신청만 성공한다.") + description = "게시된 운영 회차에서 고른 사물함을 신청하고 즉시 배정한다. 모집 기간 안에서만 받는다. " + + "운영 회차당 한 개만 신청할 수 있고 신청 후에는 취소하거나 변경할 수 없다. " + + "같은 사물함에 동시에 신청하면 먼저 완료된 신청만 성공한다.") @ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"}) - @ApiErrorCode(type = LockerErrorCode.class, codes = {"LOCKER_PERIOD_NOT_FOUND", "LOCKER_ALREADY_ASSIGNED"}) + @ApiErrorCode(type = LockerErrorCode.class, + codes = {"LOCKER_PERIOD_NOT_FOUND", "LOCKER_APPLY_NOT_OPEN", "LOCKER_ALREADY_ASSIGNED"}) ApiResponse apply(AppApiUser apiUser, LockerApplyRequest request); /** 내 사물함 신청 내역. */ diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerErrorCode.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerErrorCode.java index ca2a4b18..213d2239 100644 --- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerErrorCode.java +++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerErrorCode.java @@ -20,6 +20,7 @@ public enum LockerErrorCode implements ErrorCode { LOCKER_PERIOD_ALREADY_ENDED(ErrorStatus.CONFLICT, "종료된 회차는 수정할 수 없습니다."), LOCKER_PERIOD_APPLY_ALREADY_STARTED(ErrorStatus.CONFLICT, "신청이 시작된 회차는 신청 시작일을 바꿀 수 없습니다."), LOCKER_PERIOD_HAS_APPLICATIONS(ErrorStatus.CONFLICT, "신청이 있는 회차는 삭제할 수 없습니다."), + LOCKER_APPLY_NOT_OPEN(ErrorStatus.CONFLICT, "사물함 신청 기간이 아닙니다."), LOCKER_ALREADY_ASSIGNED(ErrorStatus.CONFLICT, "다른 사용자가 먼저 신청한 사물함입니다."); private final int status; diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerPeriod.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerPeriod.java index bf1506ac..e2aef7b6 100644 --- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerPeriod.java +++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/LockerPeriod.java @@ -94,6 +94,16 @@ public LockerPeriodStatus calculateStatus(LocalDateTime now) { return LockerPeriodStatus.IN_PROGRESS; } + /** + * 지금이 신청을 받는 기간인지. 신청 시작 일시부터 마감 일시까지 양 끝을 포함한다. + *

+ * 사용 기간은 보지 않는다. 사용을 시작한 뒤 남은 자리를 추가 모집하려고 마감 일시를 늘리는 운영이 있어 + * 두 기간이 겹칠 수 있다. + */ + public boolean isApplyOpen(LocalDateTime now) { + return !now.isBefore(applyStartAt) && !now.isAfter(applyEndAt); + } + /** * 회차 범위(신청 시작일 ~ 사용 종료일)가 다른 회차와 겹치는지. 회차는 같은 시기에 둘이 돌 수 없다. */ diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImpl.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImpl.java index 1f3f3c0c..beb80eb6 100644 --- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImpl.java +++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImpl.java @@ -30,6 +30,7 @@ class LockerApplicationServiceImpl implements LockerApplicationService { @Transactional public LockerApplicationResult apply(Long memberId, LockerApplyCommand command) { LockerPeriod period = getPublishedPeriod(command.lockerPeriodId()); + requireApplyOpen(period); Locker locker = getSelectableLocker(period.getId(), command.lockerId()); LockerApplication application = lockerApplicationRepository.save( @@ -92,6 +93,17 @@ private LockerPeriod getPublishedPeriod(Long lockerPeriodId) { .orElseThrow(() -> new BusinessException(LockerErrorCode.LOCKER_PERIOD_NOT_FOUND)); } + /** + * 모집 기간 밖에는 신청을 받지 않는다. 게시 여부만 보면 운영진이 게시를 내리기 전까지 마감 뒤에도 신청이 들어오고, + * 사물함은 선착순 즉시 배정이라 그 신청이 남의 자리를 차지한다. 행사 신청({@code EventApplicationServiceImpl})도 + * 같은 자리에서 모집 상태를 본다. + */ + private void requireApplyOpen(LockerPeriod period) { + if (!period.isApplyOpen(LocalDateTime.now())) { + throw new BusinessException(LockerErrorCode.LOCKER_APPLY_NOT_OPEN); + } + } + /** * 해당 운영 회차에 선택할 수 있는 사물함. 구역 조회와 같은 기준({@link Locker#isSelectable})으로 판정한다. *

diff --git a/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImplTest.java b/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImplTest.java index ba7a1246..6937406d 100644 --- a/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImplTest.java +++ b/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerApplicationServiceImplTest.java @@ -41,16 +41,18 @@ class LockerApplicationServiceImplTest { private static final Long MEMBER_ID = 100L; private static final Long LOCKER_ID = 11L; + /** 신청이 모집 기간 안에서만 되므로 기간을 현재 시각 기준으로 잡는다. 고정 날짜로 두면 그 기간이 지난 뒤 테스트가 깨진다. */ private static LockerPeriod period() { + LocalDateTime now = LocalDateTime.now(); return LockerPeriod.of( PERIOD_ID, - 2026, + now.getYear(), 2, - "2026-2학기", - LocalDateTime.of(2026, 8, 20, 10, 0), - LocalDateTime.of(2026, 8, 25, 18, 0), - LocalDate.of(2026, 9, 1), - LocalDate.of(2026, 12, 15), + "%d-2학기".formatted(now.getYear()), + now.minusDays(5), + now.plusDays(5), + now.toLocalDate().plusDays(12), + now.toLocalDate().plusMonths(4), true ); }