Repository navigation
[Feat/#149] 사물함 신청 현황 조회·취소·수동 배정 관리자 API 추가 - #177
Open
sangrae2325 wants to merge 9 commits into
Open
sangrae2325 wants to merge 9 commits into
sangrae2325 wants to merge 9 commits into
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
github-actions
Bot
requested review from
jjunh33,
leegain1,
tnals0924 and
xeoxxn
October 28, 2026 09:04
sangrae2325
force-pushed
the
feat/#149-admin-locker-applications
branch
from
October 29, 2026 04:58
7623091 to
dba10fd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
운영진 콘솔에서 사물함 신청 현황을 보고, 잘못된 신청을 취소하고, 학생에게 사물함을 직접 배정하는 관리자 API 3개를 추가합니다.
#146(상태 일괄 변경)에 이어지는 사물함 관리자 API의 네 번째 작업입니다.
❓ 왜 해결해야 하나요?
누가 어느 칸을 썼는지 볼 방법도, 잘못 들어간 신청을 되돌릴 방법도 DB 직접 수정뿐입니다.
그리고
locker_applications에 취소를 표현할 방법이 없습니다. 지금 구조에서 취소는 곧 행 삭제라 이력이 남지 않고, 유니크 2개(회차+회원,회차+사물함)가 취소된 신청까지 붙잡아 같은 학생·같은 칸에 다시 배정할 수 없습니다.#146이 "이용 중인 칸은 사용 중지할 수 없다"로 막아 둔 것도 이 PR의 취소가 있어야 풀립니다 — 배정된 칸이 고장 나면
취소 → 사용 중지순서로 처리합니다.⭐ 어떻게 해결했나요?
커밋 8개로 나눴고 각각 단독으로 컴파일됩니다.
1. 마이그레이션(V26) —
is_canceled를 넣고 유니크 2개를 generated column으로 바꿉니다.flyway-migration.md3-4절의 소프트 삭제 패턴을is_deleted대신is_canceled에 적용한 것입니다(V1·V16·V20과 같은 형태).status VARCHAR가 아니라 불리언입니다. 승인 절차 없이 신청이 곧 배정이라 값이 둘뿐이고, "기간종료"는 저장하지 않고 회차로 계산합니다(LockerApplicationStatus). 무엇보다 표시용LockerApplicationStatus와 이름이 겹치는 두 번째 상태 enum을 만들지 않아도 됩니다.2. 도메인 —
LockerApplication.cancel(). 이미 취소된 신청은 거부합니다.3. 기존 조회에서 취소 제외 — 취소 컬럼을 만들면서 생긴 숙제입니다. 안 하면 취소를 만들어놓고 아무도 안 봐서 재배정이 실제로는 안 됩니다.
existsByLockerPeriodIdAndLockerIdfindLockerIdsByLockerPeriodIdfindMemberIdsByLockerPeriodIdexistsByLockerPeriodIdInAndLockerIdInfindLockerIdByLockerPeriodIdAndMemberIdfindAllByMemberIdOrderByAppliedAtDescIdDescexistsByLockerPeriodId(회차 삭제 가드)만 일부러 안 거릅니다. 취소 행도 그 회차를 가리키므로, 거르면 회차를 지웠을 때 가리킬 대상이 없어집니다.4~5. 저장소·서비스 — 신청 현황 페이징 조회와 단건 조회를 더하고,
AdminLockerService에 세 동작을 넣습니다. 취소와 배정은사용 기간이 끝난 회차를 같은 이유로 막습니다 — 취소해도 되돌릴 자리가 없고, 배정해도 쓸 수 없습니다.6. 알림 — 운영진이 한 일은 학생 화면이 조용히 바뀌므로 알림이 유일한 전달 수단입니다.
LOCKER_ASSIGNED_BY_ADMINLOCKER_ASSIGNMENT_CANCELED기존
LOCKER_ASSIGNED("사물함을 신청했어요")는 학생이 직접 신청한 경우로 남깁니다.7~8. API 계층 — 신청 현황은
AdminLockerApplicationSearchUseCase가 회원을 붙이고(AdminFeeSearchUseCase와 같은 흐름), 수동 배정은AdminLockerAssignUseCase가PayerService.validatePayer로 납부자인지 확인합니다(RentalApplyUseCase선례).core:domain:event는 회비를 모릅니다.쓰기 두 개는 응답 본문이 없습니다. 결과는 신청 현황을 다시 조회해 받습니다.
🧩 이 PR의 한계 & 트레이드오프
DROP INDEX직후CREATE UNIQUE INDEX사이에 같은 칸 신청이 두 건 들어오면 둘 다 커밋되고 그 뒤 인덱스 생성이 실패합니다. 신청 기간 밖에 적용해야 합니다 — 마이그레이션 주석에 적었습니다AGENTS.md원칙에 따랐습니다⛓️ 기존 기능에 미치는 영향
LockerApplicationStatus와 [Feat/#72] 내 배정 사물함 조회 API 추가 #73(내 배정 사물함 조회)을 건드리지 않습니다save()가 사물함 유니크만 변환하고 회원 유니크는 그대로 던지고 있었습니다. 학생이 같은 회차에 두 번 신청하는 경로에도 같은 구멍이 있었는데 함께 막혔습니다is_canceled = 0이 붙습니다. 지금은 취소된 행이 없어 결과가 같습니다@ApiErrorCode가 늘지만 전부 새 엔드포인트의 것입니다🔀 Edge Case & 실패 시나리오
LOCKER_PERIOD_NOT_FOUND(404)lockerPeriodId누락INVALID_INPUT(400) —@RequestParam이 아니라LockerPeriodParams로 받아 500을 피한다name·studentId가nulllockerLabel이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)📋 검토한 대안과 선택 이유
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]수동 배정은 신청 기간을 보지 않습니다. "신청을 놓친 학생"과 "자리를 옮겨야 하는 학생"이 주 용도라 기간이 끝난 뒤가 오히려 주 사용 시점이라고 봤습니다