1385 lines
96 KiB
Markdown
1385 lines
96 KiB
Markdown
# 크리에이터 선물하기 구현 계획
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
| 문서 항목 | 내용 |
|
|
|---|---|
|
|
| 상태 | 완료 |
|
|
| 작성일 | 2026-09-29 |
|
|
| 요구사항 기준 | `docs/20260929_크리에이터_선물하기/prd.md` |
|
|
| API 기준 | `docs/20260929_크리에이터_선물하기/prd.md`의 `8. API 계약` |
|
|
| 현재 Phase | Phase 10 완료 |
|
|
| 현재 활성 Goal | 완료 |
|
|
|
|
## 목표
|
|
|
|
팬이 캔으로 크리에이터에게 실물 선물을 신청하고, 운송장 등록부터 크리에이터 배송지 입력, 운영 검수, 전달 완료/불가, 리뷰와 푸시까지 서버에서 추적할 수 있게 한다.
|
|
|
|
## 아키텍처
|
|
|
|
신규 도메인은 `kr.co.vividnext.sodalive.v2.gift` 아래에 배치하고, v2 신규 도메인 관례대로 `domain`, `application`, `port`, `adapter` 패키지를 사용한다. JPA 엔티티와 repository는 `adapter.out.persistence`에 두고, 순수 enum/정책은 `domain`, 상태 전이와 캔 결제/환불 및 푸시 조합은 `application`에 둔다. 사용자 API는 `kr.co.vividnext.sodalive.v2.api.gift`, 관리자 API는 `kr.co.vividnext.sodalive.v2.api.admin.gift` 아래에 두되 controller는 v2 web adapter 관례에 맞춰 `adapter.in.web`, DTO는 `dto` 하위에 둔다. 공개 API DTO는 API 패키지에 두되 도메인/application service가 API DTO를 import하지 않도록 한다.
|
|
|
|
## 기술 스택
|
|
|
|
- Kotlin + Java 17
|
|
- Spring Boot 2.7.14
|
|
- Spring MVC, Spring Security, Spring Data JPA, QueryDSL
|
|
- Gradle Wrapper, ktlint
|
|
- 테스트: 기존 JUnit/Spring Boot Test/MockMvc 패턴
|
|
|
|
## 현재 상태
|
|
|
|
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
|
|---:|---|---:|---|---|
|
|
| 1 | 완료 | `4/4` | 완료 | 없음 |
|
|
| 2 | 완료 | `4/4` | 완료 | 없음 |
|
|
| 3 | 완료 | `4/4` | 완료 | 없음 |
|
|
| 4 | 완료 | `3/3` | 완료 | 없음 |
|
|
| 5 | 완료 | `3/3` | 완료 | 없음 |
|
|
| 6 | 완료 | `2/2` | 완료 | 없음 |
|
|
| 7 | 완료 | `3/3` | 완료 | 없음 |
|
|
| 8 | 완료 | `1/1` | 완료 | 없음 |
|
|
| 9 | 완료 | `3/3` | 완료 | 없음 |
|
|
| 10 | 완료 | `3/3` | 완료 | 관리자 선물함 목록/상세 API 추가 |
|
|
|
|
- 동시에 하나의 미완료 goal만 운용한다.
|
|
- 구현 중 요구사항이 바뀌면 이 문서의 체크박스와 범위를 먼저 갱신한 뒤 코드를 수정한다.
|
|
- 완료된 Task와 Progress 기록은 삭제하거나 덮어쓰지 않는다.
|
|
|
|
## 범위
|
|
|
|
### 포함
|
|
|
|
- 선물 신청, 목록, 상세, 운송장 등록, 취소, 크리에이터 배송지 입력, 크리에이터 수령확인, 리뷰 작성 사용자 API
|
|
- 관리자 카테고리 API, 관리자 사이즈 가격 API, 운영 상태변경 API
|
|
- 운송장 미등록 자동취소, 배송지 미입력 전달불가, 기한 24시간 전 안내 스케줄러
|
|
- 캔 사용내역 `선물하기` 추가와 선물 신청 내부 연결
|
|
- 팬/크리에이터 대상 푸시 이벤트와 단일 딥링크 `GIFT_DETAIL` + `applicationNo`
|
|
- MySQL 운영 반영용 DDL 문서
|
|
|
|
### 제외
|
|
|
|
- 약관 본문 제공 API
|
|
- 외부 택배 조회, 송장 유효성 실시간 조회, 배송 추적 자동화
|
|
- 외부 PG 에스크로, 커머스 플랫폼, provider별 택배사 코드 매핑
|
|
- 선물 이미지, 첨부파일, 메시지 카드
|
|
- 카테고리 hard delete
|
|
- 선물 사이즈 정의 추가/삭제
|
|
- 캔 사용내역 화면에서 선물 상세로 이동하는 사용자 기능
|
|
|
|
## 파일 책임 지도
|
|
|
|
### 문서와 DDL
|
|
|
|
- Create: `docs/20260929_크리에이터_선물하기/gift-schema.sql` — MySQL 운영 반영용 신규 테이블/인덱스 DDL.
|
|
- Modify: `docs/20260929_크리에이터_선물하기/plan-task.md` — 각 Task 실행 후 체크박스와 Progress 기록 누적.
|
|
|
|
### 도메인 모델과 repository
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/domain/GiftStatus.kt` — 선물 상태 enum.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/domain/GiftSize.kt` — 선물 사이즈 enum.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/Gift.kt` — 선물 신청 엔티티.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftDelivery.kt` — 발신자/수신자 배송 정보와 상태 시각 엔티티.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftCategory.kt` — 관리자 카테고리 엔티티.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftSizePrice.kt` — 사이즈별 기본/판매 금액 설정 엔티티.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftReview.kt` — 선물 리뷰 엔티티.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftApplicationNoSequence.kt` — 날짜별 신청번호 채번 엔티티.
|
|
- Create: repository files under `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/`.
|
|
|
|
### 도메인 service
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftApplicationNoGenerator.kt` — `G{yyyyMMdd}{dailySequence6}` 발급과 동시성 재시도.
|
|
- Implemented in `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt` and `GiftCommandService.kt` — 사이즈 가격 조회와 신청 시점 최종 결제 금액 스냅샷.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt` — 신청, 운송장 등록, 취소, 배송지 입력, 수령확인, 리뷰 작성.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt` — 폼 옵션, 목록, 상세 조회.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminService.kt` — 관리자 카테고리/가격 설정과 운영 상태변경.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/scheduler/GiftScheduler.kt` — 자동취소, 배송지 미입력 전달불가, 24시간 전 안내 작업.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushService.kt` — 상태별 팬/크리에이터 푸시 이벤트 발행.
|
|
- Reuse: `kr.co.vividnext.sodalive.common.SodaException` — 선물 도메인 오류는 기존 공통 예외를 사용한다.
|
|
|
|
### 사용자 API
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftController.kt` — 사용자 API endpoint.
|
|
- Create: user request DTO files under `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/dto/` — `GiftApplicationRequest.kt`, `GiftTrackingRegistrationRequest.kt`, `GiftRecipientAddressRegistrationRequest.kt`, `GiftReviewRequest.kt`.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/dto/GiftResponse.kt` — 사용자 API response DTO.
|
|
|
|
### 관리자 API
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftController.kt` — 관리자 카테고리/가격/운영 상태변경 endpoint.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/dto/AdminGiftRequest.kt` — 관리자 request DTO.
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/dto/AdminGiftResponse.kt` — 관리자 response DTO.
|
|
|
|
### 기존 파일 변경
|
|
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/can/use/CanUsage.kt` — `선물하기` 용도 추가.
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt` — 기존 `spendCan`/`refund` 재사용 가능 여부 확인 후 필요한 최소 연결 메서드 추가.
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt` — `GIFT_DETAIL` 딥링크와 선물 푸시 이벤트 타입 추가.
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/fcm/notification/PushNotificationCategory.kt` — 선물 알림 카테고리 필요 시 `GIFT` 추가.
|
|
- Reuse: `FcmEvent.title`/`message` 기본 문구 — 선물 푸시는 별도 다국어 키 추가 없이 기존 FCM 기본 문구 경로를 사용한다.
|
|
|
|
### 테스트
|
|
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftApplicationNoGeneratorTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminServiceTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandServiceTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/scheduler/GiftSchedulerTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushServiceTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt`
|
|
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt`
|
|
|
|
## Task TDD 작성 규칙
|
|
|
|
모든 구현 Task는 아래 순서를 지킨다.
|
|
|
|
- [ ] **RED:** 가장 작은 실패 test를 작성한다.
|
|
- [ ] **RED 확인:** focused test를 실행해 요구 동작이 없어서 발생한 의도한 assertion 실패를 확인한다.
|
|
- [ ] **GREEN:** RED를 통과시키는 최소 구현을 작성한다.
|
|
- [ ] **GREEN 확인:** 같은 focused test를 다시 실행해 성공을 확인한다.
|
|
- [ ] **REFACTOR:** 새 동작을 바꾸지 않는 범위에서 이번 Task가 만든 중복만 정리하고, focused test·직접 영향 회귀·lint를 다시 실행해 실제 결과를 Progress에 기록한다.
|
|
|
|
## Phase 1: 데이터 기반과 관리자 설정 API
|
|
|
|
**Phase 결과:** 선물 도메인 테이블, 상태/사이즈 enum, 신청번호 채번, 관리자 카테고리/사이즈 가격 설정, 팬용 폼 옵션 조회가 준비된다.
|
|
|
|
**선행조건:** 없음.
|
|
|
|
**Phase 완료 조건:** `P1-T1`~`P1-T4`와 `P1-GATE` 완료, 검증 기록 누적.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 1.1 DDL과 도메인 엔티티 생성
|
|
|
|
**Goal 실행 `P1-T1`:** PRD의 데이터 모델을 MySQL DDL과 JPA 엔티티로 만든다.
|
|
|
|
- **시작 조건:** PRD `10. 데이터 모델 요구사항` 확인.
|
|
- **완료 증거:** DDL 문서, 엔티티/repository 파일, 엔티티 매핑 focused test 통과.
|
|
- **범위 밖:** 사용자 API, 결제, 푸시, 스케줄러.
|
|
|
|
**Files:**
|
|
|
|
- Create: `docs/20260929_크리에이터_선물하기/gift-schema.sql`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/domain/GiftStatus.kt`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/domain/GiftSize.kt`
|
|
- Create: entity and repository files under `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftEntityMappingTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `GiftStatus`, `GiftSize`, `Gift`, `GiftDelivery`, `GiftCategory`, `GiftSizePrice`, `GiftReview`.
|
|
|
|
- [x] **RED:** 엔티티 저장/조회, `applicationNo` unique, `GiftReview.giftId` unique, 카테고리 `categoryCode` unique를 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftEntityMappingTest"`를 실행해 신규 엔티티 미존재 또는 매핑 미구현 실패를 확인한다.
|
|
- [x] **GREEN:** 엔티티, repository, DDL을 최소 구현한다. DDL의 모든 컬럼과 테이블에는 MySQL `COMMENT`를 추가하고 `created_at`, `updated_at`은 문서 규칙을 따른다.
|
|
- [x] **GREEN 확인:** 같은 focused test가 통과하는지 확인한다.
|
|
- [x] **REFACTOR:** 엔티티별 책임을 유지하고, `./gradlew ktlintCheck`와 focused test 결과를 Progress에 기록한다.
|
|
|
|
#### Task 1.2 신청번호 채번 구현
|
|
|
|
**Goal 실행 `P1-T2`:** `G{yyyyMMdd}{dailySequence6}` 신청번호를 동시 신청에서도 중복 없이 발급한다.
|
|
|
|
- **시작 조건:** `P1-T1` 완료.
|
|
- **완료 증거:** 채번 단위/동시성 test 통과.
|
|
- **범위 밖:** 선물 신청 command 전체.
|
|
|
|
**Files:**
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftApplicationNoGenerator.kt`
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftApplicationNoSequenceRepository.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftApplicationNoGeneratorTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `fun generate(now: LocalDateTime = LocalDateTime.now()): String`.
|
|
|
|
- [x] **RED:** 같은 날짜 연속 발급이 `G20260929000001`, `G20260929000002`가 되고 병렬 발급 결과가 모두 unique인 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGeneratorTest"`로 실패를 확인한다.
|
|
- [x] **GREEN:** DB unique 제약, 원자적 채번 또는 충돌 재시도 중 가장 단순한 방식으로 구현한다.
|
|
- [x] **GREEN 확인:** 같은 focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 재시도 횟수와 실패 오류를 명확히 하고 focused test·`ktlintCheck` 결과를 기록한다.
|
|
|
|
#### Task 1.3 관리자 카테고리와 사이즈 가격 API 구현
|
|
|
|
**Goal 실행 `P1-T3`:** 관리자가 카테고리와 사이즈별 `basePriceCan`/`salePriceCan`을 조회·수정할 수 있게 한다.
|
|
|
|
- **시작 조건:** `P1-T1` 완료.
|
|
- **완료 증거:** 관리자 service/controller test 통과.
|
|
- **범위 밖:** 사용자 선물 신청, 폼 옵션 조회.
|
|
|
|
**Files:**
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminService.kt`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftController.kt`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/dto/AdminGiftRequest.kt`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/dto/AdminGiftResponse.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminServiceTest.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `GET/POST/PUT/DELETE /api/v2/admin/gift-categories`, `GET/PUT /api/v2/admin/gift-size-prices`.
|
|
|
|
- [x] **RED:** 카테고리 등록/수정/논리삭제, 비활성 전환, `salePriceCan > basePriceCan` 거부 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.AdminGiftControllerTest"`로 실패를 확인한다.
|
|
- [x] **GREEN:** 관리자 service와 controller를 최소 구현한다. 삭제는 `isActive=false`만 수행한다.
|
|
- [x] **GREEN 확인:** 같은 focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 관리자 DTO와 도메인 모델 import 방향을 점검하고 `ktlintCheck` 결과를 기록한다.
|
|
|
|
#### Task 1.4 팬용 선물 신청 폼 옵션 조회 구현
|
|
|
|
**Goal 실행 `P1-T4`:** 팬이 선물 신청 페이지에서 활성 사이즈 가격과 활성 카테고리를 한 번에 조회한다.
|
|
|
|
- **시작 조건:** `P1-T1`, `P1-T3` 완료.
|
|
- **완료 증거:** `GET /api/v2/gifts/form-options` contract test 통과.
|
|
- **범위 밖:** 선물 신청 mutation.
|
|
|
|
**Files:**
|
|
|
|
- Implemented in: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftController.kt`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/dto/GiftResponse.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `GET /api/v2/gifts/form-options` with `sizes`, `categories`.
|
|
|
|
- [x] **RED:** 비활성 카테고리 제외, `categoryCode` 미노출, `basePriceCan`/`salePriceCan` 응답을 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"`로 실패를 확인한다.
|
|
- [x] **GREEN:** 폼 옵션 조회 service/controller/response를 최소 구현한다.
|
|
- [x] **GREEN 확인:** 같은 focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** page/DTO 책임 경계를 정리하고 focused test·`ktlintCheck` 결과를 기록한다.
|
|
|
|
### Phase 1 Gate
|
|
|
|
**Goal 실행 `P1-GATE`:** 데이터 기반과 관리자 설정/폼 옵션 계약을 최종 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"
|
|
./gradlew ktlintCheck
|
|
./gradlew tasks --all
|
|
```
|
|
|
|
**Expected:** 모든 명령 exit 0. PRD `GIFT-001`, `GIFT-012`, `GIFT-013`이 구현 또는 명시적 후속 범위로 추적된다.
|
|
|
|
**결과:** 완료. 2026-09-29 기준 Gate 명령 3개 모두 `BUILD SUCCESSFUL`.
|
|
|
|
## Phase 2: 팬 선물 신청, 결제, 취소, 운송장 등록
|
|
|
|
**Phase 결과:** 팬이 선물을 신청해 캔을 결제하고, 운송장 등록 전 취소/환불하거나 운송장을 등록할 수 있다.
|
|
|
|
**선행조건:** `P1-GATE` 완료.
|
|
|
|
**Phase 완료 조건:** `P2-T1`~`P2-T4`와 `P2-GATE` 완료, 검증 기록 누적.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 2.1 캔 사용내역 `선물하기` 연결
|
|
|
|
**Goal 실행 `P2-T1`:** 선물 신청에서 사용할 캔 차감/환불 연결점을 만든다.
|
|
|
|
- **시작 조건:** `P1-GATE` 완료, 기존 `CanPaymentService`와 `CanUsage` 확인.
|
|
- **완료 증거:** 캔 차감/환불 focused test 통과.
|
|
- **범위 밖:** 선물 신청 API 전체.
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/can/use/CanUsage.kt`
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: 선물 신청에서 호출할 캔 차감/환불 contract. 기존 `spendCan`/`refund` 재사용을 우선한다.
|
|
|
|
- [x] **RED:** `CanUsage.GIFT` 또는 동등한 `선물하기` 사용내역이 생성되고 환불 시 1회만 환불되는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest"`로 실패를 확인한다.
|
|
- [x] **GREEN:** 기존 결제 service를 최대한 재사용해 최소 연결만 추가한다.
|
|
- [x] **GREEN 확인:** 같은 focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 기존 결제 호출부 회귀를 확인하고 focused test·`./gradlew test --tests "kr.co.vividnext.sodalive.can.*"` 결과를 기록한다.
|
|
|
|
#### Task 2.2 선물 보내기 등록 구현
|
|
|
|
**Goal 실행 `P2-T2`:** 팬이 약관/파손면책/발신자 정보를 입력해 선물을 신청하고 캔을 즉시 차감한다.
|
|
|
|
- **시작 조건:** `P2-T1` 완료.
|
|
- **완료 증거:** service/controller 신청 test 통과.
|
|
- **범위 밖:** 운송장 등록, 목록/상세 조회.
|
|
|
|
**Files:**
|
|
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt`
|
|
- Reuse: `kr.co.vividnext.sodalive.common.SodaException`
|
|
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/dto/GiftApplicationRequest.kt`
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftController.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/GiftControllerTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `POST /api/v2/gifts`.
|
|
|
|
- [x] **RED:** 필수 발신자 정보, 팬 약관 2개, 조건부 `damageWaiverAgreed`, 비활성 카테고리, 최종 결제 금액 스냅샷, 고유 `applicationNo`, 캔 차감을 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"`로 실패를 확인한다.
|
|
- [x] **GREEN:** 신청 API와 service를 최소 구현한다. `trackingDeadlineAt`은 신청 시각 + 3일로 저장한다.
|
|
- [x] **GREEN 확인:** 같은 focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** validation 중복만 정리하고 focused test·`ktlintCheck` 결과를 기록한다.
|
|
|
|
#### Task 2.3 선물 보내기 취소 구현
|
|
|
|
**Goal 실행 `P2-T3`:** 팬이 운송장 등록 전 선물 신청을 취소하고 전액 환불받을 수 있게 한다.
|
|
|
|
- **시작 조건:** `P2-T2` 완료.
|
|
- **완료 증거:** 취소/환불 focused test 통과.
|
|
- **범위 밖:** 자동취소 스케줄러.
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt`
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftController.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/GiftControllerTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `POST /api/v2/gifts/{applicationNo}/cancel`.
|
|
|
|
- [x] **RED:** 보낸 팬만 취소 가능, `RECEIVED`에서만 가능, `TRACKING_REGISTERED` 이후 실패, 환불 1회만 발생하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 명령으로 실패를 확인한다.
|
|
- [x] **GREEN:** 취소 상태 전이와 환불을 최소 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 상태 전이 helper가 필요하면 이 Task 범위 안에서만 추출하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 2.4 운송장 등록 구현
|
|
|
|
**Goal 실행 `P2-T4`:** 팬이 택배사 한글 표시명과 운송장 번호를 등록해 상태를 `TRACKING_REGISTERED`로 바꾼다.
|
|
|
|
- **시작 조건:** `P2-T2` 완료.
|
|
- **완료 증거:** 운송장 등록 service/controller test 통과.
|
|
- **범위 밖:** 크리에이터 배송지 입력, 푸시 실제 발송 검증.
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt`
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftController.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/GiftControllerTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: `POST /api/v2/gifts/{applicationNo}/tracking` with `courierCompanyName`, `trackingNumber`.
|
|
|
|
- [x] **RED:** 보낸 팬만 등록 가능, `RECEIVED`에서만 가능, `courierCompanyName` 필수/50자 이하, 크리에이터 배송지 입력 기한 생성, 상태 전이를 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 명령으로 실패를 확인한다.
|
|
- [x] **GREEN:** 운송장 등록과 `recipientAddressDeadlineAt = 등록 시각 + 7일` 저장을 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 받은 선물 노출 조건과 충돌하지 않도록 상태 predicate 이름을 정리하고 회귀 결과를 기록한다.
|
|
|
|
### Phase 2 Gate
|
|
|
|
**Goal 실행 `P2-GATE`:** 팬 신청/결제/취소/운송장 등록 흐름을 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"
|
|
./gradlew ktlintCheck
|
|
```
|
|
|
|
**Expected:** `GIFT-002`~`GIFT-005`의 핵심 mutation이 통과하고, 운송장 등록 후 받은 선물 조회 가능 상태가 저장된다.
|
|
|
|
## Phase 3: 선물함 조회와 크리에이터 배송지 입력
|
|
|
|
**Phase 결과:** 팬/크리에이터가 선물함 목록과 상세를 조회하고, 크리에이터가 배송지와 수령 약관을 등록할 수 있다.
|
|
|
|
**선행조건:** `P2-GATE` 완료.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 3.1 선물함 목록 조회 구현
|
|
|
|
**Goal 실행 `P3-T1`:** `type=ALL|SENT|RECEIVED` 목록과 `direction` 판정을 구현한다.
|
|
|
|
- **Files:** Modify `GiftQueryService.kt`, `GiftController.kt`, `GiftResponse.kt`; Test `GiftQueryServiceTest.kt`, `GiftControllerTest.kt`.
|
|
- [x] **RED:** 보낸 선물은 신청 직후 노출, 받은 선물은 `TRACKING_REGISTERED` 이상부터 노출, `direction`, pagination 보정을 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"` 실패를 확인한다.
|
|
- [x] **GREEN:** 목록 query와 response를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** QueryDSL 조건을 중복 없이 정리하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 3.2 선물 상세 조회 구현
|
|
|
|
**Goal 실행 `P3-T2`:** 단일 상세 API가 로그인 회원 기준으로 보낸/받은 선물을 판정한다.
|
|
|
|
- **Files:** Modify `GiftQueryService.kt`, `GiftController.kt`, `GiftResponse.kt`; Test `GiftQueryServiceTest.kt`, `GiftControllerTest.kt`.
|
|
- [x] **RED:** sender는 `SENT`, recipient는 노출 조건 만족 시 `RECEIVED`, 무관한 회원은 권한 오류 또는 미존재 오류를 반환하는 실패 test를 작성한다.
|
|
- [x] **RED:** `direction=SENT` 상세 응답이 `giftInfo.recipientCreatorNickname`, `giftInfo.sizeName`, `giftInfo.categoryName`, `giftInfo.applicationNo`, `giftInfo.paidCan`, `giftInfo.tracking`, `senderInfo.name`, `senderInfo.phoneNumber`, `senderInfo.address`, `mailbox`를 포함하는 실패 test를 작성한다.
|
|
- [x] **RED:** `direction=SENT` 상세 응답에서 `giftInfo.senderNickname`, `giftInfo.shippingRequestedAt`, `recipientAddress`가 `null`이고 크리에이터가 입력한 이름/휴대폰 번호/주소가 노출되지 않는 실패 test를 작성한다.
|
|
- [x] **RED:** `direction=SENT`이고 상태가 `RECEIVED`이면 `trackingRequired=true`, 그 외 상태이거나 `direction=RECEIVED`이면 `trackingRequired=false`인 실패 test를 작성한다.
|
|
- [x] **RED:** `direction=RECEIVED` 상세 응답이 `giftInfo.senderNickname`, `giftInfo.sizeName`, `giftInfo.categoryName`, `giftInfo.shippingRequestedAt`, `recipientAddress.name`, `recipientAddress.phoneNumber`, `recipientAddress.address`를 포함하는 실패 test를 작성한다.
|
|
- [x] **RED:** `direction=RECEIVED` 상세 응답에서 `giftInfo.recipientCreatorNickname`, `giftInfo.applicationNo`, `giftInfo.paidCan`, `giftInfo.tracking`, `senderInfo`, `mailbox`가 `null`이고 팬이 신청 시 등록한 이름/휴대폰 번호/주소가 노출되지 않는 실패 test를 작성한다.
|
|
- [x] **RED:** `direction=RECEIVED`이고 받는 주소가 미입력이며 종료 상태가 아니면 `recipientAddressRequired=true`, 주소 입력 완료 또는 종료 상태 또는 `direction=SENT`이면 `recipientAddressRequired=false`인 실패 test를 작성한다.
|
|
- [x] **RED:** 발신자/수신자 주소가 `(우편번호) 주소, 상세주소` 형식으로 조립되고, 로그인 회원이 알아야 하는 데이터가 아니거나 아직 입력되지 않은 데이터는 `null`인 실패 test를 작성한다.
|
|
- [x] **RED:** 상세 응답의 `statusTimeline`이 `RECEIVED`, `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED`, `DELIVERED` 순서로 내려오고 도달한 상태만 `occurredAt`을 가지며 미도달 상태는 `null`인 실패 test를 작성한다.
|
|
- [x] **RED:** `CANCELED`, `UNDELIVERABLE` 상세에서 정상 진행 `statusTimeline`은 유지되고 종료 정보는 각각 `delivery.canceledAt`, `delivery.undeliverableAt`, `delivery.undeliverableReason`으로 내려오는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 상세 query와 권한 판정을 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 역할별 상세 응답 조립, 본인/상대방 개인정보 노출 경계, 상태 타임라인 조립 책임을 response mapper에서 명확히 하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 3.3 크리에이터 배송지 입력 구현
|
|
|
|
**Goal 실행 `P3-T3`:** 크리에이터가 배송지와 수령 약관 2개를 등록할 수 있게 한다.
|
|
|
|
- **Files:** Modify `GiftCommandService.kt`, `GiftController.kt`, `GiftRecipientAddressRegistrationRequest.kt`; Test `GiftCommandServiceTest.kt`, `GiftControllerTest.kt`.
|
|
- [x] **RED:** 받는 크리에이터만 가능, 종료 상태 실패, 이름/휴대폰/우편번호/주소 필수, 수령 약관 2개 필수, 동의 시각 저장 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 배송지 입력 API를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 발신/수신 주소 snapshot 책임을 `GiftDelivery` 안으로 정리하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 3.4 크리에이터 수령확인 구현
|
|
|
|
**Goal 실행 `P3-T4`:** 크리에이터가 `INSPECTION_COMPLETED` 선물을 `DELIVERED`로 완료 처리한다.
|
|
|
|
- **Files:** Modify `GiftCommandService.kt`, `GiftController.kt`; Test `GiftCommandServiceTest.kt`, `GiftControllerTest.kt`.
|
|
- [x] **RED:** 받는 크리에이터만 가능, `INSPECTION_COMPLETED`에서만 가능, `deliveredAt` 저장, 팬 푸시 발행 요청을 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 수령확인 상태 전이를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 운영 전달완료와 공유 가능한 상태 전이 검증만 추출하고 회귀 결과를 기록한다.
|
|
|
|
### Phase 3 Gate
|
|
|
|
**Goal 실행 `P3-GATE`:** 조회와 크리에이터 배송지/수령 흐름을 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"
|
|
./gradlew ktlintCheck
|
|
```
|
|
|
|
**Expected:** `GIFT-007`과 상세/목록 권한 정책이 통과한다.
|
|
|
|
## Phase 4: 자동 스케줄링
|
|
|
|
**Phase 결과:** 운송장 미등록과 배송지 미입력 기한 초과, 24시간 전 안내가 자동 처리된다.
|
|
|
|
**선행조건:** `P2-GATE`, `P3-GATE` 완료.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 4.1 운송장 미등록 자동취소 구현
|
|
|
|
**Goal 실행 `P4-T1`:** 신청 후 3일 초과 `RECEIVED` 선물을 `CANCELED`로 바꾸고 전액 환불한다.
|
|
|
|
- **Files:** Create `GiftScheduler.kt`; Modify `GiftRepository.kt`, `GiftCommandService.kt`; Test `GiftSchedulerTest.kt`.
|
|
- [x] **RED:** 기한 초과 대상만 취소, 환불 1회, 중복 실행 idempotent 실패 test를 작성한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftSchedulerTest"` 실패를 확인한다.
|
|
- [x] **GREEN:** 자동취소 job service를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** scheduler와 command service의 상태 전이 중복을 정리하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 4.2 배송지 미입력 전달불가 구현
|
|
|
|
**Goal 실행 `P4-T2`:** 운송장 등록 후 7일 초과 배송지 미입력 선물을 `UNDELIVERABLE`로 바꾼다.
|
|
|
|
- **Files:** Modify `GiftScheduler.kt`, `GiftRepository.kt`; Test `GiftSchedulerTest.kt`.
|
|
- [x] **RED:** 배송지 미입력 기한 초과만 대상, 사유 `배송지 미입력 기한 초과`, 중복 실행 idempotent 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 전달불가 job service를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 대상 조회 query 이름과 상태 조건을 명확히 하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 4.3 24시간 전 안내 작업 구현
|
|
|
|
**Goal 실행 `P4-T3`:** 운송장 등록과 배송지 입력 마감 24시간 전 안내 푸시 작업을 구현한다.
|
|
|
|
- **Files:** Modify `GiftScheduler.kt`, `GiftPushService.kt`; Test `GiftSchedulerTest.kt`, `GiftPushServiceTest.kt`.
|
|
- [x] **RED:** 마감 24시간 전 범위만 대상, 같은 선물 중복 안내 방지 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 안내 작업과 중복 방지 기준을 구현한다. 중복 방지는 별도 컬럼 또는 푸시 기록 조회 중 가장 단순한 방식을 사용한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 작업별 batch size와 로그에 개인정보가 남지 않는지 확인하고 회귀 결과를 기록한다.
|
|
|
|
### Phase 4 Gate
|
|
|
|
**Goal 실행 `P4-GATE`:** 자동 처리와 idempotency를 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftSchedulerTest" --tests "kr.co.vividnext.sodalive.v2.gift.GiftPushServiceTest"
|
|
./gradlew ktlintCheck
|
|
```
|
|
|
|
**Expected:** `GIFT-006`, `GIFT-008` 자동 처리와 중복 실행 방지가 통과한다.
|
|
|
|
## Phase 5: 운영 상태변경 API
|
|
|
|
**Phase 결과:** 운영자가 사서함 도착, 검수완료, 전달불가, 전달완료를 상태별 전용 API로 처리한다.
|
|
|
|
**선행조건:** `P3-GATE` 완료.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 5.1 사서함 도착과 검수완료 API 구현
|
|
|
|
**Goal 실행 `P5-T1`:** 운영자가 `TRACKING_REGISTERED -> ARRIVED_AT_MAILBOX -> INSPECTION_COMPLETED` 전이를 처리한다.
|
|
|
|
- **Files:** Modify `GiftAdminService.kt`, `AdminGiftController.kt`; Test `GiftAdminServiceTest.kt`, `AdminGiftControllerTest.kt`.
|
|
- [x] **RED:** 허용 이전 상태 성공, 잘못된 이전 상태 실패, 상태 시각 저장, 팬/크리에이터 푸시 요청 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 두 운영 API를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 상태 전이 검증 함수 중복을 정리하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 5.2 운영 전달불가 API 구현
|
|
|
|
**Goal 실행 `P5-T2`:** 운영자가 전달불가 사유를 등록하고 `UNDELIVERABLE`로 종료 처리한다.
|
|
|
|
- **Files:** Modify `GiftAdminService.kt`, `AdminGiftController.kt`, `AdminGiftMarkUndeliverableRequest.kt`; Test `GiftAdminServiceTest.kt`, `AdminGiftControllerTest.kt`.
|
|
- [x] **RED:** 허용 상태 성공, 사유 필수, 종료 상태 실패, 팬/크리에이터 전달불가 푸시 요청 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 전달불가 API를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 사유 길이와 개인정보 로그 금지를 확인하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 5.3 운영 전달완료 API 구현
|
|
|
|
**Goal 실행 `P5-T3`:** 운영자가 `INSPECTION_COMPLETED` 선물을 `DELIVERED`로 완료 처리한다.
|
|
|
|
- **Files:** Modify `GiftAdminService.kt`, `AdminGiftController.kt`; Test `GiftAdminServiceTest.kt`, `AdminGiftControllerTest.kt`.
|
|
- [x] **RED:** `INSPECTION_COMPLETED`에서만 성공, 팬 전달완료 푸시, 운영 처리 시 크리에이터 배송완료 푸시 정책을 검증하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 운영 전달완료 API를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 크리에이터 직접 수령확인과 운영 전달완료의 푸시 차이를 명확히 하고 회귀 결과를 기록한다.
|
|
|
|
### Phase 5 Gate
|
|
|
|
**Goal 실행 `P5-GATE`:** 운영 상태변경 API를 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.AdminGiftControllerTest"
|
|
./gradlew ktlintCheck
|
|
```
|
|
|
|
**Expected:** `GIFT-009`의 상태별 전용 API와 금지 상태 전이가 통과한다.
|
|
|
|
## Phase 6: 리뷰 작성
|
|
|
|
**Phase 결과:** 팬이 전달완료 선물에 별점, 여러 키워드, 추가의견 리뷰를 1회 작성한다.
|
|
|
|
**선행조건:** `P3-GATE` 또는 `P5-GATE`에서 `DELIVERED` 전이 구현 완료.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 6.1 리뷰 작성 service 구현
|
|
|
|
**Goal 실행 `P6-T1`:** 보낸 팬만 `DELIVERED` 선물에 리뷰를 1회 작성할 수 있게 한다.
|
|
|
|
- **Files:** Modify `GiftCommandService.kt`, `GiftReviewRepository.kt`; Test `GiftCommandServiceTest.kt`.
|
|
- [x] **RED:** 보낸 팬만 가능, `DELIVERED`에서만 가능, 중복 실패, 별점 1~5, `keywords` 각 255자, `comment` 255자 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 리뷰 작성 service를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** keyword 저장 방식을 현재 DB 타입 기준으로 단순화하고 회귀 결과를 기록한다.
|
|
|
|
#### Task 6.2 리뷰 작성 API 구현
|
|
|
|
**Goal 실행 `P6-T2`:** `POST /api/v2/gifts/{applicationNo}/review` 계약을 제공한다.
|
|
|
|
- **Files:** Modify `GiftController.kt`, `GiftReviewRequest.kt`, `GiftResponse.kt`; Test `GiftControllerTest.kt`.
|
|
- [x] **RED:** request/response JSON에서 `keywords` 배열을 사용하고 단수 `keyword`를 받지 않는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 리뷰 API를 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 상세 응답의 review 포함 여부를 PRD와 맞추고 회귀 결과를 기록한다.
|
|
|
|
### Phase 6 Gate
|
|
|
|
**Goal 실행 `P6-GATE`:** 리뷰 작성 요구사항을 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"
|
|
./gradlew ktlintCheck
|
|
```
|
|
|
|
**Expected:** `GIFT-010`과 리뷰 API 계약이 통과한다.
|
|
|
|
## Phase 7: 푸시, 딥링크, 최종 회귀
|
|
|
|
**Phase 결과:** 상태별 팬/크리에이터 푸시와 `GIFT_DETAIL` 딥링크가 모든 상태 전이에서 일관되게 발행된다.
|
|
|
|
**선행조건:** `P2-GATE`~`P6-GATE` 완료.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 7.1 FCM 이벤트와 딥링크 확장
|
|
|
|
**Goal 실행 `P7-T1`:** `FcmEvent`에 선물 푸시와 `GIFT_DETAIL` 딥링크를 추가한다.
|
|
|
|
- **Files:** Modify `src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/fcm/notification/PushNotificationCategory.kt`; Test `GiftPushServiceTest.kt`.
|
|
- [x] **RED:** `deepLinkValue=GIFT_DETAIL`, `deepLinkId=applicationNo`를 발행하는 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** FCM enum과 gift push helper를 최소 확장한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** 기존 FCM 이벤트 회귀를 실행하고 결과를 기록한다.
|
|
|
|
#### Task 7.2 상태별 팬/크리에이터 푸시 구현
|
|
|
|
**Goal 실행 `P7-T2`:** PRD `9. 푸시 알림 정책`의 수신자, 제목, 본문을 구현한다.
|
|
|
|
- **Files:** Modify `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushService.kt`; Test `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushServiceTest.kt`.
|
|
- [x] **RED:** 팬 대상 5종, 크리에이터 대상 6종, 크리에이터 직접 수령확인 시 크리에이터 푸시 없음, 줄바꿈 본문 실패 test를 작성한다.
|
|
- [x] **RED 확인:** focused test 실패를 확인한다.
|
|
- [x] **GREEN:** 상태별 푸시 이벤트 생성을 구현한다.
|
|
- [x] **GREEN 확인:** focused test 통과를 확인한다.
|
|
- [x] **REFACTOR:** message key 이름을 정리하고 한국어/영어/일본어 기본값 조회 결과를 기록한다.
|
|
|
|
#### Task 7.3 최종 통합 회귀
|
|
|
|
**Goal 실행 `P7-T3`:** 선물하기 전체 흐름과 공통 회귀를 검증한다.
|
|
|
|
- **Files:** Modify `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/scheduler/GiftSchedulerTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt` only if final verification finds missing coverage; otherwise update this document's Progress section only.
|
|
- **TDD 예외 사유:** 구현을 추가하지 않고 전체 완성 상태를 검증하는 Gate 성격의 Task다.
|
|
- **대체 검증 방법:** 아래 명령과 수동 흐름 대조표를 Progress에 기록한다.
|
|
- [x] 사용자 흐름 대조표를 작성한다: 신청 → 취소, 신청 → 운송장 → 배송지 → 운영 검수 → 수령확인 → 리뷰, 신청 → 자동취소, 운송장 → 배송지 미입력 전달불가.
|
|
- [x] focused test 전체를 실행한다.
|
|
- [x] 직접 영향 회귀와 ktlint를 실행한다.
|
|
- [x] 전체 `test` 실행 여부를 판단한다. 공통 결제/FCM/스케줄러 경계를 변경했으므로 최종 Gate에서는 전체 `test`를 실행한다.
|
|
|
|
### Phase 7 Gate
|
|
|
|
**Goal 실행 `P7-GATE`:** 선물하기 릴리스 후보를 최종 판정한다.
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*" --tests "kr.co.vividnext.sodalive.fcm.*"
|
|
./gradlew ktlintCheck
|
|
./gradlew test
|
|
```
|
|
|
|
**Expected:** 모든 명령 exit 0. PRD `GIFT-001`~`GIFT-013`이 구현·검증·문서 추적으로 연결된다.
|
|
|
|
## Phase 9: 카테고리 확장과 신청번호 정책 변경
|
|
|
|
**Phase 결과:** 카테고리는 분류번호, 내부 범용코드, 분류명, 접수코드, 대표품목, 파손면책 조건을 관리하고, 신청번호는 카테고리+날짜별 sequence로 발급된다.
|
|
|
|
**선행조건:** Phase 8 완료, 사용자 확정 요구사항 반영.
|
|
|
|
**Phase 완료 조건:** `P9-T1`~`P9-T3`와 `P9-GATE` 완료, 검증 기록 누적.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 9.1 문서와 DDL 계약 갱신
|
|
|
|
**Goal 실행 `P9-T1`:** PRD, 구현 계획, DDL에 확정된 카테고리 필드와 신청번호 정책을 반영한다.
|
|
|
|
- **Files:** Modify `prd.md`, `plan-task.md`, `gift-schema.sql`.
|
|
- [x] **RED:** 문서/DDL의 기존 `G{yyyyMMdd}{dailySequence6}`와 기존 카테고리 필드 계약을 확인한다.
|
|
- [x] **GREEN:** `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive`와 새 신청번호 형식을 반영한다.
|
|
- [x] **검증:** 세 문서가 같은 제약과 응답 정책을 설명하는지 대조한다.
|
|
|
|
#### Task 9.2 카테고리 persistence/API 계약 변경
|
|
|
|
**Goal 실행 `P9-T2`:** 관리자 API는 새 카테고리 필드를 각각 관리하고, 사용자 폼 옵션은 `분류명/대표품목` 조합 name만 내려준다.
|
|
|
|
- **Files:** Modify `GiftCategory.kt`, `GiftAdminService.kt`, `AdminGiftRequest.kt`, `AdminGiftResponse.kt`, `GiftQueryService.kt`, `GiftResponse.kt` if needed.
|
|
- **Tests:** Modify `GiftEntityMappingTest.kt`, `GiftAdminServiceTest.kt`, `GiftQueryServiceTest.kt`, `AdminGiftControllerTest.kt`, `GiftControllerTest.kt`.
|
|
- [x] **RED:** 새 필드 저장/조회, 관리자 request/response 분리, 사용자 폼 옵션 name 조합, validation 실패 테스트를 작성하고 실패를 확인한다.
|
|
- [x] **GREEN:** 엔티티, DTO, service mapping, validation을 최소 구현한다.
|
|
- [x] **검증:** 카테고리 관련 focused test를 실행한다.
|
|
|
|
#### Task 9.3 신청번호 채번 정책 변경
|
|
|
|
**Goal 실행 `P9-T3`:** `receiptCode-classificationNumberYYMMDDSequenceNo` 형식과 카테고리+날짜별 sequence를 구현한다.
|
|
|
|
- **Files:** Modify `GiftApplicationNoSequence.kt`, `GiftApplicationNoSequenceRepository.kt`, `GiftApplicationNoGenerator.kt`, `GiftCommandService.kt`.
|
|
- **Tests:** Modify `GiftApplicationNoGeneratorTest.kt`, `GiftCommandServiceTest.kt`, `GiftEntityMappingTest.kt` if needed.
|
|
- [x] **RED:** 형식, 카테고리별 독립 sequence, 날짜별 독립 sequence, `9999` 초과, 병렬 unique 테스트를 작성하고 실패를 확인한다.
|
|
- [x] **GREEN:** 기존 transaction/locking 방식을 유지해 최소 구현한다.
|
|
- [x] **검증:** 신청번호 focused test와 직접 영향 테스트를 실행한다.
|
|
|
|
### Phase 9 Gate
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGeneratorTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftEntityMappingTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"
|
|
./gradlew ktlintCheck
|
|
git diff --check
|
|
```
|
|
|
|
**Expected:** 모든 명령 exit 0. 기존 datetime UTC ISO 문자열 변경은 유지된다.
|
|
|
|
**결과:** 완료. 2026-09-30 기준 Phase 9 Gate 명령 3개 모두 성공.
|
|
|
|
## Phase 10: 관리자 선물함 목록과 상세 조회
|
|
|
|
**Phase 결과:** 관리자가 전체 선물 신청을 목록/상세로 조회하고, 현재 상태에서 가능한 운영 액션을 확인할 수 있다.
|
|
|
|
**선행조건:** Phase 9 완료, 사용자 확정 요구사항 반영.
|
|
|
|
**Phase 완료 조건:** `P10-T1`~`P10-T3`와 `P10-GATE` 완료, 검증 기록 누적.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 10.1 문서 계약 갱신
|
|
|
|
**Goal 실행 `P10-T1`:** PRD, 구현 계획, 클라이언트 요약에 관리자 선물함 목록/상세 API 계약을 추가한다.
|
|
|
|
- **Files:** Modify `prd.md`, `plan-task.md`, `client-api-summary.md`.
|
|
- [x] **RED:** 기존 관리자 API 문서에 전체 선물함 목록/상세 조회 계약이 없음을 확인한다.
|
|
- [x] **GREEN:** `GET /api/v2/admin/gifts`, `GET /api/v2/admin/gifts/{applicationNo}`와 `availableActions` 정책을 문서화한다.
|
|
- [x] **검증:** 세 문서가 같은 필터, 응답 필드, 개인정보 미입력 표현을 설명하는지 대조한다.
|
|
|
|
#### Task 10.2 관리자 선물함 query service 구현
|
|
|
|
**Goal 실행 `P10-T2`:** 관리자 선물함 목록/상세 조회 service와 repository query를 구현한다.
|
|
|
|
- **Files:** Create `GiftAdminQueryService.kt`; Modify `GiftRepository.kt`, `GiftDeliveryRepository.kt`.
|
|
- **Tests:** Create `GiftAdminQueryServiceTest.kt`.
|
|
- [x] **RED:** 필터, 페이징, 목록/상세 필드, 수취인 배송지 미입력 빈 문자열, 상태별 `availableActions` 테스트를 작성하고 실패를 확인한다.
|
|
- [x] **GREEN:** 조회 service와 repository query를 최소 구현한다.
|
|
- [x] **검증:** `GiftAdminQueryServiceTest`를 실행한다.
|
|
|
|
#### Task 10.3 관리자 선물함 HTTP API 구현
|
|
|
|
**Goal 실행 `P10-T3`:** 관리자 controller와 response DTO에 목록/상세 endpoint를 연결한다.
|
|
|
|
- **Files:** Modify `AdminGiftController.kt`, `AdminGiftResponse.kt`.
|
|
- **Tests:** Modify `AdminGiftControllerTest.kt`.
|
|
- [x] **RED:** MockMvc로 목록/상세 JSON 계약과 권한/오류를 테스트하고 실패를 확인한다.
|
|
- [x] **GREEN:** 두 GET endpoint와 response DTO를 최소 구현한다.
|
|
- [x] **검증:** `AdminGiftControllerTest`, `GiftAdminQueryServiceTest`를 실행한다.
|
|
|
|
### Phase 10 Gate
|
|
|
|
```bash
|
|
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*"
|
|
./gradlew ktlintCheck
|
|
git diff --check
|
|
```
|
|
|
|
**결과:** 완료. 2026-09-30 기준 focused 테스트, `ktlintCheck`, `git diff --check` 성공.
|
|
|
|
## 실행 순서와 의존성
|
|
|
|
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|
|
|---:|---|---|---|---|
|
|
| 1 | `P1-T1` | 없음 | 아니요 | DDL/엔티티 경계 재확인 |
|
|
| 2 | `P1-T2` | `P1-T1` | 아니요 | 신청번호 채번 방식 재검토 |
|
|
| 3 | `P1-T3` | `P1-T1` | `P1-T2`와 일부 병행 가능 | 관리자 권한 패턴 확인 |
|
|
| 4 | `P1-T4` | `P1-T3` | 아니요 | 폼 옵션 계약 재확인 |
|
|
| 5 | `P1-GATE` | Phase 1 Task 전체 | 아니요 | 실패 소유 Task 수정 |
|
|
| 6 | `P2-T1` | `P1-GATE` | 아니요 | 기존 캔 결제 패턴 재확인 |
|
|
| 7 | `P2-T2` | `P2-T1` | 아니요 | 신청 validation 범위 재확인 |
|
|
| 8 | `P2-T3` | `P2-T2` | `P2-T4`와 일부 병행 가능 | 환불 idempotency 보강 |
|
|
| 9 | `P2-T4` | `P2-T2` | `P2-T3`와 일부 병행 가능 | 받은 선물 노출 조건 재확인 |
|
|
| 10 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 |
|
|
| 11 | `P3-T1` | `P2-GATE` | 아니요 | Query 조건 재확인 |
|
|
| 12 | `P3-T2` | `P3-T1` | 아니요 | 권한 정책 재확인 |
|
|
| 13 | `P3-T3` | `P2-T4` | `P3-T1` 이후 병행 가능 | 수령 약관 저장 보강 |
|
|
| 14 | `P3-T4` | `P5-T1` 또는 검수완료 fixture | 아니요 | 상태 전이 선행 구현 확인 |
|
|
| 15 | `P3-GATE` | Phase 3 Task 전체 | 아니요 | 실패 소유 Task 수정 |
|
|
| 16 | `P4-T1` | `P2-GATE` | `P4-T2`와 병행 가능 | 스케줄러 대상 query 보강 |
|
|
| 17 | `P4-T2` | `P3-T3` | `P4-T1`과 병행 가능 | 전달불가 상태 정책 재확인 |
|
|
| 18 | `P4-T3` | `P7-T1` 선행 권장 | 아니요 | 푸시 중복 방지 방식 확정 |
|
|
| 19 | `P4-GATE` | Phase 4 Task 전체 | 아니요 | 실패 소유 Task 수정 |
|
|
| 20 | `P5-T1` | `P2-T4` | 아니요 | 운영 권한 패턴 확인 |
|
|
| 21 | `P5-T2` | `P5-T1` | 아니요 | 전달불가 사유 정책 재확인 |
|
|
| 22 | `P5-T3` | `P5-T1` | 아니요 | 전달완료 푸시 정책 재확인 |
|
|
| 23 | `P5-GATE` | Phase 5 Task 전체 | 아니요 | 실패 소유 Task 수정 |
|
|
| 24 | `P6-T1` | `DELIVERED` fixture 가능 | `P6-T2` 전에는 아니요 | 리뷰 저장 방식 보강 |
|
|
| 25 | `P6-T2` | `P6-T1` | 아니요 | API 계약 재확인 |
|
|
| 26 | `P6-GATE` | Phase 6 Task 전체 | 아니요 | 실패 소유 Task 수정 |
|
|
| 27 | `P7-T1` | FCM 패턴 확인 | 아니요 | 기존 FCM 딥링크 빌더 확인 |
|
|
| 28 | `P7-T2` | `P7-T1` | 아니요 | 메시지 키 누락 보강 |
|
|
| 29 | `P7-T3` | 모든 구현 Task | 아니요 | 회귀 실패 소유 Task 생성 |
|
|
| 30 | `P7-GATE` | Phase 7 Task 전체 | 아니요 | 릴리스 차단 기록 |
|
|
|
|
```text
|
|
P1-T1 → P1-T2/P1-T3 → P1-T4 → P1-GATE
|
|
P2-T1 → P2-T2 → P2-T3/P2-T4 → P2-GATE
|
|
P3-T1 → P3-T2 → P3-T3 → P3-T4 → P3-GATE
|
|
P4, P5, P6는 선행 상태 전이 완료 후 병행 가능
|
|
P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종료
|
|
```
|
|
|
|
## 변경 금지 항목
|
|
|
|
- PRD의 API path, request/response 필드, 상태 enum을 근거 없이 변경하지 않는다.
|
|
- 외부 택배사 코드, 배송조회 연동, 약관 본문 API를 추가하지 않는다.
|
|
- 카테고리 hard delete를 구현하지 않는다.
|
|
- 운송장 등록 후 팬 취소를 허용하지 않는다.
|
|
- `salePriceCan` 대신 `basePriceCan`으로 결제하지 않는다.
|
|
- `keyword` 단수 필드를 리뷰 API에 추가하지 않는다.
|
|
- `as any`, `@ts-ignore`, 타입 오류 우회, test skip/삭제로 Gate를 통과시키지 않는다.
|
|
- 개인정보 전체를 로그, 푸시 본문, 테스트 fixture에 불필요하게 남기지 않는다.
|
|
- 선물 상세 사용자 API에 상대방 이름, 휴대폰 번호, 우편번호, 주소, 상세주소를 노출하지 않는다. 팬에게는 본인이 신청 시 등록한 발신자 정보만, 크리에이터에게는 본인이 입력한 수신자 정보만 노출한다.
|
|
|
|
## 의사결정 및 중단 규칙
|
|
|
|
- PRD와 구현 중 발견한 기존 코드 제약이 충돌하면 PRD Decision Log와 이 문서를 먼저 갱신한다.
|
|
- 관리자 권한명처럼 코드 확인으로 확정 가능한 값은 구현 Task에서 확인하고 문서에 실제 값을 기록한다.
|
|
- 캔 결제/환불 또는 푸시 이벤트에서 기존 public contract 변경이 필요하면 작업을 멈추고 사용자 확인을 받는다.
|
|
- 같은 차단 사유가 3회 반복되고 독립 작업도 불가능하면 해당 Goal을 `blocked`로 기록한다.
|
|
- 완료 증거와 Progress 기록까지 충족한 뒤에만 Goal을 완료 처리한다.
|
|
|
|
## Progress
|
|
|
|
기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 차수별로 누적한다.
|
|
|
|
### 계획 작성 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: PRD 기반 goal 실행형 구현 계획 문서를 작성했다.
|
|
- 왜: 코드 구현 전 PRD와 구현 계획/TASK 문서를 모두 준비해야 하는 저장소 규칙을 충족하기 위해서다.
|
|
- 어떻게:
|
|
- `docs/20260929_크리에이터_선물하기/prd.md` 확인
|
|
- `docs/sample/sample-plan-task.md` 확인
|
|
- 기존 관리자 API, 캔 결제, FCM, 스케줄러 테스트 패턴 확인
|
|
- 남은 항목: 구현 시작 전 사용자의 계획 검토/승인
|
|
- 다음 행동: 사용자 승인 후 `P1-T1`부터 단일 Goal로 실행
|
|
|
|
### P1-T1 DDL과 도메인 엔티티 생성 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 선물 상태/사이즈 enum, 선물·배송·카테고리·사이즈 가격·리뷰·신청번호 채번 JPA 엔티티와 repository, 운영 반영용 `gift-schema.sql`을 추가했다.
|
|
- 왜: PRD `10. 데이터 모델 요구사항`과 `P1-T1` 완료 증거를 충족하기 위해서다.
|
|
- 어떻게:
|
|
- RED test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftEntityMappingTest.kt` 작성
|
|
- RED 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftEntityMappingTest"` 실행 결과 `GiftRepository`, `Gift`, `GiftStatus`, `GiftSize` 등 미구현 참조로 `compileTestKotlin` 실패
|
|
- GREEN 구현: v2 관례에 맞춰 enum은 `domain`, JPA 엔티티/repository는 `adapter.out.persistence`에 배치
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftEntityMappingTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P1-T2` 신청번호 채번 구현
|
|
|
|
### P1-T2 신청번호 채번 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `G{yyyyMMdd}{dailySequence6}` 형식의 선물 신청번호 generator를 추가했다.
|
|
- 왜: PRD `6.3 신청번호 정책`의 Asia/Seoul 날짜 기준, 날짜별 6자리 sequence, 병렬 unique 보장 요구를 충족하기 위해서다.
|
|
- 어떻게:
|
|
- RED test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftApplicationNoGeneratorTest.kt` 작성
|
|
- RED 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGeneratorTest"` 실행 결과 `GiftApplicationNoGenerator` 미구현 참조로 `compileTestKotlin` 실패
|
|
- GREEN 구현: `GiftApplicationNoSequenceRepository.findBySequenceDateForUpdate`와 `TransactionTemplate`을 사용해 날짜별 sequence row를 갱신하고, 최초 row 생성 경합은 unique 제약 충돌 재시도로 처리
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGeneratorTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P1-T3` 관리자 카테고리와 사이즈 가격 API 구현
|
|
|
|
### P1-T3 관리자 카테고리와 사이즈 가격 API 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 관리자 카테고리 등록/수정/조회/논리삭제 API와 사이즈 가격 조회/수정 API를 추가했다.
|
|
- 왜: PRD `GIFT-012`, `GIFT-013`의 관리자 선물 카테고리와 사이즈별 가격 설정 요구를 충족하기 위해서다.
|
|
- 어떻게:
|
|
- RED test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt` 작성
|
|
- RED 확인: focused test 실행 결과 `GiftAdminService`, `GiftCategoryCommand`, `GiftSizePriceCommand` 미구현 참조로 `compileTestKotlin` 실패
|
|
- GREEN 구현: `GiftAdminService`, 관리자 request/response DTO, `AdminGiftController`를 추가하고 카테고리 삭제는 `isActive=false`로 처리
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P1-T4` 팬용 선물 신청 폼 옵션 조회 구현
|
|
|
|
### P1-T4 팬용 선물 신청 폼 옵션 조회 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 팬용 `GET /api/v2/gifts/form-options` API와 조회 service/response DTO를 추가했다.
|
|
- 왜: PRD `GIFT-001`의 선물 신청 화면 옵션 조회 요구를 충족하기 위해서다.
|
|
- 어떻게:
|
|
- RED test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt` 작성
|
|
- RED 확인: focused test 실행 결과 `GiftQueryService` 미구현 참조로 실패 확인
|
|
- GREEN 구현: 활성 `GiftSizePrice`와 활성 `GiftCategory`만 조회하고, 사용자 응답에서는 `categoryCode`를 제외하도록 구현
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: Phase 1 Gate
|
|
- 다음 행동: `P1-GATE` 실행
|
|
|
|
### P1-GATE 데이터 기반과 관리자 설정/폼 옵션 계약 판정 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: Phase 1의 도메인/관리자 API/폼 옵션 계약을 Gate 명령으로 검증했다.
|
|
- 왜: `P1-T1`~`P1-T4` 완료 후 Phase 2 진입 가능 여부를 판정하기 위해서다.
|
|
- 어떻게:
|
|
- `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew tasks --all` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P2-T1` 캔 사용내역 `선물하기` 연결
|
|
|
|
### P2-T1 캔 사용내역 `선물하기` 연결 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 선물 신청에서 사용할 `CanUsage.GIFT`, 선물용 캔 차감/환불 연결 메서드, 캔 사용내역 표시명을 추가했다.
|
|
- 왜: 선물 신청 시 캔 사용내역을 `선물하기`로 남기고 취소/실패 시 한 번만 환불할 수 있어야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt` 작성
|
|
- RED 확인: focused test 실행 결과 `CanUsage.GIFT`, `spendGiftCan`, `refundGiftCan` 미구현 참조로 실패 확인
|
|
- GREEN 구현: 기존 `CanPaymentService.spendCan`/환불 로직을 재사용해 `spendGiftCan`, `refundGiftCan`을 추가
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.can.*"`, `./gradlew ktlintCheck` 실행 결과 모두 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P2-T2` 선물 보내기 등록 구현
|
|
|
|
### P2-T2 선물 보내기 등록 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `POST /api/v2/gifts` 선물 신청 API와 신청 service를 추가했다.
|
|
- 왜: 팬이 약관/파손면책/발신자 정보를 입력해 선물을 신청하고 즉시 캔을 차감해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt` 작성
|
|
- RED 확인: focused test 실행 결과 `GiftCommandService`, request/result DTO 미구현 참조로 실패 확인
|
|
- GREEN 구현: 신청 검증, 신청번호 발급, `spendGiftCan` 차감, `Gift`/`GiftDelivery` 저장, 신청 응답 반환을 구현
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P2-T3` 선물 보내기 취소 구현
|
|
|
|
### P2-T3 선물 보내기 취소 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `POST /api/v2/gifts/{applicationNo}/cancel` 취소 API와 취소 service를 추가했다.
|
|
- 왜: 팬이 운송장 등록 전 접수된 선물을 취소하고 전액 환불받을 수 있어야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftCommandServiceTest`, `GiftControllerTest`에 발신자/상태 검증과 취소 응답 검증을 추가
|
|
- RED 확인: 취소 결과 타입과 service method 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: 보낸 팬 + `RECEIVED` 상태만 `CANCELED`로 전이, `GiftDelivery.canceledAt` 저장, `refundGiftCan` 호출
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P2-T4` 운송장 등록 구현
|
|
|
|
### P2-T4 운송장 등록 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `POST /api/v2/gifts/{applicationNo}/tracking` 운송장 등록 API와 service를 추가했다.
|
|
- 왜: 팬이 접수된 선물에 택배사명과 운송장 번호를 등록해 크리에이터가 받은 선물로 조회할 수 있는 상태로 전이해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftCommandServiceTest`, `GiftControllerTest`에 발신자/상태/택배사명/운송장번호 검증과 응답 검증을 추가
|
|
- RED 확인: 신규 command/result/request와 `registerTracking` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: 보낸 팬 + `RECEIVED` 상태만 `TRACKING_REGISTERED`로 전이, `trackingRegisteredAt`, `recipientAddressDeadlineAt = now + 7일`, 택배사명/운송장 번호 저장
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P3-T1` 선물함 목록 조회 구현
|
|
|
|
### P2-GATE 팬 신청/결제/취소/운송장 등록 판정 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 검증:
|
|
- `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 판정: `GIFT-002`~`GIFT-005` 핵심 mutation 통과, 운송장 등록 후 `TRACKING_REGISTERED` 상태와 받은 선물 노출 선행 상태 저장 확인
|
|
|
|
### P3-T1 선물함 목록 조회 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `GET /api/v2/gifts?type=ALL|SENT|RECEIVED&page=&size=` 목록 조회 API와 query service를 추가했다.
|
|
- 왜: 팬/크리에이터가 보낸 선물과 받은 선물을 역할별 `direction`으로 구분해 조회해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftQueryServiceTest`, `GiftControllerTest`에 보낸 선물 즉시 노출, 받은 선물 `TRACKING_REGISTERED` 이상 노출, `ALL` direction, page/size 보정, controller 응답 검증 추가
|
|
- RED 확인: `GiftListType`, `getGifts` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftListType`, `GiftDirection`, repository page 조회, `GiftQueryService.getGifts`, `GiftListResponse`, `GET /api/v2/gifts` 추가
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P3-T2` 선물 상세 조회 구현
|
|
|
|
### P3-T2 선물 상세 조회 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `GET /api/v2/gifts/{applicationNo}` 상세 조회 API와 role 기반 상세 query를 추가했다.
|
|
- 왜: 보낸 팬과 받는 크리에이터가 같은 선물을 보더라도 노출 가능한 개인정보와 액션 플래그가 달라야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftQueryServiceTest`, `GiftControllerTest`에 SENT/RECEIVED 권한, 개인정보 경계, 주소 조립, `trackingRequired`, `recipientAddressRequired`, 상태 타임라인, 종료 정보 검증 추가
|
|
- RED 확인: `getGiftDetail` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: 상세 권한 판정, 발신/수신 응답 분기, 주소 포맷, 상태 타임라인, `canceledAt`/`undeliverableAt` 종료 정보 반환 구현
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P3-T3` 크리에이터 배송지 입력 구현
|
|
|
|
### P3-T3 크리에이터 배송지 입력 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `POST /api/v2/gifts/{applicationNo}/recipient-address` 배송지 입력 API와 service를 추가했다.
|
|
- 왜: 크리에이터가 운송장 등록 이후 수령 주소와 수령 약관 동의를 등록해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftCommandServiceTest`, `GiftControllerTest`에 수령자 권한, `RECEIVED`/종료 상태 거부, 필수 입력, 약관 2개 동의, controller 응답 검증 추가
|
|
- RED 확인: 신규 command/request/result/service 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: 수령자 + 등록 가능 상태만 배송지 저장, `Gift.recipientTermsAgreedAt`/`recipientPrivacyAgreedAt`에 동의 시각 저장, 응답 반환 구현
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P3-T4` 크리에이터 수령확인 구현
|
|
|
|
### P3-T4 크리에이터 수령확인 구현 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `POST /api/v2/gifts/{applicationNo}/delivery-complete` 수령확인 API와 service를 추가했다.
|
|
- 왜: 크리에이터가 검수 완료된 선물을 직접 전달 완료 처리할 수 있어야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftCommandServiceTest`, `GiftControllerTest`에 수령자 권한, `INSPECTION_COMPLETED` 상태 제한, `deliveredAt` 저장, controller 응답 검증 추가
|
|
- RED 확인: 신규 결과 타입과 `confirmDelivery` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: 수령자 + `INSPECTION_COMPLETED` 상태만 `DELIVERED`로 전이, `GiftDelivery.deliveredAt` 저장, 응답 반환 구현
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P3-GATE` 조회와 크리에이터 배송지/수령 흐름 판정
|
|
|
|
### P3-GATE 조회와 크리에이터 배송지/수령 흐름 판정 — 2026-09-29
|
|
|
|
- 상태: 완료
|
|
- 검증:
|
|
- `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 판정: `GIFT-007`과 상세/목록 권한 정책, 크리에이터 배송지 입력/수령확인 흐름 통과
|
|
- 다음 행동: `P4-T1` 운송장 미등록 자동취소 구현
|
|
|
|
### P4-T1 운송장 미등록 자동취소 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 운송장 등록 기한이 지난 `RECEIVED` 선물을 자동 `CANCELED` 처리하고 전액 환불하는 scheduler를 추가했다.
|
|
- 왜: 팬이 신청한 뒤 운송장을 등록하지 않은 선물은 기한 초과 시 자동 취소되어야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftSchedulerTest`에 기한 초과 대상, 기한 동일 제외, 상태 조건, 재실행 idempotent와 환불 1회 검증 추가
|
|
- RED 확인: 신규 scheduler/service 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftScheduler.cancelExpiredTrackingRegistrationGifts`, `GiftRepository.findReceivedGiftsWithTrackingDeadlineBefore` 추가
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.scheduler.GiftSchedulerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P4-T2` 배송지 미입력 전달불가 구현
|
|
|
|
### P4-T2 배송지 미입력 전달불가 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 배송지 입력 기한이 지난 `TRACKING_REGISTERED` 선물을 자동 `UNDELIVERABLE` 처리하는 scheduler를 추가했다.
|
|
- 왜: 운송장 등록 후에도 크리에이터가 배송지를 입력하지 않으면 전달불가로 종료되어야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftSchedulerTest`에 기한 초과 대상, 기한 동일 제외, 배송지 입력 완료 제외, 재실행 idempotent 검증 추가
|
|
- RED 확인: `markExpiredRecipientAddressGiftsUndeliverable` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftScheduler.markExpiredRecipientAddressGiftsUndeliverable`, `GiftRepository.findTrackingRegisteredGiftsWithRecipientAddressDeadlineBefore` 추가
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.scheduler.GiftSchedulerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P4-T3` 24시간 전 안내 작업 구현
|
|
|
|
### P4-T3 24시간 전 안내 작업 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 운송장 등록 기한과 배송지 입력 기한 24시간 전 안내 작업을 추가했다.
|
|
- 왜: 팬과 크리에이터가 자동취소/전달불가 전에 남은 작업을 처리할 수 있어야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftSchedulerTest`에 24시간 이내 대상만 안내, 24시간 초과 제외, 배송지 입력 완료 제외, 재실행 중복 방지 검증 추가
|
|
- RED 확인: `GiftPushService`, 안내 발송 시각 필드, `sendDeadlineReminderGifts` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftScheduler.sendDeadlineReminderGifts`, 안내 대상 repository query, `GiftDelivery` 안내 발송 시각 컬럼, `GiftPushService` 추가
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.scheduler.GiftSchedulerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `gift-schema.sql`에 안내 발송 시각 컬럼 반영, `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: `GIFT_DETAIL` applicationNo 딥링크 타입 확장은 계획상 `P7-T1`에서 처리
|
|
- 다음 행동: `P4-GATE` 자동 처리와 idempotency 판정
|
|
|
|
### P4-GATE 자동 처리와 idempotency 판정 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 검증:
|
|
- `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.scheduler.GiftSchedulerTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 판정: `GIFT-006`, `GIFT-008` 자동 처리와 24시간 전 안내 중복 방지 통과
|
|
- 다음 행동: `P5-T1` 사서함 도착과 검수완료 API 구현
|
|
|
|
### P5-T1 사서함 도착과 검수완료 API 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 운영자용 사서함 도착/검수완료 상태 전이 API와 상태별 푸시 요청을 추가했다.
|
|
- 왜: 운영자가 `TRACKING_REGISTERED -> ARRIVED_AT_MAILBOX -> INSPECTION_COMPLETED` 흐름을 전용 API로 처리해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftAdminServiceTest`, `AdminGiftControllerTest`에 허용 상태, 금지 상태, 상태 시각 저장, 푸시 호출 검증 추가
|
|
- RED 확인: `arriveMailbox`, `completeInspection`, 운영 푸시 메서드 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftAdminService.arriveMailbox`, `GiftAdminService.completeInspection`, 관리자 API 2개, 운영 상태 응답 DTO, `GiftPushService` 운영 푸시 3개 추가
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `GiftPushServiceTest`에 운영 푸시 이벤트 본문 검증 추가, `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"` 실행 결과 `BUILD SUCCESSFUL`, `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P5-T2` 운영 전달불가 API 구현
|
|
|
|
### P5-T2 운영 전달불가 API 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 운영자용 전달불가 상태 전이 API와 팬/크리에이터 전달불가 푸시 요청을 추가했다.
|
|
- 왜: 운영자가 배송/검수 흐름 중 전달 불가능한 선물을 사유와 함께 `UNDELIVERABLE`로 종료 처리해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftAdminServiceTest`, `AdminGiftControllerTest`, `GiftPushServiceTest`에 허용 상태, 사유 필수, 종료 상태 실패, 전달불가 푸시 호출/본문 검증 추가
|
|
- RED 확인: `markUndeliverable`, `sendUndeliverableToSender`, `sendUndeliverableToRecipient` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftAdminService.markUndeliverable`, `POST /api/v2/admin/gifts/{applicationNo}/mark-undeliverable`, `AdminGiftMarkUndeliverableRequest`, 전달불가 푸시 2개 추가
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `AdminGiftControllerTest` fixture cleanup과 import 순서 정리 후 같은 focused test 실행 결과 `BUILD SUCCESSFUL`, `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P5-T3` 운영 전달완료 API 구현
|
|
|
|
### P5-T3 운영 전달완료 API 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 운영자용 전달완료 상태 전이 API와 팬 전달완료 푸시 요청을 추가했다.
|
|
- 왜: 운영자가 검수 완료된 선물을 `DELIVERED`로 종료 처리하고 팬에게 전달 완료를 알려야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftAdminServiceTest`, `AdminGiftControllerTest`, `GiftPushServiceTest`에 `INSPECTION_COMPLETED` 허용, 금지 상태, `deliveredAt` 저장, 팬 전달완료 푸시와 크리에이터 푸시 미발송 검증 추가
|
|
- RED 확인: `completeDelivery`, `sendDeliveredToSender` 미구현으로 focused test compile 실패 확인
|
|
- GREEN 구현: `GiftAdminService.completeDelivery`, `POST /api/v2/admin/gifts/{applicationNo}/complete-delivery`, 팬 전달완료 푸시 추가
|
|
- GREEN 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: `P5-GATE`
|
|
- 다음 행동: `P5-GATE` 운영 상태변경 API 판정
|
|
|
|
### P5-GATE 운영 상태변경 API 판정 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 검증:
|
|
- `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 판정: `GIFT-009` 운영 상태별 전용 API와 금지 상태 전이 통과
|
|
- 다음 행동: `P6-T1` 리뷰 작성 service 구현
|
|
|
|
### P6-T1 리뷰 작성 service 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 보낸 팬이 `DELIVERED` 선물에 별점, 여러 키워드, 추가의견 리뷰를 1회 작성하는 service를 추가했다.
|
|
- 왜: PRD `GIFT-010`의 보낸 팬/전달완료/중복 방지/입력 길이 제한 요구를 충족하기 위해서다.
|
|
- 어떻게:
|
|
- RED test: `GiftCommandServiceTest`에 보낸 팬 성공, 수신자 실패, 비전달완료 실패, 중복 실패, 별점 1~5, keyword/comment 255자 제한 검증 추가
|
|
- RED 확인: `./gradlew test --rerun-tasks --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest"` 실행 결과 `GiftReviewCommand`, `GiftReviewResult`, `writeReview`, `reviewRepository` 미구현으로 `compileTestKotlin` 실패 확인
|
|
- GREEN 구현: `GiftCommandService.writeReview`, `GiftReviewCommand`, `GiftReviewResult`, `GiftReviewRepository` 주입을 추가하고 `GiftReview.keywords`에는 현재 DB `text` 타입 기준으로 comma-separated 문자열을 저장
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew ktlintCheck --rerun-tasks` 실행 결과 `BUILD SUCCESSFUL`, 같은 focused test 재실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P6-T2` 리뷰 작성 API 구현
|
|
|
|
### P6-T2 리뷰 작성 API 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `POST /api/v2/gifts/{applicationNo}/review` 리뷰 작성 API와 request/response DTO를 추가했다.
|
|
- 왜: PRD `GIFT-010`의 리뷰 작성 API 계약에서 `keywords` 배열 요청/응답과 단수 `keyword` 미노출을 보장해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftControllerTest`에 리뷰 작성 성공 envelope, `keywords` 배열 응답, 단수 `keyword` 미노출 검증 추가
|
|
- RED 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"` 실행 결과 `GiftReviewRequest` 미구현으로 `compileTestKotlin` 실패 확인
|
|
- GREEN 구현: `GiftReviewRequest`, `GiftReviewResponse`, `GiftController.writeReview`를 추가하고 기존 `GiftCommandService.writeReview`에 연결
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest.shouldWriteReview"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest"`, `./gradlew ktlintCheck --rerun-tasks` 실행 결과 모두 `BUILD SUCCESSFUL`
|
|
- 남은 항목: `P6-GATE`
|
|
- 다음 행동: `P6-GATE` 리뷰 작성 요구사항 판정
|
|
|
|
### P6-GATE 리뷰 작성 요구사항 판정 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 검증:
|
|
- `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 판정: `GIFT-010` 리뷰 작성 service와 `POST /api/v2/gifts/{applicationNo}/review` API 계약 통과
|
|
- 다음 행동: `P7-T1` FCM 이벤트와 딥링크 확장
|
|
|
|
### P7-T1 FCM 이벤트와 딥링크 확장 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 선물 푸시에 `GIFT` 카테고리와 `GIFT_DETAIL` 딥링크를 추가하고 `applicationNo`를 딥링크 ID로 발행하게 했다.
|
|
- 왜: PRD의 모든 선물 푸시가 단일 `GIFT_DETAIL` + `applicationNo` 딥링크로 상세 화면에 진입해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftPushServiceTest`에 선물 푸시 이벤트의 `deepLinkValue=GIFT_DETAIL`, `deepLinkId=applicationNo`, `category=GIFT` 검증 추가
|
|
- RED 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"` 실행 결과 `PushNotificationCategory.GIFT`, `FcmDeepLinkValue.GIFT_DETAIL` 미구현으로 `compileTestKotlin` 실패 확인
|
|
- GREEN 구현: `FcmDeepLinkValue.GIFT_DETAIL`, `PushNotificationCategory.GIFT` 추가, `FcmEvent`/`FcmService`의 `deepLinkId`를 문자열 신청번호도 받을 수 있게 최소 확장, `GiftPushService`에서 신청번호 딥링크 발행
|
|
- GREEN 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest"`, `./gradlew ktlintCheck` 실행 결과 모두 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P7-T2` 상태별 팬/크리에이터 푸시 구현
|
|
|
|
### DDL 날짜 컬럼 정책 변경 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: `gift-schema.sql`의 날짜 컬럼 타입을 `DATETIME`에서 `TIMESTAMP`로 변경하고, 모든 `created_at`/`updated_at`을 `NOT NULL DEFAULT CURRENT_TIMESTAMP`로 통일했다.
|
|
- 왜: 운영 반영 DDL에서 날짜/시각 컬럼 정책을 `TIMESTAMP` 기반으로 맞추고 필수 audit 컬럼은 DB 기본값을 갖도록 하기 위해서다.
|
|
- 어떻게:
|
|
- `updated_at`에는 `ON UPDATE CURRENT_TIMESTAMP`를 적용
|
|
- 기한/상태 변경 시각 같은 비즈니스 날짜 컬럼은 앱에서 의미 있는 값을 넣어야 하므로 임의 `DEFAULT CURRENT_TIMESTAMP`는 추가하지 않음
|
|
- 확인: `rg -n "DATETIME|created_at DATETIME|updated_at DATETIME|created_at TIMESTAMP NULL|updated_at TIMESTAMP NULL" "docs/20260929_크리에이터_선물하기/gift-schema.sql"` 실행 결과 잔여 항목 없음
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P7-T2` 상태별 팬/크리에이터 푸시 구현
|
|
|
|
### P7-T2 상태별 팬/크리에이터 푸시 구현 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: PRD 푸시 정책의 누락된 자동취소, 운송장 등록 완료, 크리에이터 배송완료 푸시를 추가하고 직접 수령확인 시 크리에이터 푸시가 없는 동작을 고정했다.
|
|
- 왜: 선물 상태 전이가 끝까지 진행될 때 팬/크리에이터가 각자 해야 할 작업과 결과를 푸시로 받아야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED test: `GiftPushServiceTest`, `GiftCommandServiceTest`, `GiftSchedulerTest`, `GiftAdminServiceTest`에 누락 푸시 3종과 직접 수령확인 푸시 미발송 검증 추가
|
|
- RED 확인: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftPushServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.scheduler.GiftSchedulerTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest"` 실행 결과 신규 푸시 메서드와 `GiftCommandService.giftPushService` 미구현으로 `compileTestKotlin` 실패 확인
|
|
- GREEN 구현: `GiftPushService`에 `sendAutoCanceledToSender`, `sendTrackingRegisteredToRecipient`, `sendDeliveredToRecipient` 추가, 스케줄러/사용자 운송장 등록/운영 전달완료 호출부 연결
|
|
- GREEN 확인: 같은 focused test 실행 결과 `BUILD SUCCESSFUL`
|
|
- REFACTOR 확인: 푸시 본문은 현재 구현 패턴대로 이벤트의 기본 `title`/`message`를 사용하므로 message key 추가는 하지 않음. `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.*"`, `./gradlew ktlintCheck` 실행 결과 모두 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P7-T3` 최종 통합 회귀
|
|
|
|
### P7-T3 최종 통합 회귀 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 사용자 흐름 대조표:
|
|
- 신청 → 취소: `registerGift`, `cancelGift`, 캔 환불 회귀 통과
|
|
- 신청 → 운송장 → 배송지 → 운영 검수 → 수령확인 → 리뷰: 사용자/관리자 command, controller, query 회귀 통과
|
|
- 신청 → 자동취소: 스케줄러 자동취소, 환불, 팬 자동취소 푸시 회귀 통과
|
|
- 운송장 → 배송지 미입력 전달불가: 스케줄러 전달불가, idempotency 회귀 통과
|
|
- 검증:
|
|
- focused/direct: `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.*"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- lint: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- full: `./gradlew test` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
- 다음 행동: `P7-GATE` 릴리스 후보 판정
|
|
|
|
### P7-GATE 선물하기 릴리스 후보 판정 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 검증:
|
|
- `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.*" --tests "kr.co.vividnext.sodalive.v2.api.gift.*"` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- `./gradlew test` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 판정: PRD `GIFT-001`~`GIFT-013` 구현·검증·문서 추적 완료
|
|
- 남은 항목: 없음
|
|
|
|
### DDL FK와 선물 금액 스냅샷 정리 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 운영 반영용 `gift-schema.sql`에 FK를 추가하고, 선물 신청 건에서는 최종 결제 금액 스냅샷만 저장하도록 `gift.base_price_can`/`Gift.basePriceCan`을 제거했다.
|
|
- 왜: 기존 운영 관례가 FK 없음이 아니고, 신규 선물 도메인은 운영 반영 DDL에서 참조 무결성을 DB로 보장하는 편이 안전하기 때문이다. 신청 건에는 환불/상세/내역에 필요한 최종 결제 금액만 있으면 충분하다.
|
|
- 어떻게:
|
|
- RED test: `Gift` 생성 fixture와 저장 검증에서 `basePriceCan` 제거 후 production `Gift` 생성자가 아직 요구해 `compileTestKotlin` 실패 확인
|
|
- GREEN 구현: `Gift` 엔티티와 `GiftCommandService`에서 기본 금액 스냅샷 제거, `gift-schema.sql`의 `gift.base_price_can` 제거
|
|
- DDL 구현: `gift -> member/use_can/gift_category`, `gift_delivery -> gift`, `gift_review -> gift/member` FK 추가. `gift_category`는 FK 참조 전에 생성되도록 DDL 순서 조정
|
|
- 검증: focused gift/API/scheduler/admin test 실행 결과 `BUILD SUCCESSFUL`, `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`
|
|
- 남은 항목: 없음
|
|
|
|
## Phase 8: 선물 응답 시각 UTC ISO 문자열화
|
|
|
|
**Phase 결과:** 선물 사용자/관리자 API가 모든 응답 시각을 클라이언트가 안전하게 해석할 수 있는 UTC ISO 문자열로 반환한다.
|
|
|
|
**선행조건:** `P7-GATE` 완료와 PRD `DEC-013` 확정.
|
|
|
|
**Phase 완료 조건:** `P8-T1` 완료와 focused/controller 회귀, gift 회귀, lint 검증 기록 누적.
|
|
|
|
### 구현 항목
|
|
|
|
#### Task 8.1 선물 response datetime 계약 수정
|
|
|
|
**Goal 실행 `P8-T1`:** `GiftResponse.kt`와 `AdminGiftResponse.kt`의 응답 시각 필드를 UTC ISO `String`으로 변환한다.
|
|
|
|
- **시작 조건:** PRD `DEC-013` 확인.
|
|
- **완료 증거:** RED/GREEN/REFACTOR 체크박스, focused controller test, gift 회귀 test, `ktlintCheck` 통과.
|
|
- **범위 밖:** 전역 Jackson 설정 변경, 선물 외 API datetime 마이그레이션, DB 저장 timezone 변경.
|
|
|
|
**Files:**
|
|
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/dto/GiftResponse.kt`
|
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/dto/AdminGiftResponse.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt`
|
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt`
|
|
|
|
**Interfaces:**
|
|
|
|
- Produces: 선물 사용자/관리자 API의 모든 populated datetime JSON 값은 `2026-09-30T12:00:00Z` 같은 UTC ISO 문자열이다.
|
|
|
|
- [x] **RED:** controller test의 선물 날짜/시간 응답 기대값을 `Z` 포함 UTC ISO 문자열로 변경한다.
|
|
- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest"`를 실행해 기존 `LocalDateTime` 직렬화가 `Z`를 포함하지 않아 실패함을 확인한다.
|
|
- [x] **GREEN:** response DTO의 `LocalDateTime` 필드를 `String`, `LocalDateTime?` 필드를 `String?`로 바꾸고 기존 `LocalDateTime.toUtcIso()` extension으로 변환한다.
|
|
- [x] **GREEN 확인:** 같은 focused controller test가 통과하는지 확인한다.
|
|
- [x] **REFACTOR:** 불필요한 import를 제거하고 `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*"`, `./gradlew ktlintCheck`, `git diff --check` 결과를 Progress에 기록한다.
|
|
|
|
### P8-T1 선물 response datetime 계약 수정 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 선물 사용자/관리자 response DTO의 날짜/시간 필드를 `String`으로 바꾸고 `toUtcIso()`로 변환했다. 클라이언트 요약 문서도 UTC ISO 문자열 계약으로 동기화했다.
|
|
- 왜: Jackson 기본 `LocalDateTime` 직렬화는 `2026-09-30T12:00:00`처럼 offset이 없어 클라이언트가 UTC로 안전하게 해석할 수 없기 때문이다.
|
|
- 어떻게:
|
|
- RED test: controller test 기대값을 `Z` 포함 문자열로 변경 후 focused test 실행 결과 11개 assertion 실패 확인.
|
|
- GREEN 구현: `GiftResponse.kt`, `AdminGiftResponse.kt`에서 응답 시각 필드를 `String`/`String?`로 변경하고 기존 `LocalDateTime.toUtcIso()` extension 재사용.
|
|
- 검증: focused controller test 실행 결과 `BUILD SUCCESSFUL`, `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*"` 실행 결과 `BUILD SUCCESSFUL`, `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`, `git diff --check` 출력 없음, `rg -n -- "LocalDateTime"` 대상 response DTO/클라이언트 요약 문서 출력 없음, `./gradlew tasks --all` 실행 결과 `BUILD SUCCESSFUL`.
|
|
- 남은 항목: 없음
|
|
|
|
### Phase 9 카테고리 확장과 신청번호 정책 변경 — 2026-09-30
|
|
|
|
- 상태: 완료
|
|
- 무엇을: 카테고리에 분류번호, 접수코드, 대표품목을 추가하고 사용자 폼 옵션의 카테고리명은 `분류명/대표품목`으로 조합했다. 신청번호는 `receiptCode-classificationNumberYYMMDDSequenceNo` 형식으로 변경하고 sequence는 카테고리+날짜별로 분리했다.
|
|
- 왜: `categoryCode`는 내부 범용코드로 유지하고, 운영용 분류번호/접수코드/대표품목을 별도 관리해야 하기 때문이다.
|
|
- 어떻게:
|
|
- RED 확인 1: 카테고리 추가 필드 테스트 작성 후 `compileTestKotlin`에서 `classificationNumber`, `receiptCode`, `representativeItem` 미구현 실패 확인
|
|
- GREEN 확인 1: 카테고리 focused suite 실행 결과 `BUILD SUCCESSFUL`
|
|
- RED 확인 2: 신청번호 generator 테스트 작성 후 기존 `generate(now)`와 sequence entity가 새 시그니처/필드를 지원하지 않아 `compileTestKotlin` 실패 확인
|
|
- GREEN 확인 2: 신청번호 focused suite 실행 결과 `BUILD SUCCESSFUL`
|
|
- 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
|
|
|
|
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
|
|---|---|---|---|---|---|
|
|
| 2026-09-29 | `PLAN-DEC-001` | 확정 | 신규 선물 도메인은 `kr.co.vividnext.sodalive.v2.gift`, 사용자 API는 `v2.api.gift`, 관리자 API는 `v2.api.admin.gift`에 둔다 | 기존 v2 API와 도메인 분리 패턴 | 전체 구현 |
|
|
| 2026-09-29 | `PLAN-DEC-002` | 확정 | 운영 DB DDL은 같은 작업 디렉터리의 `gift-schema.sql`로 작성한다 | 기존 docs 작업 디렉터리 SQL 문서 패턴 | `P1-T1` |
|
|
| 2026-09-29 | `PLAN-DEC-003` | 확정 | 최종 Gate에서는 전체 `./gradlew test`를 실행한다 | 캔 결제, FCM, 스케줄러 등 공통 경계를 변경함 | `P7-GATE` |
|
|
| 2026-09-29 | `PLAN-DEC-004` | 확정 | 신규 선물 구현은 v2 신규 도메인 관례대로 `domain`, `application`, `port`, `adapter` 구조를 사용한다 | 사용자 지시: v2 아래 구현된 관례만 확인하고 처리 | 전체 구현 |
|
|
| 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` |
|
|
|
|
## 발견된 문제
|
|
|
|
| ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 |
|
|
|---|---|---|---|---|---|
|
|
| 없음 | Low | 해결 | 계획 작성 시점의 차단 문제 없음 | 없음 | 없음 |
|
|
|
|
## 최종 보고 형식
|
|
|
|
```markdown
|
|
구현 결과: 완료한 Phase와 사용자 흐름을 한 문장으로 작성
|
|
|
|
- 변경: 주요 파일과 동작 요약
|
|
- 결정: 중요한 Decision Log ID와 내용
|
|
- 검증:
|
|
- 실행 명령 — 성공/실패와 핵심 수치
|
|
- 수동 검증 — 성공/실패/불가 사유
|
|
- 남은 항목: 외부 의존, 후속 범위 또는 없음
|
|
- 문서: 갱신한 PRD/plan/review 링크
|
|
```
|