Skip to content

[Feat/#149] 사물함 신청 현황 조회·취소·수동 배정 관리자 API 추가 - #177

Open
sangrae2325 wants to merge 9 commits into
feat/#146-admin-locker-statusfrom
feat/#149-admin-locker-applications
Open

sangrae2325 wants to merge 9 commits into
feat/#146-admin-locker-statusfrom
feat/#149-admin-locker-applications

Conversation

@sangrae2325

@sangrae2325 sangrae2325 commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

운영진 콘솔에서 사물함 신청 현황을 보고, 잘못된 신청을 취소하고, 학생에게 사물함을 직접 배정하는 관리자 API 3개를 추가합니다.

GET    /v1/admin/lockers/applications?lockerPeriodId=&keyword=&page=&size=
DELETE /v1/admin/lockers/applications/{lockerApplicationId}
POST   /v1/admin/lockers/applications      { lockerPeriodId, memberId, lockerId }

#146(상태 일괄 변경)에 이어지는 사물함 관리자 API의 네 번째 작업입니다.

❓ 왜 해결해야 하나요?

누가 어느 칸을 썼는지 볼 방법도, 잘못 들어간 신청을 되돌릴 방법도 DB 직접 수정뿐입니다.

그리고 locker_applications에 취소를 표현할 방법이 없습니다. 지금 구조에서 취소는 곧 행 삭제라 이력이 남지 않고, 유니크 2개(회차+회원, 회차+사물함)가 취소된 신청까지 붙잡아 같은 학생·같은 칸에 다시 배정할 수 없습니다.

#146이 "이용 중인 칸은 사용 중지할 수 없다"로 막아 둔 것도 이 PR의 취소가 있어야 풀립니다 — 배정된 칸이 고장 나면 취소 → 사용 중지 순서로 처리합니다.

⭐ 어떻게 해결했나요?

커밋 8개로 나눴고 각각 단독으로 컴파일됩니다.

1. 마이그레이션(V26) — is_canceled를 넣고 유니크 2개를 generated column으로 바꿉니다. flyway-migration.md 3-4절의 소프트 삭제 패턴을 is_deleted 대신 is_canceled에 적용한 것입니다(V1·V16·V20과 같은 형태).

status VARCHAR가 아니라 불리언입니다. 승인 절차 없이 신청이 곧 배정이라 값이 둘뿐이고, "기간종료"는 저장하지 않고 회차로 계산합니다(LockerApplicationStatus). 무엇보다 표시용 LockerApplicationStatus와 이름이 겹치는 두 번째 상태 enum을 만들지 않아도 됩니다.

2. 도메인 — LockerApplication.cancel(). 이미 취소된 신청은 거부합니다.

3. 기존 조회에서 취소 제외 — 취소 컬럼을 만들면서 생긴 숙제입니다. 안 하면 취소를 만들어놓고 아무도 안 봐서 재배정이 실제로는 안 됩니다.

쿼리 안 거르면
existsByLockerPeriodIdAndLockerId 취소된 칸을 다시 신청 못 한다
findLockerIdsByLockerPeriodId 취소된 칸이 계속 배정됨으로 보인다
findMemberIdsByLockerPeriodId 취소한 학생이 마감 임박 알림을 못 받는다
existsByLockerPeriodIdInAndLockerIdIn 취소된 신청이 사용 중지를 막는다 (#146)
findLockerIdByLockerPeriodIdAndMemberId 취소된 칸이 내 사물함으로 보인다
findAllByMemberIdOrderByAppliedAtDescIdDesc 취소 건이 학생에게 내려간다

existsByLockerPeriodId(회차 삭제 가드)만 일부러 안 거릅니다. 취소 행도 그 회차를 가리키므로, 거르면 회차를 지웠을 때 가리킬 대상이 없어집니다.

4~5. 저장소·서비스 — 신청 현황 페이징 조회와 단건 조회를 더하고, AdminLockerService에 세 동작을 넣습니다. 취소와 배정은 사용 기간이 끝난 회차를 같은 이유로 막습니다 — 취소해도 되돌릴 자리가 없고, 배정해도 쓸 수 없습니다.

6. 알림 — 운영진이 한 일은 학생 화면이 조용히 바뀌므로 알림이 유일한 전달 수단입니다.

타입 문구
LOCKER_ASSIGNED_BY_ADMIN "운영진이 사물함을 배정했어요."
LOCKER_ASSIGNMENT_CANCELED "사물함 배정이 취소됐어요." / "신청 기간이 남아 있으면 다시 신청할 수 있어요."

기존 LOCKER_ASSIGNED("사물함을 신청했어요")는 학생이 직접 신청한 경우로 남깁니다.

7~8. API 계층 — 신청 현황은 AdminLockerApplicationSearchUseCase가 회원을 붙이고(AdminFeeSearchUseCase와 같은 흐름), 수동 배정은 AdminLockerAssignUseCase가 PayerService.validatePayer로 납부자인지 확인합니다(RentalApplyUseCase 선례). core:domain:event는 회비를 모릅니다.

쓰기 두 개는 응답 본문이 없습니다. 결과는 신청 현황을 다시 조회해 받습니다.

🧩 이 PR의 한계 & 트레이드오프

  • V26은 사실상 placeholder입니다. main이 V19까지인데 사물함 스택이 V20·V21, 행사 스택([Refactor/#133] 행사 target·강제 마감 제거 및 삭제된 행사 신청 내역 숨김 #134·[Feat/#135] 관리자 행사 등록·조회·수정 API 추가 #136·[Feat/#137] 관리자 행사 공개 상태 변경·삭제·신청 현황 API 추가 #138)도 V20·V21·V22, #140이 V22, #167이 V23~V25를 들고 있습니다. V20·V21에 주장이 셋, V22에 둘입니다. 앞이 정리되면서 밀리면 이 번호도 올려야 합니다
  • 유니크를 갈아끼우는 동안 동시 신청이 뚫릴 수 있습니다. DROP INDEX 직후 CREATE UNIQUE INDEX 사이에 같은 칸 신청이 두 건 들어오면 둘 다 커밋되고 그 뒤 인덱스 생성이 실패합니다. 신청 기간 밖에 적용해야 합니다 — 마이그레이션 주석에 적었습니다
  • 취소하면 학생의 지난 이력에서도 사라집니다. 잘못 들어온 신청을 되돌리는 용도라 괜찮다고 봤지만, 이미 기간이 끝난 신청을 취소하는 일이 생기면 다시 봐야 합니다
  • 사용 중지된 칸·없는 칸에 배정하면 메시지가 정확하지 않습니다. 각각 "다른 사용자가 먼저 신청한 사물함입니다", "해당 구역의 사물함이 아닙니다"가 나갑니다. 화면이 선택 가능한 칸만 누르게 해서 실행되지 않는 경로라 전용 에러 코드를 만들지 않았습니다
  • 테스트 코드는 없습니다. AGENTS.md 원칙에 따랐습니다

⛓️ 기존 기능에 미치는 영향

  • 학생 응답 모양은 바뀌지 않습니다. 명세의 배정 상태 뱃지가 미신청·배정완료·기간종료 셋뿐이라 취소 뱃지가 없습니다. 취소 건을 학생 응답에서 빼면 미신청으로 보이고, 기간이 남아 있으면 다시 신청할 수 있습니다. 표시용 LockerApplicationStatus와 [Feat/#72] 내 배정 사물함 조회 API 추가 #73(내 배정 사물함 조회)을 건드리지 않습니다
  • 동시 신청 시 500이 나던 것이 409로 바뀝니다. save()가 사물함 유니크만 변환하고 회원 유니크는 그대로 던지고 있었습니다. 학생이 같은 회차에 두 번 신청하는 경로에도 같은 구멍이 있었는데 함께 막혔습니다
  • 기존 신청 조회 6개에 is_canceled = 0이 붙습니다. 지금은 취소된 행이 없어 결과가 같습니다
  • @ApiErrorCode가 늘지만 전부 새 엔드포인트의 것입니다

🔀 Edge Case & 실패 시나리오

상황 동작
없는 회차로 현황 조회 LOCKER_PERIOD_NOT_FOUND (404)
lockerPeriodId 누락 INVALID_INPUT (400) — @RequestParam이 아니라 LockerPeriodParams로 받아 500을 피한다
키워드에 맞는 회원이 없음 빈 페이지 (쿼리를 더 타지 않는다)
신청이 0건인 페이지 사물함 조회를 건너뛴다
탈퇴한 회원의 지난 신청 name·studentId가 null
철거된 사물함 lockerLabel이 null이 아니라 이름을 그대로 보여준다 (삭제된 사물함까지 읽는다)
없는 신청 취소 LOCKER_APPLICATION_NOT_FOUND (404)
이미 취소된 신청 취소 LOCKER_APPLICATION_ALREADY_CANCELED (409)
사용 기간이 끝난 회차의 신청 취소·배정 LOCKER_PERIOD_ALREADY_ENDED (409)
납부자가 아닌 학생에게 배정 MEMBER_IS_NOT_PAYER (400)
이미 칸을 가진 학생에게 배정 MEMBER_ALREADY_HAS_LOCKER (409)
이미 신청된 칸에 배정 LOCKER_ALREADY_ASSIGNED (409)
취소된 칸에 다시 배정 통과 — generated column 유니크가 취소 행을 비켜준다
신청 기간 밖에 수동 배정 통과 — 기간은 보지 않는다

📋 검토한 대안과 선택 이유

  • LockerApplicationStatus에 CANCELED를 더하기: 처음엔 그렇게 적었다가 접었습니다. #73이 "배정 상태는 운영 회차가 판정한다"를 전제로 from(period, today)를 세웠는데, 취소를 넣으면 시그니처와 전제가 모두 바뀌고 테스트·응답·#146의 이용 중 판정이 함께 바뀝니다. 앱도 모르는 enum 값을 받습니다. 명세에 취소 뱃지가 없어 학생에게는 "미신청"으로 보이면 충분합니다
  • status VARCHAR(20)으로 두기: 값이 둘뿐이라 불리언으로 충분하고, 저장소의 소프트 삭제 패턴과 모양이 같습니다. 승인 절차가 생겨 상태가 셋 이상 필요해지면 그때 컬럼을 바꿉니다
  • 수동 배정에 학번을 받기: MemberService에 조회 메서드를 추가해야 해서 다른 도메인을 건드립니다. 관리자 화면이 "학생회비 납부자 목록에서 이름 또는 학번으로 검색해 선택"하는 구조라, 검색은 #166의 납부자 목록 조회를 쓰고 여기서는 식별자만 받습니다
  • 납부자 확인을 사물함 서비스에서: 서비스끼리 부르지 않는 규칙(architecture.md 레이어 규칙 4)이라 api:admin-api의 UseCase에서 묶었습니다. UseCase에 @Transactional은 걸지 않았습니다 — 확인은 읽기이고 그 전에 쓰는 것이 없어 실패해도 되돌릴 것이 없습니다
  • 신청 현황 정렬용 인덱스를 추가하기: 목록은 WHERE locker_period_id = ? ORDER BY applied_at DESC인데, 새 인덱스 (locker_period_id, locker_id, is_canceled)는 WHERE만 받고 정렬은 받지 못해 filesort가 납니다. (locker_period_id, applied_at)을 더하면 없앨 수 있지만, 한 회차의 신청 수 상한이 구역 전체 칸 수(현재 310칸) 라 정렬 비용이 의미가 없어 넣지 않았습니다. 회차당 행이 크게 늘면 다시 봅니다
  • 신청을 사물함 식별자로 먼저 찾기: locker_applications에 locker_id로 시작하는 인덱스가 없어 이력 전체를 훑습니다. 유니크를 generated column으로 옮기면서 (locker_period_id, locker_id, is_canceled) 인덱스를 다시 만들어 메웠습니다
  • assignLocker가 만든 신청을 돌려주기: 응답이 Void라 아무도 쓰지 않아 void로 뒀습니다. 화면이 배정 후 "방금 추가된 줄"을 짚어야 한다면 되돌리겠습니다

💬 리뷰 포인트

  • [r] 취소하면 학생 화면이 "미신청"으로 돌아갑니다. 명세에 취소 뱃지가 없어 고른 방향인데, 학생이 "취소됐다"를 알림으로만 알게 됩니다. 이 정도로 충분한지 봐주세요
  • [c] 회차 삭제 가드만 취소 행을 셉니다. 취소뿐인 회차는 삭제가 막힙니다. 유령 참조를 막으려고 그렇게 뒀는데, 운영에서 걸리적거리면 바꾸겠습니다
  • [a] 수동 배정은 신청 기간을 보지 않습니다. "신청을 놓친 학생"과 "자리를 옮겨야 하는 학생"이 주 용도라 기간이 끝난 뒤가 오히려 주 사용 시점이라고 봤습니다

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sangrae2325
sangrae2325 force-pushed the feat/#149-admin-locker-applications branch from 7623091 to dba10fd Compare October 29, 2026 04:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

사물함 신청 현황 조회·취소·수동 배정 관리자 API 추가

1 participant