docs(gift): 관리자 취소 요구사항과 검증 결과를 기록한다

This commit is contained in:
2026-10-22 18:25:36 +09:00
parent 30a2f7e37b
commit cd67112a8d
2 changed files with 225 additions and 10 deletions
@@ -4,15 +4,17 @@
| 항목 | 내용 |
|---|---|
| 문서 상태 | 구현 중 |
| 문서 상태 | Phase 14 서버 구현 완료, 전체 테스트 제외 검증 완료 |
| 작성일 | 2026-09-29 |
| 최종 수정일 | 2026-09-30 |
| 최종 수정일 | 2026-10-22 |
| 대상 제품 | 크리에이터 선물하기 |
| 작성자·결정권자 | 사용자 |
| 관련 API Contract | 별도 문서 없음. 이 문서의 `8. API 계약`을 기준으로 사용 |
| 관련 구현 계획 | `docs/20260929_크리에이터_선물하기/plan-task.md` |
| 관련 review | 없음 |
> Phase 14 관리자 취소와 목록·상세 `CANCEL` 액션은 서버 코드에 구현됐다. 관련 테스트·실제 JWT HTTP·리뷰·테스트 제외 빌드를 검증했으며, 전체 테스트는 사용자 요청으로 완료 기준에서 제외했다. 운영 배포 여부와 별도 프론트엔드 구현 여부는 이 문서의 완료 상태에 포함하지 않는다.
## 1. Overview
팬이 내부 재화인 캔을 사용해 크리에이터에게 실물 선물을 보낼 수 있는 API를 제공한다.
@@ -34,6 +36,7 @@
- 팬이 선물 사이즈, 카테고리, 발신자 정보, 약관 동의를 입력해 선물 보내기를 신청할 수 있다.
- 선물 신청 시 캔을 즉시 차감하고 내부적으로 선물 신청과 캔 사용내역을 연결한다.
- 팬은 운송장 등록 전 선물 신청을 취소할 수 있고, 3일 내 운송장을 등록하지 않으면 자동취소 및 전액 환불된다.
- 관리자는 운송장 등록 전 `RECEIVED` 선물을 취소하고 발신자에게 사용 캔 전액을 리워드캔으로 환불할 수 있다.
- 크리에이터는 팬이 운송장을 등록한 선물부터 받은 선물 목록과 상세를 조회하고 7일 이내 배송지와 수령 약관 동의를 등록할 수 있다.
- 팬과 크리에이터는 같은 선물함 목록 API에서 보낸/받은 내역을 함께 또는 개별 조회할 수 있다.
- 운영자는 상태별 전용 관리자 API로 사서함 도착, 검수완료, 전달불가, 전달완료를 처리할 수 있다.
@@ -55,7 +58,7 @@
|---|---|---|---|
| 팬 | 크리에이터에게 선물을 보낸다 | 사이즈/카테고리 조회, 신청, 운송장 등록, 취소, 보낸 내역 조회, 리뷰 작성 | 인증 회원 |
| 크리에이터 | 받은 선물을 확인하고 배송지를 입력한다 | 받은 내역 조회, 배송지 입력, 수령 확인 | 인증 회원 중 크리에이터 |
| 관리자 | 선물 운영 설정을 관리한다 | 카테고리, 사이즈 가격 관리 | 관리자 |
| 관리자 | 선물 운영 설정과 선물 상태를 관리한다 | 카테고리, 사이즈 가격 관리, `RECEIVED` 선물 취소 | 관리자 |
| 운영자 | 선물 검수/전달 상태를 처리한다 | 사서함 도착, 검수완료, 전달불가, 전달완료 처리 | 관리자 또는 운영 권한 |
| 스케줄러 | 기한 초과 건을 자동 처리한다 | 운송장 미등록 자동취소, 배송지 미입력 전달불가 | 시스템 |
@@ -74,7 +77,7 @@
| `INSPECTION_COMPLETED` | 검수완료 | 운영자가 선물 검수를 통과 처리한 상태 | 아니오 |
| `DELIVERED` | 전달완료 | 크리에이터 수령확인 또는 운영자 전달완료 처리 상태 | 예 |
| `UNDELIVERABLE` | 전달불가 | 검수 실패 또는 배송지 미입력 기한 초과로 전달할 수 없는 상태 | 예 |
| `CANCELED` | 신청 취소 | 운송장 등록 전 사용자 취소 또는 자동취소 상태 | 예 |
| `CANCELED` | 신청 취소 | 운송장 등록 전 사용자 취소, ADMIN 취소 또는 자동취소 상태 | 예 |
상태 전이는 다음을 기본으로 한다.
@@ -171,6 +174,7 @@ INSPECTION_COMPLETED -> UNDELIVERABLE
- 선물 신청과 캔 사용내역은 내부적으로 연결되어야 한다.
- 사용자 캔 사용내역 화면에서 선물 신청으로 이동할 수 없어도 된다.
- 운송장 등록 전 사용자 취소 또는 3일 자동취소 시 사용 캔을 전액 환불한다.
- ADMIN은 운송장 등록 전 `RECEIVED`에서 취소할 수 있으며 기존 `refundGiftCan` 경로로 발신자에게 전액 리워드캔 환불한다. 상태 변경, 취소 시각 저장, 환불은 같은 트랜잭션에서 처리한다.
- 운송장 등록 후에는 팬이 취소할 수 없다.
### 6.8 목록 노출
@@ -248,6 +252,8 @@ INSPECTION_COMPLETED -> UNDELIVERABLE
| `GIFT-012` | 확정 | 관리자는 카테고리를 등록/수정/논리삭제할 수 있다 | 삭제는 `isActive=false`만 수행한다 | `P1` |
| `GIFT-013` | 확정 | 관리자는 사이즈별 가격을 수정할 수 있다 | 새 신청에는 변경 가격이 적용되고 기존 신청 스냅샷은 바뀌지 않는다 | `P1` |
| `GIFT-015` | 확정 | 관리자는 전체 선물함 목록과 상세를 조회하고 현재 상태에서 가능한 운영 액션을 확인할 수 있다 | 목록은 상태/신청번호/닉네임 검색과 페이징을 지원하고, 상세는 발송인/수취인 개인정보와 상품/배송/상태 정보를 내려준다 | `P10` |
| `GIFT-016` | 확정 | 관리자는 운송장 등록 전 접수된 선물을 취소할 수 있다 | 관리자만 호출할 수 있고 `RECEIVED`에서만 `CANCELED`로 전이하며 `GiftDelivery.canceledAt`을 저장하고 발신자에게 사용 캔 전액을 리워드캔으로 환불한다 | `P14-T2` |
| `GIFT-017` | 확정 | 관리자 선물함 목록과 상세는 현재 가능한 액션으로 취소 가능 여부를 함께 제공한다 | `RECEIVED`에서만 `availableActions=["CANCEL"]`; 다른 상태의 기존 액션은 유지한다 | `P14-T3` |
## 8. API 계약
@@ -843,11 +849,11 @@ Request:
Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내려준다. `address`는 기존 주소 응답과 동일하게 `(우편번호) 주소, 상세주소` 형식이다. `name`, `phoneNumber`, `zipCode`, `address`는 필수이며 공백만 입력할 수 없다. `addressDetail`은 선택이다.
`availableActions`는 현재 상태별로 다음 값만 내려준다.
관리자 목록과 상세의 `availableActions`는 상태별로 다음 값을 내려준다. `RECEIVED`에서는 `CANCEL`을 제공하고, 다른 상태의 기존 매핑은 유지한다.
| 현재 상태 | availableActions |
|---|---|
| `RECEIVED` | `[]` |
| `RECEIVED` | `["CANCEL"]` |
| `TRACKING_REGISTERED` | `["ARRIVE_MAILBOX", "MARK_UNDELIVERABLE"]` |
| `ARRIVED_AT_MAILBOX` | `["COMPLETE_INSPECTION", "MARK_UNDELIVERABLE"]` |
| `INSPECTION_COMPLETED`, 수취인 정보 입력 완료 | `["COMPLETE_DELIVERY", "MARK_UNDELIVERABLE"]` |
@@ -864,6 +870,7 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내
| 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}/cancel` | `RECEIVED` | `CANCELED` | 없음 |
`complete-delivery`는 `recipientName`, `recipientPhoneNumber`, `recipientZipCode`, `recipientAddress`가 모두 null 또는 공백이 아닐 때만 성공한다. `recipientAddressDetail`은 선택 입력이다. 조건을 만족하지 않으면 상태, 전달 완료 시각, 푸시를 변경하지 않고 요청을 거부한다.
@@ -875,6 +882,29 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내
}
```
#### 8.14.1 관리자 선물 신청 취소
`POST /api/v2/admin/gifts/{applicationNo}/cancel`
Request body: 없음
기존 관리자 API의 `hasRole('ADMIN')` 권한을 따르며 관리자 계정은 선물 발신자가 아니어도 취소할 수 있다. `RECEIVED`에서만 `CANCELED`로 전이하며 운송장 등록 이후에는 취소할 수 없다. 같은 트랜잭션에서 `GiftDelivery.canceledAt`을 기록하고 기존 `refundGiftCan(gift.senderMemberId, gift.canUsageId)` 경로로 발신자에게 원 결제액 전액을 리워드캔으로 환불한다. 관리자의 잔액은 변경하지 않는다.
Response `data`는 기존 `AdminGiftOperationStatusResponse`를 사용한다.
```json
{
"applicationNo": "A-1002609300001",
"status": "CANCELED",
"statusName": "신청 취소",
"occurredAt": "2026-09-30T12:00:00Z"
}
```
`occurredAt`은 `canceledAt`과 같은 UTC ISO `Z` 문자열이다. 누락되거나 취소할 수 없는 상태의 신청번호는 기존 `SodaException(messageKey = "common.error.invalid_request")`로 거부한다. 정상 `ApiResponse` envelope를 사용하며 신규 오류 스키마나 HTTP status 정책은 추가하지 않는다.
이 관리자 취소는 기존 사용자 직접 취소와 운송장 미등록 자동취소를 변경하지 않는다. 취소 전용 푸시는 추가하지 않으며, 자동취소 푸시 문구나 자동취소 사유를 관리자 취소에 재사용하지 않는다.
### 8.15 자동 스케줄링 작업
| 작업 | 대상 | 처리 | 푸시 |
@@ -1014,6 +1044,9 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내
- 푸시는 팬/크리에이터 수신자 분리와 크리에이터 수령확인 시 크리에이터 푸시 억제를 테스트한다.
- 스케줄러는 중복 실행 시 중복 환불/중복 푸시가 없는지 테스트한다.
- 선물 사용자/관리자 API response DTO의 날짜/시간 필드는 UTC ISO 문자열(`Z` 포함)로 직렬화되는지 controller test로 고정한다.
- 관리자 취소는 `RECEIVED`만 허용하고, 발신자에게 전액 리워드캔 환불, `canceledAt` 저장, 환불 실패 시 트랜잭션 롤백 및 중복 환불 방지를 검증한다. 운송장 등록, 사용자 취소, 자동취소, 다른 관리자 취소와의 동시 경합에서도 잘못된 이중 처리/환불이 없어야 한다.
- 관리자 취소 controller는 기존 관리자 인증/권한, 공통 invalid request, 정상 `ApiResponse` envelope와 `AdminGiftOperationStatusResponse`의 UTC `occurredAt`을 검증한다.
- 관리자 목록과 상세 모두 `RECEIVED`에서만 `CANCEL` 액션을 제공하며 기존 `COMPLETE_DELIVERY` 조건부 액션을 포함한 나머지 상태 매핑은 유지한다.
- 카테고리 `classificationNumber`, `receiptCode`, `representativeItem`의 관리자 등록/수정/조회와 사용자 폼 옵션의 `분류명/대표품목` 조합을 테스트한다.
- 신청번호는 카테고리+날짜별 sequence, 4자리 padding, `9999` 초과 자리수 증가, 병렬 unique를 테스트한다.
@@ -1029,6 +1062,7 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내
- [ ] 팬/크리에이터 푸시가 지정된 문구, 수신자, 딥링크로 발송된다.
- [ ] 선물 사용자/관리자 API의 날짜/시간 응답을 클라이언트가 UTC ISO 문자열로 해석할 수 있다.
- [ ] 카테고리 추가 필드와 새 신청번호 정책이 관리자 API, 사용자 폼 옵션, 선물 신청에 반영된다.
- [x] 관리자 취소는 `RECEIVED`에서만 전액 환불과 함께 성공하고, 목록/상세의 `CANCEL` 액션은 상태에 맞게 제공된다.
## 14. Open Questions
@@ -1045,6 +1079,7 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내
| `GIFT-010` | 5 | 리뷰 API 테스트 | 전달완료 후 리뷰 작성 확인 |
| `GIFT-011` | 6 | 푸시 이벤트 테스트 | 딥링크와 수신자 확인 |
| `GIFT-014` | 8 | 선물 사용자/관리자 controller 날짜 응답 테스트 | UTC ISO 문자열 확인 |
| `GIFT-016`, `GIFT-017` | 14 | 관리자 취소 service/controller 및 query service 테스트 | `RECEIVED` 액션과 취소/환불 결과 확인 |
## 16. Decision Log
@@ -1064,3 +1099,9 @@ Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내
| 2026-09-29 | `DEC-012` | 확정 | 파손면책 동의 필요 카테고리는 팬 신청 시 `damageWaiverAgreed=true`를 요구한다 | 사용자 선택 A | `GIFT-003` |
| 2026-09-30 | `DEC-013` | 확정 | 선물 사용자/관리자 API의 response datetime 필드는 `LocalDateTime` 기본 JSON 대신 UTC ISO 문자열로 내려준다 | 클라이언트가 offset 없는 `LocalDateTime` 문자열을 안전하게 해석하지 못함 | `GIFT-014` |
| 2026-09-30 | `DEC-014` | 확정 | 카테고리는 분류번호, 내부 범용 카테고리 코드, 분류명, 접수코드, 대표품목, 파손면책 조건을 관리하고 신청번호는 `receiptCode-classificationNumberYYMMDDSequenceNo`로 발급한다 | 관리자 운영 필드와 사용자 표시 요구가 분리됨 | `GIFT-001`, `GIFT-002`, `GIFT-012` |
| 2026-10-22 | `DEC-015` | 확정 | 관리자 취소는 `RECEIVED`에서만 허용하고 기존 `refundGiftCan`으로 발신자에게 전액 리워드캔을 환불한다. 기존 operation status 응답을 재사용하며 관리자 취소 전용 푸시는 이번 범위에서 추가하지 않는다 | 사용자 확정: 취소 허용 상태와 문서 우선 반영. 응답/환불은 기존 계약 재사용, 신규 푸시는 요청 범위 밖 | `GIFT-016`, `GIFT-017`, `P14` |
| 2026-10-22 | `DEC-016` | 확정 | 기존 관리자 화면 전체 구현 프롬프트는 보존하고 취소 기능 추가 프롬프트를 `admin-gift-cancel-addition-prompt.md`로 분리한다 | 사용자 요청: 기존 페이지에 해당 기능만 추가하는 별도 프롬프트 | `GIFT-016`, `GIFT-017`, `P14-T1` |
## 문서 상태 안내
Phase 14 서버 구현과 관련 검증은 완료했다. 전체 테스트는 1차 실행이 도구의 10분 제한으로 중단되고 2차는 사용자 중단 후 제외됐다. 전체 테스트 통과를 주장하지 않으며 검증 명령과 한계는 `plan-task.md`의 P14-GATE 기록을 따른다. 관리자 프론트엔드는 별도 기능 추가 프롬프트의 확정 계약을 사용한다.