diff --git a/docs/20260929_크리에이터_선물하기/plan-task.md b/docs/20260929_크리에이터_선물하기/plan-task.md index d0682ed6..1aa30fbf 100644 --- a/docs/20260929_크리에이터_선물하기/plan-task.md +++ b/docs/20260929_크리에이터_선물하기/plan-task.md @@ -8,7 +8,7 @@ | 작성일 | 2026-09-29 | | 요구사항 기준 | `docs/20260929_크리에이터_선물하기/prd.md` | | API 기준 | `docs/20260929_크리에이터_선물하기/prd.md`의 `8. API 계약` | -| 현재 Phase | Phase 10 완료 | +| 현재 Phase | Phase 12 구현 완료 | | 현재 활성 Goal | 완료 | ## 목표 @@ -1362,6 +1362,50 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종 - 검증: `./gradlew test --tests '*GiftQueryServiceTest' --tests '*GiftControllerTest'`, `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.gift.*' --tests 'kr.co.vividnext.sodalive.v2.api.gift.*'`, `./gradlew ktlintCheck`, `git diff --check` 실행 결과 통과. - 남은 항목: 없음 +## Phase 12: 수취인 정보 전달 완료 불변조건 보강 + +**Phase 결과:** 수취인 필수 정보가 없는 선물은 전달 완료할 수 없고, 배송지 입력 기한이 지난 진행 중 선물은 관리자 진행 상태와 무관하게 전달 불가로 종료된다. + +**선행조건:** `P4-T2`, `P5-T3` 완료. + +### Task 12.1 전달 완료 수취인 정보 필수화 + +**Goal 실행 `P12-T1`:** `COMPLETE_DELIVERY`는 `INSPECTION_COMPLETED` 상태이며 수취인 이름, 휴대폰 번호, 우편번호, 주소가 모두 입력된 경우에만 허용한다. + +- **Files:** Modify `GiftDelivery.kt`, `GiftAdminService.kt`, `GiftAdminQueryService.kt`; Test `GiftAdminServiceTest.kt`, `GiftAdminQueryServiceTest.kt`, `AdminGiftControllerTest.kt`. +- [x] **RED:** 수취인 필수 정보가 없는 `INSPECTION_COMPLETED` 선물의 전달 완료 요청이 거부되고 상태/전달 완료 시각/푸시가 바뀌지 않는 실패 test를 작성한다. +- [x] **RED:** 수취인 필수 정보가 불완전하면 관리자 목록/상세의 `availableActions`에서 `COMPLETE_DELIVERY`가 제외되는 실패 test를 작성한다. +- [x] **RED 확인:** focused test가 기존 구현에서 의도한 assertion으로 실패하는지 확인한다. +- [x] **GREEN:** 수취인 정보 완비 조건을 공통화하고 command와 관리자 action 계산에 적용한다. +- [x] **GREEN 확인:** 같은 focused test 통과를 확인한다. + +### Task 12.2 배송지 미입력 기한 초과 상태 범위 보강 + +**Goal 실행 `P12-T2`:** 배송지 입력 기한이 지난 주소 미입력 선물을 `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED`에서 `UNDELIVERABLE`로 종료한다. + +- **Files:** Modify `GiftRepository.kt`; Test `GiftSchedulerTest.kt`. +- [x] **RED:** 세 상태 모두 기한 초과 + `recipientAddress=null`이면 전달 불가가 되는 실패 test를 작성한다. +- [x] **RED:** 기한 동일/미도래, 주소 입력 완료, 대상 외 상태는 변경되지 않는 회귀 test를 유지한다. +- [x] **RED 확인:** `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED` case가 기존 query에서 실패하는지 확인한다. +- [x] **GREEN:** 기존 만료 조회 query의 상태 조건만 세 상태로 확장한다. +- [x] **GREEN 확인:** scheduler focused test 통과를 확인한다. + +### Phase 12 Gate + +- [x] `GiftAdminServiceTest`, `GiftAdminQueryServiceTest`, `AdminGiftControllerTest`, `GiftSchedulerTest`의 변경 관련 test 통과. +- [x] 전체 `./gradlew --no-daemon test` 통과(8분 33초), 종료 후 embedded Redis 및 Gradle test worker 잔존 없음. +- [x] `ktlintCheck`, `build -x test`, `git diff --check` 통과. +- [x] `AdminGiftControllerTest`에서 주소 미입력 실패와 주소 입력 완료 성공 HTTP 계약 확인. + +**구현 결과:** 수취인 필수 정보 완비 조건을 `GiftDelivery.hasCompleteRecipientInformation()`으로 공통화해 전달 완료 command와 관리자 action 계산에 적용했다. 배송지 입력 기한 초과 조회는 주소 입력 가능 상태 3개로 확장했다. + +### Task 12.3 Embedded Redis 종료 누수 수정 + +- [x] `com.github.codemonstur:embedded-redis:1.4.3`의 `RedisInstance.stop()`과 shutdown hook 등록 동작을 확인했다. +- [x] initializer의 중복 JVM shutdown hook을 제거했다. +- [x] 공식 `onShutdownForceStop(true)` 옵션으로 native Redis 프로세스 종료가 무기한 `waitFor()`에 걸리지 않도록 변경했다. +- [x] 후속 요청에 따라 전체 `./gradlew --no-daemon test`를 실행해 정상 종료를 확인했다. + ## Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | @@ -1373,12 +1417,14 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종 | 2026-09-30 | `PLAN-DEC-005` | 확정 | 운영 반영용 DDL에는 선물 도메인 FK를 명시하고, 선물 신청 건에는 최종 결제 금액 스냅샷만 저장한다 | 사용자 결정: 제대로 가는 방향 선호, `basePriceCan`은 신청 건에 불필요 | `gift-schema.sql`, `Gift` | | 2026-09-30 | `PLAN-DEC-006` | 확정 | 선물 API response datetime은 전역 Jackson 설정이 아니라 DTO response boundary에서 UTC ISO 문자열로 변환한다 | 선물 API만의 공개 계약 보강이며 전역 변경은 기존 API 영향이 큼 | `P8-T1`, `client-api-summary.md` | | 2026-09-30 | `PLAN-DEC-007` | 확정 | 기존 `categoryCode`는 내부 범용코드로 유지하고 분류번호/접수코드/대표품목을 추가하며, 신청번호는 카테고리+날짜별 sequence로 발급한다 | 사용자 확정: 카테고리 필드 의미와 사용자 폼 옵션 표시 규칙 분리 | `P9-T1`~`P9-T3` | +| 2026-10-02 | `PLAN-DEC-008` | 확정 | `COMPLETE_DELIVERY`는 수취인 필수 정보가 모두 입력된 경우에만 허용하고, 배송지 미입력 기한 초과는 주소 입력 가능 상태 3개 전체에서 `UNDELIVERABLE`로 종료한다 | 배송지 없는 선물의 전달 완료와 관리자 상태 진행 후 만료 누락 방지 | `P12-T1`, `P12-T2` | ## 발견된 문제 | ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 | |---|---|---|---|---|---| -| 없음 | Low | 해결 | 계획 작성 시점의 차단 문제 없음 | 없음 | 없음 | +| `GIFT-BUG-001` | High | 해결 | 수취인 정보가 없는 `INSPECTION_COMPLETED` 선물이 전달 완료될 수 있고, 관리자 상태 진행 후 배송지 미입력 기한 초과 대상에서 누락된다 | `P12-T1`, `P12-T2` | command/action 불변조건과 만료 조회 상태 범위를 테스트 우선으로 보강 완료 | +| `TEST-INFRA-001` | Medium | 해결 | 테스트 timeout 후 embedded Redis native 프로세스와 Gradle worker가 남아 후속 테스트 실행을 방해했다 | `P12-T3` | 잔존 프로세스 정리, 중복 hook 제거, 공식 강제 종료 옵션 적용 후 전체 테스트 정상 종료 확인 | ## 최종 보고 형식 diff --git a/docs/20260929_크리에이터_선물하기/prd.md b/docs/20260929_크리에이터_선물하기/prd.md index 624672ce..1457aca8 100644 --- a/docs/20260929_크리에이터_선물하기/prd.md +++ b/docs/20260929_크리에이터_선물하기/prd.md @@ -846,7 +846,8 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내 | `RECEIVED` | `[]` | | `TRACKING_REGISTERED` | `["ARRIVE_MAILBOX", "MARK_UNDELIVERABLE"]` | | `ARRIVED_AT_MAILBOX` | `["COMPLETE_INSPECTION", "MARK_UNDELIVERABLE"]` | -| `INSPECTION_COMPLETED` | `["COMPLETE_DELIVERY", "MARK_UNDELIVERABLE"]` | +| `INSPECTION_COMPLETED`, 수취인 정보 입력 완료 | `["COMPLETE_DELIVERY", "MARK_UNDELIVERABLE"]` | +| `INSPECTION_COMPLETED`, 수취인 정보 미입력/불완전 | `["MARK_UNDELIVERABLE"]` | | `DELIVERED` | `[]` | | `UNDELIVERABLE` | `[]` | | `CANCELED` | `[]` | @@ -858,7 +859,9 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내 | POST | `/api/v2/admin/gifts/{applicationNo}/arrive-mailbox` | `TRACKING_REGISTERED` | `ARRIVED_AT_MAILBOX` | 없음 | | POST | `/api/v2/admin/gifts/{applicationNo}/complete-inspection` | `ARRIVED_AT_MAILBOX` | `INSPECTION_COMPLETED` | 없음 | | POST | `/api/v2/admin/gifts/{applicationNo}/mark-undeliverable` | `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED` | `UNDELIVERABLE` | `reason` | -| POST | `/api/v2/admin/gifts/{applicationNo}/complete-delivery` | `INSPECTION_COMPLETED` | `DELIVERED` | 없음 | +| POST | `/api/v2/admin/gifts/{applicationNo}/complete-delivery` | `INSPECTION_COMPLETED` | `DELIVERED` | 저장된 수취인 이름, 휴대폰 번호, 우편번호, 주소 | + +`complete-delivery`는 `recipientName`, `recipientPhoneNumber`, `recipientZipCode`, `recipientAddress`가 모두 null 또는 공백이 아닐 때만 성공한다. `recipientAddressDetail`은 선택 입력이다. 조건을 만족하지 않으면 상태, 전달 완료 시각, 푸시를 변경하지 않고 요청을 거부한다. 전달불가 Request: @@ -875,9 +878,10 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내 | 운송장 등록 기한 24시간 전 안내 | `RECEIVED`, 마감 24시간 전 | 상태 변경 없음 | 팬에게 안내 | | 운송장 미등록 자동취소 | `RECEIVED`, 신청 후 3일 초과 | `CANCELED`, 전액 환불 | 팬에게 자동취소 안내 | | 배송지 입력 기한 24시간 전 안내 | `TRACKING_REGISTERED` 이상, 배송지 미입력, 마감 24시간 전 | 상태 변경 없음 | 크리에이터에게 안내 | -| 배송지 미입력 전달불가 | 배송지 미입력, 운송장 등록 후 7일 초과 | `UNDELIVERABLE`, 사유 저장 | 팬에게 전달불가 안내. 크리에이터에게 추가 푸시 없음 | +| 배송지 미입력 전달불가 | `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED` 중 하나이며 배송지 미입력, 운송장 등록 후 7일 초과 | `UNDELIVERABLE`, 사유 저장 | 팬에게 전달불가 안내. 크리에이터에게 추가 푸시 없음 | 스케줄러는 중복 실행되어도 같은 선물에 중복 환불 또는 중복 푸시가 발생하지 않아야 한다. +배송지 입력 기한과 현재 시각이 같으면 아직 기한 초과가 아니며, `RECEIVED`, `DELIVERED`, `UNDELIVERABLE`, `CANCELED` 상태는 배송지 미입력 전달불가 대상에서 제외한다. ## 9. 푸시 알림 정책