diff --git a/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionApi.java b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionApi.java
index 37fe8ae5..1dc041bc 100644
--- a/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionApi.java
+++ b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionApi.java
@@ -4,8 +4,10 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import kr.ac.kookmin.stream.api.admin.AdminApiUser;
import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerPeriodParams;
+import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerStatusChangeRequest;
import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerSectionDetailResponse;
import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerSectionListResponse;
+import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerStatusChangeResponse;
import kr.ac.kookmin.stream.api.common.dto.ApiResponse;
import kr.ac.kookmin.stream.api.common.openapi.ApiErrorCode;
import kr.ac.kookmin.stream.common.CommonErrorCode;
@@ -19,7 +21,7 @@
*
* 구역은 운영 회차에 묶여 있지 않다. 요청의 회차는 "어느 회차의 배정 현황을 볼지"를 정하는 기준이다.
*/
-@Tag(name = "사물함 구역 관리", description = "운영진 사물함 구역 목록·상세 조회")
+@Tag(name = "사물함 구역 관리", description = "운영진 사물함 구역 목록·상세 조회와 사물함 상태 변경")
public interface AdminLockerSectionApi {
/** 구역별 전체·선택 가능·배정 사물함 수. */
@@ -47,4 +49,20 @@ ApiResponse getLockerSection(
Long sectionId,
@ParameterObject LockerPeriodParams params
);
+
+ /** 구역 안의 사물함 여러 개를 같은 상태로 바꾼다. */
+ @Operation(summary = "사물함 상태 일괄 변경",
+ description = "구역 안의 사물함 여러 개를 AVAILABLE(선택가능) 또는 DISABLED(선택불가)로 한 번에 바꾼다. "
+ + "하나라도 바꿀 수 없으면 아무것도 바뀌지 않는다. "
+ + "사용 기간이 끝나지 않은 회차에 배정된 사물함은 이용 중인 학생이 있으므로 DISABLED로 바꿀 수 없고, "
+ + "AVAILABLE로 되돌리는 것은 막지 않는다. "
+ + "응답은 요청한 사물함 전체로, 이미 그 상태였던 사물함도 담긴다. "
+ + "구역 상세가 내려주는 ASSIGNED는 신청으로 생기는 표시용 값이라 이 경로로 바꿀 수 없다.")
+ @ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
+ @ApiErrorCode(type = LockerErrorCode.class, codes = {"LOCKER_NOT_IN_SECTION", "LOCKER_IN_USE"})
+ ApiResponse changeLockerStatus(
+ AdminApiUser apiUser,
+ Long sectionId,
+ LockerStatusChangeRequest request
+ );
}
diff --git a/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionController.java b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionController.java
index a001e5fe..34630947 100644
--- a/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionController.java
+++ b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/AdminLockerSectionController.java
@@ -6,18 +6,24 @@
import java.util.Set;
import kr.ac.kookmin.stream.api.admin.AdminApiUser;
import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerPeriodParams;
+import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerStatusChangeRequest;
import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerSectionDetailResponse;
import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerSectionListResponse;
+import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerStatusChangeResponse;
import kr.ac.kookmin.stream.api.common.dto.ApiResponse;
+import kr.ac.kookmin.stream.event.domain.locker.domain.Locker;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerSectionDetail;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerSectionSummary;
+import kr.ac.kookmin.stream.event.domain.locker.service.AdminLockerService;
import kr.ac.kookmin.stream.event.domain.locker.service.LockerService;
import kr.ac.kookmin.stream.file.domain.File;
import kr.ac.kookmin.stream.file.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -27,6 +33,7 @@
public class AdminLockerSectionController implements AdminLockerSectionApi {
private final LockerService lockerService;
+ private final AdminLockerService adminLockerService;
private final FileService fileService;
@Override
@@ -56,4 +63,16 @@ public ApiResponse getLockerSection(
return ApiResponse.success(
AdminLockerSectionDetailResponse.of(detail, photosById, appliedLockerIds));
}
+
+ @Override
+ @PatchMapping("/{sectionId}/lockers/status")
+ public ApiResponse changeLockerStatus(
+ AdminApiUser apiUser,
+ @PathVariable Long sectionId,
+ @Valid @RequestBody LockerStatusChangeRequest request
+ ) {
+ List lockers = adminLockerService.changeLockerStatus(
+ sectionId, request.lockerIds(), request.lockerStatus());
+ return ApiResponse.success(AdminLockerStatusChangeResponse.from(lockers));
+ }
}
diff --git a/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/request/LockerStatusChangeRequest.java b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/request/LockerStatusChangeRequest.java
new file mode 100644
index 00000000..a1759a0e
--- /dev/null
+++ b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/request/LockerStatusChangeRequest.java
@@ -0,0 +1,20 @@
+package kr.ac.kookmin.stream.api.admin.event.locker.request;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+import kr.ac.kookmin.stream.event.domain.locker.domain.LockerStatus;
+
+/**
+ * 구역 안의 사물함 여러 개를 같은 상태로 바꾼다.
+ *
+ * 구역 상세가 내려주는 {@code ASSIGNED}는 신청으로 생기는 표시용 값이라 이 경로로는 바꿀 수 없다.
+ */
+public record LockerStatusChangeRequest(
+ @NotEmpty(message = "사물함을 하나 이상 선택해 주세요.")
+ List<@NotNull(message = "사물함 ID는 비워 둘 수 없습니다.") Long> lockerIds,
+
+ @NotNull(message = "변경할 상태를 입력해 주세요.")
+ LockerStatus lockerStatus
+) {
+}
diff --git a/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/response/AdminLockerStatusChangeResponse.java b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/response/AdminLockerStatusChangeResponse.java
new file mode 100644
index 00000000..037233ac
--- /dev/null
+++ b/api/admin-api/src/main/java/kr/ac/kookmin/stream/api/admin/event/locker/response/AdminLockerStatusChangeResponse.java
@@ -0,0 +1,25 @@
+package kr.ac.kookmin.stream.api.admin.event.locker.response;
+
+import java.util.List;
+import kr.ac.kookmin.stream.event.domain.locker.domain.Locker;
+import kr.ac.kookmin.stream.event.domain.locker.domain.LockerStatus;
+
+/**
+ * 요청한 사물함 전체를 바뀐 뒤 상태로 돌려준다. 이미 그 상태였던 사물함도 담긴다.
+ */
+public record AdminLockerStatusChangeResponse(List lockers) {
+
+ public static AdminLockerStatusChangeResponse from(List lockers) {
+ return new AdminLockerStatusChangeResponse(lockers.stream()
+ .map(LockerItem::from)
+ .toList());
+ }
+
+ /** 도메인 {@link Locker}와 이름이 겹치지 않도록 {@code LockerItem}으로 둔다. */
+ public record LockerItem(Long lockerId, int lockerNumber, LockerStatus lockerStatus) {
+
+ public static LockerItem from(Locker locker) {
+ return new LockerItem(locker.getId(), locker.getLockerNumber(), locker.getStatus());
+ }
+ }
+}
diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/Locker.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/Locker.java
index 552dc6f8..5bf1ef7b 100644
--- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/Locker.java
+++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/domain/Locker.java
@@ -42,4 +42,17 @@ public static Locker of(
public boolean isSelectable(boolean applied) {
return status == LockerStatus.AVAILABLE && !applied;
}
+
+ /**
+ * 사물함 자체 상태를 바꾼다. 바뀐 값이 있으면 true를 돌려준다.
+ *
+ * 운영진이 여러 칸을 한 번에 바꾸므로, 이미 그 상태인 사물함은 저장하지 않도록 바뀌었는지 알려준다.
+ */
+ public boolean changeStatus(LockerStatus status) {
+ if (this.status == status) {
+ return false;
+ }
+ this.status = status;
+ return true;
+ }
}
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 213d2239..1d8ea824 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
@@ -21,6 +21,8 @@ public enum LockerErrorCode implements ErrorCode {
LOCKER_PERIOD_APPLY_ALREADY_STARTED(ErrorStatus.CONFLICT, "신청이 시작된 회차는 신청 시작일을 바꿀 수 없습니다."),
LOCKER_PERIOD_HAS_APPLICATIONS(ErrorStatus.CONFLICT, "신청이 있는 회차는 삭제할 수 없습니다."),
LOCKER_APPLY_NOT_OPEN(ErrorStatus.CONFLICT, "사물함 신청 기간이 아닙니다."),
+ LOCKER_NOT_IN_SECTION(ErrorStatus.BAD_REQUEST, "해당 구역의 사물함이 아닙니다."),
+ LOCKER_IN_USE(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/repository/LockerApplicationRepository.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerApplicationRepository.java
index 6ee3936d..248f8365 100644
--- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerApplicationRepository.java
+++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerApplicationRepository.java
@@ -1,5 +1,6 @@
package kr.ac.kookmin.stream.event.domain.locker.repository;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -35,6 +36,11 @@ public interface LockerApplicationRepository {
*/
Optional findAppliedLockerId(Long lockerPeriodId, Long memberId);
+ /**
+ * 해당 사물함들의 신청 전체. 어느 운영 회차에 배정됐는지 보려고 읽는다.
+ */
+ List findByLockerIds(Collection lockerIds);
+
/**
* 회원의 사물함 신청 전체를 신청 일시 최신순으로 조회한다.
*/
diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerRepository.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerRepository.java
index 150da354..5ea2fb69 100644
--- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerRepository.java
+++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/repository/LockerRepository.java
@@ -92,4 +92,9 @@ public interface LockerRepository {
* 식별자에 해당하는 사물함. 지난 신청 이력의 사물함 이름을 보여줘야 하므로 삭제된 사물함도 포함한다.
*/
List findLockersByIdsIncludingDeleted(Collection lockerIds);
+
+ /**
+ * 사물함 여러 건을 저장한다. 변경은 호출한 쪽 트랜잭션이 커밋될 때 반영된다.
+ */
+ void saveLockers(List lockers);
}
diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/AdminLockerService.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/AdminLockerService.java
index 65ba2420..ca9df54e 100644
--- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/AdminLockerService.java
+++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/AdminLockerService.java
@@ -1,9 +1,11 @@
package kr.ac.kookmin.stream.event.domain.locker.service;
import java.util.List;
+import kr.ac.kookmin.stream.event.domain.locker.domain.Locker;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriodCreateCommand;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriodSummary;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriodUpdateCommand;
+import kr.ac.kookmin.stream.event.domain.locker.domain.LockerStatus;
/**
* 운영진 사물함 API가 쓰는 서비스.
@@ -36,4 +38,14 @@ public interface AdminLockerService {
* 운영 회차를 소프트 삭제한다. 신청이 한 건이라도 있으면 거부한다.
*/
void deletePeriod(Long lockerPeriodId);
+
+ /**
+ * 구역 안의 사물함 여러 개의 상태를 한 번에 바꾸고, 바뀐 사물함을 돌려준다.
+ *
+ * 하나라도 바꿀 수 없으면 아무것도 바꾸지 않는다. 사용 기간이 끝나지 않은 회차에 배정된 사물함은
+ * 이용 중인 학생이 있으므로 {@code DISABLED}로 바꿀 수 없다.
+ *
+ * @return 요청한 사물함 전체. 이미 그 상태였던 사물함도 포함한다
+ */
+ List changeLockerStatus(Long sectionId, List lockerIds, LockerStatus status);
}
diff --git a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/AdminLockerServiceImpl.java b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/AdminLockerServiceImpl.java
index 6247affd..d2d45112 100644
--- a/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/AdminLockerServiceImpl.java
+++ b/core/domain/event/src/main/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/AdminLockerServiceImpl.java
@@ -1,13 +1,22 @@
package kr.ac.kookmin.stream.event.domain.locker.service.impl;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import kr.ac.kookmin.stream.common.BusinessException;
+import kr.ac.kookmin.stream.event.domain.locker.domain.Locker;
+import kr.ac.kookmin.stream.event.domain.locker.domain.LockerApplication;
+import kr.ac.kookmin.stream.event.domain.locker.domain.LockerApplicationStatus;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerErrorCode;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriod;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriodCreateCommand;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriodSummary;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerPeriodUpdateCommand;
+import kr.ac.kookmin.stream.event.domain.locker.domain.LockerStatus;
import kr.ac.kookmin.stream.event.domain.locker.repository.LockerApplicationRepository;
import kr.ac.kookmin.stream.event.domain.locker.repository.LockerRepository;
import kr.ac.kookmin.stream.event.domain.locker.service.AdminLockerService;
@@ -66,6 +75,66 @@ public void deletePeriod(Long lockerPeriodId) {
}
}
+ @Override
+ @Transactional
+ public List changeLockerStatus(Long sectionId, List lockerIds, LockerStatus status) {
+ List lockers = findLockersInSection(sectionId, lockerIds);
+ if (status == LockerStatus.DISABLED) {
+ requireNotInUse(lockerIds);
+ }
+
+ // 이미 그 상태인 사물함은 저장하지 않는다. 36칸 구역에서 두 칸만 바꿔도 전체를 쓰지 않도록 한다
+ lockerRepository.saveLockers(lockers.stream()
+ .filter(locker -> locker.changeStatus(status))
+ .toList());
+ return lockers;
+ }
+
+ /**
+ * 요청한 사물함을 구역 소속으로 확인하고 돌려준다. 하나라도 그 구역에 없으면 아무것도 바꾸지 않도록 여기서 끊는다.
+ */
+ private List findLockersInSection(Long sectionId, List lockerIds) {
+ Map lockersInSection = lockerRepository.findLockersBySectionId(sectionId).stream()
+ .collect(Collectors.toMap(Locker::getId, Function.identity()));
+
+ return lockerIds.stream()
+ .map(lockerId -> {
+ Locker locker = lockersInSection.get(lockerId);
+ if (locker == null) {
+ throw new BusinessException(LockerErrorCode.LOCKER_NOT_IN_SECTION);
+ }
+ return locker;
+ })
+ .toList();
+ }
+
+ /**
+ * 아직 배정 상태인 신청이 걸린 사물함이 하나라도 있으면 끊는다.
+ *
+ * 배정인지 만료인지는 내 신청 내역 화면과 같은 기준을 써야 하므로 {@link LockerApplicationStatus}에 맡긴다.
+ * 저장소는 신청과 회차를 읽어 오기만 한다.
+ */
+ private void requireNotInUse(List lockerIds) {
+ List applications = lockerApplicationRepository.findByLockerIds(lockerIds);
+ if (applications.isEmpty()) {
+ return;
+ }
+
+ LocalDate today = LocalDate.now();
+ Set assignedPeriodIds = lockerRepository.findPublishedPeriodsByIds(applications.stream()
+ .map(LockerApplication::getLockerPeriodId)
+ .collect(Collectors.toSet())).stream()
+ .filter(period -> LockerApplicationStatus.from(period, today) == LockerApplicationStatus.ASSIGNED)
+ .map(LockerPeriod::getId)
+ .collect(Collectors.toSet());
+
+ boolean inUse = applications.stream()
+ .anyMatch(application -> assignedPeriodIds.contains(application.getLockerPeriodId()));
+ if (inUse) {
+ throw new BusinessException(LockerErrorCode.LOCKER_IN_USE);
+ }
+ }
+
private LockerPeriod getPeriod(Long lockerPeriodId) {
return lockerRepository.findPeriodById(lockerPeriodId)
.orElseThrow(() -> new BusinessException(LockerErrorCode.LOCKER_PERIOD_NOT_FOUND));
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 00f82834..c59c000e 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
@@ -360,6 +360,11 @@ public List findPublishedPeriodsUsageEndingOn(LocalDate usageEndAt
throw new UnsupportedOperationException();
}
+ @Override
+ public void saveLockers(List lockers) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public List findLockersByIdsIncludingDeleted(Collection lockerIds) {
batchLookups++;
@@ -427,6 +432,11 @@ private static final class FakeLockerApplicationRepository implements LockerAppl
private final List saved = new ArrayList<>();
private List memberApplications = List.of();
+ @Override
+ public List findByLockerIds(Collection lockerIds) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public boolean existsByPeriod(Long lockerPeriodId) {
throw new UnsupportedOperationException();
diff --git a/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerServiceImplTest.java b/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerServiceImplTest.java
index 2bee7381..c715f8c0 100644
--- a/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerServiceImplTest.java
+++ b/core/domain/event/src/test/java/kr/ac/kookmin/stream/event/domain/locker/service/impl/LockerServiceImplTest.java
@@ -322,6 +322,11 @@ public List findPublishedPeriodsUsageEndingOn(LocalDate usageEndAt
throw new UnsupportedOperationException();
}
+ @Override
+ public void saveLockers(List lockers) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public List findLockersByIdsIncludingDeleted(Collection lockerIds) {
throw new UnsupportedOperationException();
@@ -349,6 +354,11 @@ public Optional findAppliedLockerId(Long lockerPeriodId, Long memberId) {
// 아래는 사물함 신청(LockerApplicationService)용 메서드라 이 테스트에서는 쓰지 않는다
+ @Override
+ public List findByLockerIds(Collection lockerIds) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public boolean existsByPeriod(Long lockerPeriodId) {
throw new UnsupportedOperationException();
diff --git a/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationJpaRepository.java b/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationJpaRepository.java
index e5694261..254ead19 100644
--- a/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationJpaRepository.java
+++ b/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationJpaRepository.java
@@ -1,5 +1,6 @@
package kr.ac.kookmin.stream.db.event;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -30,6 +31,8 @@ public interface LockerApplicationJpaRepository extends JpaRepository findMemberIdsByLockerPeriodId(@Param("lockerPeriodId") Long lockerPeriodId);
+ List findAllByLockerIdIn(Collection lockerIds);
+
/**
* 회차당 회원 신청은 유니크 제약으로 한 건까지만 존재한다.
*/
diff --git a/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationRepositoryImpl.java b/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationRepositoryImpl.java
index e91dcfce..c8dab7c7 100644
--- a/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationRepositoryImpl.java
+++ b/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerApplicationRepositoryImpl.java
@@ -1,5 +1,6 @@
package kr.ac.kookmin.stream.db.event;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -45,6 +46,16 @@ public Optional findAppliedLockerId(Long lockerPeriodId, Long memberId) {
return lockerApplicationJpaRepository.findLockerIdByLockerPeriodIdAndMemberId(lockerPeriodId, memberId);
}
+ @Override
+ public List findByLockerIds(Collection lockerIds) {
+ if (lockerIds.isEmpty()) {
+ return List.of();
+ }
+ return lockerApplicationJpaRepository.findAllByLockerIdIn(lockerIds).stream()
+ .map(LockerApplicationJpaEntity::toDomain)
+ .toList();
+ }
+
@Override
public List findByMemberId(Long memberId) {
return lockerApplicationJpaRepository.findAllByMemberIdOrderByAppliedAtDescIdDesc(memberId).stream()
diff --git a/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerRepositoryImpl.java b/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerRepositoryImpl.java
index 1e86220b..460d5405 100644
--- a/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerRepositoryImpl.java
+++ b/infrastructure/db/src/main/java/kr/ac/kookmin/stream/db/event/LockerRepositoryImpl.java
@@ -138,6 +138,13 @@ public Optional findLockerById(Long lockerId) {
.map(LockerJpaEntity::toDomain);
}
+ @Override
+ public void saveLockers(List lockers) {
+ lockerJpaRepository.saveAll(lockers.stream()
+ .map(LockerJpaEntity::from)
+ .toList());
+ }
+
@Override
public List findLockersByIdsIncludingDeleted(Collection lockerIds) {
return lockerJpaRepository.findAllByIdIn(lockerIds).stream()