Test #459

Merged
klaus merged 8 commits from test into main 2026-10-22 11:21:33 +00:00
2 changed files with 127 additions and 0 deletions
Showing only changes of commit 97c7de6f6e - Show all commits
@@ -0,0 +1,120 @@
# 기존 관리자 선물함에 신청 취소 기능 추가 프롬프트
## 목적과 사용 범위
- 이 문서는 이미 존재하는 관리자 선물함에 기능 하나를 추가하기 위한 전달용 프롬프트다.
- 아래 확정된 API Request/Response 계약을 기준으로 타입, API client, 화면 액션과 테스트를 구현한다.
- [기존 전체 페이지 프롬프트](admin-page-prompts.md)는 수정하거나 다시 실행하지 않는다.
- 계약 기준: [PRD 8.14.1 관리자 선물 신청 취소](prd.md#8141-관리자-선물-신청-취소).
## 복사해서 전달할 증분 구현 프롬프트
아래 블록 전체를 기존 프론트엔드 저장소의 구현 담당자에게 전달한다.
````markdown
# 기존 관리자 선물함에 신청 취소 액션만 추가
## 1. 기존 코드 확인
이미 구현된 관리자 선물함 목록/상세에 `신청 취소` 기능만 증분으로 추가해줘.
페이지를 새로 만들거나 기존 전체 페이지 구현 프롬프트를 재실행하지 마.
- 먼저 기존 프론트엔드 코드를 읽고 목록, 상세, 액션 타입, 액션 dispatch 위치를 확인한다.
- API 인증/권한, 공통 API client, 응답 envelope 해제 방식, 확인창, 토스트, 오류 처리를 확인한다.
- 기존 상태 변경 액션의 pending 처리와 목록/상세 재조회 방식을 그대로 사용한다.
- 변경할 실제 파일과 재사용할 기존 패턴을 먼저 짧게 정리한다.
- 아래 API 계약을 기준으로 구현한다. 기존 타입이나 호출부가 계약과 다르면 차이를 보고하고 이 기능의 변경 범위 안에서 맞춘다.
## 2. 유지할 기존 화면과 액션
- 기존 `/gift/list` 라우트, 메뉴, 레이아웃, 테이블 컬럼, 필터, 페이지네이션을 보존한다.
- 기존 상세 모달/패널과 액션 표시·실행 프레임워크를 그대로 확장한다.
- `ARRIVE_MAILBOX`, `COMPLETE_INSPECTION`, `COMPLETE_DELIVERY`, `MARK_UNDELIVERABLE`을 보존한다.
- 기존 전달 불가 사유 입력/검증 및 각 액션의 확인창·성공·실패 동작을 변경하지 않는다.
- `COMPLETE_DELIVERY`의 수취인 필수 정보 조건부 허용을 유지한다.
- 모든 기존 액션도 서버 `availableActions`를 따르며 로컬 상태 매핑으로 대체하지 않는다.
- 받을 주소, 카테고리, 사이즈 설정 화면은 수정하지 않는다.
## 3. 최소 타입/dispatch 확장과 버튼 노출
- 기존 `AdminGiftAction` 타입에 `CANCEL`을 추가하되 기존 값을 삭제하거나 재정의하지 않는다.
- 기존 액션 라벨/핸들러 dispatch에 `CANCEL`과 `신청 취소`만 추가한다.
- 목록 row와 열린 상세 모두 기존 액션 렌더링/실행 경로에 연결한다.
- 각 화면의 서버 응답 `availableActions.includes("CANCEL")`일 때만 취소 버튼을 표시한다.
- API 계약상 `RECEIVED`에서만 `CANCEL`을 제공한다. 다른 상태에서는 제공하지 않는다.
- `status === "RECEIVED"`만으로 취소 권한을 만들거나 버튼을 강제로 추가하지 않는다.
- 목록/상세 중 한쪽의 로컬 값을 다른 쪽의 권한 판단에 대신 사용하지 않는다.
- 권한의 최종 판단은 서버가 하며, 관리자가 발신자 본인일 필요는 없다.
## 4. 확정 API 계약
액션 조회:
- 목록: `GET /api/v2/admin/gifts`, `data.items[].availableActions`를 사용한다.
- 상세: `GET /api/v2/admin/gifts/{applicationNo}`, `data.availableActions`를 사용한다.
- 접수 완료 응답: `status="RECEIVED"`, `statusName="접수 완료"`, `availableActions=["CANCEL"]`.
- 취소 후 응답: `status="CANCELED"`, `statusName="신청 취소"`, `availableActions=[]`.
취소 요청:
- Method: `POST`
- URL: `/api/v2/admin/gifts/{applicationNo}/cancel`
- 권한: `ADMIN` 전용. 기존 관리자 인증/권한 경로를 재사용한다.
- Request body: 없음. `{}`, 취소 사유, 환불액 등을 보내지 않는다.
- 서버 전이: `RECEIVED`에서만 `CANCELED`로 변경한다.
- 환불: 원 발신자에게 사용 캔 전액을 리워드캔으로 환불한다.
- 관리자의 잔액으로 환불하는 기능이 아니다. 프론트에서 환불/잔액 변경을 직접 처리하지 않는다.
- 성공 응답: 공통 envelope의 `success=true`와 아래 `data`이며 기존 client의 envelope 처리 방식에 맞춘다. HTTP 성공 코드만으로 업무 성공을 판단하지 않는다.
```json
{
"applicationNo": "A-1002609300001",
"status": "CANCELED",
"statusName": "신청 취소",
"occurredAt": "2026-09-30T12:00:00Z"
}
```
`occurredAt`은 서버의 `canceledAt`과 같은 UTC ISO `Z` 문자열이다.
기존 `AdminGiftOperationStatusResponse`를 재사용하고 로컬 현재 시각으로 값을 만들지 않는다.
## 5. 확인과 요청 중 처리
- 기존 확인창 컴포넌트를 사용하며 대상 `applicationNo`를 명확히 표시한다.
- 확인 문구 예: `신청번호 {applicationNo}의 선물 신청을 취소하시겠습니까?`
- 환불 안내: `사용한 캔 전액이 발신자에게 리워드캔으로 환불됩니다.`
- 확인창을 닫거나 취소하면 API를 호출하지 않는다. 명시적으로 확정한 경우에만 호출한다.
- 확정한 신청번호로 body 없는 요청을 한 번만 보낸다.
- pending 동안 확인 버튼과 같은 신청의 목록/상세 취소 버튼을 비활성화해 중복 제출을 막는다.
- 기존 client의 mutation 재시도 정책도 확인하고 취소 요청을 무조건 자동 재시도하지 않는다.
- 키보드 포커스/확인창 접근성과 pending 표시는 기존 관리자 패턴을 유지한다.
## 6. 성공과 실패
- 서버 성공 확인 후 기존 성공 토스트를 표시한다. 응답 전에 성공으로 처리하지 않는다.
- 현재 필터와 현재 페이지/페이지 크기를 유지한 채 목록을 다시 조회한다.
- 상세가 열려 있으면 해당 상세도 다시 조회해 서버의 현재 상태와 액션을 반영한다.
- 취소된 항목의 서버 상태는 `CANCELED`, `statusName`은 `신청 취소`, `availableActions`는 `[]`다.
- 현재 필터에 맞지 않아 목록에서 사라져도 필터를 초기화하거나 첫 페이지로 임의 이동하지 않는다.
- 실패는 기존 공통 오류 토스트/알림을 사용하며 별도 오류 스키마나 HTTP 정책을 만들지 않는다.
- 서버가 상태 변경/잘못된 요청을 알리거나 통신 실패로 처리 결과가 불명확하면
기존 패턴에 따라 목록과 열린 상세의 서버 현재 상태를 재조회한다. mutation은 재시도하지 않는다.
- 실패 시 로컬 상태를 가짜 `CANCELED`로 바꾸거나 성공 토스트/환불 완료를 표시하지 않는다.
## 7. 구현 후 집중 검증
확정 계약의 성공·실패 응답 fixture와 기존 API client mock을 테스트 범위에서 사용해 화면 및 요청 동작을 검증한다. 실행 코드에 자동 fallback이나 가짜 성공 처리를 추가하지 않는다. 실제 API 연동 검증 결과는 단위/컴포넌트 테스트 결과와 구분해 보고한다.
- 목록과 상세 각각 `RECEIVED` + 서버 `CANCEL`이면 버튼이 나오고 기존 dispatch로 실행되는지 확인한다.
- `RECEIVED`여도 `CANCEL`이 없으면 버튼이 없고, 다른 모든 상태에는 `CANCEL`이 없는지 확인한다.
- 확인창에 신청번호/발신자 전액 리워드캔 환불이 표시되고 확인을 닫으면 API 호출이 0회인지 확인한다.
- 확정 시 정확한 URL로 body 없이 1회만 요청하며 연속 클릭과 목록/상세 중복 실행이 막히는지 확인한다.
- 성공 후 필터/페이지가 유지되고 목록과 열린 상세가 갱신되며 취소 건의 액션이 없어지는지 확인한다.
- 실패 시 공통 오류를 표시하고 가짜 취소/성공이 없으며 필요한 현재 상태 재조회가 수행되는지 확인한다.
- 기존 운영 액션과 `COMPLETE_DELIVERY`의 수취인 정보 조건부 노출이 그대로인지 회귀 확인한다.
- 기존 프론트엔드 테스트 도구로 위 변경 범위만 검증하고 실행 결과/미검증 항목을 보고한다.
## 8. 범위 밖
취소 사유 필드, 취소 전용 푸시 흐름, 새 페이지/라우트, 새 디자인 시스템은 추가하지 않는다.
백엔드 구현, 사용자 직접 취소/자동취소 변경, 기존 페이지 전면 재작성도 이 작업에 포함하지 않는다.
````
@@ -44,6 +44,7 @@ PRD에는 상세 API 계약, request/response 예시, 요구사항을 유지한
| 관리자 | GET | `/api/v2/admin/gift-categories` | 선물 카테고리 목록을 조회한다. | 없음 | `AdminGiftCategoryResponse[]` |
| 관리자 | GET | `/api/v2/admin/gifts` | 전체 선물함 목록을 조회하고 상태/신청번호/닉네임으로 검색한다. | query: `status`, `applicationNo`, `nickname`, `page`, `size` | `totalCount`, `items[]`, `page`, `size`, `hasNext` |
| 관리자 | GET | `/api/v2/admin/gifts/{applicationNo}` | 선물 상세와 발송인/수취인 개인정보, 상품/배송/상태 정보를 조회한다. | path: `applicationNo` | `applicationNo`, `senderInfo`, `recipientInfo`, `productInfo`, `inboundDeliveryInfo`, `status`, `statusName`, `availableActions` |
| 관리자 | POST | `/api/v2/admin/gifts/{applicationNo}/cancel` | 운송장 등록 전 `RECEIVED` 선물을 취소하고 발신자에게 전액 리워드캔을 환불한다. | path: `applicationNo`, body 없음 | `AdminGiftOperationStatusResponse`: `applicationNo`, `status`, `statusName`, `occurredAt` (`canceledAt` UTC ISO `Z`) |
| 관리자 | POST | `/api/v2/admin/gift-categories` | 선물 카테고리를 등록한다. | body: `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive` | `categoryId`, `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive` |
| 관리자 | PUT | `/api/v2/admin/gift-categories/{categoryId}` | 선물 카테고리를 수정한다. | path: `categoryId`, body: `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive` | `categoryId`, `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive` |
| 관리자 | DELETE | `/api/v2/admin/gift-categories/{categoryId}` | 선물 카테고리를 비활성화한다. | path: `categoryId` | `categoryId`, `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive=false` |
@@ -63,6 +64,12 @@ PRD에는 상세 API 계약, request/response 예시, 요구사항을 유지한
- `AdminGiftSizePriceResponse`: `sizeCode`, `name`, `basePriceCan`, `salePriceCan`, `isActive`
- `AdminGiftOperationStatusResponse`: `applicationNo`, `status`, `statusName`, `occurredAt`
관리자 취소는 기존 `hasRole('ADMIN')` 권한을 따르며 관리자가 발신자가 아니어도 `RECEIVED`에서 취소할 수 있다. 같은 트랜잭션에서 `GiftDelivery.canceledAt`을 기록하고 기존 `refundGiftCan`으로 발신자에게 원 결제액 전액을 리워드캔으로 환불한다. 존재하지 않는 신청번호나 다른 상태는 기존 공통 invalid request이며 기존 `ApiResponse` envelope를 사용한다. 기존 사용자 취소 응답은 별도 계약이며 바뀌지 않는다. HTTP 성공 코드만으로 업무 성공을 판단하지 않고 envelope의 `success`를 확인한다.
관리자 선물함의 action: `RECEIVED`는 목록과 상세 모두 `availableActions=["CANCEL"]`; 그 외 상태는 기존 매핑을 유지한다. 특히 `INSPECTION_COMPLETED`의 `COMPLETE_DELIVERY`는 수취인 정보 완비 조건부 노출이다.
기존 관리자 화면에 취소 기능만 추가하는 작업은 [관리자 취소 기능 추가 프롬프트](admin-gift-cancel-addition-prompt.md)를 사용한다. 전체 화면 구현용 `admin-page-prompts.md`는 변경하지 않는다.
## Kotlin DTO 형태
아래는 JSON으로 변환되기 직전의 API request/response `data class` 형태다. `companion object` 변환 함수는 클라이언트 입력/출력 형태와 직접 관련이 없어 생략했다. 응답 시각 필드는 UTC ISO 문자열이다. 예: `2026-09-30T12:00:00Z`.