Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7e19d2e
fix: 사물함 신청 모집 기간 검증 추가
sangrae2325 Oct 16, 2026
92c0f76
fix: 사용이 끝난 사물함 회차는 신청을 받지 않도록 방어
sangrae2325 Oct 27, 2026
361105b
fix: 사물함 신청 마감 판정의 기준 시각을 신청 시각과 일치시키고 초 단위로 비교
sangrae2325 Oct 27, 2026
a7e7080
refactor: 사물함 신청 기간 검증 메서드명을 validate~로 통일
sangrae2325 Oct 27, 2026
65e4f9a
test: 사물함 신청 테스트의 회차 픽스처를 고정 날짜로 변경
sangrae2325 Oct 27, 2026
b064fed
docs: 사물함 구역 조회 명세에 모집 기간 조건 보강
sangrae2325 Oct 27, 2026
f12f495
feat: 사물함 구역 사진 여러 장 지원
sangrae2325 Oct 17, 2026
cad1e8e
feat: 사물함 구역 목록에 회차 배정 수 추가
sangrae2325 Oct 17, 2026
d495f00
feat: 사물함 구역 목록·상세 관리자 API 추가
sangrae2325 Oct 18, 2026
5a348b6
refactor: 구역 상세의 칸 상태 판정을 Locker.isSelectable로 통일
sangrae2325 Oct 27, 2026
768a0fd
refactor: 값 두 개를 받는 응답 팩토리를 of로 변경
sangrae2325 Oct 27, 2026
0fb9a88
docs: V21 마이그레이션에 적용 전 확인 사항 주석 추가
sangrae2325 Oct 27, 2026
5d5c1a2
refactor: 구역 사진 식별자 조회를 LockerSectionDetail로 모음
sangrae2325 Oct 27, 2026
f9a9030
feat: 사물함 상태 변경 도메인 동작 추가
sangrae2325 Oct 18, 2026
5bcfc2e
feat: 사물함 상태 변경 저장소 경로 추가
sangrae2325 Oct 18, 2026
4c5b2ef
feat: 사물함 상태 일괄 변경 서비스 추가
sangrae2325 Oct 18, 2026
9654f0c
feat: 사물함 상태 일괄 변경 관리자 API 추가
sangrae2325 Oct 18, 2026
e39dca0
refactor: 사물함 상태 변경 응답을 없애고 변경 동작을 단순화
sangrae2325 Oct 27, 2026
5022171
refactor: 사물함 사용 가능 판정을 LockerStatus.isUsable로 모음
sangrae2325 Oct 27, 2026
a54d64c
refactor: 사물함 이용 중 판정을 회차 먼저 좁히는 순서로 변경
sangrae2325 Oct 27, 2026
e09fccc
refactor: 사물함 상태 변경 서비스의 private 메서드명을 파일 관행에 맞춤
sangrae2325 Oct 27, 2026
92b7df2
fix: 사물함 상태 변경이 삭제 표시를 되돌리지 않도록 @DynamicUpdate 적용
sangrae2325 Oct 27, 2026
09061da
fix: 사물함 상태 변경 요청의 중복 식별자 제거
sangrae2325 Oct 27, 2026
5cb627d
fix: 사물함 상태 변경에서 없는 구역을 404로 응답하고 요청한 사물함만 조회
sangrae2325 Oct 27, 2026
d20a6ad
docs: 사물함 상태 변경이 못 막는 경합과 배정 집계 근거 정리
sangrae2325 Oct 27, 2026
bc008a4
feat: 사물함 신청에 취소 여부와 활성 유니크 추가
sangrae2325 Oct 27, 2026
503e569
feat: 사물함 신청 취소 도메인 동작 추가
sangrae2325 Oct 28, 2026
56cdbf7
fix: 취소된 사물함 신청을 기존 조회에서 제외
sangrae2325 Oct 28, 2026
a96d34a
feat: 사물함 신청 현황 조회 저장소 경로 추가
sangrae2325 Oct 28, 2026
e194999
feat: 사물함 신청 현황·취소·수동 배정 서비스 추가
sangrae2325 Oct 28, 2026
00e90f3
feat: 사물함 수동 배정·배정 취소 알림 추가
sangrae2325 Oct 28, 2026
3e56620
feat: 사물함 신청 현황 조회 관리자 API 추가
sangrae2325 Oct 28, 2026
dba10fd
feat: 사물함 신청 취소·수동 배정 관리자 API 추가
sangrae2325 Oct 28, 2026
d13e907
refactor: 요약과 겹치는 신청 API 주석 정리
sangrae2325 Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package kr.ac.kookmin.stream.api.admin.event.locker;

import io.swagger.v3.oas.annotations.Operation;
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.LockerAssignRequest;
import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerPeriodParams;
import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerApplicationSearchResponse;
import kr.ac.kookmin.stream.api.common.dto.ApiResponse;
import kr.ac.kookmin.stream.api.common.dto.PageParams;
import kr.ac.kookmin.stream.api.common.dto.PageResponse;
import kr.ac.kookmin.stream.api.common.openapi.ApiErrorCode;
import kr.ac.kookmin.stream.common.CommonErrorCode;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerErrorCode;
import kr.ac.kookmin.stream.welfare.domain.fee.domain.FeeErrorCode;
import org.springdoc.core.annotations.ParameterObject;

/**
* 운영진 사물함 신청 API의 문서 명세. 구현은 {@link AdminLockerApplicationController}가 맡는다.
* <p>
* 접근은 {@code ADMIN} role이면 된다. 부서 제한은 두지 않았다.
*/
@Tag(name = "사물함 신청 관리", description = "운영진 사물함 신청 현황 조회·취소·수동 배정")
public interface AdminLockerApplicationApi {

/** 신청자 이름·학번과 배정된 칸. 취소된 신청도 담는다. */
@Operation(summary = "사물함 신청 현황 조회",
description = "운영 회차의 신청을 신청 일시 최신순으로 조회한다. 이름·학번 일부를 keyword로 넘기면 그 회원만 본다. "
+ "운영진이 취소한 신청도 담기며 canceled로 구분한다. 학생 화면에서는 취소된 신청이 보이지 않는다. "
+ "탈퇴한 회원의 지난 신청은 name·studentId가 null이고, 철거된 사물함은 lockerLabel이 null이다.")
@ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
@ApiErrorCode(type = LockerErrorCode.class, codes = {"LOCKER_PERIOD_NOT_FOUND"})
ApiResponse<PageResponse<AdminLockerApplicationSearchResponse>> getApplications(
AdminApiUser apiUser,
@ParameterObject LockerPeriodParams params,
String keyword,
@ParameterObject PageParams pageParams
);

@Operation(summary = "사물함 신청 취소",
description = "운영진이 신청을 취소한다. 행은 남기고 취소 표시만 바꿔 같은 학생·같은 칸에 다시 배정할 수 있다. "
+ "학생 화면에서는 그 신청이 사라져 미신청으로 보이고, 취소됐다는 사실은 알림으로 전달한다. "
+ "사용 기간이 끝난 회차의 신청은 되돌릴 자리가 없어 거부한다.")
@ApiErrorCode(type = LockerErrorCode.class,
codes = {"LOCKER_APPLICATION_NOT_FOUND", "LOCKER_PERIOD_NOT_FOUND", "LOCKER_PERIOD_ALREADY_ENDED",
"LOCKER_APPLICATION_ALREADY_CANCELED"})
ApiResponse<Void> cancelApplication(AdminApiUser apiUser, Long lockerApplicationId);

@Operation(summary = "사물함 수동 배정",
description = "운영진이 학생회비 납부자에게 사물함을 직접 배정한다. 신청 기간은 보지 않는다 — 신청을 놓쳤거나 "
+ "자리를 옮겨야 하는 학생에게 쓰는 기능이라 기간이 끝난 뒤가 오히려 주 사용 시점이다. "
+ "사용 기간이 끝난 회차와 이미 신청된 칸은 거부하고, 한 학생이 한 회차에 두 칸을 가질 수 없다. "
+ "응답 본문이 없으므로 결과는 신청 현황을 다시 조회해 받는다.")
@ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
@ApiErrorCode(type = FeeErrorCode.class, codes = {"MEMBER_IS_NOT_PAYER"})
@ApiErrorCode(type = LockerErrorCode.class,
codes = {"LOCKER_PERIOD_NOT_FOUND", "LOCKER_PERIOD_ALREADY_ENDED", "LOCKER_NOT_IN_SECTION",
"LOCKER_ALREADY_ASSIGNED", "MEMBER_ALREADY_HAS_LOCKER"})
ApiResponse<Void> assignLocker(AdminApiUser apiUser, LockerAssignRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package kr.ac.kookmin.stream.api.admin.event.locker;

import jakarta.validation.Valid;
import kr.ac.kookmin.stream.api.admin.AdminApiUser;
import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerAssignRequest;
import kr.ac.kookmin.stream.api.admin.event.locker.request.LockerPeriodParams;
import kr.ac.kookmin.stream.api.admin.event.locker.response.AdminLockerApplicationSearchResponse;
import kr.ac.kookmin.stream.api.admin.event.locker.usecase.AdminLockerApplicationSearchUseCase;
import kr.ac.kookmin.stream.api.admin.event.locker.usecase.AdminLockerAssignUseCase;
import kr.ac.kookmin.stream.api.admin.event.locker.usecase.MemberLockerApplication;
import kr.ac.kookmin.stream.api.common.dto.ApiResponse;
import kr.ac.kookmin.stream.api.common.dto.PageParams;
import kr.ac.kookmin.stream.api.common.dto.PageResponse;
import kr.ac.kookmin.stream.common.PageResult;
import kr.ac.kookmin.stream.event.domain.locker.service.AdminLockerService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1/admin/lockers/applications")
@RequiredArgsConstructor
public class AdminLockerApplicationController implements AdminLockerApplicationApi {

private final AdminLockerApplicationSearchUseCase adminLockerApplicationSearchUseCase;
private final AdminLockerAssignUseCase adminLockerAssignUseCase;
private final AdminLockerService adminLockerService;

@Override
@GetMapping
public ApiResponse<PageResponse<AdminLockerApplicationSearchResponse>> getApplications(
AdminApiUser apiUser,
@Valid @ModelAttribute LockerPeriodParams params,
@RequestParam(required = false) String keyword,
@Valid @ModelAttribute PageParams pageParams
) {
PageResult<MemberLockerApplication> result = adminLockerApplicationSearchUseCase.search(
params.lockerPeriodId(), keyword, pageParams.toOffset());

return ApiResponse.success(PageResponse.from(result, vo -> AdminLockerApplicationSearchResponse.of(
vo.result().application(), vo.result().locker(), vo.member())));
}

@Override
@DeleteMapping("/{lockerApplicationId}")
public ApiResponse<Void> cancelApplication(AdminApiUser apiUser, @PathVariable Long lockerApplicationId) {
adminLockerService.cancelApplication(lockerApplicationId);
return ApiResponse.success();
}

@Override
@PostMapping
public ApiResponse<Void> assignLocker(AdminApiUser apiUser, @Valid @RequestBody LockerAssignRequest request) {
adminLockerAssignUseCase.assign(request.lockerPeriodId(), request.memberId(), request.lockerId());
return ApiResponse.success();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package kr.ac.kookmin.stream.api.admin.event.locker;

import io.swagger.v3.oas.annotations.Operation;
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.common.dto.ApiResponse;
import kr.ac.kookmin.stream.api.common.openapi.ApiErrorCode;
import kr.ac.kookmin.stream.common.CommonErrorCode;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerErrorCode;
import org.springdoc.core.annotations.ParameterObject;

/**
* 운영진 사물함 구역 API의 문서 명세. 구현은 {@link AdminLockerSectionController}가 맡는다.
* <p>
* 접근은 {@code ADMIN} role이면 된다. 부서 제한은 두지 않았다.
* <p>
* 구역은 운영 회차에 묶여 있지 않다. 요청의 회차는 "어느 회차의 배정 현황을 볼지"를 정하는 기준이다.
*/
@Tag(name = "사물함 구역 관리", description = "운영진 사물함 구역 목록·상세 조회와 사물함 상태 변경")
public interface AdminLockerSectionApi {

/** 구역별 전체·선택 가능·배정 사물함 수. */
@Operation(summary = "사물함 구역 목록 조회",
description = "운영 회차를 기준으로 구역별 전체 사물함 수, 선택 가능한 사물함 수, 배정된 사물함 수를 조회한다. "
+ "사용 중지된 사물함과 그 회차에 배정된 사물함은 선택 가능 수에서 빠진다. "
+ "배정 수는 배정된 뒤 사용 중지된 사물함도 센다. 구역은 식별자 오름차순으로 반환한다.")
@ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
@ApiErrorCode(type = LockerErrorCode.class, codes = {"LOCKER_PERIOD_NOT_FOUND"})
ApiResponse<AdminLockerSectionListResponse> getLockerSections(
AdminApiUser apiUser,
@ParameterObject LockerPeriodParams params
);

/** 구역의 칸 배치·사진과 사물함별 상태. */
@Operation(summary = "사물함 구역 상세 조회",
description = "운영 회차를 기준으로 구역의 칸 배치 구조(layout), 구역 사진, 사물함별 상태를 조회한다. "
+ "layout은 저장된 원문을 그대로 돌려주며 칸 번호는 lockers[].lockerNumber와 같다. "
+ "배치 구조를 아직 등록하지 않은 구역이면 layout이 null이고 photos가 빈 배열이다. "
+ "사물함 상태는 물리 상태와 그 회차의 배정 여부를 합친 값이고, 배정된 뒤 사용 중지된 칸은 ASSIGNED다.")
@ApiErrorCode(type = CommonErrorCode.class, codes = {"INVALID_INPUT"})
@ApiErrorCode(type = LockerErrorCode.class, codes = {"LOCKER_PERIOD_NOT_FOUND", "LOCKER_SECTION_NOT_FOUND"})
ApiResponse<AdminLockerSectionDetailResponse> getLockerSection(
AdminApiUser apiUser,
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<Void> changeLockerStatus(
AdminApiUser apiUser,
Long sectionId,
LockerStatusChangeRequest request
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package kr.ac.kookmin.stream.api.admin.event.locker;

import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
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.common.dto.ApiResponse;
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;

@RestController
@RequestMapping("/v1/admin/lockers/sections")
@RequiredArgsConstructor
public class AdminLockerSectionController implements AdminLockerSectionApi {

private final LockerService lockerService;
private final AdminLockerService adminLockerService;
private final FileService fileService;

@Override
@GetMapping
public ApiResponse<AdminLockerSectionListResponse> getLockerSections(
AdminApiUser apiUser,
@Valid @ModelAttribute LockerPeriodParams params
) {
List<LockerSectionSummary> sections = lockerService.getSections(params.lockerPeriodId());
return ApiResponse.success(AdminLockerSectionListResponse.from(sections));
}

@Override
@GetMapping("/{sectionId}")
public ApiResponse<AdminLockerSectionDetailResponse> getLockerSection(
AdminApiUser apiUser,
@PathVariable Long sectionId,
@Valid @ModelAttribute LockerPeriodParams params
) {
Long lockerPeriodId = params.lockerPeriodId();
LockerSectionDetail detail = lockerService.getSectionDetail(lockerPeriodId, sectionId);
Map<Long, File> photosById = fileService.findAllByIdIn(detail.photoFileIds());
Set<Long> appliedLockerIds = lockerService.getAppliedLockerIds(lockerPeriodId);

return ApiResponse.success(
AdminLockerSectionDetailResponse.of(detail, photosById, appliedLockerIds));
}

@Override
@PatchMapping("/{sectionId}/lockers/status")
public ApiResponse<Void> changeLockerStatus(
AdminApiUser apiUser,
@PathVariable Long sectionId,
@Valid @RequestBody LockerStatusChangeRequest request
) {
adminLockerService.changeLockerStatus(sectionId, request.distinctLockerIds(), request.lockerStatus());
return ApiResponse.success();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.ac.kookmin.stream.api.admin.event.locker.request;

import jakarta.validation.constraints.NotNull;

/**
* 운영진이 학생에게 사물함을 직접 배정한다.
* <p>
* 회원은 식별자로 받는다. 운영진 화면이 학생회비 납부자 목록을 이름·학번으로 검색해 고르는 구조라, 거기서 받은
* 식별자를 그대로 넘기면 된다.
*/
public record LockerAssignRequest(

@NotNull(message = "사물함 운영 회차를 입력해 주세요.")
Long lockerPeriodId,

@NotNull(message = "배정할 학생을 선택해 주세요.")
Long memberId,

@NotNull(message = "배정할 사물함을 선택해 주세요.")
Long lockerId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kr.ac.kookmin.stream.api.admin.event.locker.request;

import jakarta.validation.constraints.NotNull;

/**
* 운영진 조회의 기준 회차. 구역 조회와 신청 현황 조회가 함께 쓴다. 구역은 운영 회차에 묶여 있지 않고,
* 회차는 "어느 회차의 배정 현황을 볼지"를 정한다.
* <p>
* 필드가 하나뿐이지만 {@code @RequestParam}으로 받지 않는다. {@code GlobalExceptionHandler}가
* {@code MissingServletRequestParameterException}을 다루지 않아 값이 빠지면 500이 되고,
* {@code @Valid @ModelAttribute}여야 400 {@code INVALID_INPUT}으로 나간다.
*
* @param lockerPeriodId 조회할 사물함 운영 회차 식별자
*/
public record LockerPeriodParams(

@NotNull(message = "사물함 운영 회차를 입력해 주세요.")
Long lockerPeriodId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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;

/**
* 구역 안의 사물함 여러 개를 같은 상태로 바꾼다.
* <p>
* 구역 상세가 내려주는 {@code ASSIGNED}는 신청으로 생기는 표시용 값이라 이 경로로는 바꿀 수 없다.
*/
public record LockerStatusChangeRequest(
@NotEmpty(message = "사물함을 하나 이상 선택해 주세요.")
List<@NotNull(message = "사물함 ID는 비워 둘 수 없습니다.") Long> lockerIds,

@NotNull(message = "변경할 상태를 입력해 주세요.")
LockerStatus lockerStatus
) {

/**
* 중복을 걷어낸 사물함 식별자. 같은 칸이 여러 번 와도 결과는 같지만, 그대로 두면 이용 중 검사의 {@code IN} 절이
* 구역의 칸 수보다 커진다. 중복을 없애면 상한이 그 구역의 칸 수가 되어 따로 숫자를 둘 필요가 없다.
*/
public List<Long> distinctLockerIds() {
return lockerIds.stream().distinct().toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package kr.ac.kookmin.stream.api.admin.event.locker.response;

import java.time.LocalDateTime;
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.member.domain.member.domain.Member;

/**
* 운영진 신청 현황의 한 줄.
*
* @param name 신청자 이름. 탈퇴한 회원의 지난 신청이면 {@code null}
* @param studentId 신청자 학번. 탈퇴한 회원의 지난 신청이면 {@code null}
* @param lockerLabel 배정된 사물함 이름. 철거된 사물함도 이름은 보여준다
* @param canceled 운영진이 취소했는지. 기간이 끝났는지는 회차의 사용 종료일로 화면이 가른다
*/
public record AdminLockerApplicationSearchResponse(
Long lockerApplicationId,
Long memberId,
String name,
String studentId,
String lockerLabel,
LocalDateTime appliedAt,
boolean canceled
) {

public static AdminLockerApplicationSearchResponse of(
LockerApplication application, Locker locker, Member member
) {
return new AdminLockerApplicationSearchResponse(
application.getId(),
application.getMemberId(),
member == null ? null : member.getName(),
member == null ? null : member.getStudentId(),
locker == null ? null : locker.getLockerLabel(),
application.getAppliedAt(),
application.isCanceled()
);
}
}
Loading