91 KiB
크리에이터 선물하기 구현 계획
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 9 완료 |
| 현재 활성 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 |
완료 | 없음 |
- 동시에 하나의 미완료 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.ktandGiftCommandService.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. -
RED: 엔티티 저장/조회,
applicationNounique,GiftReview.giftIdunique, 카테고리categoryCodeunique를 검증하는 실패 test를 작성한다. -
RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftEntityMappingTest"를 실행해 신규 엔티티 미존재 또는 매핑 미구현 실패를 확인한다. -
GREEN: 엔티티, repository, DDL을 최소 구현한다. DDL의 모든 컬럼과 테이블에는 MySQL
COMMENT를 추가하고created_at,updated_at은 문서 규칙을 따른다. -
GREEN 확인: 같은 focused test가 통과하는지 확인한다.
-
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. -
RED: 같은 날짜 연속 발급이
G20260929000001,G20260929000002가 되고 병렬 발급 결과가 모두 unique인 실패 test를 작성한다. -
RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGeneratorTest"로 실패를 확인한다. -
GREEN: DB unique 제약, 원자적 채번 또는 충돌 재시도 중 가장 단순한 방식으로 구현한다.
-
GREEN 확인: 같은 focused test 통과를 확인한다.
-
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. -
RED: 카테고리 등록/수정/논리삭제, 비활성 전환,
salePriceCan > basePriceCan거부 test를 작성한다. -
RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.AdminGiftControllerTest"로 실패를 확인한다. -
GREEN: 관리자 service와 controller를 최소 구현한다. 삭제는
isActive=false만 수행한다. -
GREEN 확인: 같은 focused test 통과를 확인한다.
-
REFACTOR: 관리자 DTO와 도메인 모델 import 방향을 점검하고
ktlintCheck결과를 기록한다.
Task 1.4 팬용 선물 신청 폼 옵션 조회 구현
Goal 실행 P1-T4: 팬이 선물 신청 페이지에서 활성 사이즈 가격과 활성 카테고리를 한 번에 조회한다.
- 시작 조건:
P1-T1,P1-T3완료. - 완료 증거:
GET /api/v2/gifts/form-optionscontract 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-optionswithsizes,categories. -
RED: 비활성 카테고리 제외,
categoryCode미노출,basePriceCan/salePriceCan응답을 검증하는 실패 test를 작성한다. -
RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"로 실패를 확인한다. -
GREEN: 폼 옵션 조회 service/controller/response를 최소 구현한다.
-
GREEN 확인: 같은 focused test 통과를 확인한다.
-
REFACTOR: page/DTO 책임 경계를 정리하고 focused test·
ktlintCheck결과를 기록한다.
Phase 1 Gate
Goal 실행 P1-GATE: 데이터 기반과 관리자 설정/폼 옵션 계약을 최종 판정한다.
./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재사용을 우선한다. -
RED:
CanUsage.GIFT또는 동등한선물하기사용내역이 생성되고 환불 시 1회만 환불되는 실패 test를 작성한다. -
RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest"로 실패를 확인한다. -
GREEN: 기존 결제 service를 최대한 재사용해 최소 연결만 추가한다.
-
GREEN 확인: 같은 focused test 통과를 확인한다.
-
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. -
RED: 필수 발신자 정보, 팬 약관 2개, 조건부
damageWaiverAgreed, 비활성 카테고리, 최종 결제 금액 스냅샷, 고유applicationNo, 캔 차감을 검증하는 실패 test를 작성한다. -
RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftCommandServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"로 실패를 확인한다. -
GREEN: 신청 API와 service를 최소 구현한다.
trackingDeadlineAt은 신청 시각 + 3일로 저장한다. -
GREEN 확인: 같은 focused test 통과를 확인한다.
-
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. -
RED: 보낸 팬만 취소 가능,
RECEIVED에서만 가능,TRACKING_REGISTERED이후 실패, 환불 1회만 발생하는 실패 test를 작성한다. -
RED 확인: focused test 명령으로 실패를 확인한다.
-
GREEN: 취소 상태 전이와 환불을 최소 구현한다.
-
GREEN 확인: focused test 통과를 확인한다.
-
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}/trackingwithcourierCompanyName,trackingNumber. -
RED: 보낸 팬만 등록 가능,
RECEIVED에서만 가능,courierCompanyName필수/50자 이하, 크리에이터 배송지 입력 기한 생성, 상태 전이를 검증하는 실패 test를 작성한다. -
RED 확인: focused test 명령으로 실패를 확인한다.
-
GREEN: 운송장 등록과
recipientAddressDeadlineAt = 등록 시각 + 7일저장을 구현한다. -
GREEN 확인: focused test 통과를 확인한다.
-
REFACTOR: 받은 선물 노출 조건과 충돌하지 않도록 상태 predicate 이름을 정리하고 회귀 결과를 기록한다.
Phase 2 Gate
Goal 실행 P2-GATE: 팬 신청/결제/취소/운송장 등록 흐름을 판정한다.
./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; TestGiftQueryServiceTest.kt,GiftControllerTest.kt. - RED: 보낸 선물은 신청 직후 노출, 받은 선물은
TRACKING_REGISTERED이상부터 노출,direction, pagination 보정을 검증하는 실패 test를 작성한다. - RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.GiftControllerTest"실패를 확인한다. - GREEN: 목록 query와 response를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: QueryDSL 조건을 중복 없이 정리하고 회귀 결과를 기록한다.
Task 3.2 선물 상세 조회 구현
Goal 실행 P3-T2: 단일 상세 API가 로그인 회원 기준으로 보낸/받은 선물을 판정한다.
- Files: Modify
GiftQueryService.kt,GiftController.kt,GiftResponse.kt; TestGiftQueryServiceTest.kt,GiftControllerTest.kt. - RED: sender는
SENT, recipient는 노출 조건 만족 시RECEIVED, 무관한 회원은 권한 오류 또는 미존재 오류를 반환하는 실패 test를 작성한다. - RED:
direction=SENT상세 응답이giftInfo.recipientCreatorNickname,giftInfo.sizeName,giftInfo.categoryName,giftInfo.applicationNo,giftInfo.paidCan,giftInfo.tracking,senderInfo.name,senderInfo.phoneNumber,senderInfo.address,mailbox를 포함하는 실패 test를 작성한다. - RED:
direction=SENT상세 응답에서giftInfo.senderNickname,giftInfo.shippingRequestedAt,recipientAddress가null이고 크리에이터가 입력한 이름/휴대폰 번호/주소가 노출되지 않는 실패 test를 작성한다. - RED:
direction=SENT이고 상태가RECEIVED이면trackingRequired=true, 그 외 상태이거나direction=RECEIVED이면trackingRequired=false인 실패 test를 작성한다. - RED:
direction=RECEIVED상세 응답이giftInfo.senderNickname,giftInfo.sizeName,giftInfo.categoryName,giftInfo.shippingRequestedAt,recipientAddress.name,recipientAddress.phoneNumber,recipientAddress.address를 포함하는 실패 test를 작성한다. - RED:
direction=RECEIVED상세 응답에서giftInfo.recipientCreatorNickname,giftInfo.applicationNo,giftInfo.paidCan,giftInfo.tracking,senderInfo,mailbox가null이고 팬이 신청 시 등록한 이름/휴대폰 번호/주소가 노출되지 않는 실패 test를 작성한다. - RED:
direction=RECEIVED이고 받는 주소가 미입력이며 종료 상태가 아니면recipientAddressRequired=true, 주소 입력 완료 또는 종료 상태 또는direction=SENT이면recipientAddressRequired=false인 실패 test를 작성한다. - RED: 발신자/수신자 주소가
(우편번호) 주소, 상세주소형식으로 조립되고, 로그인 회원이 알아야 하는 데이터가 아니거나 아직 입력되지 않은 데이터는null인 실패 test를 작성한다. - RED: 상세 응답의
statusTimeline이RECEIVED,TRACKING_REGISTERED,ARRIVED_AT_MAILBOX,INSPECTION_COMPLETED,DELIVERED순서로 내려오고 도달한 상태만occurredAt을 가지며 미도달 상태는null인 실패 test를 작성한다. - RED:
CANCELED,UNDELIVERABLE상세에서 정상 진행statusTimeline은 유지되고 종료 정보는 각각delivery.canceledAt,delivery.undeliverableAt,delivery.undeliverableReason으로 내려오는 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: 상세 query와 권한 판정을 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 역할별 상세 응답 조립, 본인/상대방 개인정보 노출 경계, 상태 타임라인 조립 책임을 response mapper에서 명확히 하고 회귀 결과를 기록한다.
Task 3.3 크리에이터 배송지 입력 구현
Goal 실행 P3-T3: 크리에이터가 배송지와 수령 약관 2개를 등록할 수 있게 한다.
- Files: Modify
GiftCommandService.kt,GiftController.kt,GiftRecipientAddressRegistrationRequest.kt; TestGiftCommandServiceTest.kt,GiftControllerTest.kt. - RED: 받는 크리에이터만 가능, 종료 상태 실패, 이름/휴대폰/우편번호/주소 필수, 수령 약관 2개 필수, 동의 시각 저장 실패 test를 작성한다.
- RED 확인: focused test 실패를 확인한다.
- GREEN: 배송지 입력 API를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 발신/수신 주소 snapshot 책임을
GiftDelivery안으로 정리하고 회귀 결과를 기록한다.
Task 3.4 크리에이터 수령확인 구현
Goal 실행 P3-T4: 크리에이터가 INSPECTION_COMPLETED 선물을 DELIVERED로 완료 처리한다.
- Files: Modify
GiftCommandService.kt,GiftController.kt; TestGiftCommandServiceTest.kt,GiftControllerTest.kt. - RED: 받는 크리에이터만 가능,
INSPECTION_COMPLETED에서만 가능,deliveredAt저장, 팬 푸시 발행 요청을 검증하는 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: 수령확인 상태 전이를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 운영 전달완료와 공유 가능한 상태 전이 검증만 추출하고 회귀 결과를 기록한다.
Phase 3 Gate
Goal 실행 P3-GATE: 조회와 크리에이터 배송지/수령 흐름을 판정한다.
./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; ModifyGiftRepository.kt,GiftCommandService.kt; TestGiftSchedulerTest.kt. - RED: 기한 초과 대상만 취소, 환불 1회, 중복 실행 idempotent 실패 test를 작성한다.
- RED 확인:
./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.GiftSchedulerTest"실패를 확인한다. - GREEN: 자동취소 job service를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: scheduler와 command service의 상태 전이 중복을 정리하고 회귀 결과를 기록한다.
Task 4.2 배송지 미입력 전달불가 구현
Goal 실행 P4-T2: 운송장 등록 후 7일 초과 배송지 미입력 선물을 UNDELIVERABLE로 바꾼다.
- Files: Modify
GiftScheduler.kt,GiftRepository.kt; TestGiftSchedulerTest.kt. - RED: 배송지 미입력 기한 초과만 대상, 사유
배송지 미입력 기한 초과, 중복 실행 idempotent 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: 전달불가 job service를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 대상 조회 query 이름과 상태 조건을 명확히 하고 회귀 결과를 기록한다.
Task 4.3 24시간 전 안내 작업 구현
Goal 실행 P4-T3: 운송장 등록과 배송지 입력 마감 24시간 전 안내 푸시 작업을 구현한다.
- Files: Modify
GiftScheduler.kt,GiftPushService.kt; TestGiftSchedulerTest.kt,GiftPushServiceTest.kt. - RED: 마감 24시간 전 범위만 대상, 같은 선물 중복 안내 방지 실패 test를 작성한다.
- RED 확인: focused test 실패를 확인한다.
- GREEN: 안내 작업과 중복 방지 기준을 구현한다. 중복 방지는 별도 컬럼 또는 푸시 기록 조회 중 가장 단순한 방식을 사용한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 작업별 batch size와 로그에 개인정보가 남지 않는지 확인하고 회귀 결과를 기록한다.
Phase 4 Gate
Goal 실행 P4-GATE: 자동 처리와 idempotency를 판정한다.
./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; TestGiftAdminServiceTest.kt,AdminGiftControllerTest.kt. - RED: 허용 이전 상태 성공, 잘못된 이전 상태 실패, 상태 시각 저장, 팬/크리에이터 푸시 요청 실패 test를 작성한다.
- RED 확인: focused test 실패를 확인한다.
- GREEN: 두 운영 API를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 상태 전이 검증 함수 중복을 정리하고 회귀 결과를 기록한다.
Task 5.2 운영 전달불가 API 구현
Goal 실행 P5-T2: 운영자가 전달불가 사유를 등록하고 UNDELIVERABLE로 종료 처리한다.
- Files: Modify
GiftAdminService.kt,AdminGiftController.kt,AdminGiftMarkUndeliverableRequest.kt; TestGiftAdminServiceTest.kt,AdminGiftControllerTest.kt. - RED: 허용 상태 성공, 사유 필수, 종료 상태 실패, 팬/크리에이터 전달불가 푸시 요청 실패 test를 작성한다.
- RED 확인: focused test 실패를 확인한다.
- GREEN: 전달불가 API를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 사유 길이와 개인정보 로그 금지를 확인하고 회귀 결과를 기록한다.
Task 5.3 운영 전달완료 API 구현
Goal 실행 P5-T3: 운영자가 INSPECTION_COMPLETED 선물을 DELIVERED로 완료 처리한다.
- Files: Modify
GiftAdminService.kt,AdminGiftController.kt; TestGiftAdminServiceTest.kt,AdminGiftControllerTest.kt. - RED:
INSPECTION_COMPLETED에서만 성공, 팬 전달완료 푸시, 운영 처리 시 크리에이터 배송완료 푸시 정책을 검증하는 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: 운영 전달완료 API를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 크리에이터 직접 수령확인과 운영 전달완료의 푸시 차이를 명확히 하고 회귀 결과를 기록한다.
Phase 5 Gate
Goal 실행 P5-GATE: 운영 상태변경 API를 판정한다.
./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; TestGiftCommandServiceTest.kt. - RED: 보낸 팬만 가능,
DELIVERED에서만 가능, 중복 실패, 별점 1~5,keywords각 255자,comment255자 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: 리뷰 작성 service를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: keyword 저장 방식을 현재 DB 타입 기준으로 단순화하고 회귀 결과를 기록한다.
Task 6.2 리뷰 작성 API 구현
Goal 실행 P6-T2: POST /api/v2/gifts/{applicationNo}/review 계약을 제공한다.
- Files: Modify
GiftController.kt,GiftReviewRequest.kt,GiftResponse.kt; TestGiftControllerTest.kt. - RED: request/response JSON에서
keywords배열을 사용하고 단수keyword를 받지 않는 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: 리뷰 API를 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 상세 응답의 review 포함 여부를 PRD와 맞추고 회귀 결과를 기록한다.
Phase 6 Gate
Goal 실행 P6-GATE: 리뷰 작성 요구사항을 판정한다.
./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; TestGiftPushServiceTest.kt. - RED:
deepLinkValue=GIFT_DETAIL,deepLinkId=applicationNo를 발행하는 실패 test를 작성한다. - RED 확인: focused test 실패를 확인한다.
- GREEN: FCM enum과 gift push helper를 최소 확장한다.
- GREEN 확인: focused test 통과를 확인한다.
- REFACTOR: 기존 FCM 이벤트 회귀를 실행하고 결과를 기록한다.
Task 7.2 상태별 팬/크리에이터 푸시 구현
Goal 실행 P7-T2: PRD 9. 푸시 알림 정책의 수신자, 제목, 본문을 구현한다.
- Files: Modify
src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushService.kt; Testsrc/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushServiceTest.kt. - RED: 팬 대상 5종, 크리에이터 대상 6종, 크리에이터 직접 수령확인 시 크리에이터 푸시 없음, 줄바꿈 본문 실패 test를 작성한다.
- RED 확인: focused test 실패를 확인한다.
- GREEN: 상태별 푸시 이벤트 생성을 구현한다.
- GREEN 확인: focused test 통과를 확인한다.
- 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.ktonly if final verification finds missing coverage; otherwise update this document's Progress section only. - TDD 예외 사유: 구현을 추가하지 않고 전체 완성 상태를 검증하는 Gate 성격의 Task다.
- 대체 검증 방법: 아래 명령과 수동 흐름 대조표를 Progress에 기록한다.
- 사용자 흐름 대조표를 작성한다: 신청 → 취소, 신청 → 운송장 → 배송지 → 운영 검수 → 수령확인 → 리뷰, 신청 → 자동취소, 운송장 → 배송지 미입력 전달불가.
- focused test 전체를 실행한다.
- 직접 영향 회귀와 ktlint를 실행한다.
- 전체
test실행 여부를 판단한다. 공통 결제/FCM/스케줄러 경계를 변경했으므로 최종 Gate에서는 전체test를 실행한다.
Phase 7 Gate
Goal 실행 P7-GATE: 선물하기 릴리스 후보를 최종 판정한다.
./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. - RED: 문서/DDL의 기존
G{yyyyMMdd}{dailySequence6}와 기존 카테고리 필드 계약을 확인한다. - GREEN:
classificationNumber,categoryCode,name,receiptCode,representativeItem,requiresDamageWaiver,isActive와 새 신청번호 형식을 반영한다. - 검증: 세 문서가 같은 제약과 응답 정책을 설명하는지 대조한다.
Task 9.2 카테고리 persistence/API 계약 변경
Goal 실행 P9-T2: 관리자 API는 새 카테고리 필드를 각각 관리하고, 사용자 폼 옵션은 분류명/대표품목 조합 name만 내려준다.
- Files: Modify
GiftCategory.kt,GiftAdminService.kt,AdminGiftRequest.kt,AdminGiftResponse.kt,GiftQueryService.kt,GiftResponse.ktif needed. - Tests: Modify
GiftEntityMappingTest.kt,GiftAdminServiceTest.kt,GiftQueryServiceTest.kt,AdminGiftControllerTest.kt,GiftControllerTest.kt. - RED: 새 필드 저장/조회, 관리자 request/response 분리, 사용자 폼 옵션 name 조합, validation 실패 테스트를 작성하고 실패를 확인한다.
- GREEN: 엔티티, DTO, service mapping, validation을 최소 구현한다.
- 검증: 카테고리 관련 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.ktif needed. - RED: 형식, 카테고리별 독립 sequence, 날짜별 독립 sequence,
9999초과, 병렬 unique 테스트를 작성하고 실패를 확인한다. - GREEN: 기존 transaction/locking 방식을 유지해 최소 구현한다.
- 검증: 신청번호 focused test와 직접 영향 테스트를 실행한다.
Phase 9 Gate
./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개 모두 성공.
실행 순서와 의존성
| 순서 | 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 전체 | 아니요 | 릴리스 차단 기록 |
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
P1-T4팬용 선물 신청 폼 옵션 조회 구현
P1-T4 팬용 선물 신청 폼 옵션 조회 구현 — 2026-09-29
- 상태: 완료
- 무엇을: 팬용
GET /api/v2/gifts/form-optionsAPI와 조회 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
- RED test:
- 남은 항목: 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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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이상 노출,ALLdirection, 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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
P4-T324시간 전 안내 작업 구현
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
- RED test:
- 남은 항목:
GIFT_DETAILapplicationNo 딥링크 타입 확장은 계획상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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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 확인:
AdminGiftControllerTestfixture cleanup과 import 순서 정리 후 같은 focused test 실행 결과BUILD SUCCESSFUL,./gradlew ktlintCheck --rerun-tasks실행 결과BUILD SUCCESSFUL
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목:
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에는 현재 DBtext타입 기준으로 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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목:
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}/reviewAPI 계약 통과 - 다음 행동:
P7-T1FCM 이벤트와 딥링크 확장
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- RED test:
- 남은 항목: 없음
- 다음 행동:
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
- focused/direct:
- 남은 항목: 없음
- 다음 행동:
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제거 후 productionGift생성자가 아직 요구해compileTestKotlin실패 확인 - GREEN 구현:
Gift엔티티와GiftCommandService에서 기본 금액 스냅샷 제거,gift-schema.sql의gift.base_price_can제거 - DDL 구현:
gift -> member/use_can/gift_category,gift_delivery -> gift,gift_review -> gift/memberFK 추가.gift_category는 FK 참조 전에 생성되도록 DDL 순서 조정 - 검증: focused gift/API/scheduler/admin test 실행 결과
BUILD SUCCESSFUL,./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL
- RED test:
- 남은 항목: 없음
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 문자열이다. -
RED: controller test의 선물 날짜/시간 응답 기대값을
Z포함 UTC ISO 문자열로 변경한다. -
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를 포함하지 않아 실패함을 확인한다. -
GREEN: response DTO의
LocalDateTime필드를String,LocalDateTime?필드를String?로 바꾸고 기존LocalDateTime.toUtcIso()extension으로 변환한다. -
GREEN 확인: 같은 focused controller test가 통과하는지 확인한다.
-
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.
- RED test: controller test 기대값을
- 남은 항목: 없음
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모두 성공
- RED 확인 1: 카테고리 추가 필드 테스트 작성 후
- 남은 항목: 없음
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 | 해결 | 계획 작성 시점의 차단 문제 없음 | 없음 | 없음 |
최종 보고 형식
구현 결과: 완료한 Phase와 사용자 흐름을 한 문장으로 작성
- 변경: 주요 파일과 동작 요약
- 결정: 중요한 Decision Log ID와 내용
- 검증:
- 실행 명령 — 성공/실패와 핵심 수치
- 수동 검증 — 성공/실패/불가 사유
- 남은 항목: 외부 의존, 후속 범위 또는 없음
- 문서: 갱신한 PRD/plan/review 링크