docs(gift): 선물 받을 주소 요구사항을 기록한다
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
# 선물 관리자 페이지 구현 프롬프트
|
# 선물 관리자 페이지 구현 프롬프트
|
||||||
|
|
||||||
이 문서는 관리자 페이지의 선물 관련 3개 화면을 프론트엔드 구현 에이전트에게 전달하기 위한 프롬프트 모음이다.
|
이 문서는 관리자 페이지의 선물 관련 4개 화면을 프론트엔드 구현 에이전트에게 전달하기 위한 프롬프트 모음이다.
|
||||||
|
|
||||||
공통 전제:
|
공통 전제:
|
||||||
|
|
||||||
- 관리자 메뉴에는 parent 메뉴 `선물함 관리` 아래에 다음 3개 하위 메뉴가 있다.
|
- 관리자 메뉴에는 parent 메뉴 `선물함 관리` 아래에 다음 4개 하위 메뉴가 있다.
|
||||||
- `선물함 리스트` → `/gift/list`
|
- `선물함 리스트` → `/gift/list`
|
||||||
|
- `받을 주소` → `/gift/mailbox`
|
||||||
- `선물 카테고리` → `/gift/category`
|
- `선물 카테고리` → `/gift/category`
|
||||||
- `선물 사이즈` → `/gift/size`
|
- `선물 사이즈` → `/gift/size`
|
||||||
- 모든 API 응답은 기존 공통 envelope를 사용한다. 화면에서는 `response.data`를 실제 payload로 사용한다.
|
- 모든 API 응답은 기존 공통 envelope를 사용한다. 화면에서는 `response.data`를 실제 payload로 사용한다.
|
||||||
@@ -237,7 +238,84 @@ type AdminGiftOperationStatusResponse = {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 선물 카테고리 CRUD 페이지 프롬프트
|
## 2. 받을 주소 설정 페이지 프롬프트
|
||||||
|
|
||||||
|
```text
|
||||||
|
관리자 선물 받을 주소 설정 페이지를 구현해줘.
|
||||||
|
|
||||||
|
목표:
|
||||||
|
- 관리자 메뉴 `선물함 관리 > 받을 주소`에서 팬이 선물을 보낼 전역 단일 주소를 조회하고 저장한다.
|
||||||
|
- 이 주소는 팬의 선물 상세 API에서 운송장 등록 전 상태일 때 `mailbox`로 노출된다.
|
||||||
|
- 기존 관리자 페이지의 폼, 저장 버튼, 토스트, 에러 표시 패턴을 그대로 따른다.
|
||||||
|
|
||||||
|
라우트:
|
||||||
|
- `/gift/mailbox`
|
||||||
|
|
||||||
|
필수 화면 구성:
|
||||||
|
- 상단 제목: `받을 주소`
|
||||||
|
- 설명 문구: `팬이 선물을 발송할 때 확인하는 받을 주소입니다.`
|
||||||
|
- 입력 폼:
|
||||||
|
- 받을 사람 이름 `name`
|
||||||
|
- 연락처 `phoneNumber`
|
||||||
|
- 우편번호 `zipCode`
|
||||||
|
- 주소 `address`
|
||||||
|
- 상세주소 `addressDetail`
|
||||||
|
- 저장 버튼: `저장`
|
||||||
|
|
||||||
|
초기 조회 API:
|
||||||
|
- Method: `GET`
|
||||||
|
- URL: `/api/v2/admin/gift-mailbox`
|
||||||
|
- response `data`:
|
||||||
|
```ts
|
||||||
|
type AdminGiftMailboxResponse = {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
} | null;
|
||||||
|
```
|
||||||
|
- `data`가 null이면 빈 폼을 표시한다.
|
||||||
|
|
||||||
|
저장 API:
|
||||||
|
- Method: `PUT`
|
||||||
|
- URL: `/api/v2/admin/gift-mailbox`
|
||||||
|
- Request:
|
||||||
|
```ts
|
||||||
|
type AdminGiftMailboxRequest = {
|
||||||
|
name: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
zipCode: string;
|
||||||
|
address: string;
|
||||||
|
addressDetail: string | null;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
- Response `data`:
|
||||||
|
```ts
|
||||||
|
type AdminGiftMailboxResponse = {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `name`, `phoneNumber`, `zipCode`, `address`는 필수다.
|
||||||
|
- 공백만 입력할 수 없다.
|
||||||
|
- 저장 실패 시 기존 관리자 페이지의 공통 에러 표시 방식을 따른다.
|
||||||
|
|
||||||
|
저장 성공 UX:
|
||||||
|
- 성공 토스트를 표시한다.
|
||||||
|
- 저장 API response 기준으로 화면 값을 갱신한다.
|
||||||
|
- `address`는 서버가 조립한 `(우편번호) 주소, 상세주소` 형식으로 표시해도 되고, 입력 필드는 사용자가 입력한 값을 유지해도 된다.
|
||||||
|
|
||||||
|
테스트/검증:
|
||||||
|
- 초기 조회 시 null이면 빈 폼을 표시하는지 확인한다.
|
||||||
|
- 저장 시 PUT body가 정확히 전달되는지 확인한다.
|
||||||
|
- 필수값 공백 validation을 확인한다.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 선물 카테고리 CRUD 페이지 프롬프트
|
||||||
|
|
||||||
```text
|
```text
|
||||||
관리자 선물 카테고리 CRUD 페이지를 구현해줘.
|
관리자 선물 카테고리 CRUD 페이지를 구현해줘.
|
||||||
@@ -345,7 +423,7 @@ UX 요구사항:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 선물 사이즈 가격 CRUD 페이지 프롬프트
|
## 4. 선물 사이즈 가격 CRUD 페이지 프롬프트
|
||||||
|
|
||||||
```text
|
```text
|
||||||
관리자 선물 사이즈 가격 CRUD 페이지를 구현해줘.
|
관리자 선물 사이즈 가격 CRUD 페이지를 구현해줘.
|
||||||
@@ -464,7 +542,8 @@ type GiftSize = "SMALL" | "MEDIUM" | "LARGE";
|
|||||||
|
|
||||||
1. API client 함수와 TypeScript 타입을 먼저 추가한다.
|
1. API client 함수와 TypeScript 타입을 먼저 추가한다.
|
||||||
2. `/gift/list` 선물함 리스트와 상세/상태변경 모달을 구현한다.
|
2. `/gift/list` 선물함 리스트와 상세/상태변경 모달을 구현한다.
|
||||||
3. `/gift/category` 카테고리 CRUD를 구현한다.
|
3. `/gift/mailbox` 받을 주소 설정 화면을 구현한다.
|
||||||
4. `/gift/size` 사이즈 가격 수정 화면을 구현한다.
|
4. `/gift/category` 카테고리 CRUD를 구현한다.
|
||||||
5. 기존 관리자 메뉴의 `선물함 관리` 하위 route와 연결되는지 확인한다.
|
5. `/gift/size` 사이즈 가격 수정 화면을 구현한다.
|
||||||
6. 각 페이지별 loading/empty/error/success 상태를 확인한다.
|
6. 기존 관리자 메뉴의 `선물함 관리` 하위 route와 연결되는지 확인한다.
|
||||||
|
7. 각 페이지별 loading/empty/error/success 상태를 확인한다.
|
||||||
|
|||||||
@@ -1309,6 +1309,48 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종
|
|||||||
- Gate 확인: Phase 9 focused suite, `./gradlew ktlintCheck`, `git diff --check` 모두 성공
|
- Gate 확인: Phase 9 focused suite, `./gradlew ktlintCheck`, `git diff --check` 모두 성공
|
||||||
- 남은 항목: 없음
|
- 남은 항목: 없음
|
||||||
|
|
||||||
|
## Phase 11: 선물 받을 주소 설정
|
||||||
|
|
||||||
|
**Phase 결과:** 관리자는 전역 단일 선물 받을 주소를 등록/수정하고, 팬은 운송장 등록 전 상세에서 해당 주소를 확인할 수 있다.
|
||||||
|
|
||||||
|
**선행조건:** `P3-GATE`, `P10-GATE` 완료.
|
||||||
|
|
||||||
|
### 구현 항목
|
||||||
|
|
||||||
|
#### Task 11.1 받을 주소 관리자 설정 API 구현
|
||||||
|
|
||||||
|
**Goal 실행 `P11-T1`:** 전역 단일 받을 주소 조회/저장 API를 구현한다.
|
||||||
|
|
||||||
|
- **Files:** Create `GiftMailbox.kt`, `GiftMailboxRepository.kt`; Modify `GiftAdminService.kt`, `AdminGiftController.kt`, `AdminGiftRequest.kt`, `AdminGiftResponse.kt`, `gift-schema.sql`; Test `GiftAdminServiceTest.kt`, `AdminGiftControllerTest.kt`.
|
||||||
|
- [x] **RED:** 설정 미등록 시 `GET /api/v2/admin/gift-mailbox`가 `data=null`을 반환하는 실패 test를 작성한다.
|
||||||
|
- [x] **RED:** `PUT /api/v2/admin/gift-mailbox`가 전역 단일 주소를 생성하고, 재호출 시 같은 row를 갱신하는 실패 test를 작성한다.
|
||||||
|
- [x] **RED:** 이름/연락처/우편번호/주소 공백 입력을 거부하는 실패 test를 작성한다.
|
||||||
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
||||||
|
- [x] **GREEN:** 단일 row persistence, 관리자 service, request/response DTO, controller endpoint를 구현한다.
|
||||||
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
||||||
|
|
||||||
|
#### Task 11.2 선물 상세 받을 주소 노출 구현
|
||||||
|
|
||||||
|
**Goal 실행 `P11-T2`:** 보낸 선물의 운송장 등록 전 상세에만 받을 주소를 노출한다.
|
||||||
|
|
||||||
|
- **Files:** Modify `GiftQueryService.kt`; Test `GiftQueryServiceTest.kt`, `GiftControllerTest.kt`.
|
||||||
|
- [x] **RED:** `direction=SENT`이고 `status=RECEIVED`인 상세만 `mailbox`를 반환하는 실패 test를 작성한다.
|
||||||
|
- [x] **RED:** 받을 주소 미등록 또는 다른 상태/방향이면 `mailbox=null`인 실패 test를 작성한다.
|
||||||
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
||||||
|
- [x] **GREEN:** `GiftQueryService`에서 받을 주소 repository를 조회해 기존 `GiftMailboxResult`로 매핑한다.
|
||||||
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
||||||
|
|
||||||
|
### Phase 11 Gate
|
||||||
|
|
||||||
|
**Goal 실행 `P11-GATE`:** 관리자 설정과 사용자 상세 노출을 판정한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test --tests "*GiftAdminServiceTest" --tests "*AdminGiftControllerTest" --tests "*GiftQueryServiceTest" --tests "*GiftControllerTest"
|
||||||
|
./gradlew ktlintCheck
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** 받을 주소 미등록/등록/수정, 상세 `mailbox` 노출 조건, 기존 선물 상세 권한 정책이 통과한다.
|
||||||
|
|
||||||
## Decision Log
|
## Decision Log
|
||||||
|
|
||||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
||||||
|
|||||||
@@ -384,8 +384,8 @@ Response:
|
|||||||
{
|
{
|
||||||
"applicationNo": "A-1002609300001",
|
"applicationNo": "A-1002609300001",
|
||||||
"direction": "SENT",
|
"direction": "SENT",
|
||||||
"status": "TRACKING_REGISTERED",
|
"status": "RECEIVED",
|
||||||
"statusName": "발송 확인",
|
"statusName": "접수 완료",
|
||||||
"giftInfo": {
|
"giftInfo": {
|
||||||
"recipientCreatorNickname": "크리에이터닉네임",
|
"recipientCreatorNickname": "크리에이터닉네임",
|
||||||
"senderNickname": null,
|
"senderNickname": null,
|
||||||
@@ -393,7 +393,7 @@ Response:
|
|||||||
"categoryName": "인형",
|
"categoryName": "인형",
|
||||||
"applicationNo": "A-1002609300001",
|
"applicationNo": "A-1002609300001",
|
||||||
"paidCan": 80,
|
"paidCan": 80,
|
||||||
"tracking": "CJ대한통운_1234567890",
|
"tracking": null,
|
||||||
"shippingRequestedAt": null
|
"shippingRequestedAt": null
|
||||||
},
|
},
|
||||||
"senderInfo": {
|
"senderInfo": {
|
||||||
@@ -403,11 +403,11 @@ Response:
|
|||||||
},
|
},
|
||||||
"recipientAddress": null,
|
"recipientAddress": null,
|
||||||
"mailbox": {
|
"mailbox": {
|
||||||
"name": "소다라이브 사서함",
|
"name": "소다라이브 선물 담당자",
|
||||||
"address": "구현 후 제공",
|
"address": "(06234) 서울시 강남구 ..., 3층 선물 접수처",
|
||||||
"phoneNumber": "구현 후 제공"
|
"phoneNumber": "01012345678"
|
||||||
},
|
},
|
||||||
"trackingRequired": false,
|
"trackingRequired": true,
|
||||||
"recipientAddressRequired": false,
|
"recipientAddressRequired": false,
|
||||||
"recipientAddressDeadlineAt": "2026-10-09T03:00:00Z",
|
"recipientAddressDeadlineAt": "2026-10-09T03:00:00Z",
|
||||||
"delivery": {
|
"delivery": {
|
||||||
@@ -446,6 +446,8 @@ Response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`mailbox`는 관리자가 등록한 전역 단일 받을 주소다. 보낸 사람 관점(`direction=SENT`)이고 상태가 운송장 등록 전인 `RECEIVED`일 때만 내려준다. 받을 주소가 등록되지 않았거나 다른 상태/방향이면 `mailbox`는 `null`이다. 주소는 상세 조회 시점의 최신 관리자 설정을 사용한다.
|
||||||
|
|
||||||
받는 크리에이터 관점 응답 예시는 다음과 같다.
|
받는 크리에이터 관점 응답 예시는 다음과 같다.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -802,6 +804,40 @@ Response:
|
|||||||
|
|
||||||
크리에이터가 배송지를 아직 입력하지 않은 경우에도 `recipientInfo.nickname`은 내려주고, `recipientInfo.name`, `recipientInfo.phoneNumber`, `recipientInfo.address`는 빈 문자열 `""`로 내려준다.
|
크리에이터가 배송지를 아직 입력하지 않은 경우에도 `recipientInfo.nickname`은 내려주고, `recipientInfo.name`, `recipientInfo.phoneNumber`, `recipientInfo.address`는 빈 문자열 `""`로 내려준다.
|
||||||
|
|
||||||
|
#### 8.13.3 관리자 선물 받을 주소 설정 API
|
||||||
|
|
||||||
|
선물 받을 주소는 전역 단일 설정이다. 팬이 운송장을 등록하기 전에 보낸 선물 상세에서 이 주소를 확인하고 택배를 발송한다.
|
||||||
|
|
||||||
|
`GET /api/v2/admin/gift-mailbox`
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "소다라이브 선물 담당자",
|
||||||
|
"address": "(06234) 서울시 강남구 ..., 3층 선물 접수처",
|
||||||
|
"phoneNumber": "01012345678"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
설정이 없으면 공통 envelope의 `data`는 `null`이다.
|
||||||
|
|
||||||
|
`PUT /api/v2/admin/gift-mailbox`
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "소다라이브 선물 담당자",
|
||||||
|
"phoneNumber": "01012345678",
|
||||||
|
"zipCode": "06234",
|
||||||
|
"address": "서울시 강남구 ...",
|
||||||
|
"addressDetail": "3층 선물 접수처"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response는 저장된 받을 주소를 `name`, `address`, `phoneNumber`로 내려준다. `address`는 기존 주소 응답과 동일하게 `(우편번호) 주소, 상세주소` 형식이다. `name`, `phoneNumber`, `zipCode`, `address`는 필수이며 공백만 입력할 수 없다. `addressDetail`은 선택이다.
|
||||||
|
|
||||||
`availableActions`는 현재 상태별로 다음 값만 내려준다.
|
`availableActions`는 현재 상태별로 다음 값만 내려준다.
|
||||||
|
|
||||||
| 현재 상태 | availableActions |
|
| 현재 상태 | availableActions |
|
||||||
|
|||||||
Reference in New Issue
Block a user