Files
sodalive-backend-spring-boot/docs/20260929_크리에이터_선물하기/plan-task.md
T

102 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 12 구현 완료
현재 활성 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.

  • RED: 엔티티 저장/조회, applicationNo unique, GiftReview.giftId unique, 카테고리 categoryCode unique를 검증하는 실패 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-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.

  • 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}/tracking with courierCompanyName, 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; Test GiftQueryServiceTest.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; Test GiftQueryServiceTest.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; Test GiftCommandServiceTest.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; Test GiftCommandServiceTest.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; Modify GiftRepository.kt, GiftCommandService.kt; Test GiftSchedulerTest.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; Test GiftSchedulerTest.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; Test GiftSchedulerTest.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; Test GiftAdminServiceTest.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; Test GiftAdminServiceTest.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; Test GiftAdminServiceTest.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; Test GiftCommandServiceTest.kt.
  • RED: 보낸 팬만 가능, DELIVERED에서만 가능, 중복 실패, 별점 1~5, keywords 각 255자, comment 255자 실패 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; Test GiftControllerTest.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; Test GiftPushServiceTest.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; Test src/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.kt only 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.kt if 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.kt if 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개 모두 성공.

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.
  • RED: 기존 관리자 API 문서에 전체 선물함 목록/상세 조회 계약이 없음을 확인한다.
  • GREEN: GET /api/v2/admin/gifts, GET /api/v2/admin/gifts/{applicationNo}와 availableActions 정책을 문서화한다.
  • 검증: 세 문서가 같은 필터, 응답 필드, 개인정보 미입력 표현을 설명하는지 대조한다.

Task 10.2 관리자 선물함 query service 구현

Goal 실행 P10-T2: 관리자 선물함 목록/상세 조회 service와 repository query를 구현한다.

  • Files: Create GiftAdminQueryService.kt; Modify GiftRepository.kt, GiftDeliveryRepository.kt.
  • Tests: Create GiftAdminQueryServiceTest.kt.
  • RED: 필터, 페이징, 목록/상세 필드, 수취인 배송지 미입력 빈 문자열, 상태별 availableActions 테스트를 작성하고 실패를 확인한다.
  • GREEN: 조회 service와 repository query를 최소 구현한다.
  • 검증: GiftAdminQueryServiceTest를 실행한다.

Task 10.3 관리자 선물함 HTTP API 구현

Goal 실행 P10-T3: 관리자 controller와 response DTO에 목록/상세 endpoint를 연결한다.

  • Files: Modify AdminGiftController.kt, AdminGiftResponse.kt.
  • Tests: Modify AdminGiftControllerTest.kt.
  • RED: MockMvc로 목록/상세 JSON 계약과 권한/오류를 테스트하고 실패를 확인한다.
  • GREEN: 두 GET endpoint와 response DTO를 최소 구현한다.
  • 검증: AdminGiftControllerTest, GiftAdminQueryServiceTest를 실행한다.

Phase 10 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.*"
./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 전체 아니요 릴리스 차단 기록
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 문자열이다.

  • 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.
  • 남은 항목: 없음

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.
  • RED: 설정 미등록 시 GET /api/v2/admin/gift-mailbox가 data=null을 반환하는 실패 test를 작성한다.
  • RED: PUT /api/v2/admin/gift-mailbox가 전역 단일 주소를 생성하고, 재호출 시 같은 row를 갱신하는 실패 test를 작성한다.
  • RED: 이름/연락처/우편번호/주소 공백 입력을 거부하는 실패 test를 작성한다.
  • RED 확인: focused test 실패를 확인한다.
  • GREEN: 단일 row persistence, 관리자 service, request/response DTO, controller endpoint를 구현한다.
  • GREEN 확인: focused test 통과를 확인한다.

Task 11.2 선물 상세 받을 주소 노출 구현

Goal 실행 P11-T2: 보낸 선물의 전달 완료 전 정상 진행 상세에만 받을 주소를 노출한다.

  • Files: Modify GiftQueryService.kt; Test GiftQueryServiceTest.kt, GiftControllerTest.kt.
  • RED: direction=SENT이고 status가 RECEIVED, TRACKING_REGISTERED, ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED인 상세에서 mailbox를 반환하는 실패 test를 작성한다.
  • RED: 받을 주소 미등록, 종료 상태(DELIVERED, UNDELIVERABLE, CANCELED), 또는 다른 방향이면 mailbox=null인 실패 test를 작성한다.
  • RED 확인: focused test 실패를 확인한다.
  • GREEN: GiftQueryService에서 받을 주소 repository를 조회해 기존 GiftMailboxResult로 매핑한다.
  • GREEN 확인: focused test 통과를 확인한다.

Phase 11 Gate

Goal 실행 P11-GATE: 관리자 설정과 사용자 상세 노출을 판정한다.

./gradlew test --tests "*GiftAdminServiceTest" --tests "*AdminGiftControllerTest" --tests "*GiftQueryServiceTest" --tests "*GiftControllerTest"
./gradlew ktlintCheck

Expected: 받을 주소 미등록/등록/수정, 상세 mailbox 노출 조건, 기존 선물 상세 권한 정책이 통과한다.

P11-T2 선물 상세 받을 주소 노출 조건 확장 — 2026-10-02

  • 상태: 완료
  • 무엇을: 보낸 선물 상세의 mailbox 노출 조건을 RECEIVED 단일 상태에서 정상 진행 상태 중 DELIVERED 전(RECEIVED, TRACKING_REGISTERED, ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED)으로 확장했다.
  • 왜: 팬이 운송장을 등록한 뒤에도 전달 완료 전까지 관리자 전역 선물 받을 주소를 상세에서 확인할 수 있어야 하기 때문이다.
  • 어떻게:
    • RED 확인: GiftQueryServiceTest, GiftControllerTest에 정상 진행 상태/종료 상태/받은 선물 방향 검증을 추가하고 기존 구현에서 focused test 실패를 확인했다.
    • GREEN 구현: GiftQueryService.mailboxFor 조건을 direction=SENT이고 종료 상태가 아닌 경우로 단순화했다.
    • 검증: ./gradlew test --tests '*GiftQueryServiceTest' --tests '*GiftControllerTest', ./gradlew test --tests 'kr.co.vividnext.sodalive.v2.gift.*' --tests 'kr.co.vividnext.sodalive.v2.api.gift.*', ./gradlew ktlintCheck, git diff --check 실행 결과 통과.
  • 남은 항목: 없음

Phase 12: 수취인 정보 전달 완료 불변조건 보강

Phase 결과: 수취인 필수 정보가 없는 선물은 전달 완료할 수 없고, 배송지 입력 기한이 지난 진행 중 선물은 관리자 진행 상태와 무관하게 전달 불가로 종료된다.

선행조건: P4-T2, P5-T3 완료.

Task 12.1 전달 완료 수취인 정보 필수화

Goal 실행 P12-T1: COMPLETE_DELIVERY는 INSPECTION_COMPLETED 상태이며 수취인 이름, 휴대폰 번호, 우편번호, 주소가 모두 입력된 경우에만 허용한다.

  • Files: Modify GiftDelivery.kt, GiftAdminService.kt, GiftAdminQueryService.kt; Test GiftAdminServiceTest.kt, GiftAdminQueryServiceTest.kt, AdminGiftControllerTest.kt.
  • RED: 수취인 필수 정보가 없는 INSPECTION_COMPLETED 선물의 전달 완료 요청이 거부되고 상태/전달 완료 시각/푸시가 바뀌지 않는 실패 test를 작성한다.
  • RED: 수취인 필수 정보가 불완전하면 관리자 목록/상세의 availableActions에서 COMPLETE_DELIVERY가 제외되는 실패 test를 작성한다.
  • RED 확인: focused test가 기존 구현에서 의도한 assertion으로 실패하는지 확인한다.
  • GREEN: 수취인 정보 완비 조건을 공통화하고 command와 관리자 action 계산에 적용한다.
  • GREEN 확인: 같은 focused test 통과를 확인한다.

Task 12.2 배송지 미입력 기한 초과 상태 범위 보강

Goal 실행 P12-T2: 배송지 입력 기한이 지난 주소 미입력 선물을 TRACKING_REGISTERED, ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED에서 UNDELIVERABLE로 종료한다.

  • Files: Modify GiftRepository.kt; Test GiftSchedulerTest.kt.
  • RED: 세 상태 모두 기한 초과 + recipientAddress=null이면 전달 불가가 되는 실패 test를 작성한다.
  • RED: 기한 동일/미도래, 주소 입력 완료, 대상 외 상태는 변경되지 않는 회귀 test를 유지한다.
  • RED 확인: ARRIVED_AT_MAILBOX, INSPECTION_COMPLETED case가 기존 query에서 실패하는지 확인한다.
  • GREEN: 기존 만료 조회 query의 상태 조건만 세 상태로 확장한다.
  • GREEN 확인: scheduler focused test 통과를 확인한다.

Phase 12 Gate

  • GiftAdminServiceTest, GiftAdminQueryServiceTest, AdminGiftControllerTest, GiftSchedulerTest의 변경 관련 test 통과.
  • 전체 ./gradlew --no-daemon test 통과(8분 33초), 종료 후 embedded Redis 및 Gradle test worker 잔존 없음.
  • ktlintCheck, build -x test, git diff --check 통과.
  • AdminGiftControllerTest에서 주소 미입력 실패와 주소 입력 완료 성공 HTTP 계약 확인.

구현 결과: 수취인 필수 정보 완비 조건을 GiftDelivery.hasCompleteRecipientInformation()으로 공통화해 전달 완료 command와 관리자 action 계산에 적용했다. 배송지 입력 기한 초과 조회는 주소 입력 가능 상태 3개로 확장했다.

Task 12.3 Embedded Redis 종료 누수 수정

  • com.github.codemonstur:embedded-redis:1.4.3의 RedisInstance.stop()과 shutdown hook 등록 동작을 확인했다.
  • initializer의 중복 JVM shutdown hook을 제거했다.
  • 공식 onShutdownForceStop(true) 옵션으로 native Redis 프로세스 종료가 무기한 waitFor()에 걸리지 않도록 변경했다.
  • 후속 요청에 따라 전체 ./gradlew --no-daemon test를 실행해 정상 종료를 확인했다.

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
2026-10-02 PLAN-DEC-008 확정 COMPLETE_DELIVERY는 수취인 필수 정보가 모두 입력된 경우에만 허용하고, 배송지 미입력 기한 초과는 주소 입력 가능 상태 3개 전체에서 UNDELIVERABLE로 종료한다 배송지 없는 선물의 전달 완료와 관리자 상태 진행 후 만료 누락 방지 P12-T1, P12-T2

발견된 문제

ID 심각도 상태 발견 내용 영향 Goal 처리 계획
GIFT-BUG-001 High 해결 수취인 정보가 없는 INSPECTION_COMPLETED 선물이 전달 완료될 수 있고, 관리자 상태 진행 후 배송지 미입력 기한 초과 대상에서 누락된다 P12-T1, P12-T2 command/action 불변조건과 만료 조회 상태 범위를 테스트 우선으로 보강 완료
TEST-INFRA-001 Medium 해결 테스트 timeout 후 embedded Redis native 프로세스와 Gradle worker가 남아 후속 테스트 실행을 방해했다 P12-T3 잔존 프로세스 정리, 중복 hook 제거, 공식 강제 종료 옵션 적용 후 전체 테스트 정상 종료 확인

최종 보고 형식

구현 결과: 완료한 Phase와 사용자 흐름을 한 문장으로 작성

- 변경: 주요 파일과 동작 요약
- 결정: 중요한 Decision Log ID와 내용
- 검증:
  - 실행 명령 — 성공/실패와 핵심 수치
  - 수동 검증 — 성공/실패/불가 사유
- 남은 항목: 외부 의존, 후속 범위 또는 없음
- 문서: 갱신한 PRD/plan/review 링크