Repository navigation
[Feat/#189] 빌릴게 근무 시간표 관리자 API 추가 - #193
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
| @@ -0,0 +1,3 @@ | |||
| -- 요일만 저장된 근무 시간은 어느 주의 근무인지 알 수 없어 근무일로 옮길 수 없다. | |||
There was a problem hiding this comment.
[r] 리뷰 포인트 주신 V28·V29 마이그레이션과 번호에 대한 의견이에요. main에 이미 V28__replace_locker_section_layout_photo_with_list.sql과 V29__add_is_canceled_to_locker_applications.sql이 들어와 있어서, 이대로 머지하면 같은 버전이 두 개씩 생겨 Flyway가 기동할 때 실패해요. 파일명이 달라 git에서는 충돌로 잡히지 않는 부분이라(지금 GitHub에도 충돌 없음으로 나와요) 미리 말씀드려요. 본문의 "main은 V19까지"가 그사이 V29까지 올라갔어요.
outOfOrder 설정이 없어서(기본 false) 번호가 실제 머지 순서대로 올라가야 해요. 브랜치가 #171 머지 시점에서 갈라진 뒤로 PR 7개가 main에 들어왔으니, main을 받으신 뒤 머지 직전에 main과 열린 PR들이 쓰는 번호를 확인해서 다시 매겨주시면 좋겠습니다(flyway-migration.md 2절). 지금 열린 PR들도 V29~V34를 나눠 쓰고 있어요. 번호를 바꾸실 때 2번째 줄 주석의 (V29)도 같이 고쳐 주세요.
파일 구성은 좋아 보여요. 데이터 삭제와 스키마 변경을 3-5절대로 나누셨고, 기존 행은 날짜로 옮길 수 없으니 먼저 비운 뒤 work_date를 NOT NULL로 추가하는 순서도 맞아요. 인덱스 이름(idx_work_schedules_work_date)과 WorkScheduleJpaEntity의 @Index도 같이 맞춰 주셨고요.
There was a problem hiding this comment.
머지 직전에 main과 열린 PR 번호를 확인해서 다시 매기고, 주석의 (V29)도 같이 고치겠습니다.
There was a problem hiding this comment.
b172273 수정했습니다. main과 열린 PR 번호를 피해 V38·V39로 다시 매기고, 주석의 (V29)는 "바로 다음 마이그레이션"으로 바꿨습니다.
| @@ -0,0 +1,7 @@ | |||
| -- 근무 시간표는 주마다 달라서 요일 대신 근무일을 저장한다. | |||
| ALTER TABLE work_schedules | |||
| DROP COLUMN day_of_week, | |||
There was a problem hiding this comment.
[a] 컬럼을 지우는 마이그레이션이라 한 가지만 말씀드려요. 이 파일이 적용된 뒤 이전 버전 이미지로 롤백하면, 그 이미지의 WorkScheduleJpaEntity가 day_of_week를 찾지 못해 ddl-auto: validate에서 기동이 안 돼요. 테이블을 쓰는 코드가 없어 데이터 쪽은 괜찮지만 롤백이 막히는 건 그와 별개라, main의 V24·V25처럼 주석을 두 줄 남겨두면 배포하는 사람이 확인하기 좋을 것 같아요.
- V28: 적용 전
SELECT COUNT(*) FROM work_schedules로 행이 있는지 확인한다 - V29: 적용 후에는 이전 버전 이미지로 되돌릴 수 없다(ddl-auto: validate에서 기동 실패)
There was a problem hiding this comment.
V28에 적용 전 행 수 확인, V29에 롤백 불가 주석을 추가했습니다. b87209e
| // 한 시간 칸에는 근무자 한 명만 들어간다. 저장된 근무 시간 위에 요청한 근무 시간을 하나씩 올려 보며 | ||
| // 먼저 올라간 것(저장된 것·앞선 요청)과 겹치면 막는다. 근무자가 달라도 해당한다. | ||
| // 구간 겹침은 유니크 제약으로 막을 수 없어 저장 전에 확인한다(조회-후-삽입이라 동시 등록 구멍은 남는다) | ||
| private void validateNotConflicted(List<WorkSchedule> targets) { |
There was a problem hiding this comment.
[a] 리뷰 포인트 주신 validateNotConflicted의 수정 대상 제외와 요청 안 겹침 판정은 맞아 보여요. 저장된 행이 id 1 = 화 1012시, id 2 = 화 1315시라고 두고 따라가 봤어요.
- id 1을 화 10~11시로 수정 →
findSavedSchedulesOnSameDates가 id 1을 빼서 id 2와만 비교하고 통과 - id 1을 화 14~16시로 수정 → id 2와 겹쳐 409
- 화 15
16시와 화 1517시를 한 번에 등록 → 첫 건은 id 2와 맞닿기만 해서placed에 올라가고, 둘째 건이 첫 건과 겹쳐 409.saveAll전에 던지니 아무것도 저장되지 않아요
등록은 id가 null이라 빠지는 행이 없고 수정은 자기 id만 빠져서, 두 경로가 한 메서드로 깔끔하게 모였네요. 동시 등록 구멍을 주석과 PR 한계에 적어 두신 것도 AdminFeedbackServiceImpl.java:133의 회차 기간 겹침과 같은 방식이라 좋아 보여요.
| } | ||
|
|
||
| // 없는 회원(탈퇴 포함)은 MEMBER_NOT_FOUND, 학생회 부서가 없는 회원은 WORKER_NOT_FOUND다 | ||
| private Member getWorker(Long memberId) { |
There was a problem hiding this comment.
[a] 리뷰 포인트 주신 근무자 판정을 UseCase에 둔 구조는 좋아 보여요.
core:domain:internal은core:common에만 의존해서 member를 읽을 수 없으니, 판정 자리는 UseCase가 맞아요(coding-style.md2-7절: 다른 도메인 조합은 서비스가 아니라api:admin-api의 UseCase가 맡는다).- 다른 도메인을 조회해 확인하고 자기 도메인 에러 코드를 던지는 모양이
FeeSubmitUseCase.validateProofImage(file을 조회해FeeErrorCode.INVALID_PROOF_IMAGE)와 같고, 쓰기가 서비스 하나라 트랜잭션을 걸지 않는 이유를 클래스 주석에 남긴 것도 같아요. - member에는
hasCouncilDepartment()라는 사실만 둬서 member가 근무 개념을 몰라도 되는 점도 좋습니다.
하나만 확인하고 싶어요. AuthTokenIssuer.java:32는 role이 ADMIN이 아니면 학생회 부서를 없는 것으로 보고, CouncilDepartment 주석에도 "ADMIN에게만 부여"라고 되어 있어요. 근무자 판정은 부서만 보니, 운영진에서 내려가 role은 STUDENT인데 부서 값이 남아 있는 회원이 있으면 드롭다운에 계속 나와요. 운영진을 해제할 때 부서도 같이 비운다는 전제라면 지금 그대로 괜찮고요, 의도하신 동작인지 궁금합니다!
There was a problem hiding this comment.
role이 ADMIN이면서 학생회 부서가 있는 회원만 근무자로 보도록 수정했습니다. 6b699ca
|
|
||
| @Schema(description = "조회할 주에 속한 날짜. 토·일이면 그 앞 월요일의 주, 생략하면 이번 주", example = "2026-10-21") | ||
| @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) | ||
| LocalDate date |
There was a problem hiding this comment.
[a] PR 한계에 적어주신 date 형식 오류 메시지는 후속으로 두셔도 될 것 같아요. GlobalExceptionHandler.handleValidation이 첫 FieldError의 기본 메시지를 그대로 내보내는 구조라 ArchiveListParams.year도 같은 상황이고, 공통 핸들러에서 한 번에 고치는 게 맞아 보여요. @RequestParam으로 받았다면 MethodArgumentTypeMismatchException을 받는 핸들러가 없어 Exception 핸들러에서 500이 됐을 테니, Params record로 받으신 선택도 좋습니다.
There was a problem hiding this comment.
date를 문자열로 받아 직접 파싱해서 형식 오류 메시지를 정리했습니다(공통 핸들러는 후속). 3156b15
…rk-schedule-admin-api # Conflicts: # core/domain/member/src/main/java/kr/ac/kookmin/stream/member/domain/member/domain/Member.java
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
관리자
기타 관리 → 근무 시간표 관리화면이 쓸 API가 없었습니다. 화면은 월금, 1017시 그리드에 근무자를 1시간 단위로 배치하고 주 단위로 넘겨 보는데, 지금work_schedules는 요일(day_of_week)만 저장해서 주마다 다른 시간표를 담을 수 없고, 이 테이블에 쓰는 코드도 없었습니다.명세의 5개 엔드포인트를 만들었습니다.
GET /v1/admin/billilge/work-schedules?date=date생략 시 이번 주)GET /v1/admin/billilge/work-schedules/workersPOST /v1/admin/billilge/work-schedules+ 근무 시간 등록— 근무자 1명 + 시간대 여러 개PUT /v1/admin/billilge/work-schedules/{scheduleId}DELETE /v1/admin/billilge/work-schedules/{scheduleId}❓ 왜 해결해야 하나요?
빌릴게 근무자 배치를 관리자 화면에서 할 수 있어야 하는데, 화면은 만들어져 있어도 붙일 API가 없어 mock 데이터로만 돌고 있습니다. 요일만 저장하는 지금 구조로는 "다음 주 월요일만 바꾸기"도, "지난 주 기록은 그대로 두기"도 할 수 없습니다.
⭐ 어떻게 해결했나요?
1. 요일 대신 근무일을 저장합니다 (V48·V49)
요일은 저장하지 않고 응답을 만들 때
workDate.getDayOfWeek()로 계산합니다. 기존 행은 어느 주의 근무인지 알 수 없어 날짜로 옮길 수 없고, 이 테이블에 쓰는 API도 없었기 때문에 비우고(V48) 컬럼을 바꿨습니다(V49). 데이터 삭제와 스키마 변경은flyway-migration.md3-5절대로 파일을 나눴습니다.2. 근무 시간 규칙은 도메인이, 오늘·다른 근무 시간이 필요한 규칙은 서비스가 검증합니다
WorkSchedule.create/changeINVALID_WORK_DATE17시, 시작 < 종료, 점심시간(1213시)에 걸치지 않음WorkSchedule.create/changeINVALID_WORK_HOURSAdminWorkScheduleServiceImpl.validateNotPastWeekPAST_WEEK_NOT_EDITABLEAdminWorkScheduleServiceImpl.validateNotConflictedWORK_SCHEDULE_CONFLICTFeedbackRound.create의 기간 검증처럼 값 하나로 판단할 수 있는 규칙은 생성할 때 막고, 지난 주·겹침처럼 오늘 날짜나 다른 행이 필요한 규칙은 서비스의validate~에 뒀습니다. 지난 주는 날짜가 아니라 주(월요일) 단위로 판단해서 이번 주의 지난 요일은 고칠 수 있습니다.3. "근무자"는 근무 시간표 쪽에서 조립합니다
근무자는 **학생회 부서가 있는 운영진(role
ADMIN)**입니다. 운영진에서 내려가 부서 값만 남은 회원은 빠집니다(AuthTokenIssuer가ADMIN에게만 부서를 싣는 것과 같은 기준). 이 판단을 member에 두면 member가 빌릴게 근무라는 개념에 묶이므로, member에는 사실만 범용으로 두고 해석은AdminWorkScheduleUseCase가 합니다.Member.isCouncilMember()(ADMIN이면서 학생회 부서가 있음),MemberService.findAllCouncilMembers()추가 (기존 메서드는 그대로)AdminWorkScheduleUseCase: 근무자 목록 = 학생회 구성원, 배정할 회원 확인 =getById(없거나 탈퇴면MEMBER_NOT_FOUND) 후 학생회 구성원이 아니면WORKER_NOT_FOUND근무자 규칙이 바뀌면(예: 회장단 제외) 이 UseCase만 고치면 됩니다. 근무 시간(internal)에 이름·학번(member)을 붙이는 조합도 여기서 합니다(
AdminFeedbackSearchUseCase와 같은 방식).🧩 이 PR의 한계 & 트레이드오프
work_schedules행 삭제: 요일만 있는 데이터는 날짜로 옮길 방법이 없습니다.date를 문자열로 받음:LocalDate로 받으면 형식 오류 메시지에 스프링 변환 오류 원문이 나가서, 이 PR에서는WorkScheduleWeekParams가 문자열로 받아toDate()에서 파싱합니다. 공통GlobalExceptionHandler(기존ArchiveListParams.year도 같은 상황)는 후속으로 남깁니다.⛓️ 기존 기능에 미치는 영향
work_schedules스키마가 바뀝니다. 이 테이블을 쓰던 코드는 없었습니다.SELECT COUNT(*) FROM work_schedules로 행이 있는지 확인하고, V49 적용 후에는 이전 버전 이미지로 되돌릴 수 없습니다(ddl-auto: validate에서 기동 실패). 두 파일 머리 주석에도 남겼습니다.🔀 Edge Case & 실패 시나리오
date가 토·일date형식 오류 (abc,2026-13-01,2026-02-30)INVALID_INPUT"날짜는 yyyy-MM-dd 형식이어야 합니다."name·studentId는 nullWORKER_NOT_FOUND라 다른 근무자로 바꿔야 한다11시, 1112시)PAST_WEEK_NOT_EDITABLE📋 검토한 대안과 선택 이유
(work_date, hour)유니크: 동시 등록까지 DB가 막아 주지만, 화면이 구간 단위로 등록·수정·삭제하고 명세도 구간(startHour~endHour)이라 행 쪼개기·합치기 비용이 큽니다. 구간 저장 + 조회 후 확인을 택했습니다.SELECT ... FOR UPDATE, named lock): 위 동시성 구멍을 막을 수 있지만 사용 빈도에 비해 코드가 늘어 보류했습니다.isWorker(),AdminMemberService): 처음엔 그렇게 만들었다가, member가 근무 개념에 묶이는 문제로 위 3번 구조로 바꿨습니다.@RequestParam LocalDate date:GlobalExceptionHandler에MethodArgumentTypeMismatchException처리가 없어 형식 오류가 500이 됩니다.@ModelAttributeParams record로 받고, 그 안에서도LocalDate가 아니라 문자열로 받아 직접 파싱합니다(EventApplicationListParams.cursor와 같은 방식).💬 리뷰 포인트
AdminWorkScheduleServiceImpl.validateNotConflicted— 수정 대상 제외, 요청 안 겹침 판정이 맞는지AdminWorkScheduleUseCase— 근무자 판정(학생회 부서가 있는 운영진)을 UseCase에 둔 구조명세(노션 「API 명세서」 빌릴게 근무 시간표 5건)는 관리자 화면과 대조해 근무자 기준·에러 코드를 고쳐 두었습니다(🔁 표시).
./gradlew check(ModularityTests·DomainImplAccessTests 포함) 통과했습니다.