diff --git a/docs/20260929_크리에이터_선물하기/plan-task.md b/docs/20260929_크리에이터_선물하기/plan-task.md index 9075d095..6b73c835 100644 --- a/docs/20260929_크리에이터_선물하기/plan-task.md +++ b/docs/20260929_크리에이터_선물하기/plan-task.md @@ -8,7 +8,7 @@ | 작성일 | 2026-09-29 | | 요구사항 기준 | `docs/20260929_크리에이터_선물하기/prd.md` | | API 기준 | `docs/20260929_크리에이터_선물하기/prd.md`의 `8. API 계약` | -| 현재 Phase | Phase 7 완료 | +| 현재 Phase | Phase 9 완료 | | 현재 활성 Goal | 완료 | ## 목표 @@ -38,6 +38,8 @@ | 5 | 완료 | `3/3` | 완료 | 없음 | | 6 | 완료 | `2/2` | 완료 | 없음 | | 7 | 완료 | `3/3` | 완료 | 없음 | +| 8 | 완료 | `1/1` | 완료 | 없음 | +| 9 | 완료 | `3/3` | 완료 | 없음 | - 동시에 하나의 미완료 goal만 운용한다. - 구현 중 요구사항이 바뀌면 이 문서의 체크박스와 범위를 먼저 갱신한 뒤 코드를 수정한다. @@ -655,6 +657,57 @@ **Expected:** 모든 명령 exit 0. PRD `GIFT-001`~`GIFT-013`이 구현·검증·문서 추적으로 연결된다. +## Phase 9: 카테고리 확장과 신청번호 정책 변경 + +**Phase 결과:** 카테고리는 분류번호, 내부 범용코드, 분류명, 접수코드, 대표품목, 파손면책 조건을 관리하고, 신청번호는 카테고리+날짜별 sequence로 발급된다. + +**선행조건:** Phase 8 완료, 사용자 확정 요구사항 반영. + +**Phase 완료 조건:** `P9-T1`~`P9-T3`와 `P9-GATE` 완료, 검증 기록 누적. + +### 구현 항목 + +#### Task 9.1 문서와 DDL 계약 갱신 + +**Goal 실행 `P9-T1`:** PRD, 구현 계획, DDL에 확정된 카테고리 필드와 신청번호 정책을 반영한다. + +- **Files:** Modify `prd.md`, `plan-task.md`, `gift-schema.sql`. +- [x] **RED:** 문서/DDL의 기존 `G{yyyyMMdd}{dailySequence6}`와 기존 카테고리 필드 계약을 확인한다. +- [x] **GREEN:** `classificationNumber`, `categoryCode`, `name`, `receiptCode`, `representativeItem`, `requiresDamageWaiver`, `isActive`와 새 신청번호 형식을 반영한다. +- [x] **검증:** 세 문서가 같은 제약과 응답 정책을 설명하는지 대조한다. + +#### Task 9.2 카테고리 persistence/API 계약 변경 + +**Goal 실행 `P9-T2`:** 관리자 API는 새 카테고리 필드를 각각 관리하고, 사용자 폼 옵션은 `분류명/대표품목` 조합 name만 내려준다. + +- **Files:** Modify `GiftCategory.kt`, `GiftAdminService.kt`, `AdminGiftRequest.kt`, `AdminGiftResponse.kt`, `GiftQueryService.kt`, `GiftResponse.kt` if needed. +- **Tests:** Modify `GiftEntityMappingTest.kt`, `GiftAdminServiceTest.kt`, `GiftQueryServiceTest.kt`, `AdminGiftControllerTest.kt`, `GiftControllerTest.kt`. +- [x] **RED:** 새 필드 저장/조회, 관리자 request/response 분리, 사용자 폼 옵션 name 조합, validation 실패 테스트를 작성하고 실패를 확인한다. +- [x] **GREEN:** 엔티티, DTO, service mapping, validation을 최소 구현한다. +- [x] **검증:** 카테고리 관련 focused test를 실행한다. + +#### Task 9.3 신청번호 채번 정책 변경 + +**Goal 실행 `P9-T3`:** `receiptCode-classificationNumberYYMMDDSequenceNo` 형식과 카테고리+날짜별 sequence를 구현한다. + +- **Files:** Modify `GiftApplicationNoSequence.kt`, `GiftApplicationNoSequenceRepository.kt`, `GiftApplicationNoGenerator.kt`, `GiftCommandService.kt`. +- **Tests:** Modify `GiftApplicationNoGeneratorTest.kt`, `GiftCommandServiceTest.kt`, `GiftEntityMappingTest.kt` if needed. +- [x] **RED:** 형식, 카테고리별 독립 sequence, 날짜별 독립 sequence, `9999` 초과, 병렬 unique 테스트를 작성하고 실패를 확인한다. +- [x] **GREEN:** 기존 transaction/locking 방식을 유지해 최소 구현한다. +- [x] **검증:** 신청번호 focused test와 직접 영향 테스트를 실행한다. + +### Phase 9 Gate + +```bash +./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGeneratorTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftAdminServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.application.GiftQueryServiceTest" --tests "kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftEntityMappingTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest" +./gradlew ktlintCheck +git diff --check +``` + +**Expected:** 모든 명령 exit 0. 기존 datetime UTC ISO 문자열 변경은 유지된다. + +**결과:** 완료. 2026-09-30 기준 Phase 9 Gate 명령 3개 모두 성공. + ## 실행 순서와 의존성 | 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | @@ -1147,6 +1200,65 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종 - 검증: focused gift/API/scheduler/admin test 실행 결과 `BUILD SUCCESSFUL`, `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL` - 남은 항목: 없음 +## Phase 8: 선물 응답 시각 UTC ISO 문자열화 + +**Phase 결과:** 선물 사용자/관리자 API가 모든 응답 시각을 클라이언트가 안전하게 해석할 수 있는 UTC ISO 문자열로 반환한다. + +**선행조건:** `P7-GATE` 완료와 PRD `DEC-013` 확정. + +**Phase 완료 조건:** `P8-T1` 완료와 focused/controller 회귀, gift 회귀, lint 검증 기록 누적. + +### 구현 항목 + +#### Task 8.1 선물 response datetime 계약 수정 + +**Goal 실행 `P8-T1`:** `GiftResponse.kt`와 `AdminGiftResponse.kt`의 응답 시각 필드를 UTC ISO `String`으로 변환한다. + +- **시작 조건:** PRD `DEC-013` 확인. +- **완료 증거:** RED/GREEN/REFACTOR 체크박스, focused controller test, gift 회귀 test, `ktlintCheck` 통과. +- **범위 밖:** 전역 Jackson 설정 변경, 선물 외 API datetime 마이그레이션, DB 저장 timezone 변경. + +**Files:** + +- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/gift/dto/GiftResponse.kt` +- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/dto/AdminGiftResponse.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt` + +**Interfaces:** + +- Produces: 선물 사용자/관리자 API의 모든 populated datetime JSON 값은 `2026-09-30T12:00:00Z` 같은 UTC ISO 문자열이다. + +- [x] **RED:** controller test의 선물 날짜/시간 응답 기대값을 `Z` 포함 UTC ISO 문자열로 변경한다. +- [x] **RED 확인:** `./gradlew test --tests "kr.co.vividnext.sodalive.v2.api.gift.adapter.in.web.GiftControllerTest" --tests "kr.co.vividnext.sodalive.v2.api.admin.gift.adapter.in.web.AdminGiftControllerTest"`를 실행해 기존 `LocalDateTime` 직렬화가 `Z`를 포함하지 않아 실패함을 확인한다. +- [x] **GREEN:** response DTO의 `LocalDateTime` 필드를 `String`, `LocalDateTime?` 필드를 `String?`로 바꾸고 기존 `LocalDateTime.toUtcIso()` extension으로 변환한다. +- [x] **GREEN 확인:** 같은 focused controller test가 통과하는지 확인한다. +- [x] **REFACTOR:** 불필요한 import를 제거하고 `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*"`, `./gradlew ktlintCheck`, `git diff --check` 결과를 Progress에 기록한다. + +### P8-T1 선물 response datetime 계약 수정 — 2026-09-30 + +- 상태: 완료 +- 무엇을: 선물 사용자/관리자 response DTO의 날짜/시간 필드를 `String`으로 바꾸고 `toUtcIso()`로 변환했다. 클라이언트 요약 문서도 UTC ISO 문자열 계약으로 동기화했다. +- 왜: Jackson 기본 `LocalDateTime` 직렬화는 `2026-09-30T12:00:00`처럼 offset이 없어 클라이언트가 UTC로 안전하게 해석할 수 없기 때문이다. +- 어떻게: + - RED test: controller test 기대값을 `Z` 포함 문자열로 변경 후 focused test 실행 결과 11개 assertion 실패 확인. + - GREEN 구현: `GiftResponse.kt`, `AdminGiftResponse.kt`에서 응답 시각 필드를 `String`/`String?`로 변경하고 기존 `LocalDateTime.toUtcIso()` extension 재사용. + - 검증: focused controller test 실행 결과 `BUILD SUCCESSFUL`, `./gradlew test --tests "kr.co.vividnext.sodalive.v2.gift.*"` 실행 결과 `BUILD SUCCESSFUL`, `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL`, `git diff --check` 출력 없음, `rg -n -- "LocalDateTime"` 대상 response DTO/클라이언트 요약 문서 출력 없음, `./gradlew tasks --all` 실행 결과 `BUILD SUCCESSFUL`. +- 남은 항목: 없음 + +### Phase 9 카테고리 확장과 신청번호 정책 변경 — 2026-09-30 + +- 상태: 완료 +- 무엇을: 카테고리에 분류번호, 접수코드, 대표품목을 추가하고 사용자 폼 옵션의 카테고리명은 `분류명/대표품목`으로 조합했다. 신청번호는 `receiptCode-classificationNumberYYMMDDSequenceNo` 형식으로 변경하고 sequence는 카테고리+날짜별로 분리했다. +- 왜: `categoryCode`는 내부 범용코드로 유지하고, 운영용 분류번호/접수코드/대표품목을 별도 관리해야 하기 때문이다. +- 어떻게: + - RED 확인 1: 카테고리 추가 필드 테스트 작성 후 `compileTestKotlin`에서 `classificationNumber`, `receiptCode`, `representativeItem` 미구현 실패 확인 + - GREEN 확인 1: 카테고리 focused suite 실행 결과 `BUILD SUCCESSFUL` + - RED 확인 2: 신청번호 generator 테스트 작성 후 기존 `generate(now)`와 sequence entity가 새 시그니처/필드를 지원하지 않아 `compileTestKotlin` 실패 확인 + - GREEN 확인 2: 신청번호 focused suite 실행 결과 `BUILD SUCCESSFUL` + - Gate 확인: Phase 9 focused suite, `./gradlew ktlintCheck`, `git diff --check` 모두 성공 +- 남은 항목: 없음 + ## Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | @@ -1156,6 +1268,8 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종 | 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` | ## 발견된 문제 diff --git a/docs/20260929_크리에이터_선물하기/prd.md b/docs/20260929_크리에이터_선물하기/prd.md index 672f854d..6a559c83 100644 --- a/docs/20260929_크리에이터_선물하기/prd.md +++ b/docs/20260929_크리에이터_선물하기/prd.md @@ -6,7 +6,7 @@ |---|---| | 문서 상태 | 구현 중 | | 작성일 | 2026-09-29 | -| 최종 수정일 | 2026-09-29 | +| 최종 수정일 | 2026-09-30 | | 대상 제품 | 크리에이터 선물하기 | | 작성자·결정권자 | 사용자 | | 관련 API Contract | 별도 문서 없음. 이 문서의 `8. API 계약`을 기준으로 사용 | @@ -25,6 +25,7 @@ - 선물 가격은 사이즈별로 다르고 운영자가 변경해야 하므로 코드 상수만으로는 운영 요구를 충족할 수 없다. - 운송장 미등록, 크리에이터 배송지 미입력, 검수 실패 같은 종료 상태와 환불/푸시 정책이 명확히 정의되어야 한다. - 팬과 크리에이터가 서로 다른 시점에 약관과 개인정보 처리에 동의해야 하므로 신청 건 기준의 동의 스냅샷이 필요하다. +- 클라이언트가 날짜/시간 응답을 안정적으로 해석할 수 있도록 선물 API의 응답 시각은 timezone 없는 `LocalDateTime` JSON이 아니라 UTC ISO 문자열이어야 한다. 문제를 해결했다는 판단은 선물 신청부터 전달 완료 또는 종료 상태까지 모든 상태 전이가 API/스케줄링/운영 API로 추적되고, 캔 사용내역과 푸시가 상태별 정책대로 처리되는 것으로 한다. @@ -97,10 +98,12 @@ INSPECTION_COMPLETED -> UNDELIVERABLE ### 6.3 신청번호 정책 - 신청번호 `applicationNo`는 내부 기본키와 별도로 발급한다. -- 형식은 `G{yyyyMMdd}{dailySequence6}`로 한다. -- `yyyyMMdd`는 Asia/Seoul 기준 신청일이다. -- `dailySequence6`는 해당 날짜 안에서 1부터 증가하는 6자리 숫자다. -- 예시는 `G20260929000001`이다. +- 형식은 `{receiptCode}-{classificationNumber}{YYMMDD}{SequenceNo}`로 한다. +- `receiptCode`는 선택한 카테고리의 접수코드이고, `classificationNumber`는 선택한 카테고리의 분류번호다. +- `YYMMDD`는 Asia/Seoul 기준 신청일이다. +- `SequenceNo`는 선택한 카테고리와 신청일 조합 안에서 1부터 증가한다. +- `SequenceNo`는 최소 4자리로 왼쪽을 0으로 채우고, `9999`를 초과하면 자리수를 늘린다. +- 예시는 `A-1002609300001`, `C-20026093010000`이다. - `applicationNo`는 unique이며 생성 후 변경하지 않는다. - 동시에 여러 선물 신청이 발생해도 같은 `applicationNo`가 발급되지 않아야 한다. - 구현은 DB unique 제약, 원자적 채번, 충돌 시 재시도 중 하나 이상의 방식으로 경합을 처리해야 한다. @@ -129,12 +132,16 @@ INSPECTION_COMPLETED -> UNDELIVERABLE | 필드 | 타입 | 제약 | 설명 | |---|---|---|---| -| `categoryCode` | string | 50자 이하, unique | 내부 분류번호. 사용자에게 표시하지 않음 | -| `name` | string | 50자 이하 | 사용자 표시명 | +| `classificationNumber` | string | 숫자 3자리, unique | 신청번호에 사용하는 분류번호. 사용자에게 별도 필드로 표시하지 않음 | +| `categoryCode` | string | 50자 이하, unique | 내부 범용코드. 사용자에게 표시하지 않음 | +| `name` | string | 50자 이하 | 분류명 | +| `receiptCode` | string | 영문 대문자 1~9자, unique | 신청번호에 사용하는 접수코드 | +| `representativeItem` | string | 100자 이하 | 대표품목. 사용자 폼 옵션의 `name` 조합에 사용 | | `requiresDamageWaiver` | boolean | DDL은 `tinyint(1)` | 파손면책 동의 필요 여부 | | `isActive` | boolean | DDL은 `tinyint(1)` | 활성 여부 | `requiresDamageWaiver == true`인 카테고리를 선택한 팬은 선물 신청 시 `damageWaiverAgreed=true`를 추가로 보내야 한다. +사용자 선물 신청 폼 옵션에서는 카테고리 응답의 `name`을 `분류명/대표품목` 형태로 조합해 내려준다. 관리자 API에서는 `name`과 `representativeItem`을 각각 내려주고 각각 수정할 수 있어야 한다. ### 6.6 약관 동의 @@ -189,6 +196,8 @@ INSPECTION_COMPLETED -> UNDELIVERABLE - 현재 로그인 회원이 처리할 일이 아니거나 이미 처리할 수 없는 상태이면 해당 flag는 `false`다. - 상세 조회는 정상 진행 상태 5개(`RECEIVED`, `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED`, `DELIVERED`)의 날짜/시간을 순서대로 내려준다. - `CANCELED`, `UNDELIVERABLE`은 정상 진행 타임라인 상태가 아니며, 각각 별도 종료 정보로 `canceledAt`, `undeliverableAt`, `undeliverableReason`을 내려준다. +- 선물 사용자/관리자 API의 모든 응답 시각 필드는 UTC ISO 문자열로 내려준다. 예: `2026-09-30T12:00:00Z`. +- 응답 시각 필드는 `Z` 또는 offset 없이 내려가는 `LocalDateTime` 기본 직렬화 형식을 사용하지 않는다. ### 6.9 리뷰 @@ -272,14 +281,14 @@ Response: "categories": [ { "categoryId": 1, - "name": "인형", + "name": "인형/피규어", "requiresDamageWaiver": true } ] } ``` -사용자 API는 `categoryCode`를 응답하지 않고 활성 카테고리만 내려준다. +사용자 API는 `categoryCode`, `classificationNumber`, `receiptCode`, `representativeItem`을 별도 필드로 응답하지 않고 활성 카테고리만 내려준다. ### 8.3 선물 보내기 등록 @@ -307,7 +316,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "status": "RECEIVED", "statusName": "접수 완료", "priceCan": 80, @@ -342,7 +351,7 @@ Response: { "items": [ { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "direction": "SENT", "status": "TRACKING_REGISTERED", "statusName": "발송 확인", @@ -372,7 +381,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "direction": "SENT", "status": "TRACKING_REGISTERED", "statusName": "발송 확인", @@ -381,7 +390,7 @@ Response: "senderNickname": null, "sizeName": "소형", "categoryName": "인형", - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "paidCan": 80, "tracking": "CJ대한통운_1234567890", "shippingRequestedAt": null @@ -440,7 +449,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "direction": "RECEIVED", "status": "INSPECTION_COMPLETED", "statusName": "검수완료", @@ -534,7 +543,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "status": "TRACKING_REGISTERED", "recipientAddressDeadlineAt": "2026-10-09T03:00:00Z" } @@ -557,7 +566,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "status": "CANCELED", "refundedCan": 80 } @@ -591,7 +600,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "recipientAddressRegisteredAt": "2026-10-03T03:00:00Z" } ``` @@ -613,7 +622,7 @@ Response: ```json { - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "status": "DELIVERED", "deliveredAt": "2026-10-10T03:00:00Z" } @@ -645,7 +654,7 @@ Response: ```json { "reviewId": 1, - "applicationNo": "G20260929000001", + "applicationNo": "A-1002609300001", "rating": 5, "keywords": ["배송이 빨라요", "안내가 친절해요"], "comment": "선물 전달 과정이 만족스러웠습니다.", @@ -675,8 +684,11 @@ Response: ```json { + "classificationNumber": "100", "categoryCode": "DOLL", "name": "인형", + "receiptCode": "A", + "representativeItem": "피규어", "requiresDamageWaiver": true, "isActive": true } @@ -804,8 +816,11 @@ Response: | 필드 | 설명 | |---|---| | `id` | 내부 기본키 | -| `categoryCode` | 50자 이하 내부 분류번호 | -| `name` | 50자 이하 카테고리명 | +| `classificationNumber` | 숫자 3자리 unique 분류번호 | +| `categoryCode` | 50자 이하 unique 내부 범용코드 | +| `name` | 50자 이하 분류명 | +| `receiptCode` | 영문 대문자 1~9자 unique 접수코드 | +| `representativeItem` | 100자 이하 대표품목 | | `requiresDamageWaiver` | 파손면책 동의 필요 여부. DDL은 `tinyint(1)` | | `isActive` | 활성 여부. DDL은 `tinyint(1)` | | `createdAt`, `updatedAt` | 생성/수정 시각 | @@ -854,6 +869,9 @@ Response: - 리뷰는 작성 권한, 상태, 중복 작성, 글자수 제한을 테스트한다. - 푸시는 팬/크리에이터 수신자 분리와 크리에이터 수령확인 시 크리에이터 푸시 억제를 테스트한다. - 스케줄러는 중복 실행 시 중복 환불/중복 푸시가 없는지 테스트한다. +- 선물 사용자/관리자 API response DTO의 날짜/시간 필드는 UTC ISO 문자열(`Z` 포함)로 직렬화되는지 controller test로 고정한다. +- 카테고리 `classificationNumber`, `receiptCode`, `representativeItem`의 관리자 등록/수정/조회와 사용자 폼 옵션의 `분류명/대표품목` 조합을 테스트한다. +- 신청번호는 카테고리+날짜별 sequence, 4자리 padding, `9999` 초과 자리수 증가, 병렬 unique를 테스트한다. ## 13. 성공 기준 @@ -865,6 +883,8 @@ Response: - [ ] 팬은 `DELIVERED` 이후 리뷰를 1회 작성할 수 있다. - [ ] 관리자 카테고리 논리삭제와 사이즈별 가격 변경이 새 신청에만 반영된다. - [ ] 팬/크리에이터 푸시가 지정된 문구, 수신자, 딥링크로 발송된다. +- [ ] 선물 사용자/관리자 API의 날짜/시간 응답을 클라이언트가 UTC ISO 문자열로 해석할 수 있다. +- [ ] 카테고리 추가 필드와 새 신청번호 정책이 관리자 API, 사용자 폼 옵션, 선물 신청에 반영된다. ## 14. Open Questions @@ -880,6 +900,7 @@ Response: | `GIFT-009` | 4 | 운영 상태변경 API 테스트 | 운영 상태 전이 확인 | | `GIFT-010` | 5 | 리뷰 API 테스트 | 전달완료 후 리뷰 작성 확인 | | `GIFT-011` | 6 | 푸시 이벤트 테스트 | 딥링크와 수신자 확인 | +| `GIFT-014` | 8 | 선물 사용자/관리자 controller 날짜 응답 테스트 | UTC ISO 문자열 확인 | ## 16. Decision Log @@ -897,3 +918,5 @@ Response: | 2026-09-29 | `DEC-010` | 확정 | 가격은 `basePriceCan`과 실제 결제 금액인 `salePriceCan`으로 표현한다 | 사용자 정정 | `GIFT-001`, `GIFT-002`, `GIFT-013` | | 2026-09-29 | `DEC-011` | 확정 | 크리에이터 배송지 입력 시에도 약관 2개 동의와 동의시각을 별도 저장한다 | 사용자 정정 | `GIFT-007` | | 2026-09-29 | `DEC-012` | 확정 | 파손면책 동의 필요 카테고리는 팬 신청 시 `damageWaiverAgreed=true`를 요구한다 | 사용자 선택 A | `GIFT-003` | +| 2026-09-30 | `DEC-013` | 확정 | 선물 사용자/관리자 API의 response datetime 필드는 `LocalDateTime` 기본 JSON 대신 UTC ISO 문자열로 내려준다 | 클라이언트가 offset 없는 `LocalDateTime` 문자열을 안전하게 해석하지 못함 | `GIFT-014` | +| 2026-09-30 | `DEC-014` | 확정 | 카테고리는 분류번호, 내부 범용 카테고리 코드, 분류명, 접수코드, 대표품목, 파손면책 조건을 관리하고 신청번호는 `receiptCode-classificationNumberYYMMDDSequenceNo`로 발급한다 | 관리자 운영 필드와 사용자 표시 요구가 분리됨 | `GIFT-001`, `GIFT-002`, `GIFT-012` |