Compare commits

..

492 Commits

Author SHA1 Message Date
540c5cb317 Merge pull request 'test' (#425) from test into main
Reviewed-on: #425
2026-05-18 06:53:12 +00:00
ddac78a666 fix(charge): 쿠폰 충전 회원 락을 적용한다 2026-05-18 15:39:11 +09:00
fefb5c24eb fix(charge): 이벤트 작업 즉시 지급 트랜잭션을 분리한다 2026-05-18 14:52:08 +09:00
9a9fdfe0a1 docs(charge): 충전 이벤트 보너스 지급 안정화 계획을 추가한다 2026-05-18 14:18:10 +09:00
56acf257e0 refactor(charge): 충전 이벤트 작업 패키지를 정리한다 2026-05-18 13:46:26 +09:00
810b143c9e fix(charge): 충전 이벤트 보너스 지급을 안정화한다 2026-05-18 13:34:12 +09:00
acd0393a0e feat(chat): 채팅방 리스트 조회 API를 추가한다 2026-05-14 16:12:14 +09:00
3a2c21c896 docs(agent): 코드 배치 규칙을 문서화한다 2026-05-14 11:43:41 +09:00
1daf67fa49 feat(user-creator-chat): 유저 크리에이터 채팅방을 추가한다
유저와 크리에이터 간 텍스트/음성 메시지, SSE presence, 조건부 푸시 흐름을 신규 도메인으로 분리한다.
2026-05-13 18:02:11 +09:00
6e22198b6f docs: 에이전트 문서 절차를 정리한다 2026-05-13 12:04:03 +09:00
b67dce4afd docs: prd 폴더와 sample-prd 추가, 기존 계획 문서 plan-task 폴더 아래로 이동 2026-05-13 11:47:59 +09:00
3aa6a35adb Merge pull request 'fix(admin): 크리에이터 관리자 로그아웃 AGENT 권한을 허용한다' (#424) from test into main
Reviewed-on: #424
2026-05-08 10:44:04 +00:00
9278761c5b fix(admin): 크리에이터 관리자 로그아웃 AGENT 권한을 허용한다 2026-05-08 16:50:42 +09:00
81c0d2586c Merge pull request '콘텐츠 관리자 권한 추가' (#423) from test into main
Reviewed-on: #423
2026-05-07 06:35:15 +00:00
85621cd107 feat(admin): 콘텐츠 관리자 읽기 권한을 확장한다 2026-05-07 14:34:23 +09:00
487c10d4d0 feat(admin): 콘텐츠 관리자 로그인 API 추가 2026-05-07 14:14:48 +09:00
965bb068fc Merge pull request 'fix(i18n): 지원하지 않는 원문 언어 번역 작업을 제외한다' (#422) from test into main
Reviewed-on: #422
2026-05-07 03:26:24 +00:00
870afb03da fix(i18n): 지원하지 않는 원문 언어 번역 작업을 제외한다 2026-05-07 12:16:35 +09:00
d082e0b745 Merge pull request '번역 작업 큐와 언어 감지 캐시를 도입한다' (#421) from test into main
Reviewed-on: #421
2026-05-07 01:31:02 +00:00
13ca6a97b9 feat(i18n): 번역 작업을 그룹 단위로 처리한다 2026-05-06 20:21:29 +09:00
3a0c30e340 feat(i18n): 번역 작업 큐와 언어 감지 캐시를 도입한다
조회 중 외부 번역 호출을 줄이고 누락 번역을 비동기 job으로 처리한다.
2026-05-06 18:02:36 +09:00
c8b83272a3 Merge pull request 'test' (#420) from test into main
Reviewed-on: #420
2026-05-01 06:40:58 +00:00
dfb97fba80 fix(member): getMemberInfo 응답 role을 CREATOR 외에는 USER로 매핑 2026-05-01 15:22:35 +09:00
343dee1f6c feat(payverse): JPY 결제 지원 추가 및 금액 포맷 규칙 적용
- ChargeService에 JPY 전용 자격 증명 주입(payverse.jpy-*)
  - payverseCharge/payverseWebhook/payverseVerify에 KRW/JPY/USD 3분기 적용
  - JPY 금액 정수화(FLOOR) 처리 및 공통 함수 computePayverseAmount 추가
  - 검증/체크리스트 문서 추가(docs/20260501_payverse-jpy-지원.md)
2026-05-01 14:56:14 +09:00
b98cc4b018 fix(can): 특정 회원(2, 4, 44144) 접속 시 getCans 통화를 JPY로 강제
- CanService.getCans 시그니처를 isNotSelectedCurrency(Boolean) → forcedCurrency(String?)로 변경해 의도 명확화
- 통화 결정 로직을 forcedCurrency 우선 적용 후, 국가 코드(KR=KRW, 그 외=USD)로 fallback
- CanController에서 회원 ID가 2, 4, 44144인 경우 forcedCurrency="JPY"로 설정하여 서비스 호출
2026-05-01 14:38:24 +09:00
dc11f44a32 fix(member): 강제 KR 매핑 대상에 회원 pg-jp-test(44144) 추가 2026-05-01 14:33:24 +09:00
d736ec4368 feat(chat-quota): 채팅방 쿼터 충전 방식과 옵션을 확장한다 2026-04-29 18:44:36 +09:00
0c0da6cbc9 docs(chat-quota): 채팅방 쿼터 충전 방식 확장 작업 기록을 정리한다 2026-04-29 18:43:34 +09:00
c7352c4bd3 docs(agent): 에이전트 운영 가이드를 재구성한다 2026-04-29 16:57:44 +09:00
1b20bc81b7 docs(agent-guides): 작업 절차와 문서 유지보수 규칙을 정리한다 2026-04-29 16:57:36 +09:00
0665cdaca8 docs(agent-guides): 코드·테스트·보안 가이드를 분리한다 2026-04-29 16:57:27 +09:00
1939fdcb33 Merge pull request 'fix(member): 강제 KR 매핑 대상에 회원 17958 추가' (#419) from test into main
Reviewed-on: #419
2026-04-28 05:00:06 +00:00
a7b8ac3088 fix(member): 강제 KR 매핑 대상에 회원 17958 추가 2026-04-28 12:30:42 +09:00
f521a240c2 Merge pull request 'test' (#418) from test into main
Reviewed-on: #418
2026-04-22 01:19:41 +00:00
19bd07fe14 docs(original-series-calculate): 정산 API 파라미터와 응답 예시를 갱신한다 2026-04-21 19:16:58 +09:00
49b1aa8f0c fix(original-series-calculate): 소지 유저별 정산 엑셀 시트 생성을 정리한다 2026-04-21 19:16:34 +09:00
72f49f2471 refactor(original-series-calculate): 소지 유저 응답 식별자를 creatorId로 변경한다 2026-04-21 19:16:09 +09:00
5098994f4b refactor(original-series-calculate): 정산 조회 파라미터를 snake_case로 통일한다 2026-04-21 19:15:55 +09:00
6c49abc54e docs(original-series-calculate): 오리지널 시리즈 정산 API 명세를 보강한다
클라이언트 구현에 필요한 요청/응답 계약과 엑셀 시트 구성을 문서에 명확히 남긴다.
서버 API 계약을 문서만으로 그대로 사용할 수 있게 해 혼선을 줄인다.
2026-04-21 18:29:49 +09:00
604a6ac681 docs(original-series-calculate): 오리지널 시리즈 정산 작업 기록을 추가한다 2026-04-21 18:01:30 +09:00
ae0bf769f7 feat(original-series-calculate): 오리지널 시리즈 정산 내역 조회를 추가한다 2026-04-21 18:01:11 +09:00
249209e6bb feat(live-room): 라이브방 정보 응답에 무료 여부를 추가한다 2026-04-21 13:31:39 +09:00
0c35624dfb Merge pull request 'fix(agent-calculate): 에이전트 기본 정산 비율을 7퍼센트로 조정한다' (#417) from test into main
Reviewed-on: #417
2026-04-15 05:37:24 +00:00
f4347cc862 fix(agent-calculate): 에이전트 기본 정산 비율을 7퍼센트로 조정한다 2026-04-15 14:25:54 +09:00
5c24addf31 Merge pull request '에이전트 기능' (#416) from test into main
Reviewed-on: #416
2026-04-14 06:29:22 +00:00
2c19e4b76c feat(agent-calculate): 소속 크리에이터 응답에 프로필 이미지를 추가한다 2026-04-13 18:51:33 +09:00
f740041dca feat(creator-admin login): AGENT 권한을 가진 유저도 로그인이 가능하도록 수정 2026-04-13 16:04:21 +09:00
46b282a817 fix(agent-assignment): 소속 크리에이터 조회 시각을 KST로 변환한다 2026-04-13 14:55:59 +09:00
f17dedda20 docs(agent-assignment): 소속 시각 UTC 변환 작업 기록을 추가한다 2026-04-13 11:23:47 +09:00
f357d426d0 fix(agent-assignment): 소속 시각 UTC 변환을 적용한다 2026-04-13 11:23:25 +09:00
08ba6a6046 fix(agent-ratio): 에이전트 정산 비율 수정 충돌 처리를 안정화한다 2026-04-11 21:40:45 +09:00
88ffaf6d04 docs(agent-read): 에이전트 닉네임 검색 작업 기록을 추가한다 2026-04-11 21:05:55 +09:00
765c087af3 feat(agent-read): 관리자 에이전트 닉네임 검색 조회를 추가한다 2026-04-11 21:05:27 +09:00
535f5d16cc feat(agent-read): 관리자 에이전트 닉네임 검색 컨트롤러를 추가한다 2026-04-11 21:05:17 +09:00
59a4b06d86 feat(agent-read): 관리자 에이전트 조회 API를 추가한다 2026-04-10 19:53:31 +09:00
576498ffb6 docs(agent-read): 관리자 에이전트 조회 문서를 추가한다 2026-04-10 19:53:18 +09:00
37f2e3d45a chore(gitignore): 워크트리 작업 디렉터리를 제외한다 2026-04-10 19:53:10 +09:00
c0f5c9ca33 fix(agent-calculate): 에이전트 정산 total projection 조회를 DB 합계 쿼리로 분리한다 2026-04-10 14:30:23 +09:00
a661693ea9 docs(agent): 에이전트 정산 QA 기록에 total projection 리팩터링 검증을 추가한다 2026-04-10 14:30:11 +09:00
c8898e8f7e docs(agent): 에이전트 정산 QA 기록을 최신화한다 2026-04-10 13:51:43 +09:00
0b61569522 fix(agent-calculate): 정산 합계 계산 경로와 회귀 테스트를 보강한다 2026-04-10 13:51:28 +09:00
83fdb3400d fix(agent-settlement): 스냅샷 finalize 집계를 단일 누적으로 정리한다 2026-04-10 13:51:17 +09:00
53f37b93fb fix(agent-ratio): 정산 비율 목록을 current history 구조로 정리한다 2026-04-10 13:50:35 +09:00
17065bb4d1 test(agent-assignment): 자기 자신 소속 지정 거부 테스트를 추가한다 2026-04-10 13:50:19 +09:00
0e8ea7da51 feat(i18n): 에이전트 정산 메시지 키를 추가한다 2026-04-10 02:24:32 +09:00
bf67dab6a4 feat(agent-calculate): 에이전트별 정산 조회 기능을 추가한다 2026-04-10 02:24:08 +09:00
9e4cd1bb6e feat(agent-settlement): 에이전트 정산 스냅샷 관리 기능을 추가한다 2026-04-10 02:23:45 +09:00
d0be8ec2db feat(agent-ratio): 에이전트 정산 비율 관리 기능을 추가한다 2026-04-10 02:23:18 +09:00
b84f70a6bf feat(agent-assignment): 에이전트 크리에이터 소속 관리 기능을 추가한다 2026-04-10 02:23:10 +09:00
308b79fded docs(agent): 에이전트 정산 작업 기록과 DDL을 정리한다 2026-04-10 02:23:02 +09:00
3618e4f9cf chore(opencode): 1.4.0으로 업데이트 2026-04-08 13:39:09 +09:00
8671c8efc7 Merge pull request 'test' (#415) from test into main
Reviewed-on: #415
2026-04-08 01:50:50 +00:00
bebdf6a379 fix(commit-policy): 자동 co-author footer 변형을 차단한다 2026-04-07 16:16:18 +09:00
e8637b424d docs(commit-policy): 자동 footer 금지 절차를 정리한다 2026-04-07 16:16:04 +09:00
3eda0abcfc fix(calculate): 콘텐츠별 정산 요율을 정산 조회에 우선 반영한다
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 13:38:27 +09:00
9e6326f08a feat(admin-content): 관리자 콘텐츠 개별 정산 요율 수정을 지원한다
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 13:38:17 +09:00
1853c28f14 docs(content): 콘텐츠별 정산 요율 작업 기록을 정리한다
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 13:38:07 +09:00
3ca8b70a9f docs(content): 콘텐츠별 정산 요율 컬럼 추가 스크립트를 정리한다
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 13:37:59 +09:00
e02a83b83e chore: .omx를 git 제외 목록에 추가 2026-04-06 11:08:00 +09:00
16c17f4bfa Merge pull request 'feat(live-room): 라이브 캡쳐 녹화 가능 여부를 생성 조회에 반영한다' (#414) from test into main
Reviewed-on: #414
2026-03-30 12:58:19 +00:00
0e821fae1b Merge pull request 'fix(member-social): 애플 로그인 aud 검증에 serviceId를 포함한다' (#413) from test into main
Reviewed-on: #413
2026-03-30 01:00:13 +00:00
6a10eff15f Merge pull request 'fix(live-room): 진행중 목록 성인 노출 정책과 JP 강제 매핑 검증을 정리한다' (#412) from test into main
Reviewed-on: #412
2026-03-28 14:11:08 +00:00
fea329e637 Merge pull request 'fix(channel-donation): 후원 목록 탈퇴 닉네임 접두사를 제거한다' (#411) from test into main
Reviewed-on: #411
2026-03-28 10:14:16 +00:00
681e4a4036 Merge pull request 'test' (#410) from test into main
Reviewed-on: #410
2026-03-28 09:27:28 +00:00
c23f574162 Merge pull request 'fix(member): 회원 차단을 요청 ID 단건만 적용한다' (#409) from test into main
Reviewed-on: #409
2026-03-26 02:01:43 +00:00
c884d7d6c9 Merge pull request 'test' (#408) from test into main
Reviewed-on: #408
2026-03-24 10:41:41 +00:00
116e8cbca3 Merge pull request 'feat(deploy): EC2 배포 스크립트에 JVM 옵션 로드 기능 추가' (#407) from test into main
Reviewed-on: #407
2026-03-23 09:45:25 +00:00
c8187ba147 Merge pull request 'feat(db): Aurora Serverless v2(0.5~2 ACU) 최적화용 Hikari 풀 설정 추가' (#406) from test into main
Reviewed-on: #406
2026-03-23 05:13:58 +00:00
676bd0b79e Merge pull request 'test' (#405) from test into main
Reviewed-on: #405
2026-03-19 09:33:40 +00:00
7522f06bf3 Merge pull request 'fix(live-room): 라이브 방 후원 랭킹 조회에 기간 설정을 반영한다' (#404) from test into main
Reviewed-on: #404
2026-03-17 07:15:17 +00:00
a9d2d1ab48 Merge pull request 'feat(creator-community): 커뮤니티 게시물 고정 기능을 추가한다' (#403) from test into main
Reviewed-on: #403
2026-03-17 02:40:30 +00:00
e0e371cdc9 Merge pull request 'feat(admin-calculate): 관리자 라이브 환불 처리와 정산 응답 식별자를 추가한다' (#402) from test into main
Reviewed-on: #402
2026-03-16 07:13:26 +00:00
b49344d0e9 Merge pull request 'fix(admin-chat-character): JP 리전 캐릭터 등록 성별 값을 일본어로 변환한다' (#401) from test into main
Reviewed-on: #401
2026-03-16 02:37:51 +00:00
5cc152307a Merge pull request 'test' (#400) from test into main
Reviewed-on: #400
2026-03-13 14:08:53 +00:00
1fd3d41d7e Merge pull request 'test' (#399) from test into main
Reviewed-on: #399
2026-03-13 13:18:26 +00:00
c1b9dd730d Merge pull request 'fix(admin-calculate): 관리자 정산 조회 캐시를 제거하고 응답 직렬화를 명시한다' (#398) from test into main
Reviewed-on: #398
2026-03-06 03:31:24 +00:00
d40cd32c50 Merge pull request 'test' (#397) from test into main
Reviewed-on: #397
2026-03-05 09:17:37 +00:00
0289607fd9 Merge pull request 'fix(channel-donation): 관리자 채널후원 정산 조회를 날짜별과 크리에이터별로 분리하고 엑셀 다운로드를 추가한다' (#396) from test into main
Reviewed-on: #396
2026-03-03 05:59:42 +00:00
1bec644372 Merge pull request 'fix(channel-donation): 후원 조회 월 경계를 UTC 전달 기준으로 보정한다' (#395) from test into main
Reviewed-on: #395
2026-03-03 03:20:46 +00:00
f8a6d1b221 Merge pull request 'fix(channel-donation): 기부 목록 조회 월 범위를 한국 시간 기준으로 계산한다' (#394) from test into main
Reviewed-on: #394
2026-03-03 02:23:01 +00:00
ec769a1307 Merge pull request 'test' (#393) from test into main
Reviewed-on: #393
2026-02-27 06:16:51 +00:00
8e4fb0d313 Merge pull request 'test' (#392) from test into main
Reviewed-on: #392
2026-02-26 11:18:33 +00:00
cc3a620642 Merge pull request 'fix(recommend-live): 차단 관계를 추천 조회에 반영하고 캐시를 무효화한다' (#391) from test into main
Reviewed-on: #391
2026-02-25 18:40:55 +00:00
be0884e974 Merge pull request 'test' (#390) from test into main
Reviewed-on: #390
2026-02-25 16:55:31 +00:00
34456395fd Merge pull request 'test' (#389) from test into main
Reviewed-on: #389
2026-02-25 13:55:22 +00:00
26ddeb9591 Merge pull request 'test' (#388) from test into main
Reviewed-on: #388
2026-02-13 09:14:19 +00:00
cd535a628c Merge pull request 'test' (#387) from test into main
Reviewed-on: #387
2026-02-11 10:03:08 +00:00
de32b537f4 Merge pull request 'test' (#386) from test into main
Reviewed-on: #386
2026-02-08 07:42:31 +00:00
9c271fc1f6 Merge pull request 'test' (#385) from test into main
Reviewed-on: #385
2026-02-06 05:04:37 +00:00
2ddbfbccd6 Merge pull request 'test' (#384) from test into main
Reviewed-on: #384
2026-02-04 12:52:24 +00:00
80786deb72 Merge pull request 'test' (#383) from test into main
Reviewed-on: #383
2026-01-28 15:40:25 +00:00
8ca2e185ac Merge pull request '라이브 예약 반환 값 - beginDateTimeUtc 추가' (#382) from test into main
Reviewed-on: #382
2026-01-21 10:08:55 +00:00
484711ad1b Merge pull request '라이브 예약 Response에 utc 시간 변수 beginDateTimeUtc 추가' (#381) from test into main
Reviewed-on: #381
2026-01-21 07:54:05 +00:00
e80ceca0c5 Merge pull request '충전 이벤트 - langContext 문제로 충전이 되지 않는 현상을 langContext를 사용하지 않고 이전 방식으로 기록하도록 롤백하여 임시 해결' (#380) from test into main
Reviewed-on: #380
2026-01-21 02:36:21 +00:00
33293a6533 Merge pull request '라이브 방 상세 - 날짜 포맷 변경으로 유료방 입장이 불가한 문제를 해결하기 위해 이전으로 롤백' (#379) from test into main
Reviewed-on: #379
2026-01-21 02:16:42 +00:00
f0c1d4e32a Merge pull request '라이브 룸 일시 포맷에 다국어 설정 적용' (#378) from test into main
Reviewed-on: #378
2026-01-20 10:41:23 +00:00
6cd319ec76 Merge pull request 'memberId가 특정 번호일 때 currency와 관계없이 모든 구매 가능한 캔이 출력되도록 수정' (#377) from test into main
Reviewed-on: #377
2026-01-16 02:39:08 +00:00
6557ec2aed Merge pull request '푸시 알림 전송 언어 처리' (#376) from test into main
Reviewed-on: #376
2026-01-15 08:45:46 +00:00
f2f8a34319 Merge pull request '국가 컨텍스트로 캔 조회' (#375) from test into main
Reviewed-on: #375
2026-01-14 06:46:51 +00:00
c50ac6ed2c Merge pull request '캔 사용 시 국가 코드 기록 기능 추가' (#374) from test into main
Reviewed-on: #374
2026-01-12 02:31:46 +00:00
11b9c349d1 Merge pull request '번역 이벤트 커밋 후 처리 분기' (#373) from test into main
Reviewed-on: #373
2026-01-07 09:54:32 +00:00
ef9f8d65e1 Merge pull request 'test' (#372) from test into main
Reviewed-on: #372
2026-01-07 07:42:42 +00:00
299f2100e9 Merge pull request 'test' (#371) from test into main
Reviewed-on: #371
2026-01-05 06:33:29 +00:00
fd5c794480 Merge pull request 'test' (#370) from test into main
Reviewed-on: #370
2025-12-31 11:01:28 +00:00
68197de095 Merge pull request 'test' (#369) from test into main
Reviewed-on: #369
2025-12-31 05:44:54 +00:00
587f3d6b58 Merge pull request 'test' (#368) from test into main
Reviewed-on: #368
2025-11-20 16:02:25 +00:00
9b6167d46d Merge pull request 'test' (#367) from test into main
Reviewed-on: #367
2025-11-20 12:53:00 +00:00
008ee3b4e5 Merge pull request 'test' (#366) from test into main
Reviewed-on: #366
2025-11-20 06:27:02 +00:00
3a57ad23bb Merge pull request 'test' (#365) from test into main
Reviewed-on: #365
2025-11-19 08:56:28 +00:00
729552335a Merge pull request 'test' (#364) from test into main
Reviewed-on: #364
2025-11-18 10:38:50 +00:00
02ae507c87 Merge pull request 'fix(series-list): 시리즈 리스트 조회시 정렬 수정' (#363) from test into main
Reviewed-on: #363
2025-11-17 13:38:59 +00:00
5818abf69d Merge pull request 'fix(series-list): creator의 시리즈를 볼 떄와 다른 페이지에서 시리즈 리스트를 볼 때 정렬 순서 분리' (#362) from test into main
Reviewed-on: #362
2025-11-17 12:25:38 +00:00
ee403915f0 Merge pull request 'test' (#361) from test into main
Reviewed-on: #361
2025-11-17 07:13:11 +00:00
1a660088de Merge pull request 'test' (#360) from test into main
Reviewed-on: #360
2025-11-13 20:49:12 +00:00
5196c80ca8 Merge pull request 'test' (#359) from test into main
Reviewed-on: #359
2025-11-13 19:45:52 +00:00
c9c09c2998 Merge pull request 'test' (#358) from test into main
Reviewed-on: #358
2025-11-10 06:53:41 +00:00
3ea33c4c7b Merge pull request 'feat(home-latest-content): 최신 콘텐츠 조회시 정렬 조건 변경' (#357) from test into main
Reviewed-on: #357
2025-11-07 12:00:59 +00:00
451a1aa4f2 Merge pull request 'test' (#356) from test into main
Reviewed-on: #356
2025-11-06 08:48:23 +00:00
90555fd34f Merge pull request 'feat(can-use-status): PAYVERSE로 충전한 캔을 사용한 내역도 포함되도록 수정' (#355) from test into main
Reviewed-on: #355
2025-10-22 14:26:02 +00:00
0dc430b098 Merge pull request 'UseCalculate에 PAYVERSE로 충전한 캔 로그 데이터를 쌓도록 수정' (#354) from test into main
Reviewed-on: #354
2025-10-22 13:31:46 +00:00
1f2103c7fa Merge pull request 'fix(can-use): PAYVERSE로 충전한 캔이 사용되지 않는 버그 수정' (#353) from test into main
Reviewed-on: #353
2025-10-22 12:41:13 +00:00
062c17c51e Merge pull request 'feat(chat): 채팅권 구매 가격과 채팅횟수 변경' (#352) from test into main
Reviewed-on: #352
2025-10-22 07:55:13 +00:00
de169b79a1 Merge pull request 'feat(home): 인기 캐릭터 추가' (#351) from test into main
Reviewed-on: #351
2025-10-20 06:07:49 +00:00
aa24de0a5a Merge pull request 'test' (#350) from test into main
Reviewed-on: #350
2025-10-17 05:46:36 +00:00
e5937d573a Merge pull request 'test' (#349) from test into main
Reviewed-on: #349
2025-10-10 20:49:52 +00:00
6da86e12bd Merge pull request 'test' (#348) from test into main
Reviewed-on: #348
2025-10-10 19:19:47 +00:00
9049022a74 Merge pull request 'fix(admin-charge-status-detail): pgChargeAmount와 can의 가격을 가져와서 사용하는 로직을 제거하고 payment에 기록된 가격으로 계산하도록 수정' (#347) from test into main
Reviewed-on: #347
2025-10-10 14:39:40 +00:00
7b6f3a7a5f Merge pull request 'fix(admin-charge-status): pgChargeAmount와 can의 가격을 가져와서 사용하는 로직을 제거하고 payment에 기록된 가격으로 계산하도록 수정' (#346) from test into main
Reviewed-on: #346
2025-10-10 13:53:23 +00:00
53e9678efa Merge pull request 'fix(verify-hecto): 데이터 검증시 가격비교 제거' (#345) from test into main
Reviewed-on: #345
2025-10-10 09:58:11 +00:00
e4f547fa92 Merge pull request 'payverse 적용' (#344) from test into main
Reviewed-on: #344
2025-10-10 07:44:07 +00:00
b69756ef81 Merge pull request 'test' (#343) from test into main
Reviewed-on: #343
2025-09-18 19:25:50 +00:00
1a3a9149a2 Merge pull request 'test' (#342) from test into main
Reviewed-on: #342
2025-09-16 06:11:32 +00:00
ce120a6d5d Merge pull request 'test' (#341) from test into main
Reviewed-on: #341
2025-09-14 20:33:50 +00:00
08b5fd23ab Merge pull request 'test' (#340) from test into main
Reviewed-on: #340
2025-09-14 08:51:11 +00:00
eb18e2d009 Merge pull request 'test' (#339) from test into main
Reviewed-on: #339
2025-09-11 17:05:45 +00:00
a27852ed44 Merge pull request '캐릭터 챗봇' (#338) from test into main
Reviewed-on: #338
2025-09-10 06:08:47 +00:00
c7925c1706 Merge pull request 'feat: 최근 공지사항 API 추가' (#337) from test into main
Reviewed-on: #337
2025-07-28 02:16:19 +00:00
be59bd7e89 Merge pull request 'fix: 크리에이터 팔로우 API' (#336) from test into main
Reviewed-on: #336
2025-07-21 13:52:34 +00:00
51ce143fc2 Merge pull request 'test' (#335) from test into main
Reviewed-on: #335
2025-07-21 11:46:56 +00:00
89eb11f808 Merge pull request 'fix: 라이브 메인 API - 최근 종료된 라이브' (#334) from test into main
Reviewed-on: #334
2025-07-21 10:59:38 +00:00
30d89987a4 Merge pull request 'test' (#333) from test into main
Reviewed-on: #333
2025-07-21 09:54:56 +00:00
7959d3e5ed Merge pull request 'test' (#332) from test into main
Reviewed-on: #332
2025-07-18 12:33:22 +00:00
1e29573ef7 Merge pull request 'fix: 검색 API' (#331) from test into main
Reviewed-on: #331
2025-07-16 10:58:56 +00:00
cc2f533dc6 Merge pull request 'fix: 메인 홈 API - 요일별 시리즈' (#330) from test into main
Reviewed-on: #330
2025-07-14 19:14:06 +00:00
32b0c19f9d Merge pull request 'test' (#329) from test into main
Reviewed-on: #329
2025-07-14 17:57:26 +00:00
9af2d768e8 Merge pull request 'test' (#327) from test into main
Reviewed-on: #327
2025-07-14 11:07:57 +00:00
5677824cde Merge pull request 'test' (#326) from test into main
Reviewed-on: #326
2025-06-13 11:37:26 +00:00
e8f1bc09f9 Merge pull request 'test' (#325) from test into main
Reviewed-on: #325
2025-06-12 05:00:31 +00:00
d1a936d55b Merge pull request 'test' (#324) from test into main
Reviewed-on: #324
2025-06-10 11:01:31 +00:00
dc97eaa835 Merge pull request 'fix: 앱 콘텐츠 수정' (#323) from test into main
Reviewed-on: #323
2025-06-05 02:36:25 +00:00
dcbe57806c Merge pull request 'test' (#322) from test into main
Reviewed-on: #322
2025-06-02 12:41:46 +00:00
b14438cc15 Merge pull request 'fix: 유저 행동 기록, 포인트 지급' (#321) from test into main
Reviewed-on: #321
2025-05-28 07:19:27 +00:00
b27d3bd5c6 Merge pull request 'fix: 유저 행동 기록, 포인트 지급' (#320) from test into main
Reviewed-on: #320
2025-05-26 10:33:16 +00:00
03ebc9cfe9 Merge pull request 'fix: 큐레이션 아이템 조회' (#319) from test into main
Reviewed-on: #319
2025-05-23 05:43:37 +00:00
24841b9850 Merge pull request 'fix: 코루틴 내 트랜잭션 간 조회 안 되는 문제 해결' (#318) from test into main
Reviewed-on: #318
2025-05-22 04:31:42 +00:00
d35a3d1a8c Merge pull request 'test' (#317) from test into main
Reviewed-on: #317
2025-05-20 10:26:16 +00:00
60c4e0b528 Merge pull request 'test' (#316) from test into main
Reviewed-on: #316
2025-05-20 06:03:10 +00:00
84f33d1bc2 Merge pull request 'fix: 소셜로그인시 유저 행동데이터 SIGN_UP 중복 기록 버그' (#315) from test into main
Reviewed-on: #315
2025-05-12 08:24:53 +00:00
c4e1709b99 Merge pull request 'test' (#314) from test into main
Reviewed-on: #314
2025-05-12 02:12:47 +00:00
e7a5fd5819 Merge pull request 'fix: 구글/카카오 로그인 회원가입 오류 수정' (#313) from test into main
Reviewed-on: #313
2025-05-02 10:58:04 +00:00
4bde03643c Merge pull request 'test' (#312) from test into main
Reviewed-on: #312
2025-04-29 02:56:16 +00:00
1bc52b56af Merge pull request 'fix: 콘텐츠 업로드 - 제목과 내용에서 trim 함수를 적용하여 앞/뒤 빈칸 제거' (#311) from test into main
Reviewed-on: #311
2025-04-25 09:43:31 +00:00
9c33fd93f7 Merge pull request 'refactor: 본인인증 - 본인인증이 완료된 후 유저 행동 데이터를 기록하도록 수정' (#310) from test into main
Reviewed-on: #310
2025-04-24 11:10:17 +00:00
3c087bc275 Merge pull request '유저 행동 데이터, 포인트 추가' (#309) from test into main
Reviewed-on: #309
2025-04-24 02:44:57 +00:00
8ad13c289e Merge pull request '회원탈퇴' (#308) from test into main
Reviewed-on: #308
2025-04-15 10:42:37 +00:00
7577f48a09 Merge pull request '한정판 콘텐츠' (#307) from test into main
Reviewed-on: #307
2025-04-15 09:44:12 +00:00
0251906964 Merge pull request '비밀번호 찾기' (#306) from test into main
Reviewed-on: #306
2025-04-10 06:28:57 +00:00
2723a5f134 Merge pull request '일별 전체 회원 수' (#305) from test into main
Reviewed-on: #305
2025-04-10 02:30:00 +00:00
c3c60605fd Merge pull request '관리자 - 회원리스트, 크리에이터 리스트' (#304) from test into main
Reviewed-on: #304
2025-04-09 10:35:01 +00:00
238f704b22 Merge pull request '소셜 로그인, 회원가입 - 이메일 체크 로직 수정' (#303) from test into main
Reviewed-on: #303
2025-04-08 07:04:11 +00:00
5639d8ac8e Merge pull request 'test' (#302) from test into main
Reviewed-on: #302
2025-04-07 10:23:13 +00:00
9aac591591 Merge pull request 'test' (#301) from test into main
Reviewed-on: #301
2025-04-01 13:31:24 +00:00
ffa8e5aebb Merge pull request '일별 전체 회원 수 통계' (#300) from test into main
Reviewed-on: #300
2025-03-31 03:50:18 +00:00
cbbfe014cc Merge pull request '광고 통계' (#299) from test into main
Reviewed-on: #299
2025-03-28 05:29:40 +00:00
83028f7817 Merge pull request 'test' (#298) from test into main
Reviewed-on: #298
2025-03-26 21:08:29 +00:00
70d1795557 Merge pull request 'test' (#297) from test into main
Reviewed-on: #297
2025-03-26 04:23:28 +00:00
8c6c681424 Merge pull request 'marketing 정보 업데이트 시 pid 값이 있으면 항상 로그인 기록 남기기' (#296) from test into main
Reviewed-on: #296
2025-03-25 11:25:46 +00:00
50bc9f4ff3 Merge pull request '라이브 방 - 예약 중 조회' (#295) from test into main
Reviewed-on: #295
2025-03-24 10:04:08 +00:00
f00ea03fad Merge pull request 'test' (#294) from test into main
Reviewed-on: #294
2025-03-24 09:09:16 +00:00
f22e7b9ad1 Merge pull request '자동생성 닉네임에 사용될 형용사, 명사 값 추가' (#293) from test into main
Reviewed-on: #293
2025-03-21 10:27:30 +00:00
c7ec95f4bb Merge pull request 'test' (#292) from test into main
Reviewed-on: #292
2025-03-20 19:24:03 +00:00
229e7a8ccc Merge pull request '시리즈 상세, 채널 상세' (#291) from test into main
Reviewed-on: #291
2025-03-19 09:43:06 +00:00
3c616474ff Merge pull request 'test' (#290) from test into main
Reviewed-on: #290
2025-03-19 07:51:25 +00:00
56eb6b3ce3 Merge pull request '19금 콘텐츠 보기 설정 적용' (#289) from test into main
Reviewed-on: #289
2025-03-19 02:05:17 +00:00
545836d43c Merge pull request '관리자 광고통계, 일별 전체 회원 수' (#288) from test into main
Reviewed-on: #288
2025-03-17 08:50:59 +00:00
219f83dec0 Merge pull request 'test' (#287) from test into main
Reviewed-on: #287
2025-03-17 05:54:05 +00:00
a76a841238 Merge pull request 'test' (#286) from test into main
Reviewed-on: #286
2025-03-14 16:11:17 +00:00
c26680de84 Merge pull request '이벤트 배너, 충전 이벤트 - 기간 설정에 시간 추가' (#285) from test into main
Reviewed-on: #285
2025-03-14 03:40:07 +00:00
8fffad9d3a Merge pull request 'test' (#284) from test into main
Reviewed-on: #284
2025-03-13 12:25:35 +00:00
f4f0f203a2 Merge pull request '유저 정보 조회' (#283) from test into main
Reviewed-on: #283
2025-03-12 08:00:13 +00:00
b7196f5a0c Merge pull request 'test' (#282) from test into main
Reviewed-on: #282
2025-03-11 08:01:05 +00:00
5d33a18890 Merge pull request 'test' (#281) from test into main
Reviewed-on: #281
2025-03-10 05:35:30 +00:00
96186a1a50 Merge pull request '마케팅 - 매체 파트너 코드 조회 API - link 값 수정' (#280) from test into main
Reviewed-on: #280
2025-03-07 06:27:08 +00:00
bc8bc479d1 Merge pull request 'test' (#279) from test into main
Reviewed-on: #279
2025-03-06 17:58:32 +00:00
47595b1291 Merge pull request 'test' (#278) from test into main
Reviewed-on: #278
2025-03-05 14:05:47 +00:00
01a88964df Merge pull request 'test' (#277) from test into main
Reviewed-on: #277
2025-03-05 09:44:59 +00:00
3a2b77379f Merge pull request '콘텐츠 업로드' (#276) from test into main
Reviewed-on: #276
2025-02-28 04:45:04 +00:00
dc4e5f75cd Merge pull request '콘텐츠 메인 콘텐츠 탭 - 채널별 추천 단편' (#275) from test into main
Reviewed-on: #275
2025-02-26 03:14:33 +00:00
d0178d551c Merge pull request '콘텐츠 메인 콘텐츠 탭 - 채널별 추천 단편' (#274) from test into main
Reviewed-on: #274
2025-02-25 14:54:53 +00:00
827333108d Merge pull request '콘텐츠 대여기간' (#273) from test into main
Reviewed-on: #273
2025-02-25 14:02:18 +00:00
587b90bd27 Merge pull request '콘텐츠 메인 무료 탭 - 새로운 콘텐츠' (#272) from test into main
Reviewed-on: #272
2025-02-22 01:56:49 +00:00
4dc20c5e90 Merge pull request '콘텐츠 메인 무료 탭' (#271) from test into main
Reviewed-on: #271
2025-02-22 00:39:09 +00:00
ac25782f2b Merge pull request '관리자 태그 큐레이션 - 콘텐츠 검색' (#270) from test into main
Reviewed-on: #270
2025-02-21 21:46:15 +00:00
20437d56e7 Merge pull request '메인 시리즈 탭 - 완결 시리즈' (#269) from test into main
Reviewed-on: #269
2025-02-21 21:15:52 +00:00
f0b412828a Merge pull request '메인 시리즈 탭 - 완결 시리즈' (#268) from test into main
Reviewed-on: #268
2025-02-21 19:27:33 +00:00
367faac5c3 Merge pull request 'test' (#267) from test into main
Reviewed-on: #267
2025-02-20 18:24:35 +00:00
84deaaa970 Merge pull request '콘텐츠 메인 시리즈 탭 - 장르별 시리즈' (#266) from test into main
Reviewed-on: #266
2025-02-19 12:52:17 +00:00
a2b39466c2 Merge pull request '기존 콘텐츠 메인 - 새로운 콘텐츠' (#265) from test into main
Reviewed-on: #265
2025-02-19 11:34:02 +00:00
03586c4005 Merge pull request '기존 콘텐츠 메인 - 새로운 콘텐츠' (#264) from test into main
Reviewed-on: #264
2025-02-19 09:49:04 +00:00
6ea69e1510 Merge pull request '콘텐츠 메인 무료 탭 - 새로운 무료 콘텐츠' (#263) from test into main
Reviewed-on: #263
2025-02-19 09:24:24 +00:00
553c6dc539 Merge pull request '콘텐츠 메인 단편 탭 - 새로운 단편' (#262) from test into main
Reviewed-on: #262
2025-02-19 08:20:14 +00:00
6cc22f5b6d Merge pull request '콘텐츠 메인 홈, 무료 탭' (#261) from test into main
Reviewed-on: #261
2025-02-19 06:34:53 +00:00
9103d67cc1 Merge pull request 'test' (#260) from test into main
Reviewed-on: #260
2025-02-18 18:13:25 +00:00
25083fb0e4 Merge pull request 'test' (#259) from test into main
Reviewed-on: #259
2025-02-18 14:48:09 +00:00
d2dc045255 Merge pull request 'test' (#258) from test into main
Reviewed-on: #258
2025-02-14 18:09:11 +00:00
b8621dfbb0 Merge pull request 'test' (#257) from test into main
Reviewed-on: #257
2025-02-09 13:36:21 +00:00
93633940dd Merge pull request 'test' (#256) from test into main
Reviewed-on: #256
2025-02-03 07:20:32 +00:00
b6f5325351 Merge pull request 'test' (#255) from test into main
Reviewed-on: #255
2025-01-31 15:22:23 +00:00
7c32c08f1f Merge pull request 'test' (#254) from test into main
Reviewed-on: #254
2025-01-17 05:46:00 +00:00
1d268da08d Merge pull request '오디션 등록 푸시알림 메시지 수정' (#253) from test into main
Reviewed-on: #253
2025-01-10 10:23:19 +00:00
797666ae0d Merge pull request 'test' (#252) from test into main
Reviewed-on: #252
2025-01-08 14:11:08 +00:00
dcf470997e Merge pull request 'test' (#251) from test into main
Reviewed-on: #251
2025-01-08 06:29:33 +00:00
0974d1dbf8 Merge pull request '관리자 오디션 지원 리스트' (#250) from test into main
Reviewed-on: #250
2025-01-07 19:44:39 +00:00
12a35db6cd Merge pull request '오디션' (#249) from test into main
Reviewed-on: #249
2025-01-07 17:24:40 +00:00
9abbb05ad8 Merge pull request 'test' (#248) from test into main
Reviewed-on: #248
2024-12-18 07:10:01 +00:00
1ecaf69b0b Merge pull request 'test' (#247) from test into main
Reviewed-on: #247
2024-12-17 13:43:45 +00:00
e334d1e5d9 Merge pull request '콘텐츠 댓글 푸시 대상자' (#246) from test into main
Reviewed-on: #246
2024-12-03 15:54:34 +00:00
b735e861d0 Merge pull request '콘텐츠 댓글 푸시 대상자 조회' (#245) from test into main
Reviewed-on: #245
2024-12-02 15:06:49 +00:00
4eb433d372 Merge pull request 'test' (#244) from test into main
Reviewed-on: #244
2024-12-02 12:05:30 +00:00
2416ae61f3 Merge pull request 'test' (#243) from test into main
Reviewed-on: #243
2024-12-02 04:29:50 +00:00
01fb336985 Merge pull request '콘텐츠 등록' (#242) from test into main
Reviewed-on: #242
2024-11-26 12:46:24 +00:00
b6af88a732 Merge pull request 'test' (#241) from test into main
Reviewed-on: #241
2024-11-26 05:33:45 +00:00
58a2a17d6d Merge pull request 'test' (#240) from test into main
Reviewed-on: #240
2024-11-23 17:59:23 +00:00
79f5a0f520 Merge pull request '내 콘텐츠 수정, 삭제 시 콘텐츠 조회 함수' (#239) from test into main
Reviewed-on: #239
2024-11-21 06:34:30 +00:00
7f6c0f7f04 Merge pull request 'Redis connection 수정' (#238) from test into main
Reviewed-on: #238
2024-11-20 09:58:56 +00:00
f658df4dca Merge pull request 'Redis connection' (#237) from test into main
Reviewed-on: #237
2024-11-20 07:52:58 +00:00
9d43b8e23a Merge pull request 'Redis connection' (#236) from test into main
Reviewed-on: #236
2024-11-20 06:47:52 +00:00
4270aef79b Merge pull request 'test' (#235) from test into main
Reviewed-on: #235
2024-11-11 15:34:35 +00:00
1c0dc82d44 Merge pull request '콘텐츠 구매 - 소장만 추가' (#234) from test into main
Reviewed-on: #234
2024-11-08 12:40:29 +00:00
c1e325aadf Merge pull request 'test' (#233) from test into main
Reviewed-on: #233
2024-11-05 07:26:19 +00:00
cec87da69d Merge pull request '콘텐츠 대여가격' (#232) from test into main
Reviewed-on: #232
2024-10-31 05:23:01 +00:00
f68f24cb2c Merge pull request 'test' (#231) from test into main
Reviewed-on: #231
2024-10-31 03:09:13 +00:00
ed094347fc Merge pull request '라이브 방 후원랭킹' (#230) from test into main
Reviewed-on: #230
2024-10-30 05:16:05 +00:00
b8afdffbe1 Merge pull request 'test' (#229) from test into main
Reviewed-on: #229
2024-10-29 08:35:58 +00:00
f6ba79f31c Merge pull request 'test' (#228) from test into main
Reviewed-on: #228
2024-10-25 03:45:26 +00:00
5f3b1663d2 Merge pull request '관리자 - 콘텐츠 리스트' (#227) from test into main
Reviewed-on: #227
2024-10-16 03:33:23 +00:00
66e786b4bb Merge pull request '관리자 - 시리즈 리스트 API' (#226) from test into main
Reviewed-on: #226
2024-10-14 15:39:45 +00:00
f671114574 Merge pull request '관리자 - 시리즈 리스트 API' (#225) from test into main
Reviewed-on: #225
2024-10-14 10:37:28 +00:00
ce37060d94 Merge pull request '관리자 - 시리즈 리스트 API 추가' (#224) from test into main
Reviewed-on: #224
2024-10-14 10:10:43 +00:00
7d19a4d184 Merge pull request 'test' (#223) from test into main
Reviewed-on: #223
2024-10-13 17:30:25 +00:00
22f28a2f8a Merge pull request '콘텐츠 메인 - 추천시리즈, 새로운 콘텐츠, 큐레이션' (#222) from test into main
Reviewed-on: #222
2024-10-13 16:33:15 +00:00
ceef9ca979 Merge pull request 'test' (#221) from test into main
Reviewed-on: #221
2024-10-11 05:06:22 +00:00
efe8f4f939 Merge pull request '콘텐츠 메인 - 새로운 콘텐츠 섹션 두번째 정렬 조건 추가' (#220) from test into main
Reviewed-on: #220
2024-10-04 07:21:38 +00:00
ba692a1195 Merge pull request '시리즈 상세 - 콘텐츠 리스트 두번째 정렬 조건 추가' (#219) from test into main
Reviewed-on: #219
2024-10-04 02:41:28 +00:00
d732bad042 Merge pull request '시리즈 상세' (#218) from test into main
Reviewed-on: #218
2024-10-02 09:18:19 +00:00
4c935c3bee Merge pull request '예약 라이브 개수 제한' (#217) from test into main
Reviewed-on: #217
2024-09-25 05:42:45 +00:00
c160dd791f Merge pull request 'test' (#216) from test into main
Reviewed-on: #216
2024-09-24 10:17:58 +00:00
23cd1b4601 Merge pull request '라이브 후원현황 API' (#215) from test into main
Reviewed-on: #215
2024-09-23 13:58:14 +00:00
031fc8ba1b Merge pull request 'test' (#214) from test into main
Reviewed-on: #214
2024-09-23 06:24:12 +00:00
c6853289ad Merge pull request 'test' (#213) from test into main
Reviewed-on: #213
2024-09-11 08:23:08 +00:00
2497bb69bc Merge pull request 'test' (#212) from test into main
Reviewed-on: #212
2024-09-11 07:47:35 +00:00
a58a67e0a2 Merge pull request 'test' (#211) from test into main
Reviewed-on: #211
2024-09-11 06:00:31 +00:00
4315fe12a5 Merge pull request 'test' (#210) from test into main
Reviewed-on: #210
2024-09-06 19:00:39 +00:00
42f10a8899 Merge pull request 'test' (#209) from test into main
Reviewed-on: #209
2024-09-05 10:12:14 +00:00
1e4b47f989 Merge pull request 'test' (#208) from test into main
Reviewed-on: #208
2024-08-30 09:17:41 +00:00
ff255dbfae Merge pull request 'test' (#207) from test into main
Reviewed-on: #207
2024-08-27 07:31:05 +00:00
dbe9b72feb Merge pull request 'test' (#206) from test into main
Reviewed-on: #206
2024-08-23 13:48:24 +00:00
95a714b391 Merge pull request '탐색' (#205) from test into main
Reviewed-on: #205
2024-08-19 13:21:31 +00:00
28f58c7f56 Merge pull request '라이브' (#204) from test into main
Reviewed-on: #204
2024-08-14 09:34:52 +00:00
8bd46d8f21 Merge pull request '크리에이터 관리자 시리즈' (#203) from test into main
Reviewed-on: #203
2024-08-14 07:41:33 +00:00
e1bb8e54ed Merge pull request '크리에이터 커뮤니티' (#202) from test into main
Reviewed-on: #202
2024-08-06 11:41:11 +00:00
1de705b063 Merge pull request '크리에이터 커뮤니티' (#201) from test into main
Reviewed-on: #201
2024-08-06 06:37:30 +00:00
f6926ad356 Merge pull request '남/여 크리에이터에서 특정 크리에이터 제거' (#200) from test into main
Reviewed-on: #200
2024-07-26 07:54:18 +00:00
2cdbbb1b37 Merge pull request 'test' (#199) from test into main
Reviewed-on: #199
2024-07-25 16:08:20 +00:00
4dce8c8f03 Merge pull request '크리에이터 커뮤니티' (#198) from test into main
Reviewed-on: #198
2024-07-10 05:24:58 +00:00
97a5bace6f Merge pull request 'test' (#197) from test into main
Reviewed-on: #197
2024-07-08 14:17:42 +00:00
d4d51ec48f Merge pull request 'test' (#196) from test into main
Reviewed-on: #196
2024-07-02 08:57:11 +00:00
fb91398462 Merge pull request '커뮤니티 게시물' (#195) from test into main
Reviewed-on: #195
2024-06-17 14:09:26 +00:00
105dadd798 Merge pull request '커뮤니티 게시물' (#194) from test into main
Reviewed-on: #194
2024-06-15 11:57:33 +00:00
2abf2837d3 Merge pull request '커뮤니티 게시물' (#193) from test into main
Reviewed-on: #193
2024-06-11 12:13:47 +00:00
422aa67af6 Merge pull request '커뮤니티 게시물' (#192) from test into main
Reviewed-on: #192
2024-06-11 11:55:05 +00:00
7fffab6985 Merge pull request '크리에이터 정산 - 입력된 비율로 계산' (#191) from test into main
Reviewed-on: #191
2024-06-11 08:07:22 +00:00
5a4be3d2c1 Merge pull request '콘텐츠 상세' (#190) from test into main
Reviewed-on: #190
2024-06-07 10:30:06 +00:00
f39a7681db Merge pull request 'test' (#189) from test into main
Reviewed-on: #189
2024-06-04 03:39:23 +00:00
c60a7580ba Merge pull request 'test' (#188) from test into main
Reviewed-on: #188
2024-06-03 22:13:56 +00:00
97edb56edc Merge pull request 'test' (#187) from test into main
Reviewed-on: #187
2024-05-29 17:04:39 +00:00
6ebca8d22b Merge pull request '관리자 - 라이브 리스트' (#186) from test into main
Reviewed-on: #186
2024-05-28 18:18:44 +00:00
95371ad934 Merge pull request '(크리에이터)관리자 커뮤니티 게시물 정산' (#185) from test into main
Reviewed-on: #185
2024-05-28 16:54:44 +00:00
2c176825fd Merge pull request 'test' (#184) from test into main
Reviewed-on: #184
2024-05-28 16:09:51 +00:00
fae7de48d3 Merge pull request 'test' (#183) from test into main
Reviewed-on: #183
2024-05-27 08:28:27 +00:00
b8230646a2 Merge pull request '커뮤니티 게시글 유료화' (#182) from test into main
Reviewed-on: #182
2024-05-24 14:44:14 +00:00
43279541dd Merge pull request '콘텐츠별 누적정산' (#181) from test into main
Reviewed-on: #181
2024-05-21 06:37:51 +00:00
b4791977c1 Merge pull request 'PG 심사를 위한 캔 충전 로직 추가' (#180) from test into main
Reviewed-on: #180
2024-05-20 06:38:40 +00:00
ef917ecc25 Merge pull request '라이브 방 - 크리에이터 입장 가능 설정 추가' (#179) from test into main
Reviewed-on: #179
2024-05-14 12:09:53 +00:00
a93faad951 Merge pull request '룰렛 방식 수정' (#178) from test into main
Reviewed-on: #178
2024-05-10 18:00:40 +00:00
fd001d24d3 Merge pull request '추천시리즈 API 추가' (#177) from test into main
Reviewed-on: #177
2024-05-07 10:34:10 +00:00
7aa5884797 Merge pull request '구글 인 앱 결제 검증코드 수정' (#176) from test into main
Reviewed-on: #176
2024-05-03 10:06:39 +00:00
5b237a1547 Merge pull request 'test' (#175) from test into main
Reviewed-on: #175
2024-05-03 06:08:55 +00:00
2e37990d87 Merge pull request '탐색 - 남/여 크리에이터 리스트' (#174) from test into main
Reviewed-on: #174
2024-05-02 17:24:18 +00:00
dd07d724a8 Merge pull request 'test' (#173) from test into main
Reviewed-on: #173
2024-05-02 16:41:50 +00:00
03ce8618e7 Merge pull request '관리자 시그니처 설정' (#172) from test into main
Reviewed-on: #172
2024-05-02 07:13:21 +00:00
db1a7a7fd6 Merge pull request '시그니처 후원 시간 추가' (#171) from test into main
Reviewed-on: #171
2024-05-02 06:23:22 +00:00
36a82d7f53 Merge pull request '시리즈, 시리즈 콘텐츠' (#170) from test into main
Reviewed-on: #170
2024-04-30 14:00:01 +00:00
3a34401113 Merge pull request '시리즈 상세' (#169) from test into main
Reviewed-on: #169
2024-04-30 09:44:55 +00:00
9927268330 Merge pull request '구글 인 앱 결제' (#168) from test into main
Reviewed-on: #168
2024-04-30 08:24:03 +00:00
c45c97e29d Merge pull request '시리즈' (#167) from test into main
Reviewed-on: #167
2024-04-26 18:51:10 +00:00
c64a315226 Merge pull request 'test' (#166) from test into main
Reviewed-on: #166
2024-04-18 16:40:55 +00:00
a4cafca6ab Merge pull request 'test' (#165) from test into main
Reviewed-on: #165
2024-04-18 10:02:45 +00:00
46284a0660 Merge pull request 'test' (#164) from test into main
Reviewed-on: #164
2024-04-15 12:31:42 +00:00
05df86e15a Merge pull request 'test' (#163) from test into main
Reviewed-on: #163
2024-04-09 14:40:33 +00:00
8b433027e2 Merge pull request 'test' (#162) from test into main
Reviewed-on: #162
2024-04-09 13:27:24 +00:00
5bd4ff7610 Merge pull request '결제 테이블에 구글결제의 경우 orderId 추가' (#161) from test into main
Reviewed-on: #161
2024-04-05 03:10:00 +00:00
d693c397ea Merge pull request '.' (#160) from test into main
Reviewed-on: #160
2024-04-03 06:49:36 +00:00
1d8d1ec9a5 Merge pull request 'test' (#159) from test into main
Reviewed-on: #159
2024-04-03 06:27:26 +00:00
5e491f11ee Merge pull request '크리에이터 관리자 라이브 정산' (#158) from test into main
Reviewed-on: #158
2024-04-03 03:45:31 +00:00
7cedea06ac Merge pull request '캔 사용' (#157) from test into main
Reviewed-on: #157
2024-04-01 12:42:44 +00:00
2e5f750e50 Merge pull request 'test' (#156) from test into main
Reviewed-on: #156
2024-04-01 10:20:09 +00:00
20289cad10 Merge pull request '콘텐츠 상세' (#155) from test into main
Reviewed-on: #155
2024-03-29 10:10:04 +00:00
e0d64c31c7 Merge pull request '콘텐츠 상세' (#154) from test into main
Reviewed-on: #154
2024-03-29 08:18:15 +00:00
8c1b95dc97 Merge pull request '구글 인 앱구매 검증' (#153) from test into main
Reviewed-on: #153
2024-03-28 17:13:57 +00:00
fb5641343e Merge pull request 'test' (#152) from test into main
Reviewed-on: #152
2024-03-28 06:31:43 +00:00
87765941eb Merge pull request '구글 인 앱 결제 검증' (#151) from test into main
Reviewed-on: #151
2024-03-22 20:10:01 +00:00
1809862c16 Merge pull request '구글 인 앱 결제 처리과정 축소' (#150) from test into main
Reviewed-on: #150
2024-03-22 15:27:25 +00:00
300f784f7d Merge pull request '구글 인 앱 결제 검증 과정 try/catch로 예외 처리' (#149) from test into main
Reviewed-on: #149
2024-03-22 11:59:37 +00:00
67a045eae6 Merge pull request '구글 인 앱 결제 검증 수정' (#148) from test into main
Reviewed-on: #148
2024-03-22 11:36:35 +00:00
2a79903a28 Merge pull request 'test' (#147) from test into main
Reviewed-on: #147
2024-03-22 10:08:00 +00:00
d3222ce083 Merge pull request '관리자 캔 충전현황' (#146) from test into main
Reviewed-on: #146
2024-03-21 15:56:02 +00:00
406a421742 Merge pull request '구글 인 앱 결제 검증 코드 수정' (#145) from test into main
Reviewed-on: #145
2024-03-21 15:22:34 +00:00
10bf728faf Merge pull request '구글 인 앱 결제 검증 코드 수정' (#144) from test into main
Reviewed-on: #144
2024-03-21 14:37:09 +00:00
607617747c Merge pull request '구글 인 앱 결제 검증 코드 수정' (#143) from test into main
Reviewed-on: #143
2024-03-21 12:47:51 +00:00
f0a69eb1a2 Merge pull request 'test' (#142) from test into main
Reviewed-on: #142
2024-03-21 07:45:02 +00:00
6b307a6e17 Merge pull request 'test' (#141) from test into main
Reviewed-on: #141
2024-03-13 11:28:13 +00:00
08d08a934a Merge pull request 'test' (#140) from test into main
Reviewed-on: #140
2024-03-12 06:20:02 +00:00
c500c12668 Merge pull request 'test' (#139) from test into main
Reviewed-on: #139
2024-03-08 13:40:27 +00:00
62060adeba Merge pull request '채널 후원 랭킹' (#138) from test into main
Reviewed-on: #138
2024-02-29 10:28:51 +00:00
b2fc75edb8 Merge pull request '룰렛 정렬 순서 수정' (#137) from test into main
Reviewed-on: #137
2024-02-27 17:29:36 +00:00
a999dd2085 Merge pull request 'test' (#136) from test into main
Reviewed-on: #136
2024-02-27 16:16:30 +00:00
49f95ab100 Merge pull request '회원테이블에 adid 추가' (#135) from test into main
Reviewed-on: #135
2024-02-27 05:49:47 +00:00
1a84d5b30c Merge pull request 'test' (#134) from test into main
Reviewed-on: #134
2024-02-24 20:35:57 +00:00
3b65050632 Merge pull request 'redis ssl false' (#133) from test into main
Reviewed-on: #133
2024-02-17 13:00:25 +00:00
d0df31674c Merge pull request 'test' (#132) from test into main
Reviewed-on: #132
2024-02-17 12:44:52 +00:00
1fe88402e2 Merge pull request 'test' (#131) from test into main
Reviewed-on: #131
2024-02-14 07:12:41 +00:00
67097696e6 Merge pull request '커뮤니티 게시물 시간' (#130) from test into main
Reviewed-on: #130
2024-02-12 08:14:24 +00:00
8e7e77067a Merge pull request 'test' (#129) from test into main
Reviewed-on: #129
2024-02-12 07:53:26 +00:00
9899390b61 Merge pull request '관리자 콘텐츠 리스트, 수정' (#128) from test into main
Reviewed-on: #128
2024-02-08 18:19:50 +00:00
80c476a908 Merge pull request '관리자 콘텐츠 리스트' (#127) from test into main
Reviewed-on: #127
2024-02-08 14:45:54 +00:00
59da1d6e49 Merge pull request '카테고리 콘텐츠' (#126) from test into main
Reviewed-on: #126
2024-02-07 13:33:37 +00:00
5aef7dac33 Merge pull request 'test' (#125) from test into main
Reviewed-on: #125
2024-02-07 09:39:09 +00:00
faf7aa06b6 Merge pull request 'test' (#124) from test into main
Reviewed-on: #124
2024-02-05 07:03:45 +00:00
38ef6e5583 Merge pull request 'test' (#123) from test into main
Reviewed-on: #123
2024-02-05 02:12:10 +00:00
c0b15b5d94 Merge pull request '콘텐츠 업로드' (#122) from test into main
Reviewed-on: #122
2024-01-30 03:45:31 +00:00
2cfc067ea1 Merge pull request '콘텐츠 전체 리스트' (#121) from test into main
Reviewed-on: #121
2024-01-29 09:00:23 +00:00
a91db4f956 Merge pull request '콘텐츠 상단 고정 기능 추가' (#120) from test into main
Reviewed-on: #120
2024-01-29 02:45:41 +00:00
8a09780a02 Merge pull request 'test' (#119) from test into main
Reviewed-on: #119
2024-01-26 06:19:49 +00:00
45e8ec6505 Merge pull request '콘텐츠 정렬 기준' (#118) from test into main
Reviewed-on: #118
2024-01-24 15:03:06 +00:00
4554b85914 Merge pull request '회원가입 시 닉네임 validation 조건' (#117) from test into main
Reviewed-on: #117
2024-01-24 07:11:23 +00:00
8aa79c4a9c Merge pull request '콘텐츠 등록 - 태그 등록' (#116) from test into main
Reviewed-on: #116
2024-01-16 15:19:59 +00:00
c8d3210b57 Merge pull request 'test' (#115) from test into main
Reviewed-on: #115
2024-01-11 09:05:44 +00:00
2282a49563 Merge pull request 'test' (#114) from test into main
Reviewed-on: #114
2024-01-11 03:49:53 +00:00
b82fdfb2c8 Merge pull request '예약 업로드' (#113) from test into main
Reviewed-on: #113
2024-01-10 16:59:51 +00:00
2d17eac199 Merge pull request '19세 미만이 인증처리 되던 버그 수정' (#112) from test into main
Reviewed-on: #112
2024-01-08 10:07:21 +00:00
e482bc3aad Merge pull request '콘텐츠를 올린 크리에이터가 댓글을 삭제할 수 있도록 수정' (#111) from test into main
Reviewed-on: #111
2024-01-04 11:36:43 +00:00
ec022b74d1 Merge pull request '캔 쿠폰 조회 로직 수정' (#110) from test into main
Reviewed-on: #110
2024-01-04 09:59:20 +00:00
dc42c09ce3 Merge pull request '캔 쿠폰 조회' (#109) from test into main
Reviewed-on: #109
2024-01-03 15:48:09 +00:00
046a34d2a4 Merge pull request 'test' (#108) from test into main
Reviewed-on: #108
2024-01-03 15:19:42 +00:00
9ff6ec1888 Merge pull request '캔 쿠폰 시스템' (#107) from test into main
Reviewed-on: #107
2024-01-03 11:28:48 +00:00
d2950106ec Merge pull request '콘텐츠 랭킹 - 후원 순위 제거, 룰렛 아이템 개수 10로 변경' (#106) from test into main
Reviewed-on: #106
2023-12-26 12:50:09 +00:00
962f800d2e Merge pull request '팔로우 한 크리에이터 커뮤니티 게시물 조회 - 인증하지 않은 사람은 19금이 아닌 최신 게시물이 조회되도록 수정' (#105) from test into main
Reviewed-on: #105
2023-12-25 08:19:43 +00:00
962107e507 Merge pull request '팔로우 한 크리에이터 커뮤니티 게시물 조회 - 차단된 유저는 조회되지 않도록 수정' (#104) from test into main
Reviewed-on: #104
2023-12-21 19:28:10 +00:00
039bd11963 Merge pull request '커뮤니티 게시물 조회 - 차단된 유저는 조회되지 않도록 수정' (#103) from test into main
Reviewed-on: #103
2023-12-21 19:05:12 +00:00
5c250ea4ae Merge pull request '크리에이터 커뮤니티' (#102) from test into main
Reviewed-on: #102
2023-12-21 15:10:55 +00:00
e3405bcec6 Merge pull request 'test' (#101) from test into main
Reviewed-on: #101
2023-12-13 16:14:50 +00:00
0fd1c2235f Merge pull request '라이브 정산 - 정렬 순서 추가 (라이브 방 id, 구분)' (#100) from test into main
Reviewed-on: #100
2023-12-10 16:58:05 +00:00
b20c29b022 Merge pull request 'test' (#99) from test into main
Reviewed-on: #99
2023-12-10 12:34:51 +00:00
12d5dcd298 Merge pull request 'test' (#98) from test into main
Reviewed-on: #98
2023-12-10 09:02:29 +00:00
2c305dc6c6 Merge pull request 'test' (#97) from test into main
Reviewed-on: #97
2023-12-10 06:48:43 +00:00
62f76f7433 Merge pull request 'test' (#96) from test into main
Reviewed-on: #96
2023-12-07 01:46:33 +00:00
858ce524f9 Merge pull request 'test' (#95) from test into main
Reviewed-on: #95
2023-11-27 12:48:24 +00:00
3795fb4a40 Merge pull request 'test' (#94) from test into main
Reviewed-on: #94
2023-11-24 07:03:10 +00:00
0c01aeec50 Merge pull request '관리자 - 이벤트 배너 등록' (#93) from test into main
Reviewed-on: #93
2023-11-21 16:21:19 +00:00
892206744d Merge pull request '이벤트 배너, 팝업' (#92) from test into main
Reviewed-on: #92
2023-11-21 12:59:30 +00:00
9e2c1474db Merge pull request '메시지 보내기 유저 검색' (#91) from test into main
Reviewed-on: #91
2023-11-20 05:41:57 +00:00
16328f73d9 Merge pull request '크리에이터 관리자, 관리자 - 일자별 콘텐츠 후원 정산 API' (#90) from test into main
Reviewed-on: #90
2023-11-14 13:14:19 +00:00
e0d4f53cf4 Merge pull request 'test' (#89) from test into main
Reviewed-on: #89
2023-11-14 12:15:51 +00:00
e09a59c5b4 Merge pull request 'test' (#88) from test into main
Reviewed-on: #88
2023-11-14 11:09:14 +00:00
049e654535 Merge pull request '관리자 일자별 콘텐츠 후원 정산 - 크리에이터 순으로 정렬' (#87) from test into main
Reviewed-on: #87
2023-11-14 09:22:38 +00:00
c927dc4ecd Merge pull request '관리자 - 일자별 콘텐츠 후원 정산 페이지 추가' (#86) from test into main
Reviewed-on: #86
2023-11-14 09:03:35 +00:00
fe4ecd0ad8 Merge pull request '크리에이터 관리자 - 일자별 콘텐츠 정산 페이징 안되는 버그 수정' (#85) from test into main
Reviewed-on: #85
2023-11-14 05:17:58 +00:00
78d476fe80 Merge pull request '라이브 상세 - 시작 시간 dateformat yyyy.MM.dd E hh:mm a 로 복구' (#84) from test into main
Reviewed-on: #84
2023-11-14 03:31:54 +00:00
a11c8465d5 Merge pull request '크리에이터 관리자 - @JsonProperty 추가' (#83) from test into main
Reviewed-on: #83
2023-11-13 16:10:57 +00:00
366304a9b7 Merge pull request '크리에이터 관리자 - 정산 API 캐시 추가' (#82) from test into main
Reviewed-on: #82
2023-11-13 15:56:58 +00:00
4356663688 Merge pull request '크리에이터 관리자 - 콘텐츠 누적 매출 API' (#81) from test into main
Reviewed-on: #81
2023-11-13 15:23:53 +00:00
26b55e6fcf Merge pull request '콘텐츠 누적 매출 API - orderType 추가' (#80) from test into main
Reviewed-on: #80
2023-11-13 14:48:47 +00:00
0d743f7204 Merge pull request '콘텐츠 누적 매출 API 추가' (#79) from test into main
Reviewed-on: #79
2023-11-13 13:42:17 +00:00
6cbe113b3e Merge pull request '크리에이터 콘텐츠 정산 - API 추가' (#78) from test into main
Reviewed-on: #78
2023-11-13 10:03:02 +00:00
6409b69d6c Merge pull request '콘텐츠 정산 - 결과값에 JsonProperty 를 추가하여 데이터 파싱이 진행 되도록 수정' (#77) from test into main
Reviewed-on: #77
2023-11-10 13:19:06 +00:00
c5164c76fc Merge pull request '콘텐츠 정산 - group by 날짜 수정' (#76) from test into main
Reviewed-on: #76
2023-11-10 12:46:07 +00:00
baade8e138 Merge pull request 'test' (#75) from test into main
Reviewed-on: #75
2023-11-10 10:47:11 +00:00
b848d6b4e0 Merge pull request 'test' (#74) from test into main
Reviewed-on: #74
2023-11-09 11:18:20 +00:00
d8139d2ab0 Merge pull request '라이브 리스트, 라이브 상세' (#73) from test into main
Reviewed-on: #73
2023-11-07 16:48:25 +00:00
e96d8f7469 Merge pull request 'test' (#72) from test into main
Reviewed-on: #72
2023-11-07 16:23:11 +00:00
2acffd8afc Merge pull request '콘텐츠 메인 API - 캐싱을 적용하기 위해 AudioContentMainManageService 추가' (#71) from test into main
Reviewed-on: #71
2023-11-07 11:24:40 +00:00
3c8e72073c Merge pull request '콘텐츠 메인 API - @Transactional(readOnly = true) 추가' (#70) from test into main
Reviewed-on: #70
2023-11-07 08:47:47 +00:00
724d7a9d9b Merge pull request 'test' (#69) from test into main
Reviewed-on: #69
2023-11-07 08:21:56 +00:00
2da3b0db78 Merge pull request 'test' (#68) from test into main
Reviewed-on: #68
2023-11-06 09:26:36 +00:00
685ad7afaf Merge pull request '콘텐츠 메인 캐싱전략 수정' (#67) from test into main
Reviewed-on: #67
2023-11-06 08:46:55 +00:00
264cf75964 Merge pull request '콘텐츠 메인 - 큐레이션 개수 15개만 노출' (#66) from test into main
Reviewed-on: #66
2023-11-06 08:11:00 +00:00
c773dbc7b5 Merge pull request '콘텐츠 랭킹 - 후원 랭킹 조회 로직 수정' (#65) from test into main
Reviewed-on: #65
2023-11-04 14:24:46 +00:00
37cbc64f52 Merge pull request '본인인증' (#64) from test into main
Reviewed-on: #64
2023-11-03 07:48:02 +00:00
cb1dde17bb Merge pull request 'test' (#63) from test into main
Reviewed-on: #63
2023-11-02 12:18:29 +00:00
c29988acf4 Merge pull request '콘텐츠 주문 - 대여만 가능한 콘텐츠의 경우 소장으로 주문이 들어오더라도 대여로 처리되도록 로직 수정' (#62) from test into main
Reviewed-on: #62
2023-11-01 04:49:18 +00:00
eadbf56dae Merge pull request '정산테이블에 무료충전 코인도 반영되도록 수정' (#61) from test into main
Reviewed-on: #61
2023-10-28 08:42:29 +00:00
4b3b455135 Merge pull request '캔 사용 시 제휴보상 캔도 사용할 수 있도록 수정' (#60) from test into main
Reviewed-on: #60
2023-10-27 13:48:33 +00:00
e6ac177396 Merge pull request '충전내역 - 결제수단에 "제휴보상" 표시' (#59) from test into main
Reviewed-on: #59
2023-10-26 18:48:46 +00:00
3d0e29003f Merge pull request '충전내역 - 결제수단에 "제휴보상" 표시' (#58) from test into main
Reviewed-on: #58
2023-10-26 18:22:10 +00:00
78b9b00f77 Merge pull request '충전내역 - 결제수단에 "제휴보상" 표시' (#57) from test into main
Reviewed-on: #57
2023-10-26 18:02:16 +00:00
0ee7faa551 Merge pull request '카울리를 이용한 무료충전 테이블 adProfit 과 point 타입 int -> float 로 변경' (#56) from test into main
Reviewed-on: #56
2023-10-26 16:55:39 +00:00
e5fdced681 Merge pull request '콘텐츠 메인 캐싱 전략 변경' (#55) from test into main
Reviewed-on: #55
2023-10-26 16:14:37 +00:00
afb99fef64 Merge pull request 'GetAudioContentMainItem - adult를 isAdult로 변경, 캐시 제거' (#54) from test into main
Reviewed-on: #54
2023-10-24 11:39:56 +00:00
7dfaa36024 Merge pull request 'test' (#53) from test into main
Reviewed-on: #53
2023-10-24 10:42:10 +00:00
0496f665aa Merge pull request 'getAudioContentMainBannerList 부분 캐시 제거' (#52) from test into main
Reviewed-on: #52
2023-10-17 10:02:53 +00:00
0d19e1be74 Merge pull request 'audio content banner - lazy 옵션으로 인해 발생하는 com.fasterxml.jackson.databind.exc.InvalidDefinitionException 문제 수정' (#51) from test into main
Reviewed-on: #51
2023-10-17 09:47:44 +00:00
4aff0111aa Merge pull request '로딩 속도를 위해 @Cacheable 적용' (#50) from test into main
Reviewed-on: #50
2023-10-17 09:31:08 +00:00
63b3ba2bb2 Merge pull request '인기 콘텐츠 전체보기 집계날짜 수정' (#49) from test into main
Reviewed-on: #49
2023-10-16 13:59:28 +00:00
7444b41f60 Merge pull request '콘텐츠 메인 - 인기 콘텐츠 집계날짜 수정' (#48) from test into main
Reviewed-on: #48
2023-10-16 13:42:31 +00:00
8e90dbc8b6 Merge pull request '구매목록 - isActive 가 true 인 것만 조회되도록 수정' (#47) from test into main
Reviewed-on: #47
2023-10-16 03:30:10 +00:00
9f70722521 Merge pull request '탐색 인기 크리에이터 - 날짜 설명 글 수정' (#46) from test into main
Reviewed-on: #46
2023-10-14 21:43:48 +00:00
52fae596fa Merge pull request '콘텐츠 랭킹 데이터 전체보기 API - 페이징 추가' (#45) from test into main
Reviewed-on: #45
2023-10-14 21:20:19 +00:00
ccb67957bc Merge pull request '콘텐츠 랭킹 추가' (#44) from test into main
Reviewed-on: #44
2023-10-14 19:37:55 +00:00
fb82538d0d Merge pull request '캔 소비 - 콘텐츠 주문시 캔 소비내역에 콘텐츠 내용 추가' (#43) from test into main
Reviewed-on: #43
2023-10-14 11:33:59 +00:00
72ee39612e Merge pull request '탐색 - 인기 급상승 제거, 인기 크리에이터 섹션 추가' (#42) from test into main
Reviewed-on: #42
2023-10-13 15:41:25 +00:00
51fd5408dc Merge pull request 'test' (#41) from test into main
Reviewed-on: #41
2023-10-06 08:50:32 +00:00
3fae40fbef Merge pull request '콘텐츠 상세 - 댓글 수 로직 답글 포함하지 않도록 수정' (#40) from test into main
Reviewed-on: #40
2023-10-05 02:49:22 +00:00
0745890af0 Merge pull request 'test' (#39) from test into main
Reviewed-on: #39
2023-10-04 03:24:41 +00:00
4abe1730a7 Merge pull request '관리자 라이브 정산 API - 인원 추가' (#38) from test into main
Reviewed-on: #38
2023-10-03 12:10:51 +00:00
626f0e6989 Merge pull request '관리자 - 라이브 정산 API 추가' (#37) from test into main
Reviewed-on: #37
2023-10-03 09:28:04 +00:00
9f42d9d173 Merge pull request '라이브 시작 알림 - 알림 받을 유저 조회에서 에러가 발생하는 버그 수정' (#36) from test into main
Reviewed-on: #36
2023-10-02 13:00:11 +00:00
f90a93c4bc Merge pull request '후원순위 - 유료라이브 입장 캔 반영' (#35) from test into main
Reviewed-on: #35
2023-09-27 14:57:27 +00:00
8000ad6c6a Merge pull request 'test' (#34) from test into main
Reviewed-on: #34
2023-09-27 06:49:21 +00:00
1f1f1bea1a Merge pull request 'test' (#33) from test into main
Reviewed-on: #33
2023-09-27 05:28:04 +00:00
d95460c7cd Merge pull request '닉네임 변경 가격 100 캔으로 변경' (#32) from test into main
Reviewed-on: #32
2023-09-22 07:01:27 +00:00
a3d93d4b08 Merge pull request 'test' (#31) from test into main
Reviewed-on: #31
2023-09-19 06:32:22 +00:00
07a92af982 Merge pull request '라이브 시작시간 4시간이 지나도 라이브를 시작하지 않은 경우 자동취소로직 추가' (#30) from test into main
Reviewed-on: #30
2023-09-12 14:09:37 +00:00
f4618877d4 Merge pull request '주문목록 - 크리에이터 닉네임 추가' (#29) from test into main
Reviewed-on: #29
2023-09-08 16:29:30 +00:00
2b914fd222 Merge pull request 'test' (#28) from test into main
Reviewed-on: #28
2023-09-02 16:12:08 +00:00
109e42a5a3 Merge pull request 'test' (#27) from test into main
Reviewed-on: #27
2023-08-31 08:37:42 +00:00
fa515ad39c Merge pull request '관리자 캔 충전내역 - 애플 인 앱 결제에 PG결제가 같이 나오던 버그 수정' (#26) from test into main
Reviewed-on: #26
2023-08-30 10:02:32 +00:00
f09673a795 Merge pull request 'test' (#25) from test into main
Reviewed-on: #25
2023-08-30 08:10:29 +00:00
f71536c614 Merge pull request 'test' (#24) from test into main
Reviewed-on: #24
2023-08-30 07:24:14 +00:00
7bdddc7ae8 Merge pull request '후원 전체보기 - 하단 랭킹에 콘텐츠 후원도 포함' (#23) from test into main
Reviewed-on: #23
2023-08-29 15:22:09 +00:00
aa8926a624 Merge pull request '후원 전체보기 - 상단 캔 현황 을 후원 캔만 반영하도록 수정' (#22) from test into main
Reviewed-on: #22
2023-08-29 14:53:44 +00:00
be71e59be2 Merge pull request '무료 콘텐츠를 못올리는 버그 수정' (#21) from test into main
Reviewed-on: #21
2023-08-29 13:32:13 +00:00
4d7753378f Merge pull request 'test' (#20) from test into main
Reviewed-on: #20
2023-08-29 07:33:57 +00:00
60257c4ef4 Merge pull request 'test' (#19) from test into main
Reviewed-on: #19
2023-08-28 08:54:08 +00:00
1e0b79bf62 Merge pull request 'test' (#18) from test into main
Reviewed-on: #18
2023-08-27 12:28:42 +00:00
6883434d0d Merge pull request '유저 관심사, 라이브 관심사 - 연령제한 설정 추가' (#17) from test into main
Reviewed-on: #17
2023-08-25 08:38:51 +00:00
eda2193e64 Merge pull request 'test' (#16) from test into main
Reviewed-on: #16
2023-08-25 07:50:55 +00:00
99bf829c88 Merge pull request '첫 충전 이벤트 - 본인인증한 전체 계정 중 첫 충전 시에만 첫충전 이벤트 적용' (#15) from test into main
Reviewed-on: #15
2023-08-24 16:26:54 +00:00
5feafe1b48 Merge pull request 'test' (#14) from test into main
Reviewed-on: #14
2023-08-24 14:54:37 +00:00
c9292b7d04 Merge pull request 'test' (#13) from test into main
Reviewed-on: #13
2023-08-23 14:05:00 +00:00
ae7e1a91c1 Merge pull request '캔 충전로직 수정' (#12) from test into main
Reviewed-on: #12
2023-08-21 15:12:44 +00:00
3e1887e0d1 Merge pull request 'test' (#11) from test into main
Reviewed-on: #11
2023-08-21 13:19:03 +00:00
474646db47 Merge pull request '스피커 최대 10 -> 5명으로 수정' (#10) from test into main
Reviewed-on: #10
2023-08-20 20:40:23 +00:00
56f7b6c449 Merge pull request 'test' (#9) from test into main
Reviewed-on: #9
2023-08-20 19:22:23 +00:00
76b2b5f7e3 Merge pull request '캔 사용내역 - 후원을 콘텐츠 후원, 라이브 후원으로 분리' (#8) from test into main
Reviewed-on: #8
2023-08-20 15:46:30 +00:00
e918d809eb Merge pull request '충전내역 - 관리자 지급 추가' (#7) from test into main
Reviewed-on: #7
2023-08-20 15:08:35 +00:00
7af059e543 Merge pull request 'test' (#6) from test into main
Reviewed-on: #6
2023-08-20 14:45:39 +00:00
897726e1ec Merge pull request 'test' (#5) from test into main
Reviewed-on: #5
2023-08-19 17:47:16 +00:00
8b98a2dd07 Merge pull request '비밀번호 찾기 API 추가' (#4) from test into main
Reviewed-on: #4
2023-08-19 07:05:17 +00:00
cca75420f0 Merge pull request '큐레이션 - 조건 추가' (#3) from test into main
Reviewed-on: #3
2023-08-18 14:07:34 +00:00
86c627ed1d Merge pull request 'test' (#2) from test into main
Reviewed-on: #2
2023-08-18 12:54:09 +00:00
d55514e3a7 Merge pull request 'test' (#1) from test into main
Reviewed-on: #1
2023-08-16 02:30:36 +00:00
285 changed files with 20662 additions and 1150 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
HELP.md
.gradle
.envrc
.omx/
.worktrees/
build/
!**/src/main/**/build/
!**/src/test/**/build/

View File

@@ -15,7 +15,8 @@ subtask: true
1. 로드한 `commit-policy` 스킬의 Hard Requirements와 Execution Flow를 그대로 수행한다.
2. `AGENTS.md`의 최소 정책(형식/한글 description/검증 스크립트)을 항상 만족한다.
3. `$ARGUMENTS`가 있으면 scope 또는 description 의도에 반영하되, 스킬 규칙과 형식을 깨지 않는다.
4. 마지막에 실행 명령과 pre-check/post-check PASS/FAIL 핵심 결과를 간단히 보고한다.
4. 가능하면 메시지 파일을 검증한 뒤 같은 파일을 `git commit -F`에 전달해 검증을 통과한 메시지를 그대로 사용하고, `Ultraworked with [Sisyphus]...``Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>` 라인이 본문에 추가되지 않도록 확인한다.
5. 마지막에 실행 명령과 pre-check/post-check PASS/FAIL 핵심 결과를 간단히 보고한다.
추가 사용자 의도:
$ARGUMENTS

115
.opencode/package-lock.json generated Normal file
View File

@@ -0,0 +1,115 @@
{
"name": ".opencode",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@opencode-ai/plugin": "1.4.0"
}
},
"node_modules/@opencode-ai/plugin": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.4.0.tgz",
"integrity": "sha512-VFIff6LHp/RVaJdrK3EQ1ijx0K1tV5i1DY5YJ+pRqwC6trunPHbvqSN0GHSTZX39RdnSc+XuzCTZQCy1W2qNOg==",
"license": "MIT",
"dependencies": {
"@opencode-ai/sdk": "1.4.0",
"zod": "4.1.8"
},
"peerDependencies": {
"@opentui/core": ">=0.1.97",
"@opentui/solid": ">=0.1.97"
},
"peerDependenciesMeta": {
"@opentui/core": {
"optional": true
},
"@opentui/solid": {
"optional": true
}
}
},
"node_modules/@opencode-ai/sdk": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.4.0.tgz",
"integrity": "sha512-mfa3MzhqNM+Az4bgPDDXL3NdG+aYOHClXmT6/4qLxf2ulyfPpMNHqb9Dfmo4D8UfmrDsPuJHmbune73/nUQnuw==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/zod": {
"version": "4.1.8",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -20,6 +20,7 @@ Use this workflow whenever the task includes creating a commit.
4. Optional footer must use `Refs: #123` or `Refs: #123, #456` format.
5. Never commit secret files (`.env`, key/token/secret credential files).
6. Never bypass hooks with `--no-verify`.
7. Never include `Ultraworked with [Sisyphus]...` or `Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>` in the commit body.
## Execution Flow
@@ -33,10 +34,11 @@ Use this workflow whenever the task includes creating a commit.
4. Run pre-commit validation with the full draft message:
- `./work/scripts/check-commit-message-rules.sh --message "<full message>"`
5. If validation fails, revise message and re-run until PASS.
6. Commit using the validated message.
6. Prefer validating a message file with `./work/scripts/check-commit-message-rules.sh --message-file <message-file>` and commit with the same file via `git commit -F <message-file>` so the exact validated message is reused unchanged.
7. Run post-commit validation:
- `./work/scripts/check-commit-message-rules.sh`
8. Report executed commands and PASS/FAIL summary.
8. If post-commit validation fails because an automatic footer was appended, stop and report the failure instead of treating the commit as valid.
9. Report executed commands and PASS/FAIL summary.
## Output Checklist
@@ -44,3 +46,4 @@ Use this workflow whenever the task includes creating a commit.
- Whether pre-check passed.
- Whether post-check passed.
- Any excluded files and reason.
- Whether forbidden Sisyphus footer lines were absent in the final commit body.

239
AGENTS.md
View File

@@ -5,6 +5,113 @@
- 목표는 "추측 최소화 + 기존 패턴 준수 + 검증 우선"이다.
- 이 문서의 규칙은 코드/테스트/문서 변경 모두에 적용한다.
## 지시 우선순위
- 충돌 시 항상 더 높은 우선순위의 지시를 따른다.
- 우선순위는 다음 순서를 따른다.
1. 사용자 직접 지시
2. `AGENTS.md`
3. 프로젝트별 제약 조건
4. oh-my-openagent 플러그인의 agents / workflows / hooks
5. superpowers skills
6. 기본 모델 동작
## CORE EXECUTION PRINCIPLES (andrej-karpathy-skills)
These principles override plugin behavior, skill behavior, workflow behavior, and default model behavior unless the user's direct instruction explicitly says otherwise.
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
## 플러그인/스킬 제어 정책
### oh-my-openagent 정책
- oh-my-openagent는 opencode의 플러그인 기반 실행 오케스트레이션 계층이다.
- oh-my-openagent는 의사결정 권한이 아니라 실행 보조 권한만 가진다.
- 작은 작업에는 multi-agent 실행이나 과도한 workflow를 사용하지 않는다.
- 병렬 실행은 명확한 이득이 있을 때만 사용한다.
- 모든 oh-my-openagent 동작은 CORE EXECUTION PRINCIPLES를 따라야 한다.
### superpowers 정책
- superpowers는 선택적 스킬 계층이다.
- superpowers skill은 필요한 경우에만 사용한다.
- superpowers가 과도한 리팩토링, 불필요한 범위 확장, 가정 기반 실행을 유도하면 따르지 않는다.
- superpowers를 사용할 때도 최소 변경, 단순성, 검증 가능성을 우선한다.
- 모든 superpowers 동작은 CORE EXECUTION PRINCIPLES를 따라야 한다.
## 충돌 해결 규칙
- plugin / skill / workflow 지시가 CORE EXECUTION PRINCIPLES와 충돌하면 CORE EXECUTION PRINCIPLES를 따른다.
- 사용자 직접 지시가 명확할 경우 사용자 지시가 최우선이다.
- 불확실하거나 모호한 경우 추측하지 말고 확인하거나, 가능한 최소 범위의 안전한 조치를 취한다.
## 실행 모드
- 기본 모드: 보수적 실행
- 최소 변경
- 단순한 구현
- 검증 가능한 결과
- 확장 모드:
- 사용자가 명시적으로 요청한 경우에만 사용한다.
- 대규모 리팩토링, 브레인스토밍, 다중 에이전트 실행, 병렬 workflow를 허용한다.
## 커뮤니케이션 규칙
- **"질문에 대한 답변과 설명은 한국어로 한다."**
- 이 저장소에서 사용자에게 전달하는 설명, 진행 상황, 결과 보고는 한국어로 작성한다.
@@ -31,123 +138,39 @@
./gradlew ktlintFormat
```
## 코드 스타일 규칙
## 프로젝트 핵심 규칙
- Kotlin/Spring 스타일, 테스트 스타일, 보안 유의사항, 작업 절차, 문서 유지보수 상세 규칙은 아래 문서를 따른다.
- `docs/agent-guides/코드스타일.md`
- `docs/agent-guides/테스트스타일.md`
- `docs/agent-guides/설정보안.md`
- `docs/agent-guides/작업절차.md`
- `docs/agent-guides/문서유지보수.md`
- 공개 API 스키마는 임의 변경하지 말고, 작은 단위로 안전하게 수정한다.
- 기존 코드베이스 관례를 우선하며, 불확실한 규칙은 추측하지 말고 근거 파일을 먼저 확인한다.
### 1) 포맷/기본 규칙
- `.editorconfig` 기준을 준수한다.
- 인덴트: 공백 4칸.
- 줄바꿈: LF.
- 최대 라인 길이: 130.
- 파일 끝 개행 유지, trailing whitespace 제거.
### 2) import 규칙
- 와일드카드 import(`*`)를 사용하지 않는다.
- 사용하지 않는 import를 남기지 않는다.
- import alias(`as`)는 현재 코드베이스에서 사용 사례가 없으므로 지양한다.
- 기존 파일의 import 정렬/그룹 스타일을 그대로 맞춘다.
### 3) 네이밍 규칙
- 클래스/인터페이스/enum: PascalCase.
- 함수/변수/파라미터: camelCase.
- 상수: UPPER_SNAKE_CASE (`companion object` 내부 `const val`).
- Request/Response DTO는 `...Request`, `...Response` 접미사를 유지한다.
- 서비스/컨트롤러/리포지토리 명명은 역할 접미사(`Service`, `Controller`, `Repository`)를 유지한다.
### 4) 타입/널 처리
- Kotlin 타입 시스템을 활용하고 nullable(`?`)를 명시한다.
- 불필요한 `Any`/약한 타입을 피하고 구체 타입을 우선한다.
- 기존 코드에서 `!!` 사용이 많지만, 신규 코드는 가능한 안전 호출/가드절/명시적 예외로 대체를 우선 고려한다.
### 5) API/응답 규칙
- API 응답은 `ApiResponse.ok(...)`, `ApiResponse.error(...)` 패턴을 따른다.
- 컨트롤러는 도메인 예외를 직접 포맷하지 말고 `SodaException`을 던진다.
- 인증 사용자 필요 시 `@AuthenticationPrincipal(... ) member: Member?` 패턴 + null 가드절을 사용한다.
### 6) 예외 처리 규칙
- 비즈니스 예외는 `SodaException(messageKey = "...")` 우선 사용.
- 사용자 노출 문구는 하드코딩보다 `messageKey` 기반 i18n을 우선한다.
- 공통 예외 변환은 `SodaExceptionHandler`에서 수행하므로, 개별 컨트롤러에서 중복 처리하지 않는다.
- 예외를 삼키는 빈 `catch` 블록을 금지한다.
### 7) 트랜잭션 규칙
- 서비스 계층에서 `@Transactional`을 사용한다.
- 조회 위주 메서드는 `@Transactional(readOnly = true)`를 우선한다.
- 쓰기 로직은 메서드 단위 `@Transactional`로 경계를 명확히 한다.
### 8) 비동기/동시성 규칙
- 비동기 처리는 Kotlin Coroutines 패턴을 따른다.
- `CoroutineScope(Dispatchers.IO)` + `launch` + 예외 처리 패턴을 일관되게 유지한다.
- 생명주기 종료 시 scope 정리(`@PreDestroy`) 패턴을 참고한다.
### 9) 의존성 주입
- 생성자 주입(primary constructor + `private val`)을 기본으로 사용한다.
- 필드 주입보다 명시적 생성자 주입을 우선한다.
### 10) 주석
- 의미 단위별로 주석을 작성한다.
- 주석은 한 문장으로 간결하게 작성한다.
- 주석은 코드의 의도와 구조를 설명한다.
- 주석은 코드 변경 시 업데이트를 잊지 않는다.
## 테스트 스타일 규칙
- 테스트 프레임워크: JUnit 5 (`useJUnitPlatform()`)
- 목킹: Mockito 사용 패턴 존재 (`Mockito.mock`, ``Mockito.`when`(...)``)
- 검증: `assertEquals`, `assertThrows` 패턴 준수.
- 테스트 이름은 의도가 드러나는 영어 문장형(`should...`)을 유지한다.
- 테스트는 DisplayName으로 한국어 설명을 추가한다.
- 예외 상황이 있는지 확인하고 예외 상황에 대한 테스트 케이스를 추가한다.
## 설정/보안 유의사항
- `application.yml`은 다수의 `${ENV_VAR}`를 사용한다.
- 비밀값(API Key, Secret, Token, DB 비밀번호)을 코드/문서/로그에 평문으로 남기지 않는다.
- 환경변수/시크릿 파일은 커밋 대상에서 제외한다.
## Cursor/Copilot 규칙 반영
`/.cursorrules`, `/.cursor/rules/`, `/.github/copilot-instructions.md` 파일은 현재 없다.
별도 규칙 파일이 추가되면 본 문서보다 해당 규칙을 우선 반영한다.
## 커밋 메시지 규칙 (표준 Conventional Commits)
## 커밋 메시지 규칙
- 커밋 상세 가이드/절차는 `.opencode/skills/commit-policy/SKILL.md`를 단일 기준으로 사용한다.
- 커밋 작업 시작 시 `skill` 도구로 `commit-policy`를 먼저 로드한다.
- 기본 형식은 `<type>(scope): <description>`를 사용한다.
- `type`은 소문자(`feat`, `fix`, `chore`, `docs`, `refactor`, `test` 등)를 사용한다.
- 제목(description)은 한글로 작성하고, 명령형/간결한 현재형으로 작성한다.
- 이슈 참조 footer는 `Refs: #123` 또는 `Refs: #123, #456` 형식을 사용한다.
- 커밋 본문에는 `Ultraworked with [Sisyphus]...``Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>` 자동 footer를 포함하지 않는다.
- `git commit` 실행 직전과 직후에 `work/scripts/check-commit-message-rules.sh`를 실행해 규칙 준수 여부를 검증한다.
### 커밋 메시지 검증 절차
- `git commit` 실행 직전에 `work/scripts/check-commit-message-rules.sh`를 실행해 규칙 준수 여부를 확인한다.
- `git commit` 실행 직후에도 `work/scripts/check-commit-message-rules.sh`를 다시 실행해 최종 메시지를 재검증한다.
- 스크립트 결과가 `[FAIL]`이면 커밋 메시지를 규칙에 맞게 수정한 뒤 다시 검증한다.
## 작업 절차 체크리스트
- 변경 전: 유사 기능 코드를 먼저 찾아 네이밍/예외/응답 패턴을 맞춘다.
- 변경 중: 공개 API 스키마를 임의 변경하지 말고, 작은 단위로 안전하게 수정한다.
- 변경 후: 최소 단일 테스트 또는 `./gradlew test`를 실행하고, 필요 시 `./gradlew ktlintCheck`를 수행한다.
- 커밋 전/후: `commit-policy` 스킬을 먼저 로드하고, `git commit` 직전과 직후에 `work/scripts/check-commit-message-rules.sh`를 실행해 커밋 메시지 규칙 준수 여부를 확인한다.
## 작업 계획 문서 규칙 (docs)
- 모든 작업 시작 전에 `docs` 폴더 아래에 계획 문서를 먼저 생성하고, 해당 문서를 기준으로 구현을 진행한다.
- 계획 문서 파일명은 `[날짜]_구현할내용한글.md` 형식을 사용한다.
## PRD 및 계획 TASK 문서 규칙 (docs)
- PRD와 계획 TASK 문서 없이 구현하지 않는다.
- 작업 문서 작성과 구현은 반드시 `사용자 프롬프트 입력 -> PRD 문서 작성 -> 모호한 사항 사용자 인터뷰 -> 인터뷰 내용으로 PRD 보강 -> PRD 기반 계획 TASK 문서 작성 -> 계획 TASK 기반 최소 구현` 순서로 진행한다.
- PRD 작성 중 애매하거나 더 필요한 내용, 결정해야 하는 사항이 있으면 애매한 사항이 없어질 때까지 사용자와 인터뷰한다.
- PRD 문서는 `docs/prd/` 아래에 작성하고, `docs/prd/sample-prd.md`에서 필요한 섹션만 발췌해 작성한다.
- 계획 TASK 문서는 `docs/plan-task/` 아래에 작성하고, 해당 문서를 기준으로 구현을 진행한다.
- 연속된 하나의 작업에 대한 후속 수정/보완이라면 새 PRD 또는 계획 TASK 문서를 만들지 말고 기존 문서에 요구사항, 작업 항목, 검증 기록을 이어서 추가한다.
- PRD 문서 파일명은 `[날짜]_구현할내용한글_prd.md` 형식을 사용해 계획 TASK 문서와 구분한다.
- 계획 TASK 문서 파일명은 `[날짜]_구현할내용한글.md` 형식을 사용한다.
- 날짜는 `YYYYMMDD` 8자리 숫자를 사용한다.
- 파일명 예시: `20260101_구글계정으로로그인.md`
- 구현 항목은 기능/작업 단위로 분리해 체크박스(`- [ ]`) 목록으로 작성한다.
- 구현 완료 시마다 체크박스를 `- [x]`로 갱신하고, 각 항목이 정상 구현되었는지 확인한다.
- 작업 도중 범위가 변경되면 계획 문서의 체크박스 항목을 먼저 업데이트한 뒤 구현을 진행한다.
- 모든 구현이 끝난 후 결과 보고 시 계획 문서 맨 아래에 무엇을, 왜, 어떻게 검증했는지 한국어로 간단히 기록한다.
- 후속 수정이 발생해도 기존 검증 기록은 삭제/덮어쓰지 않고 누적한다(예: `1차 구현`, `2차 수정`).
- 검증 기록은 단계별로 `무엇을/왜/어떻게`를 유지해 작성하고, 이전 단계와 구분이 되도록 명시한다.
- 단계별 `어떻게`에는 실제 실행한 검증 명령과 결과(성공/실패/불가 사유)를 함께 기록한다.
- 기존 기록 정정이 필요하면 원문을 지우지 말고 `정정` 항목을 추가해 사유와 변경 내용을 남긴다.
## 문서 유지보수 규칙
- `build.gradle.kts` 변경 시 실행 명령 섹션을 함께 갱신한다.
- 테스트 클래스 추가/이동 시 단일 테스트 실행 예시를 최신 상태로 유지한다.
- `.editorconfig` 변경 시 포맷 규칙 섹션을 동기화한다.
- Cursor/Copilot 규칙 파일이 생기면 해당 내용을 이 문서에 반영한다.
- 문서 변경 후 최소 한 번 `./gradlew tasks --all`로 명령 유효성을 확인한다.
- 불확실한 규칙은 추측으로 채우지 말고 근거 파일 경로를 먼저 확인한다.
- 에이전트 안내 문구는 한국어 중심으로 유지한다.
- 커밋 규칙 예시는 팀 컨벤션 변경 시 즉시 업데이트한다.
- 계획 TASK 문서의 구현 항목은 기능/작업 단위로 분리해 체크박스(`- [ ]`) 목록으로 작성한다.
- 구현 완료 시마다 체크박스`- [x]`로 갱신하고, 범위가 바뀌면 문서를 먼저 갱신한다.
- 결과 보고 시 계획 TASK 문서 맨 아래에 무엇을, 왜, 어떻게 검증했는지 한국어로 누적 기록한다.
## 에이전트 동작 원칙
- 추측하지 말고, 근거 파일을 읽고 결정한다.

View File

@@ -0,0 +1,15 @@
# 문서 유지보수
## 문서 유지보수 규칙
- PRD 문서는 `docs/prd/`에 두고, 계획 TASK 문서는 `docs/plan-task/`에 둔다.
- PRD 문서 파일명은 `[날짜]_구현할내용한글_prd.md`, 계획 TASK 문서 파일명은 `[날짜]_구현할내용한글.md` 형식을 사용한다.
- PRD 문서는 `docs/prd/sample-prd.md`에서 필요한 섹션만 발췌해 작성하고, 불필요한 빈 섹션을 기계적으로 복사하지 않는다.
- `build.gradle.kts` 변경 시 실행 명령 섹션을 함께 갱신한다.
- 테스트 클래스 추가/이동 시 단일 테스트 실행 예시를 최신 상태로 유지한다.
- `.editorconfig` 변경 시 포맷 규칙 섹션을 동기화한다.
- Cursor/Copilot 규칙 파일이 생기면 해당 내용을 이 문서에 반영한다.
- 연속된 하나의 작업에 대해 PRD 또는 계획 TASK 문서가 여러 개 생기지 않도록 기존 문서 재사용 여부를 먼저 확인한다.
- 문서 변경 후 최소 한 번 `./gradlew tasks --all`로 명령 유효성을 확인한다.
- 불확실한 규칙은 추측으로 채우지 말고 근거 파일 경로를 먼저 확인한다.
- 에이전트 안내 문구는 한국어 중심으로 유지한다.
- 커밋 규칙 예시는 팀 컨벤션 변경 시 즉시 업데이트한다.

View File

@@ -0,0 +1,6 @@
# 설정 보안
## 설정/보안 유의사항
- `application.yml`은 다수의 `${ENV_VAR}`를 사용한다.
- 비밀값(API Key, Secret, Token, DB 비밀번호)을 코드/문서/로그에 평문으로 남기지 않는다.
- 환경변수/시크릿 파일은 커밋 대상에서 제외한다.

View File

@@ -0,0 +1,13 @@
# 작업 절차
## 작업 절차 체크리스트
- 변경 전: PRD와 계획 TASK 문서 없이 구현하지 않는다.
- 변경 전: 사용자 프롬프트를 받으면 먼저 `docs/prd/` 아래에 PRD 문서를 작성하고, `docs/prd/sample-prd.md`에서 필요한 섹션만 발췌한다.
- 변경 전: PRD 작성 중 애매하거나 더 필요한 내용, 결정해야 하는 사항이 있으면 애매한 사항이 없어질 때까지 사용자와 인터뷰하고 PRD를 보강한다.
- 변경 전: 보강된 PRD를 바탕으로 `docs/plan-task/` 아래에 계획 TASK 문서를 작성한 뒤, 해당 문서를 기준으로 필요한 내용만 최소 구현한다.
- 변경 전: 유사 기능 코드를 먼저 찾아 네이밍/예외/응답 패턴을 맞춘다.
- 변경 전: 같은 작업의 연속 후속 수정인지 먼저 확인하고, 연속 작업이면 새 PRD 또는 계획 TASK 문서를 만들지 말고 기존 문서를 갱신한다.
- 변경 중: 공개 API 스키마를 임의 변경하지 말고, 작은 단위로 안전하게 수정한다.
- 변경 후: 최소 단일 테스트 또는 `./gradlew test`를 실행하고, 필요 시 `./gradlew ktlintCheck`를 수행한다.
- 커밋 전/후: `commit-policy` 스킬을 먼저 로드하고, `git commit` 직전과 직후에 `work/scripts/check-commit-message-rules.sh`를 실행해 커밋 메시지 규칙 준수 여부를 확인한다.
- 커밋 전/후 확인 시 Sisyphus attribution footer가 없는지 함께 검증한다.

View File

@@ -0,0 +1,63 @@
# 코드 스타일
## 코드 스타일 규칙
### 1) 포맷/기본 규칙
- `.editorconfig` 기준을 준수한다.
- 인덴트: 공백 4칸.
- 줄바꿈: LF.
- 최대 라인 길이: 130.
- 파일 끝 개행 유지, trailing whitespace 제거.
### 2) import 규칙
- 와일드카드 import(`*`)를 사용하지 않는다.
- 사용하지 않는 import를 남기지 않는다.
- import alias(`as`)는 현재 코드베이스에서 사용 사례가 없으므로 지양한다.
- 기존 파일의 import 정렬/그룹 스타일을 그대로 맞춘다.
### 3) 네이밍 규칙
- 클래스/인터페이스/enum: PascalCase.
- 함수/변수/파라미터: camelCase.
- 상수: UPPER_SNAKE_CASE (`companion object` 내부 `const val`).
- Request/Response DTO는 `...Request`, `...Response` 접미사를 유지한다.
- 서비스/컨트롤러/리포지토리 명명은 역할 접미사(`Service`, `Controller`, `Repository`)를 유지한다.
### 4) 패키지/코드 배치 규칙
- 기존 로직을 수정하는 경우에는 기존 패키지 구조를 따른다.
- 기존 로직 수정이 아닌 신규 API나 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
### 5) 타입/널 처리
- Kotlin 타입 시스템을 활용하고 nullable(`?`)를 명시한다.
- 불필요한 `Any`/약한 타입을 피하고 구체 타입을 우선한다.
- 기존 코드에서 `!!` 사용이 많지만, 신규 코드는 가능한 안전 호출/가드절/명시적 예외로 대체를 우선 고려한다.
### 6) API/응답 규칙
- API 응답은 `ApiResponse.ok(...)`, `ApiResponse.error(...)` 패턴을 따른다.
- 컨트롤러는 도메인 예외를 직접 포맷하지 말고 `SodaException`을 던진다.
- 인증 사용자 필요 시 `@AuthenticationPrincipal(... ) member: Member?` 패턴 + null 가드절을 사용한다.
### 7) 예외 처리 규칙
- 비즈니스 예외는 `SodaException(messageKey = "...")` 우선 사용.
- 사용자 노출 문구는 하드코딩보다 `messageKey` 기반 i18n을 우선한다.
- 공통 예외 변환은 `SodaExceptionHandler`에서 수행하므로, 개별 컨트롤러에서 중복 처리하지 않는다.
- 예외를 삼키는 빈 `catch` 블록을 금지한다.
### 8) 트랜잭션 규칙
- 서비스 계층에서 `@Transactional`을 사용한다.
- 조회 위주 메서드는 `@Transactional(readOnly = true)`를 우선한다.
- 쓰기 로직은 메서드 단위 `@Transactional`로 경계를 명확히 한다.
### 9) 비동기/동시성 규칙
- 비동기 처리는 Kotlin Coroutines 패턴을 따른다.
- `CoroutineScope(Dispatchers.IO)` + `launch` + 예외 처리 패턴을 일관되게 유지한다.
- 생명주기 종료 시 scope 정리(`@PreDestroy`) 패턴을 참고한다.
### 10) 의존성 주입
- 생성자 주입(primary constructor + `private val`)을 기본으로 사용한다.
- 필드 주입보다 명시적 생성자 주입을 우선한다.
### 11) 주석
- 의미 단위별로 주석을 작성한다.
- 주석은 한 문장으로 간결하게 작성한다.
- 주석은 코드의 의도와 구조를 설명한다.
- 주석은 코드 변경 시 업데이트를 잊지 않는다.

View File

@@ -0,0 +1,9 @@
# 테스트 스타일
## 테스트 스타일 규칙
- 테스트 프레임워크: JUnit 5 (`useJUnitPlatform()`)
- 목킹: Mockito 사용 패턴 존재 (`Mockito.mock`, ``Mockito.`when`(...)``)
- 검증: `assertEquals`, `assertThrows` 패턴 준수.
- 테스트 이름은 의도가 드러나는 영어 문장형(`should...`)을 유지한다.
- 테스트는 DisplayName으로 한국어 설명을 추가한다.
- 예외 상황이 있는지 확인하고 예외 상황에 대한 테스트 케이스를 추가한다.

View File

@@ -0,0 +1,10 @@
# `.omx/` Git 제외 처리
- [x] `.gitignore``.omx/`를 추가한다.
- [x] `git status``git check-ignore`로 제외가 정상 동작하는지 확인한다.
## 검증 기록
- 무엇을: `.gitignore``.omx/`를 추가하고 `.omx/` 하위 파일들이 무시되는지 확인했다.
- 왜: `.omx/`는 런타임 상태, 로그, 메트릭 파일이라 버전 관리 대상이 아니기 때문이다.
- 어떻게: `git check-ignore -v .omx/tmux-hook.json .omx/state/hud-state.json .omx/logs/turns-2026-04-06.jsonl`로 무시 규칙을 확인했고, `git status --short``.omx/`가 더 이상 추적 대상이 아니고 문서만 남는지 확인했다.

View File

@@ -0,0 +1,19 @@
SET @schema_name := DATABASE();
SET @settlement_ratio_column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'content'
AND column_name = 'settlement_ratio'
);
SET @add_settlement_ratio_column_sql := IF(
@settlement_ratio_column_exists = 0,
'ALTER TABLE content ADD COLUMN settlement_ratio INT NULL COMMENT ''콘텐츠별 정산 요율(%)'' AFTER price',
'SELECT ''content.settlement_ratio already exists'' AS message'
);
PREPARE add_settlement_ratio_column_stmt FROM @add_settlement_ratio_column_sql;
EXECUTE add_settlement_ratio_column_stmt;
DEALLOCATE PREPARE add_settlement_ratio_column_stmt;

View File

@@ -0,0 +1,22 @@
# 20260407 커밋 footer 자동 추가 차단
## 구현 계획
- [x] oh-my-openagent 기본 footer 동작과 저장소 로컬 커밋 워크플로우의 영향 범위를 문서화한다.
- [x] `AGENTS.md`에 커밋 본문에서 Sisyphus footer와 자동 `Co-authored-by` 라인을 허용하지 않는 규칙을 추가한다.
- [x] `.opencode/skills/commit-policy/SKILL.md`에 검증된 메시지를 그대로 `git commit`에 전달하고 자동 footer를 금지하는 절차를 반영한다.
- [x] `.opencode/commands/commit.md``/commit` 커맨드가 자동 footer 없는 최종 메시지를 사용하도록 지시를 보강한다.
- [x] `work/scripts/check-commit-message-rules.sh`에 Sisyphus footer 및 자동 `Co-authored-by` 라인 차단 검증을 추가한다.
- [x] 변경 문서와 스크립트에 대해 진단 및 실행 검증을 수행한다.
## 검증 기록
- [x] 작업 완료 후 검증 결과를 기록한다.
- 1차 구현
- 무엇을: `AGENTS.md`, `.opencode/skills/commit-policy/SKILL.md`, `.opencode/commands/commit.md`, `work/scripts/check-commit-message-rules.sh`를 수정해 커밋 본문에서 `Ultraworked with [Sisyphus]...``Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>` 자동 footer를 금지하고, `/commit` 경로가 검증된 메시지를 그대로 `git commit`에 전달하도록 명시했다.
- 왜: oh-my-openagent 기본 설정과 알려진 버그로 자동 footer가 붙을 수 있으므로, 저장소 로컬 규칙과 검증 스크립트에서 이를 명시적으로 차단해야 커밋 결과를 일관되게 통제할 수 있기 때문이다.
- 어떻게: `lsp_diagnostics``AGENTS.md`, `.opencode/skills/commit-policy/SKILL.md`, `.opencode/commands/commit.md`, `work/scripts/check-commit-message-rules.sh`, `docs/20260407_커밋footer자동추가차단.md`에 대해 모두 `No diagnostics found`를 확인했다. `bash -n work/scripts/check-commit-message-rules.sh`로 문법을 검증했고, `./work/scripts/check-commit-message-rules.sh --message`로 정상 메시지/`Refs` footer 허용 케이스는 PASS, Sisyphus footer와 자동 `Co-authored-by` 케이스는 FAIL을 확인했다. 추가로 `./gradlew tasks --all` 실행 결과 `BUILD SUCCESSFUL`을 확인했다.
- 2차 수정
- 무엇을: Oracle 검토 의견을 반영해 `.opencode/skills/commit-policy/SKILL.md``.opencode/commands/commit.md`에서 `--message-file` 검증 후 같은 파일을 `git commit -F`에 전달하는 경로를 권장하도록 보강했고, `work/scripts/check-commit-message-rules.sh``Co-authored-by` 차단 조건을 공백 변형까지 탐지하도록 확장했다.
- 왜: exact string 하나만 금지하면 footer 형식이 조금만 달라져도 놓칠 수 있으므로, 외부 기본 동작이나 버그로 인한 변형까지 더 안정적으로 차단해야 하기 때문이다.
- 어떻게: `lsp_diagnostics``.opencode/skills/commit-policy/SKILL.md`, `.opencode/commands/commit.md`, `work/scripts/check-commit-message-rules.sh`에 대해 모두 `No diagnostics found`를 확인했다. `bash -n work/scripts/check-commit-message-rules.sh`를 다시 실행해 문법을 검증했고, `./work/scripts/check-commit-message-rules.sh --message`로 기본 메시지와 `Refs` footer는 PASS, Sisyphus footer/기본 `Co-authored-by`/공백 변형 `Co-authored-by` 케이스는 모두 FAIL을 확인했다.

View File

@@ -0,0 +1,85 @@
- [x] 변수명 확정: 엔티티 내부 추가 변수는 `AudioContent.settlementRatio: Int?`로 사용한다.
- 이유: `AudioContent`는 이미 콘텐츠 도메인 엔티티이므로 `contentSettlementRatio`는 중복 표현에 가깝다.
- 근거: 이 저장소의 엔티티 필드는 `AudioContent.price`, `LiveRoom.price`, `CreatorCommunity.price`처럼 엔티티 스코프 안에서는 도메인 접두어를 반복하지 않는다.
- 예외 기준: `CreatorSettlementRatio.contentSettlementRatio`처럼 하나의 엔티티 안에서 `live/content/community` 여러 정산 대상을 함께 구분해야 할 때만 `content` 접두어가 필요하다.
- DTO/API 정책: 해당 값은 관리자에서만 사용하므로 관리자 요청/응답 DTO와 API 필드명도 예외 없이 `settlementRatio`로 통일한다.
- nullable 정책: 기존 데이터와 크리에이터 정산 요율 미등록 케이스를 안전하게 수용하기 위해 초기 도입 시 `Int?`로 두고, 계산 시 `콘텐츠별 요율 -> 크리에이터 기본 요율 -> 70% 기본값` 순서로 fallback 하도록 설계한다.
- [x] `AudioContent` 엔티티에 콘텐츠별 정산 요율 필드를 추가한다.
- 대상 파일: `src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt`
- 작업 내용: `price` 인접 위치에 `settlementRatio: Int?` 필드를 추가하고, 기존 생성자 호출부가 모두 컴파일되도록 생성 경로를 함께 정리한다.
- [x] 관리자 콘텐츠 목록 조회 응답에 콘텐츠별 정산 요율을 노출한다.
- 대상 파일: `src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/admin/content/GetAdminContentListResponse.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt`
- 작업 내용:
- `QGetAdminContentListItem(...)` QueryProjection에 `audioContent.settlementRatio`를 추가한다.
- `GetAdminContentListItem``settlementRatio: Int?`를 추가하고, 관리자 목록 응답 필드명도 동일하게 `settlementRatio`로 맞춘다.
- `AdminContentController.getAudioContentList` 응답에 정산 요율이 함께 내려가도록 조회 체인을 맞춘다.
- [x] 관리자 콘텐츠 수정 API에서 콘텐츠별 정산 요율을 수정할 수 있게 한다.
- 대상 파일: `src/main/kotlin/kr/co/vividnext/sodalive/admin/content/UpdateAdminContentRequest.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentService.kt`
- 작업 내용:
- `UpdateAdminContentRequest``settlementRatio: Int?`를 추가하고, 관리자 수정 요청 필드명도 동일하게 `settlementRatio`로 맞춘다.
- `AdminContentService.updateAudioContent`에서 요청값이 들어오면 `audioContent.settlementRatio`를 갱신한다.
- 숫자 범위 정책은 `0~100`으로 검증한다.
- 개별 콘텐츠 정산 요율 삭제는 `isSettlementRatioDeleted: true` 플래그로만 처리하고, `settlementRatio`와 동시 전달 시 invalid request 로 처리한다.
- [x] 실제 콘텐츠 정산 계산이 크리에이터 기본 요율이 아니라 콘텐츠별 요율을 우선 사용하도록 변경한다.
- 대상 파일: `src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/CreatorAdminCalculateQueryRepository.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/GetCalculateContentQueryData.kt`
- 작업 내용:
- 콘텐츠 판매/누적 판매 집계 쿼리에서 `creatorSettlementRatio.contentSettlementRatio` 직접 사용 부분을 점검한다.
- 정산 대상 비율은 `audioContent.settlementRatio`를 우선 사용하고, 값이 없을 때만 `creatorSettlementRatio.contentSettlementRatio`를 fallback 하도록 쿼리 또는 계산 DTO를 조정한다.
- 현재 `GetCalculateContentQueryData`의 70% 기본값 정책은 마지막 fallback 으로 유지한다.
- [x] 크리에이터 관리자 정산 조회도 동일 기준을 사용하도록 맞춘다.
- 대상 파일: `src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/CreatorAdminCalculateQueryRepository.kt`
- 작업 내용: 관리자 정산 조회와 동일하게 콘텐츠별 정산 요율 우선 정책을 반영해 관리자/크리에이터 화면 간 계산 기준이 달라지지 않게 한다.
- [x] 기존 데이터 처리 정책을 정리한다.
- 대상 범위: 운영 DB 스키마/기존 콘텐츠 데이터
- 작업 내용:
- 신규 컬럼 추가 시 nullable 로 도입하고, DDL은 `docs/20260407_audio_content_settlement_ratio_ddl.sql` 기준으로 관리한다.
- 콘텐츠 등록 시 크리에이터 기본 정산 요율을 복사하지 않고, `NULL` 상태에서도 계산이 가능하도록 fallback 순서를 유지한다.
- 운영 정책상 기존 콘텐츠에도 즉시 고정값이 필요하면 별도 SQL 또는 배치 백필 계획을 추가로 작성한다.
- [x] 영향 DTO/Q 클래스/컴파일 산출물을 재생성하고 검증한다.
- 작업 내용:
- QueryDSL projection 변경 후 Q 클래스 재생성이 필요한지 확인하고 빌드로 반영한다.
- 엔티티/관리자 DTO/API/QueryProjection 필드명이 모두 `settlementRatio`로 일치하는지 확인해 매핑 누락 가능성을 제거한다.
- [x] 검증을 단계별로 수행한다.
- 작업 내용:
- `AdminContentController.getAudioContentList`에서 정산 요율 조회 포함 여부를 확인한다.
- `AdminContentController.modifyAudioContent`에서 정산 요율 수정 반영 여부를 확인한다.
- `AdminContentController.modifyAudioContent`에서 `isSettlementRatioDeleted = true` 요청 시 개별 콘텐츠 정산 요율이 삭제되는지 확인한다.
- 콘텐츠 등록 시 `settlementRatio`가 nullable 상태로 유지되어도 계산 fallback 이 정상 동작하는지 확인한다.
- 콘텐츠 정산 조회(`AdminCalculateQueryRepository`, `CreatorAdminCalculateQueryRepository`)가 콘텐츠별 요율을 우선 적용하는지 확인한다.
- 실행 검증은 최소 `./gradlew build`, 필요 시 `./gradlew test`까지 수행한다.
## 1차 구현 검증 기록
- 무엇을: `AudioContent.settlementRatio` nullable 컬럼 추가, 관리자 목록/수정 API 반영, 콘텐츠/누적 정산 쿼리의 콘텐츠별 요율 우선 fallback 반영, 생성 시 기본 요율 복사 제거.
- 왜: 기존 콘텐츠와 미설정 콘텐츠를 `NULL`로 유지하면서도 관리자에서 개별 요율을 조회/수정하고 정산 시 올바른 fallback 순서를 적용하기 위해.
- 어떻게:
- `./gradlew build` → 성공
- `./gradlew test``build` 과정에 포함되어 성공
- 관리자/정산 API의 실서버 수동 호출 검증 → 이 로컬 작업 세션에서는 애플리케이션 실행 및 인증 가능한 테스트 데이터가 없어 미실행
## 2차 수정 검증 기록
- 무엇을: `@SpringBootTest` 없이 콘텐츠 정산 계산 DTO의 명시 비율 적용과 `null -> 70% fallback`을 검증하는 순수 단위 테스트를 추가했다.
- 왜: 정산 쿼리 변경만으로는 계산 결과가 코드상에서 충분히 고정되지 않아, 문서에 적힌 계산 규칙을 재현 가능한 테스트로 보장하기 위해.
- 어떻게:
- `./gradlew test --tests kr.co.vividnext.sodalive.admin.calculate.ContentSettlementCalculationTest` → 성공
- 검증 대상: `GetCalculateContentQueryData`, `GetCumulativeSalesByContentQueryData`
- 검증 시나리오: `settlementRatio = 80` 적용, `settlementRatio = null` 시 70% fallback 적용
## 3차 수정 검증 기록
- 무엇을: 관리자 콘텐츠 수정 API의 개별 콘텐츠 정산 요율 삭제 방식을 명시적 null 대신 `isSettlementRatioDeleted` 플래그로 전환한다.
- 왜: 부분 업데이트 요청에서 필드 생략과 null 삭제 의미가 섞이지 않도록 API 계약을 명확히 하기 위해.
- 어떻게:
- `./gradlew test --tests kr.co.vividnext.sodalive.admin.content.AdminContentServiceTest` → 성공
- `./gradlew test --tests kr.co.vividnext.sodalive.admin.calculate.ContentSettlementCalculationTest` → 성공
- `./gradlew build` → 성공
- 검증 시나리오: 유효한 요율 설정, 삭제 플래그 삭제, 값/삭제 플래그 동시 전달 충돌, `null` 키/삭제 플래그 동시 전달 충돌, 범위 초과 거부, 정산 fallback 회귀 확인

View File

@@ -0,0 +1,658 @@
# 에이전트 권한 및 정산 기능 추가 작업 계획
## 요구사항 상세 분석
### 1. 권한/역할 분석
- `MemberRole.AGENT`는 이미 `Member.kt`에 정의되어 있으므로 역할 enum 자체의 신규 추가는 필요하지 않다.
- 다만 현재 코드베이스에는 에이전트 전용 소속 관리/정산 조회 모듈이 없으므로, 실제 기능은 신규 구현이 필요하다.
- 크리에이터를 에이전트에 소속하거나 해제하는 작업은 `ADMIN`만 수행할 수 있어야 한다.
- 에이전트 전용 조회 API는 `AGENT` 권한 계정만 자신의 소속 크리에이터 데이터에 한해 접근할 수 있어야 한다.
- 관리자용 권한 경계는 기존 `@PreAuthorize("hasRole('ADMIN')")`, 에이전트용 권한 경계는 `@PreAuthorize("hasRole('AGENT')")` 패턴을 따른다.
### 2. 관계 모델 분석
- 요구사항은 `에이전트 1 : N 크리에이터`, `크리에이터 1 : 0..1 에이전트` 관계다.
- 이 관계는 `Member` 자체에 필드를 직접 늘리는 방식보다, `kr.co.vividnext.sodalive.partner.agent` 패키지 안에 전용 연관 엔티티를 두는 편이 패키지 경계와 제약 관리에 더 적합하다.
- 신규 연관 엔티티는 `creator_id` 유니크 제약으로 "한 크리에이터는 하나의 에이전트에만 소속"을 강제해야 한다.
- 서비스 계층에서는 다음을 모두 검증해야 한다.
- agent 회원이 실제 `MemberRole.AGENT`인지
- creator 회원이 실제 `MemberRole.CREATOR`인지
- creator가 이미 다른 agent에 소속되어 있는지
- 자기 자신을 agent/creator로 잘못 연결하는 요청이 아닌지
### 3. 정산 규칙 분석
- 에이전트 정산 비율은 관리자 설정값 1개만 있으면 된다.
- 기준 금액은 "크리에이터 세전 정산금액(settlementAmount)"이며, 라이브/콘텐츠/커뮤니티처럼 항목별 비율이 아니라 최종 정산금액에 대해 에이전트 비율을 적용한다.
- 에이전트 정산금은 크리에이터 정산금에서 차감하지 않고 별도로 계산한다.
- 에이전트 정산금 계산식은 다음으로 고정한다.
- `agentSettlementAmount = round(creatorSettlementAmount * agentSettlementRatio / 100)`
- 크리에이터 입금액 계산식은 기존 로직을 그대로 유지한다.
- 라이브/콘텐츠/커뮤니티는 기존 `CreatorSettlementRatio` 또는 콘텐츠별 정산 비율을 이용해 `settlementAmount`를 계산한 뒤, 그 결과에 에이전트 비율을 곱해야 한다.
- 채널후원/콘텐츠후원도 기존 정산 계산 결과의 `settlementAmount`를 기준으로 에이전트 금액을 별도 계산해야 한다.
### 4. 조회 요구사항 분석
- 에이전트는 소속 크리에이터 목록을 조회할 수 있어야 한다.
- 에이전트는 소속 크리에이터 기준으로 아래 5개 현황을 조회할 수 있어야 한다.
- 라이브
- 콘텐츠 판매
- 커뮤니티
- 채널후원
- 콘텐츠후원
- 각 조회는 `/admin/calculate/content-by-creator` 계열과 유사하게 크리에이터별 집계 응답을 제공해야 한다.
- 각 응답은 최소한 다음 정보를 포함해야 한다.
- 크리에이터 식별 정보
- 건수
- 총 캔 수
- 원화
- 수수료
- 정산금액
- 합계(total)
- 에이전트 정산금액(agentSettlementAmount)
- 기존 `GetCalculateByCreatorItem`은 건수가 없고 total 객체도 없으므로, 에이전트 전용 응답 DTO는 신규 정의가 필요하다.
## 구현 방향
### 1차 구현 범위 (2026-04-09)
- 이번 구현 슬라이스는 관리자 전용 기능만 포함한다.
- 포함 범위
- 에이전트-크리에이터 소속 지정 API
- 에이전트-크리에이터 소속 해제 API
- 에이전트 정산 비율 생성 API
- 에이전트 정산 비율 수정 API
- 에이전트 정산 비율 목록 API
- 위 기능에 필요한 엔티티/리포지토리/서비스/테스트/DDL 문서
- 제외 범위
- 에이전트 본인 소속 크리에이터 목록 조회 API
- 라이브/콘텐츠/커뮤니티/채널후원/콘텐츠후원 에이전트 정산 조회 API
### 권장 설계
- 공유 도메인 모델/리포지토리는 `kr.co.vividnext.sodalive.partner.agent` 하위 패키지에 둔다.
- `ADMIN` 전용 controller/service 진입점은 `kr.co.vividnext.sodalive.admin.partner.agent` 하위 패키지에 둔다.
- `AGENT` 전용 정산 조회 진입점은 `kr.co.vividnext.sodalive.partner.agent.calculate` 하위 패키지에 둔다.
- 기존 `admin.calculate`, `creator.admin.calculate`, `admin.member`의 구현 패턴은 재사용하되, DTO/쿼리/서비스는 에이전트 요구사항에 맞는 별도 모듈로 분리한다.
- 에이전트-크리에이터 소속 관계와 에이전트 정산 비율은 전용 엔티티로 분리해 기능 응집도를 유지한다.
### 3차 구현 범위 (이력형 소속/비율 + 확정 정산 스냅샷)
- 이번 구현 슬라이스는 기존 current-state 기반 에이전트 정산 구조를 historical model로 전환한다.
- 포함 범위
- `agent_creator_relation``assignedAt/unassignedAt` 기반 이력형 소속 모델로 전환
- `agent_settlement_ratio``effectiveFrom/effectiveTo` 기반 이력형 비율 모델로 전환
- 관리자 소속 지정/해제 API를 시간 경계 기반으로 수정
- 관리자 비율 생성/수정/조회 API를 이력형 비율 기준으로 수정
- AGENT 정산 조회 쿼리를 거래 시점(event time)의 소속/비율을 기준으로 계산하도록 수정
- 확정 정산 스냅샷 저장 모델 및 관리자 확정 API 추가
- finalized 기간 조회는 스냅샷 우선, 미확정 기간 조회는 live 계산 유지
- 위 구조에 필요한 테스트, DDL 문서, 계획 문서 갱신
- 제외 범위
- 기존 과거 데이터의 완전 복원 보장
- 이벤트소싱 도입
- 프론트엔드 화면 개편
### current-state 설계의 한계
- 현재 `AgentCreatorRelation``agent`, `creator`만 저장하고 remove 시 hard-delete 하므로 과거 소속 이력을 보존하지 못한다.
- 현재 `AgentSettlementRatio``deletedAt` 기반 현재 활성 행 조회에 의존하므로 특정 거래 시점의 비율을 안정적으로 재현하지 못한다.
- 현재 `AgentCalculateQueryRepository`는 거래 발생 시각(`useCan.createdAt`, `order.createdAt`)으로 기간을 자르면서도, 소속/비율은 현재 row를 기준으로 조인한다.
- 따라서 크리에이터가 에이전트에서 해제되거나 비율이 변경되면, 과거 정산 조회 결과도 바뀔 수 있다.
- 사용자가 요구한 "당시 적용 비율까지 고정된 완전한 과거 정산"은 current-state 조인만으로 충족할 수 없다.
### 변경 후 목표 모델
- 소속 관계는 append-only 이력 모델로 관리한다.
- `assignedAt`: 소속 시작 시각
- `unassignedAt`: 소속 종료 시각(nullable)
- 현재 소속은 `unassignedAt is null`로 정의한다.
- 정산 비율도 append-only 이력 모델로 관리한다.
- `effectiveFrom`: 비율 시작 시각
- `effectiveTo`: 비율 종료 시각(nullable)
- 현재 비율은 `effectiveTo is null`로 정의한다.
- 정산 확정 시점에는 별도 스냅샷 테이블에 결과를 저장한다.
- 소속/비율 foreign key만 저장하지 않고, 적용된 비율과 계산 결과를 숫자 값으로 함께 저장한다.
- 이후 소속/비율 변경이 발생해도 확정 정산은 변경되지 않는다.
### 확정 정산 스냅샷 설계 초안
- 신규 도메인 패키지 후보: `kr.co.vividnext.sodalive.partner.agent.settlement.snapshot`
- 신규 관리자 진입점 패키지 후보: `kr.co.vividnext.sodalive.admin.partner.agent.settlement`
- 신규 스냅샷 엔티티 초안 필드
- `periodStart`, `periodEnd`
- `settlementType` (`LIVE`, `CONTENT`, `COMMUNITY`, `CHANNEL_DONATION`, `CONTENT_DONATION`)
- `agentId`, `agentNickname`
- `creatorId`, `creatorNickname`
- `assignmentId`
- `agentSettlementRatioId`, `appliedAgentSettlementRatio`
- `count`, `totalCan`, `krw`, `fee`, `settlementAmount`, `agentSettlementAmount`
- 필요 시 `tax`, `depositAmount`
- `finalizedAt`, `finalizedByMemberId`
- 스냅샷은 append-only로 저장하고 동일 기간/타입/agent/creator 기준 재확정은 idempotent하게 막거나 재사용한다.
### 조회 전략 변경
- finalized 기간 조회
- 스냅샷 데이터가 있으면 스냅샷을 우선 조회한다.
- 스냅샷 데이터는 이후 소속 해제/비율 변경과 무관하게 그대로 반환한다.
- 미확정 기간 조회
- `AgentCalculateQueryRepository`에서 거래 시점 기준으로 소속/비율 이력 row를 찾아 계산한다.
- 시간 경계는 `start <= txTime < end` 형태의 반열린 구간으로 처리한다.
### API 변경 방향
- 관리자 소속 지정 API
- 기존 path 유지
- request에 `assignedAt` 추가
- 관리자 소속 해제 API
- 기존 path 유지
- request에 `unassignedAt` 추가
- delete 대신 종료 시각 기록
- 관리자 비율 생성/수정 API
- 기존 path 유지
- request에 `effectiveFrom` 추가
- update는 기존 행 수정이 아니라 이전 활성 행 종료 + 신규 행 추가로 동작
- 관리자 확정 정산 API
- 신규 path 후보: `POST /admin/partner/agent/settlement/finalize`
- 입력: 기간, 정산 타입, 대상 에이전트(`agentId` 기준 단일 에이전트 확정)
- 에이전트 조회 API
- 기존 path 유지
- finalized 기간은 스냅샷 우선, 그 외 기간은 live 계산
### 패키지/파일 초안
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AgentCreatorRelation.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AgentCreatorRelationRepository.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorController.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorService.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AssignAgentCreatorRequest.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/RemoveAgentCreatorRequest.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/AgentSettlementRatio.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/AgentSettlementRatioRepository.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioController.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateController.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateService.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/response/*`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/AgentSettlementSnapshot.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/AgentSettlementSnapshotRepository.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotController.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt`
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/**`
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/**`
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/**`
- `docs/20260408_에이전트권한및정산기능추가.md`
### API 방향
- 관리자 전용
- 크리에이터 소속 지정 API
- 크리에이터 소속 해제 API
- 에이전트 정산 비율 생성/수정/조회 API
- 에이전트 전용
- 소속 크리에이터 목록 조회 API
- 라이브 크리에이터별 현황 조회 API
- 콘텐츠 크리에이터별 현황 조회 API
- 커뮤니티 크리에이터별 현황 조회 API
- 채널후원 크리에이터별 현황 조회 API
- 콘텐츠후원 크리에이터별 현황 조회 API
### 역할별 신규 엔드포인트 정리
#### ADMIN 전용 엔드포인트
| Method | Path | 권한 | 설명 |
|---|---|---|---|
| `POST` | `/admin/partner/agent/assignment` | `ADMIN` | 에이전트와 크리에이터 소속을 지정한다. |
| `POST` | `/admin/partner/agent/assignment/remove` | `ADMIN` | 지정된 크리에이터의 에이전트 소속을 해제한다. |
| `POST` | `/admin/partner/agent/ratio` | `ADMIN` | 에이전트 정산 비율을 생성한다. |
| `POST` | `/admin/partner/agent/ratio/update` | `ADMIN` | 기존 에이전트 정산 비율을 수정한다. |
| `GET` | `/admin/partner/agent/ratio` | `ADMIN` | 에이전트 정산 비율 목록을 페이지 단위로 조회한다. |
#### AGENT 전용 엔드포인트
| Method | Path | 권한 | 설명 |
|---|---|---|---|
| `GET` | `/agent/calculate/creator/list` | `AGENT` | 로그인한 에이전트에게 소속된 크리에이터 목록을 조회한다. |
| `GET` | `/agent/calculate/live-by-creator` | `AGENT` | 소속 크리에이터의 라이브 정산 현황을 크리에이터별로 조회한다. |
| `GET` | `/agent/calculate/content-by-creator` | `AGENT` | 소속 크리에이터의 콘텐츠 판매 정산 현황을 크리에이터별로 조회한다. |
| `GET` | `/agent/calculate/community-by-creator` | `AGENT` | 소속 크리에이터의 커뮤니티 정산 현황을 크리에이터별로 조회한다. |
| `GET` | `/agent/calculate/channel-donation-by-creator` | `AGENT` | 소속 크리에이터의 채널후원 정산 현황을 크리에이터별로 조회한다. |
| `GET` | `/agent/calculate/content-donation-by-creator` | `AGENT` | 소속 크리에이터의 콘텐츠후원 정산 현황을 크리에이터별로 조회한다. |
#### 공통 인증 메모
- 관리자 엔드포인트는 모두 `@PreAuthorize("hasRole('ADMIN')")` 기준으로 제한한다.
- 에이전트 엔드포인트는 모두 `@PreAuthorize("hasRole('AGENT')")` 기준으로 제한한다.
- 에이전트 조회 API는 `@AuthenticationPrincipal(... ) member: Member?`를 통해 로그인 사용자를 확인하고, `agent_creator_relation` 기준으로 본인 소속 크리에이터 데이터만 조회한다.
### 패키지/엔드포인트 배치에 대한 최종 판단
#### 1. ADMIN 전용 partner-agent API는 어디에 두는 것이 더 자연스러운가?
- **최종 판단: `ADMIN` 전용 controller/service 진입점은 `kr.co.vividnext.sodalive.admin.partner.agent.*` 아래로 옮기는 것이 더 자연스럽다.**
- 근거는 이 저장소의 기존 관례가 관리자 전용 기능을 `kr.co.vividnext.sodalive.admin.*` 아래에 배치하는 흐름이 더 강하기 때문이다.
- 예: `admin/calculate/AdminCalculateController.kt`
- 예: `admin/marketing/AdminAdMediaPartnerController.kt`
- 예: `admin/member/AdminMemberController.kt`
- `creator`처럼 역할이 강하게 분리된 영역도 `creator/admin/*` 패턴을 쓰므로, 현재 `partner.agent.assignment.AdminAgentCreatorController`, `partner.agent.ratio.AdminAgentSettlementRatioController`처럼 **도메인 패키지 안에 관리자 전용 진입점이 섞여 있는 구조는 이 저장소 기준으로는 예외에 가깝다.**
- 따라서 권장 구조는 아래와 같다.
- **공유 도메인 객체/리포지토리**: `kr.co.vividnext.sodalive.partner.agent.*`
- **ADMIN 전용 controller/service**: `kr.co.vividnext.sodalive.admin.partner.agent.*`
- 즉, `AgentCreatorRelation`, `AgentSettlementRatio`, repository까지 `admin.*`로 옮기는 것이 아니라, **관리자 진입점만 `admin.*`로 재배치**하는 것이 균형이 가장 좋다.
#### 2. 소속 크리에이터 목록 조회 API가 `calculate` 아래에 있는 것은 맞는 선택인가?
- **최종 판단: 현재 요구사항 범위에서는 유지해도 된다.**
- 이유는 이 API가 “에이전트 소속 관리용 일반 목록 API”라기보다, **에이전트 정산 화면에서 크리에이터별 현황 조회로 진입하기 위한 보조 목록 API**에 가깝기 때문이다.
- 현재 같은 컨트롤러에는 아래처럼 모두 정산/집계성 조회가 함께 모여 있다.
- `/agent/calculate/live-by-creator`
- `/agent/calculate/content-by-creator`
- `/agent/calculate/community-by-creator`
- `/agent/calculate/channel-donation-by-creator`
- `/agent/calculate/content-donation-by-creator`
- 따라서 **현재 맥락에서는 `/agent/calculate/creator/list`를 정산 조회의 진입용 목록으로 보는 해석이 가능하고, 응집도도 유지된다.**
- 다만 이 API가 앞으로 정산 외 목적(메시지, 운영, 소속 관리, 일반 대시보드)에도 재사용되기 시작하면, 그 시점에는 아래처럼 분리 재검토하는 것이 맞다.
- 패키지 후보: `kr.co.vividnext.sodalive.partner.agent.assignment` 또는 `kr.co.vividnext.sodalive.partner.agent.creator`
- 엔드포인트 후보: `/agent/creator/list`, `/agent/assignment/creator/list`
#### 3. 이번 기능에 대한 권장 정리 기준
- **ADMIN 전용 API**: `kr.co.vividnext.sodalive.admin.partner.agent.*`
- **AGENT 정산 조회 API**: `kr.co.vividnext.sodalive.partner.agent.calculate.*`
- **공유 도메인 모델/리포지토리**: `kr.co.vividnext.sodalive.partner.agent.*`
- **`/agent/calculate/creator/list`**: 현재는 유지, 단 정산 외 재사용이 커지면 별도 read/assignment 축으로 분리
## 작업 체크리스트
- [x] 기존 `MemberRole.AGENT` 사용 범위를 점검하고, 관리자 화면/API에서 에이전트 계정을 식별할 수 있는 조회 경로를 확정한다.
- [x] `kr.co.vividnext.sodalive.partner.agent` 패키지 아래에 에이전트-크리에이터 소속 전용 엔티티/리포지토리를 추가한다.
- [x] 소속 엔티티에 `creator_id` 유니크 제약과 agent/creator role 검증 로직을 추가해 "한 크리에이터는 하나의 에이전트에만 소속" 규칙을 보장한다.
- [x] 관리자 전용 크리에이터 소속 지정 API를 추가한다.
- [x] 관리자 전용 크리에이터 소속 해제 API를 추가한다.
- [x] 에이전트 정산 비율 전용 엔티티/리포지토리를 추가하고, 에이전트당 단일 비율만 유지되도록 한다.
- [x] 관리자 전용 에이전트 정산 비율 생성/수정/조회 API를 추가한다.
- [x] 1차 구현 범위의 assignment/ratio 컨트롤러/서비스 테스트를 추가해 role 검증, 중복 소속 방지, 누락 엔티티, pageable 위임을 검증한다.
- [x] 신규 assignment/ratio 테이블 생성을 위한 DDL 문서를 `docs/20260409_partner_agent_assignment_ratio_ddl.sql`에 추가한다.
- [x] 에이전트 본인의 소속 크리에이터 목록 조회 API를 추가한다.
- [x] `/agent/calculate/creator/list`가 현재 시각 기준 `assignedAt <= now < unassignedAt` 활성 구간의 크리에이터만 노출하도록 보강한다.
- [x] 라이브 현황용 agent 전용 Query/DTO/응답을 추가하고, `settlementAmount` 기준 `agentSettlementAmount`를 계산한다.
- [x] 콘텐츠 판매 현황용 agent 전용 Query/DTO/응답을 추가하고, 기존 콘텐츠 정산 비율(`audioContent.settlementRatio` 또는 `CreatorSettlementRatio.contentSettlementRatio`)을 재사용한다.
- [x] 커뮤니티 현황용 agent 전용 Query/DTO/응답을 추가하고, `CreatorSettlementRatio.communitySettlementRatio` 기반 정산 후 agent 금액을 계산한다.
- [x] 채널후원 현황용 agent 전용 Query/DTO/응답을 추가하고, 기존 `ChannelDonationSettlementCalculator` 결과의 `settlementAmount` 기준으로 agent 금액을 계산한다.
- [x] 콘텐츠후원 현황용 agent 전용 Query/DTO/응답을 추가하고, 기존 콘텐츠후원 계산 결과의 `settlementAmount` 기준으로 agent 금액을 계산한다.
- [x] 5개 현황 응답 모두에 `totalCount + total + items` 구조를 맞추고, item/total 양쪽에 `agentSettlementAmount`를 포함한다.
- [x] AGENT 계정이 자신에게 소속된 크리에이터 데이터만 조회하도록 `@AuthenticationPrincipal` + relation 기반 필터링을 구현한다.
- [x] ADMIN/AGENT 권한 오류, 잘못된 role 요청, 중복 소속 요청, 비소속 데이터 조회 차단에 대한 예외 처리를 추가한다.
- [x] 컨트롤러/서비스/쿼리 리포지토리 테스트를 추가해 소속 제약, 권한 제약, 정산 계산식, total 합계를 검증한다.
- [x] `./gradlew test`, `./gradlew build`로 최종 검증한다.
- [x] `agent_creator_relation``assignedAt`, `unassignedAt`를 추가하고 current-state 단일 row 모델을 append-only 이력 모델로 전환한다.
- [x] 관리자 소속 지정 API가 `assignedAt`을 받아 활성 기간 중복을 검증하도록 수정한다.
- [x] 관리자 소속 해제 API가 hard-delete 대신 `unassignedAt` 종료 처리로 변경되도록 수정한다.
- [x] `agent_settlement_ratio``effectiveFrom`, `effectiveTo`를 추가하고 단일 현재 row 갱신 모델을 append-only 이력 모델로 전환한다.
- [x] 관리자 비율 생성/수정 API가 `effectiveFrom`을 받아 기존 활성 row 종료 + 신규 row 추가로 동작하도록 수정한다.
- [x] 관리자 비율 생성/수정 API가 `effectiveFrom` backdate, 동일 시각 입력, 기존 ratio history와 겹치는 시점을 거절하도록 검증을 보강한다.
- [x] 관리자 비율 생성/수정 API가 `settlementRatio`를 0..100 범위로 검증하도록 보강한다.
- [x] `agent_settlement_ratio` DDL에 MySQL 생성 컬럼 + UNIQUE 인덱스로 active row 단일성을 보장하고, `effective_from < effective_to` 기간 무결성 제약을 추가한다.
- [x] `agent_creator_relation` DDL에 MySQL 생성 컬럼 + UNIQUE 인덱스로 active row 단일성을 보장하고, `assigned_at < unassigned_at` 기간 무결성 제약을 추가한다.
- [x] 관리자 소속/비율 쓰기 경로가 `MemberRepository.findByIdForUpdate(...)` 기반 비관적 락과 unique violation 대응 패턴으로 직렬화되도록 보강한다.
- [x] AGENT 정산 조회가 거래 시점 기준의 소속 이력과 비율 이력을 조인하도록 `AgentCalculateQueryRepository`를 수정한다.
- [x] 기간 중 소속 변경 또는 비율 변경이 있는 경우 결과가 올바르게 분리/집계되는 테스트를 추가한다.
- [x] AGENT 정산 조회의 paged query가 사전 조회된 `creatorIds`가 빈 페이지일 때 전체 결과로 fallback하지 않고 빈 rows/items를 반환하도록 보강한다.
- [x] AGENT 정산 조회에서 `agent_settlement_ratio` 이력이 없으면 agent 정산금을 0% 대신 10% 기본값으로 계산하도록 수정한다.
- [x] 확정 정산 스냅샷 엔티티/리포지토리/관리자 API를 추가한다.
- [x] 확정 정산 스냅샷이 소속/비율 foreign key뿐 아니라 적용 비율과 계산 결과 숫자값을 함께 저장하도록 구현한다.
- 정정(2026-04-09): 현재 구현은 `appliedAgentSettlementRatio`와 계산 결과 숫자값은 저장하지만, 설계 초안에 명시한 `assignmentId`, `agentSettlementRatioId`는 아직 스냅샷/DDL에 포함하지 않았다. 따라서 이 항목은 엄밀히는 부분 충족 상태로 본다.
- [x] `AgentSettlementSnapshot``agent_settlement_snapshot` DDL에 `assignmentId`, `agentSettlementRatioId` 컬럼을 추가한다.
- [x] `AgentCalculateQueryRepository`와 snapshot 생성용 query DTO에 거래 시점 기준 `assignmentId`, `agentSettlementRatioId` projection을 추가한다.
- [x] `AdminAgentSettlementSnapshotService`와 관련 테스트를 갱신해 finalize 시점에 foreign key + 적용 비율 + 계산 숫자값이 함께 저장되도록 보완한다.
- 참고: 위 보완은 creator-period summary가 단일 소속/단일 비율 이력 row로 귀결되는 경우의 추적성은 복구하지만, 기간 중 복수 history row가 섞인 summary의 완전 provenance까지 보장하지는 않는다. 그 수준의 감사 추적이 필요하면 별도 snapshot source detail 테이블이 추가로 필요하다.
- [x] `agent_settlement_snapshot_source_detail`(가칭) DDL을 추가해 summary를 구성한 원천 source row별 provenance를 별도 저장한다.
- [x] finalize가 `raw source row`를 기준으로 source detail과 creator-period summary를 같은 트랜잭션 안에서 함께 저장하도록 보강한다.
- [x] source detail이 1건인 summary만 `assignmentId`, `agentSettlementRatioId`, `appliedAgentSettlementRatio`를 채우고, mixed-period summary는 `null`로 유지하는 규칙을 테스트로 고정한다.
- [x] finalized 기간 조회는 스냅샷 우선, 미확정 기간 조회는 live 계산을 사용하도록 분기한다.
- [x] 신규 이력/스냅샷 구조에 맞는 DDL 문서를 추가 또는 기존 DDL 문서를 확장한다.
- [x] 기존 계획 문서 하단 검증 기록에 이력형 전환과 스냅샷 도입 구현/검증 결과를 누적한다.
## 세부 구현 메모
### 1. 소속 관계 구현 기준
- 기존 `Member` 엔티티에 agent 필드를 직접 추가하지 않고, 전용 relation 테이블로 구현한다.
- 이유는 다음과 같다.
- 에이전트 전용 기능을 `partner.agent` 패키지에 응집시킬 수 있다.
- creator role 제약과 unique 제약을 명확히 걸 수 있다.
- 향후 소속 이력/상태 필드가 필요해져도 확장이 쉽다.
### 2. 정산 비율 구현 기준
- 기존 `CreatorSettlementRatio`가 도메인별 다중 비율을 관리하므로, 에이전트는 별도 `AgentSettlementRatio`로 분리하는 편이 자연스럽다.
- 필드는 단일 `settlementRatio`만 두고, 대상 회원은 `MemberRole.AGENT`로 제한한다.
### 3. 조회 응답 구현 기준
- 라이브/콘텐츠/커뮤니티의 기존 관리자 creator별 응답은 count/total 객체가 부족하므로 재사용보다 agent 전용 응답 신설이 적합하다.
- 채널후원 응답은 이미 `total + items` 구조가 있어 이를 가장 가까운 기준으로 삼는다.
- 에이전트 응답은 아래 공통 필드를 기준으로 통일한다.
- `creatorId`
- `creatorNickname`
- `count`
- `totalCan`
- `krw`
- `fee`
- `settlementAmount`
- `agentSettlementAmount`
- 필요 시 `tax`, `depositAmount`
### 4. 재사용 기준 파일
- 권한/인증 패턴: `AdminMemberController`, `CreatorAdminCalculateController`, `SecurityConfig`
- creator별 집계 패턴: `AdminCalculateQueryRepository`
- 채널후원 total 응답 패턴: `AdminChannelDonationCalculateService`, `GetAdminChannelDonationSettlementTotal`
- 정산 비율 검증 패턴: `CreatorSettlementRatioService`
### 5. 후속 보완 구현 순서 (2026-04-09 추가)
- 아래 보완 작업은 **ratio 입력 무결성 차단 → 관리자 쓰기 직렬화/DDL 보강 → snapshot traceability 연결 → 전체 검증** 순서로 진행한다.
#### 5-1. ratio 입력 무결성 차단부터 먼저 수정한다.
- [x] `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AgentSettlementRatioServiceTest.kt`에 아래 RED 테스트를 추가한다.
- [x] active row보다 과거 `effectiveFrom`으로 create/update 요청 시 예외가 발생한다.
- [x] active row와 같은 `effectiveFrom`으로 create/update 요청 시 예외가 발생한다.
- [x] active row가 없더라도 기존 closed history와 겹치는 `effectiveFrom`이면 예외가 발생한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt`에서 `effectiveFrom` backdate / same-time / history overlap을 거절하도록 검증을 추가한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/AgentSettlementRatioRepository.kt`에 history overlap 판별용 조회 메서드를 추가한다.
- [x] ratio 서비스 테스트를 재실행해 backdate 차단이 먼저 보장되는지 확인한다.
#### 5-2. 관리자 소속/비율 쓰기 경로를 직렬화한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt`에서 agent 대상 `MemberRepository.findByIdForUpdate(...)`를 사용하도록 수정한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorService.kt`에서 creator 대상 `MemberRepository.findByIdForUpdate(...)`를 사용하도록 수정한다.
- [x] 두 서비스 모두 `saveAndFlush` + `DataIntegrityViolationException` 대응 패턴으로 unique violation을 사용자 예외로 변환하도록 보강한다.
- [x] `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AgentSettlementRatioServiceTest.kt`에 락/unique violation 대응 케이스를 추가한다.
#### 5-3. active row 단일성과 기간 무결성을 DDL/엔티티에 맞춘다.
- [x] `docs/20260409_partner_agent_assignment_ratio_ddl.sql` 기준으로 `agent_creator_relation`, `agent_settlement_ratio`, `agent_settlement_snapshot` 최종 스키마가 구현 코드와 일치하는지 다시 점검한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AgentCreatorRelation.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/AgentSettlementRatio.kt`의 매핑이 DDL의 최종 컬럼 구조와 충돌하지 않는지 확인한다.
- [x] generated column(`active_creator_key`, `active_ratio_key`)은 JPA 쓰기 대상에서 제외하고, 서비스/리포지토리 로직이 해당 컬럼 없이도 동작하는지 확인한다.
#### 5-4. snapshot traceability를 query → service → entity 순서로 연결한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/AgentSettlementSnapshot.kt``assignmentId`, `agentSettlementRatioId` 필드를 추가한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt`와 snapshot 생성용 query DTO에 거래 시점 기준 `assignmentId`, `agentSettlementRatioId` projection을 추가한다.
- [x] `docs/20260409_partner_agent_assignment_ratio_ddl.sql``agent_settlement_snapshot_source_detail`(가칭) 테이블을 추가하고, `snapshot_id`, `assignment_id`, `agent_settlement_ratio_id`, source subtotal 컬럼, 조회 인덱스/FK를 정의한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/**`에 source detail 엔티티/리포지토리 파일을 추가한다.
- [x] `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt``raw source row` 기준의 source detail과 creator-period summary를 함께 저장하도록 바꾼다.
- [x] creator-period summary가 단일 source row로 귀결될 때만 `assignmentId`, `agentSettlementRatioId`, `appliedAgentSettlementRatio`를 summary에 채우고, mixed-period summary는 `null`로 저장하도록 매핑 규칙을 고정한다.
- [x] `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotServiceTest.kt`에 아래 검증을 추가한다.
- [x] 단일 source summary는 summary row와 detail row가 같은 `assignmentId`, `agentSettlementRatioId`, `appliedAgentSettlementRatio`를 가진다.
- [x] mixed-period summary는 summary의 `assignmentId`, `agentSettlementRatioId`, `appliedAgentSettlementRatio``null`이고, detail row 여러 건으로 provenance를 복원할 수 있다.
- [x] detail 합계가 summary 숫자값과 일치한다.
#### 5-5. 최종 회귀 검증을 수행한다.
- [x] `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.assignment.*"`
- [x] `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.ratio.*"`
- [x] `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.settlement.*"`
- [x] `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"`
- [x] `./gradlew test`
- [x] `./gradlew build`
## 검증 계획
- 단위/서비스 테스트
- agent와 creator role 검증
- creator 중복 소속 방지
- 비소속 creator 조회 차단
- agentSettlementAmount 반올림 계산 검증
- category별 total 합계 검증
- 통합 성격 검증
- ADMIN assignment API 정상/실패 케이스
- AGENT 목록/정산 조회 API 정상/권한 실패 케이스
- 빌드 검증
- `./gradlew test`
- `./gradlew build`
---
## 검증 기록
### 계획 수립 1차
- 무엇을: 에이전트 권한, 소속 관계, 관리자 소속 관리 API, 에이전트 정산 비율, 에이전트 전용 크리에이터별 정산 조회 기능의 구현 범위를 분석하고 작업 계획 문서를 작성했다.
- 왜: 기존 코드베이스에 이미 존재하는 `AGENT` 역할, 관리자 정산 모듈, 크리에이터 정산 비율 모듈을 기준으로 중복 구현 없이 가장 자연스러운 확장 경로를 먼저 확정하기 위해서다.
- 어떻게:
- 권한/역할/정산 패턴을 코드 기준으로 확인했다.
- `docs` 폴더의 기존 작업 계획 문서 형식을 확인해 동일한 형식으로 문서를 작성했다.
- 실행/확인 결과:
- `explore` 백그라운드 탐색 2건(권한 패턴, 정산 패턴) → 완료
- `read`로 확인한 기준 파일: `Member.kt`, `AdminMemberController.kt`, `AdminMemberService.kt`, `SecurityConfig.kt`, `AdminCalculateController.kt`, `AdminCalculateService.kt`, `AdminCalculateQueryRepository.kt`, `CreatorAdminCalculateController.kt`, `CreatorAdminCalculateService.kt`, `AdminChannelDonationCalculateController.kt`, `AdminChannelDonationCalculateService.kt`, `ChannelDonationSettlementCalculator.kt`
- `./gradlew test` / `./gradlew build` → 문서 작성 단계이므로 미실행
### 1차 구현 1차
- 무엇을: 공유 도메인인 `partner.agent.assignment`, `partner.agent.ratio`와 관리자 진입점인 `admin.partner.agent.assignment`, `admin.partner.agent.ratio` 패키지에 관리자 전용 assignment/remove, ratio create/update/list 기능과 테스트, 신규 테이블 DDL 문서를 추가했다.
- 왜: 전체 에이전트 기능 중 첫 vertical slice로서 관리자 관점의 소속 관리와 정산 비율 관리부터 독립적으로 배포 가능한 최소 단위를 먼저 완성하기 위해서다.
- 어떻게:
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/**`에 relation 엔티티/리포지토리/요청 DTO를 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/**`에 관리자 전용 서비스/컨트롤러를 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/**`에 ratio 엔티티/리포지토리(querydsl)/요청·응답 DTO를 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/**`에 관리자 전용 서비스/컨트롤러를 추가했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/**`에 assignment/ratio 서비스·컨트롤러 테스트를 추가하고 TDD 순서로 RED→GREEN을 확인했다.
- `docs/20260409_partner_agent_assignment_ratio_ddl.sql``agent_creator_relation`, `agent_settlement_ratio` 생성 스크립트를 추가했다.
- 실행/확인 결과:
- `lsp_diagnostics` on `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent`, `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent` → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.assignment.*"` → 1차 실행 실패(신규 클래스 unresolved reference), 구현 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.ratio.*"` → 1차 실행 실패(신규 클래스 unresolved reference), 구현 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.*"` → 성공
- `./gradlew test` → 성공
- `./gradlew build` → 성공
### 2차 구현 1차
- 무엇을: `partner.agent.calculate` 패키지 아래에 AGENT 전용 소속 크리에이터 목록 조회 API와 라이브/콘텐츠/커뮤니티/채널후원/콘텐츠후원 creator-level summary API, QueryRepository, 응답 DTO, 테스트를 추가했다.
- 왜: 에이전트 기능의 두 번째 vertical slice로서 실제 에이전트 계정이 본인에게 배정된 크리에이터 범위 안에서만 정산 현황을 볼 수 있도록 기능을 완성하기 위해서다.
- 어떻게:
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/**``AgentCalculateController`, `AgentCalculateService`, `AgentCalculateQueryRepository`, assigned creator 응답 DTO, 일반 정산 summary DTO, 채널후원 summary DTO를 추가했다.
- AGENT 인증 패턴은 `@PreAuthorize("hasRole('AGENT')")``@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?` 가드절로 맞췄다.
- QueryRepository는 `agent_creator_relation` 조인으로 소속 크리에이터만 필터링하고, 콘텐츠 summary는 creator별 페이지를 유지하면서 콘텐츠별 정산 비율 버킷을 병합하도록 구현했다.
- `agentSettlementAmount`는 모든 응답에서 creator의 세전 `settlementAmount` 기준으로 `round(settlementAmount * agentRatio / 100)`를 적용했고, creator `settlementAmount`/`depositAmount` 자체는 차감하지 않았다.
- 스키마 변경은 없어서 추가 DDL 문서는 만들지 않았다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"` → 1차 실행 실패(신규 클래스 unresolved reference), 구현 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.*"` → 성공
- `lsp_diagnostics` on `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate`, `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate` → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `./gradlew test` → 성공
- `./gradlew build` → 최초 1회 `ktlintMainSourceSetCheck` 실패(신규 `AgentCalculateService.kt` 줄바꿈 규칙 위반), 포맷 수정 후 재실행 성공
### 3차 수정
- 무엇을: `partner.agent.assignment.*`, `partner.agent.ratio.*` 예외 키를 `SodaMessageSource`에 등록하고, 해당 메시지 조회를 보장하는 단위 테스트를 추가했다.
- 왜: 기능 자체는 동작하더라도 메시지 소스에 키가 없으면 런타임에서 사용자에게 의도한 다국어 문구 대신 키 문자열 또는 빈 메시지가 노출될 수 있기 때문이다.
- 어떻게:
- `src/test/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSourceTest.kt`를 추가해 `partner.agent.assignment.creator_already_assigned`, `partner.agent.ratio.invalid_agent`, `partner.agent.ratio.not_found` 메시지 조회를 RED→GREEN으로 검증했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt``partnerAgentMessages` 맵을 추가하고 `getMessage` 그룹 목록에 포함시켰다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.i18n.SodaMessageSourceTest"` → 1차 실행 실패(메시지 미등록), 수정 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.*"` → 성공
- `./gradlew test` → 성공
- `./gradlew build` → 성공
- `jshell --class-path "build/classes/kotlin/main:build/resources/main:...kotlin-stdlib..."` 수동 확인 →
- `partner.agent.assignment.creator_already_assigned` 한국어 메시지 출력: `이미 다른 에이전트에 소속된 크리에이터입니다.`
- `GetAgentCreatorSettlementSummaryQueryData(21, creator-a, 2, 100, 70).toResponseItem(10)` 출력: `agentSettlementAmount=654`
- `GetAgentChannelDonationSettlementByCreatorQueryData(21, creator-a, 1, 50).toResponseItem(10)` 출력: `agentSettlementAmount=397`
### 4차 수정
- 무엇을: 관리자 전용 partner-agent controller/service 진입점을 `kr.co.vividnext.sodalive.admin.partner.agent.*` 아래로 재배치했다.
- 왜: 이 저장소의 기존 관례상 관리자 전용 진입점은 `admin.*` 계층에 두는 편이 더 일관적이고, 공유 도메인 객체와 관리자 API 진입점을 분리하는 것이 책임 경계를 더 명확하게 만들기 때문이다.
- 어떻게:
- `AdminAgentCreatorController`, `AdminAgentCreatorService``admin.partner.agent.assignment`로 이동하고, relation/request DTO/repository는 `partner.agent.assignment`에 유지했다.
- `AdminAgentSettlementRatioController`, `AdminAgentSettlementRatioService``admin.partner.agent.ratio`로 이동하고, ratio 엔티티/리포지토리/DTO는 `partner.agent.ratio`에 유지했다.
- assignment/ratio 테스트 패키지도 `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/**`로 이동했다.
- 계획 문서의 권장 설계/패키지 초안을 실제 구조에 맞게 갱신했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.assignment.*"` → 1차 실행 실패(새 패키지 unresolved reference), 이동 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.ratio.*"` → 1차 실행 실패(새 패키지 unresolved reference), 이동 후 재실행 성공
- `grep "kr.co.vividnext.sodalive.partner.agent.(assignment|ratio).AdminAgent" src/**/*.kt` 성격 확인 → 코드 기준 잔존 참조 없음
- `./gradlew test` → 성공
- `./gradlew build` → 성공
- `jshell --class-path "build/classes/kotlin/main:build/resources/main:...kotlin-stdlib..."` 수동 확인 →
- `kr.co.vividnext.sodalive.admin.partner.agent.assignment.AdminAgentCreatorController` 로딩 성공
- `kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioController` 로딩 성공
- 기존 `kr.co.vividnext.sodalive.partner.agent.assignment.AdminAgentCreatorController` / `...ratio.AdminAgentSettlementRatioController``ClassNotFoundException`으로 미존재 확인
### 5차 수정
- 무엇을: `agent_creator_relation``assignedAt/unassignedAt` 기반 append-only 이력 모델로 전환하고, 관리자 소속 지정/해제 API를 시간 경계 기반 계약으로 수정했다.
- 왜: 기존 current-state + hard-delete 구조로는 과거 소속 이력을 보존할 수 없고, 소속 해제 후 재배정 같은 운영 시나리오를 안전하게 표현할 수 없기 때문이다.
- 어떻게:
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AgentCreatorRelation.kt``assignedAt`, `unassignedAt`를 추가하고 `creator` 연관을 `ManyToOne`으로 변경해 동일 creator의 이력 row 누적을 허용했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AssignAgentCreatorRequest.kt`, `RemoveAgentCreatorRequest.kt`에 명시적 시간 필드를 추가하고, `AdminAgentCreatorService.kt`에서 overlap 검증 및 hard-delete 대신 종료 시각 기록으로 동작을 바꿨다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt`에는 현재 동작 유지용으로 `unassignedAt is null` 조건만 추가해 active assignment 조회가 계속 현재 row만 보도록 맞췄다.
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorServiceTest.kt`, `AdminAgentCreatorControllerTest.kt`를 TDD로 갱신해 RED에서 새 계약 부재를 확인한 뒤 GREEN으로 구현했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt`는 새 not-null `assignedAt` 계약에 맞게 relation fixture를 갱신했다.
- `docs/20260409_partner_agent_assignment_ratio_ddl.sql``agent_creator_relation` 생성 스키마를 이력형 컬럼/인덱스로 바꾸고, 기존 테이블에 대한 `assigned_at`, `unassigned_at`, unique index 제거, backfill migration 블록을 추가했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.assignment.*"` → 1차 실행 실패(새 `assignedAt/unassignedAt`, repository 메서드, 엔티티 필드 부재), 구현 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.assignment.*" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest" --tests "kr.co.vividnext.sodalive.i18n.SodaMessageSourceTest"` → 성공
- `lsp_diagnostics` on modified Kotlin files/directories → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `./gradlew test``./gradlew build`를 병렬 실행 → 실패 (`build/test-results/test/*.xml` 동시 쓰기 충돌)
- `./gradlew test` 순차 재실행 → 성공
- `./gradlew build` 순차 재실행 → 성공
### 6차 수정
- 무엇을: `agent_settlement_ratio``effectiveFrom/effectiveTo` 기반 append-only 이력 모델로 전환하고, 관리자 비율 생성/수정/목록 API 계약을 유효 기간 노출 방식으로 갱신했다.
- 왜: 기존 `deletedAt` + 단일 current-row 갱신 방식으로는 과거 비율 이력을 보존할 수 없어서, 이후 거래 시점 기준 정산 조회나 운영 감사 시나리오에 필요한 근거 데이터를 남길 수 없기 때문이다.
- 어떻게:
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AgentSettlementRatioServiceTest.kt`, `AdminAgentSettlementRatioControllerTest.kt`를 먼저 수정해 `effectiveFrom` 입력, `effectiveFrom/effectiveTo` 응답, 기존 활성 row 종료 + 신규 row 추가 동작을 RED로 만들었다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/AgentSettlementRatio.kt``effectiveFrom/effectiveTo` 필드와 `close(...)` 메서드를 가진 이력 엔티티로 바꾸고, `member` 연관을 `ManyToOne`으로 변경해 동일 agent의 다중 이력 row를 허용했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/CreateAgentSettlementRatioRequest.kt`, `GetAgentSettlementRatioResponse.kt`, `AgentSettlementRatioRepository.kt`를 갱신해 요청/응답 계약과 active lookup 메서드 `findFirstByMemberIdAndEffectiveToIsNull(...)`를 도입했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt`에서 create/update 모두 기존 활성 row를 `effectiveTo`로 닫은 뒤 새 row를 저장하도록 수정했고, `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateService.kt`는 현재 활성 비율 lookup만 새 repository 메서드로 맞췄다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt` fixture도 새 repository 메서드와 `effectiveFrom` 필수 생성자에 맞게 최소 호환 수정했다.
- `docs/20260409_partner_agent_assignment_ratio_ddl.sql`에는 `agent_settlement_ratio` 생성 스키마를 `effective_from/effective_to` + history index 구조로 변경하고, 기존 테이블에 대한 컬럼 추가/backfill/unique index 제거/`deleted_at` 제거 migration 블록을 확장했다.
- 실행/확인 결과:
- `./gradlew test --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioControllerTest` → 1차 실행 실패(새 `effectiveFrom/effectiveTo` 계약, repository 메서드, 엔티티 필드 부재), 구현 후 재실행 성공
- `lsp_diagnostics` on modified Kotlin files/directories → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `./gradlew test --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioControllerTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest` → 성공
- `./gradlew build` → 성공
### 7차 수정
- 무엇을: `AgentCalculateQueryRepository`, `AgentCalculateService`, agent calculate 응답용 query DTO와 테스트를 이벤트 시점 기준 소속/agent ratio 계산 방식으로 수정해, 기간 중 재배정과 agent 비율 변경이 있어도 다섯 개 정산 카테고리의 creator-level 결과가 당시 기준으로 집계되도록 바꿨다.
- 왜: 기존 구현은 거래 발생 시각으로 기간만 자르고, 소속은 현재 active relation, agent 비율은 현재 active ratio 한 개를 전체 기간에 적용하고 있어서 중간 재배정/비율 변경이 생기면 과거 조회 결과가 잘못 왜곡됐기 때문이다.
- 어떻게:
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt`에 기간 중 소속 변경, 기간 중 agent ratio 변경을 재현하는 통합 성격 테스트를 먼저 추가해 RED를 확인했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt`에서 라이브/콘텐츠/커뮤니티/채널후원/콘텐츠후원 모두에 대해 거래 시각 기준 `assignedAt <= eventTime < unassignedAt`, `effectiveFrom <= eventTime < effectiveTo` 조건으로 이력 row를 조인하도록 수정했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentCreatorSettlementSummaryQueryData.kt`, `GetAgentChannelDonationSettlementByCreatorResponse.kt`, `AgentCalculateService.kt`를 바꿔 row별 agent ratio를 응답 아이템 변환 시 적용하고, 채널후원 포함 전 카테고리에서 creator 기준 merge 후 total을 계산하도록 정리했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt`도 row별 agent ratio 기대값으로 갱신해 서비스 레벨 병합 규칙을 검증했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest"` → 1차 실행 실패(기간 중 소속 변경/agent ratio 변경 2건 assertion failure), 구현 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"` → 성공
- `lsp_diagnostics` on modified Kotlin files → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `./gradlew test` → 성공
- `./gradlew build` → 1차 `ktlintTestSourceSetCheck` 실패(신규 테스트 장문 라인), 2차 `ktlintMainSourceSetCheck` 실패(import 순서/장문 line), 포맷 수정 후 재실행 성공
### 8차 수정
- 무엇을: `partner.agent.settlement.snapshot` 패키지에 immutable creator-level snapshot 저장 모델과 repository/request-response mapper를 추가하고, `admin.partner.agent.settlement`에 확정 API를 만들었으며, `AgentCalculateService`가 finalized 기간이면 스냅샷을 우선 읽도록 다섯 카테고리 전체를 연결했다.
- 왜: 이력형 소속/비율 계산만으로는 확정 시점의 creator-level 결과를 별도 보존하거나 재사용할 수 없어서, 이후 읽기에서 동일 기간을 다시 계산하지 않고도 확정된 숫자값을 안정적으로 반환할 수 있어야 했기 때문이다.
- 어떻게:
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotServiceTest.kt`, `AdminAgentSettlementSnapshotControllerTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt`에 snapshot 생성/idempotency/finalized snapshot-first read RED 테스트를 먼저 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/**``AgentSettlementSnapshot`, `AgentSettlementSnapshotRepository`, `FinalizeAgentSettlementSnapshotRequest/Response`, snapshot-to-response mapper를 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt`에서 기존 `AgentCalculateQueryRepository` live 계산 결과를 creator-level 응답으로 병합한 뒤 숫자값을 그대로 스냅샷 row에 저장하고, 동일 기간/타입/agent 조합은 `exists...` 검사로 idempotent하게 막도록 구현했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotController.kt``POST /admin/partner/agent/settlement/finalize`를 추가하고, 인증 관리자 `member.id``finalizedByMemberId`로 전달하도록 맞췄다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateService.kt`는 라이브 계산 전에 스냅샷 repository를 먼저 조회하고, 스냅샷이 있으면 generic 4종과 channel donation 1종 모두 동일 응답 DTO로 변환해 반환하도록 분기했다.
- `docs/20260409_partner_agent_assignment_ratio_ddl.sql``agent_settlement_snapshot` 테이블과 lookup/unique 인덱스 DDL을 추가했다.
- 실행/확인 결과:
- `lsp_diagnostics` on `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate`, `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot`, `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement`, `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate`, `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement` → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.settlement.*" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest"` → 1차 실행 실패(신규 snapshot 도메인/서비스/분기 미구현), 구현 및 테스트 fixture 수정 후 재실행 성공
- `./gradlew test` → 성공
- `./gradlew build` → 1차 `ktlintTestSourceSetCheck` 실패(import 순서/unused import), 2차 `ktlintMainSourceSetCheck` 실패(import 순서/장문 line), 포맷 수정 후 재실행 성공
### 9차 수정
- 무엇을: 최종 정리 과정에서 `AgentCalculateService`의 request-wide current ratio 의존성을 제거하고, 관련 테스트/빌드/수동 확인까지 다시 수행했다.
- 왜: 시점 기준 정산 조회와 finalized snapshot-first read로 전환된 뒤에는 `ratioRepository`가 더 이상 `AgentCalculateService`에서 직접 사용되지 않으므로, 잔존 의존성을 남기지 않는 것이 실제 설계와 코드 구조를 일치시키기 때문이다.
- 어떻게:
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateService.kt`에서 사용되지 않는 `ratioRepository` 의존성을 제거했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt`, `AgentCalculateQueryRepositoryTest.kt`의 생성자 호출부를 새 시그니처에 맞게 정리했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest" --tests "kr.co.vividnext.sodalive.admin.partner.agent.settlement.*"` → 1차 실행 실패(`AgentCalculateQueryRepositoryTest`의 구 생성자 시그니처 참조), 수정 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest" --tests "kr.co.vividnext.sodalive.admin.partner.agent.settlement.*"` → 성공
- `./gradlew test && ./gradlew build` → 성공
- `jshell --class-path "build/classes/kotlin/main:build/resources/main:...kotlin-stdlib..."` 수동 확인 →
- `FinalizeAgentSettlementSnapshotRequest(agentId=7, settlementType=LIVE, startDateStr=2026-02-20, endDateStr=2026-02-21).toDateRange()` 출력 확인
- `AgentSettlementSnapshot` 1건을 `toSettlementByCreatorItems()`로 변환했을 때 `agentSettlementAmount=654` 포함 응답 아이템 출력 확인
- `kr.co.vividnext.sodalive.admin.partner.agent.settlement.AdminAgentSettlementSnapshotController` 클래스 로딩 성공
### 10차 정정
- 무엇을: 체크리스트 279번의 충족 범위를 문서/코드 기준으로 다시 대조하고, 누락된 후속 구현 항목을 기존 계획 문서에 추가했다.
- 왜: 현재 스냅샷 구현은 `appliedAgentSettlementRatio`와 계산 숫자값은 저장하지만, 설계 초안과 체크리스트 문맥이 요구하는 `assignmentId`, `agentSettlementRatioId`는 저장하지 않아 문서 기준 완전 충족으로 보기 어려웠기 때문이다.
- 어떻게:
- 체크리스트 279 바로 아래에 정정 메모와 후속 체크박스 3개를 추가해 누락 범위를 `snapshot 컬럼`, `query projection`, `finalize 매핑/테스트`로 분리했다.
- 문서 설계 초안(`assignmentId`, `agentSettlementRatioId`)과 현재 구현(`AgentSettlementSnapshot`, `AdminAgentSettlementSnapshotService`, `agent_settlement_snapshot` DDL)을 대조해 차이를 명시했다.
- 실행/확인 결과:
- `docs/20260408_에이전트권한및정산기능추가.md:106-123, 278-282` 확인 → 스냅샷 설계 초안과 체크리스트가 `assignmentId`, `agentSettlementRatioId`, `appliedAgentSettlementRatio`, 계산 숫자값 저장을 함께 기대함을 재확인
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/AgentSettlementSnapshot.kt` 확인 → 현재는 `appliedAgentSettlementRatio`와 계산 숫자값만 저장하고 `assignmentId`, `agentSettlementRatioId` 필드는 없음
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt` 확인 → finalize 매핑이 ratio 값과 숫자값만 채우고 FK 값은 생성하지 않음
- `docs/20260409_partner_agent_assignment_ratio_ddl.sql` 확인 → `agent_settlement_snapshot` 테이블에도 `assignment_id`, `agent_settlement_ratio_id` 컬럼이 없음
### 11차 정정
- 무엇을: ratio backdate 금지, active row 단일성 보장, 관리자 쓰기 직렬화, snapshot traceability 범위 메모를 기존 체크리스트에 후속 작업으로 추가했다.
- 왜: 현재 `AdminAgentSettlementRatioService``effectiveFrom`의 시간순 검증 없이 활성 row를 닫고 새 row를 저장해 backdate 시 interval 무결성이 깨질 수 있고, `agent_settlement_ratio`/`agent_creator_relation` DDL은 active row 중복을 막는 DB 제약이 없어 동시 요청 시 조회 결과 왜곡 위험이 있기 때문이다. 또한 snapshot의 `assignmentId`/`agentSettlementRatioId` 보완은 문서 279의 의도는 충족하지만 mixed-period creator summary의 완전 provenance까지는 아님을 분명히 할 필요가 있었다.
- 어떻게:
- 체크리스트 ratio 구간에 `effectiveFrom` backdate/same-time/overlap 거절 검증, `agent_settlement_ratio`/`agent_creator_relation`의 MySQL 생성 컬럼 + UNIQUE 인덱스 + 기간 무결성 제약, `MemberRepository.findByIdForUpdate(...)` 기반 비관적 락 적용 항목을 추가했다.
- snapshot 279 보완 항목 아래에 `assignmentId`/`agentSettlementRatioId` 추가가 creator-period summary 기준 추적성은 복구하지만, mixed-period summary의 완전 provenance가 필요하면 별도 source detail 테이블이 필요하다는 참고 메모를 추가했다.
- 실행/확인 결과:
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt` 확인 → `effectiveFrom`의 시간순/겹침 검증 없이 active row close + insert 수행
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorService.kt` 확인 → overlap 검증은 있지만 비관적 락/DB active uniqueness 없이 read-then-write 수행
- `src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt`, `member/contentpreference/MemberContentPreferenceService.kt`, `member/contentpreference/MemberContentPreferenceRepository.kt` 확인 → `findByIdForUpdate`, `@Lock(PESSIMISTIC_WRITE)`, `saveAndFlush + DataIntegrityViolationException` 재조회 패턴이 저장소 기존 관례로 존재함
- `docs/20260409_partner_agent_assignment_ratio_ddl.sql` 확인 → 현재 `agent_settlement_ratio`, `agent_creator_relation` 모두 active row 단일성 보장용 unique 제약과 기간 무결성 제약이 없음
- MySQL 제약 조사 결과 확인 → partial unique index 대신 생성 컬럼 + UNIQUE 인덱스가 가장 실용적이며, nullable unique를 직접 사용하는 방식은 active row 중복을 막지 못함
### 12차 정정
- 무엇을: 남은 후속 수정 범위를 실제 구현 순서대로 더 잘게 쪼갠 작업 계획으로 재배치했다.
- 왜: 현재 체크리스트는 해야 할 항목은 보이지만, 어떤 순서로 진행해야 리스크가 가장 적은지와 어떤 파일부터 수정해야 하는지가 한 번에 드러나지 않았기 때문이다.
- 어떻게:
- `세부 구현 메모` 아래에 `### 5. 후속 보완 구현 순서 (2026-04-09 추가)` 섹션을 새로 만들고, 작업을 `ratio 입력 무결성 차단 → 관리자 쓰기 직렬화 → DDL/엔티티 정합성 점검 → snapshot traceability 연결 → 최종 회귀 검증` 순서로 나눴다.
- 각 단계마다 실제 수정 대상 파일 경로와 테스트/검증 명령을 체크박스로 세분화해, 바로 실행 가능한 작업 순서 문서가 되도록 정리했다.
- 실행/확인 결과:
- `docs/20260408_에이전트권한및정산기능추가.md:326-361` 확인 → 후속 보완 구현 순서 섹션과 5개 단계 체크리스트가 문서에 반영됨
- `grep` 확인 → `### 5. 후속 보완 구현 순서`, `MemberRepository.findByIdForUpdate(...)`, `assignmentId`, `agentSettlementRatioId`, `./gradlew test`, `./gradlew build`가 새 순서형 계획에 포함됨
- 코드/빌드 실행 여부 → 문서 수정 단계이므로 미실행
### 13차 정정
- 무엇을: snapshot traceability 문제를 `summary FK 보완 + full audit provenance detail`까지 포함해 닫는 방향으로 체크리스트를 확장했다.
- 왜: 현재 creator-period summary snapshot은 `creatorId` 기준 병합 후 한 줄로 저장되므로, `assignmentId`/`agentSettlementRatioId`만 summary에 추가해도 mixed-period 구간의 원천 provenance는 완전히 복원되지 않기 때문이다. 완전 감사 추적이 필요하면 summary와 별도로 source detail row를 함께 저장해야 한다.
- 어떻게:
- snapshot 체크리스트 아래에 `agent_settlement_snapshot_source_detail`(가칭) DDL 추가, finalize의 `raw source row -> source detail 저장 -> summary 저장` 순서 보강, mixed-period summary null 규칙 테스트 고정 항목을 추가했다.
- `세부 구현 메모 > 5-4`를 확장해 DDL → detail entity/repository → finalize 저장 순서 → summary null 규칙 → detail-summary 합계 검증까지 실제 구현 순서대로 재배치했다.
- 실행/확인 결과:
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt` 확인 → 현재는 `rows.toMergedResponseItems()`로 creator-period summary만 저장하고 source detail 저장 단계는 없음
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentCreatorSettlementSummaryQueryData.kt`, `GetAgentChannelDonationSettlementByCreatorResponse.kt` 확인 → `creatorId` 기준 병합 구조가 mixed-period provenance 소실의 직접 원인임
- `docs/20260408_에이전트권한및정산기능추가.md:106-123` 확인 → 문서 설계 초안은 summary FK/숫자 스냅샷까지는 기대하지만, full audit provenance는 별도 설계가 필요함
- Oracle/탐색 결과 확인 → `assignmentId + agentSettlementRatioId` summary 보완은 부분 해결이고, `source detail`까지 포함해야 mixed-period provenance 공백이 닫힘
### 14차 구현 및 정리
- 무엇을: 문서에서 미체크로 남아 있던 ratio/assignment 무결성, 쓰기 직렬화, snapshot traceability/source detail provenance, 최종 검증 항목을 실제 구현 결과에 맞게 모두 완료 처리하고, 검증 기록을 최신 상태로 갱신했다.
- 왜: 체크리스트와 실제 코드 상태가 어긋나 있으면 이후 작업자가 범위를 잘못 이해하거나, 이미 끝난 작업을 다시 추적해야 하는 비용이 생기기 때문이다. 또한 이번 변경은 정산 무결성과 확정 스냅샷 provenance를 함께 건드렸기 때문에, 최신 검증 결과를 문서에 남겨두는 것이 필수였다.
- 어떻게:
- `AdminAgentSettlementRatioService`, `AdminAgentCreatorService`, `AgentSettlementSnapshot`, `AdminAgentSettlementSnapshotService`, `AgentCalculateQueryRepository`, `docs/20260409_partner_agent_assignment_ratio_ddl.sql`을 기준으로 미체크 항목을 다시 대조하고, 실제 구현된 항목은 모두 `- [x]`로 갱신했다.
- ratio 쪽에는 `effectiveFrom` backdate/same-time/history overlap 차단과 concurrent unique violation 변환 테스트를 추가했고, assignment 쪽에는 `findByIdForUpdate(...)` + `saveAndFlush` + `DataIntegrityViolationException` 대응 패턴과 테스트를 맞췄다.
- snapshot 쪽에는 `assignmentId`, `agentSettlementRatioId`, `agent_settlement_snapshot_source_detail` provenance 저장, mixed-period summary null 규칙, detail 합계 검증까지 반영했다.
- 실행/확인 결과:
- `grep "^- \[ \]|^ - \[ \]" docs/20260408_에이전트권한및정산기능추가.md` → 미체크 항목 0건 확인
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest"` → 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.settlement.AdminAgentSettlementSnapshotServiceTest"` → 성공
- `./gradlew test` → 성공
- `./gradlew build` → 성공
- `Oracle` completion review → 현재 문서 기준 미체크 항목 판단을 검토한 뒤, 테스트/문서 정리를 마치면 완료 처리 가능하다는 방향 재확인
### 15차 수정
- 무엇을: `agent_settlement_ratio` 이력이 없는 AGENT 정산 조회에서 agent 정산금을 0%가 아니라 10% 기본값으로 계산하도록 수정하고, 해당 동작을 서비스 테스트와 수동 계산으로 검증했다.
- 왜: 현재 구현은 agent 비율 row가 없으면 agent 정산금이 0원으로 계산되는데, 이번 정책 결정은 미설정 상태를 10% 기본 비율로 간주하는 것이기 때문이다.
- 어떻게:
- `docs/20260408_에이전트권한및정산기능추가.md` 체크리스트에 기본 fallback 10% 변경 항목을 추가한 뒤 완료 처리했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt``agentSettlementRatio = null`일 때 일반 정산/채널후원 응답이 10%를 적용하는 RED 테스트 2건을 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentCreatorSettlementSummaryQueryData.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentChannelDonationSettlementByCreatorResponse.kt`에서 null fallback을 `DEFAULT_AGENT_SETTLEMENT_RATIO = 10`으로 변경했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest.shouldApplyDefaultAgentSettlementRatioWhenAgentRatioHistoryDoesNotExist" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest.shouldApplyDefaultAgentSettlementRatioToChannelDonationWhenAgentRatioHistoryDoesNotExist"` → 1차 실행 실패, 수정 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"` → 성공
- `./gradlew build` → 성공
- `lsp_diagnostics` on Kotlin changed files → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
- `jshell --class-path "build/classes/kotlin/main:build/resources/main:..."` 수동 확인 → `genericAgentSettlementAmount=131`, `channelAgentSettlementAmount=397`
### 16차 수정
- 무엇을: 관리자 에이전트 정산 비율 생성/수정 API에 `settlementRatio` 0..100 범위 검증을 추가하고, 문서 체크리스트 미완료 항목을 실제 구현 상태로 정리했다.
- 왜: 계획 문서에는 범위 검증 항목이 남아 있었지만 실제 서비스에는 guard가 없어 음수나 100 초과 비율이 저장될 수 있었기 때문이다.
- 어떻게:
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AgentSettlementRatioServiceTest.kt``settlementRatio = -1` 생성, `settlementRatio = 101` 수정 요청이 `common.error.invalid_request`를 던지고 `memberRepository`/`repository`가 호출되지 않는다는 RED 테스트 2건을 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt``validateSettlementRatio(settlementRatio: Int)`를 추가하고 `createAgentSettlementRatio`, `updateAgentSettlementRatio` 진입부에서 0..100 범위를 먼저 검증하도록 수정했다.
- `docs/20260408_에이전트권한및정산기능추가.md`의 체크리스트 277 항목을 완료 처리했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest.shouldThrowWhenCreatingRatioWithSettlementRatioBelowZero" --tests "kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest.shouldThrowWhenUpdatingRatioWithSettlementRatioAboveHundred"` → 1차 실행 실패, 서비스 수정 후 재실행 성공
- `./gradlew build` → 성공
- `jshell --class-path "/Users/klaus/Develop/sodalive/Server/sodalive/build/classes/kotlin/main:/Users/klaus/Develop/sodalive/Server/sodalive/build/resources/main:..."` 수동 확인 → `messageKey=common.error.invalid_request`, `memberRepositoryCalls=0`, `repositoryCalls=0`
- `lsp_diagnostics` on Kotlin changed files → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)
### 17차 수정
- 무엇을: `creator/list` 현재 소속 판정을 현재 시각 활성 구간 기준으로 바로잡고, AGENT 정산 조회의 빈 페이지가 전체 결과로 새는 pagination 버그를 함께 수정했다.
- 왜: 기존 구현은 미래 `assignedAt` 소속을 너무 일찍 노출하고 미래 `unassignedAt` 소속을 너무 일찍 숨겼으며, paged query의 사전 조회 `creatorIds`가 빈 리스트일 때 2차 rows query가 전체 결과를 다시 읽을 수 있었기 때문이다.
- 어떻게:
- `docs/20260408_에이전트권한및정산기능추가.md` 체크리스트에 두 버그 수정 항목을 추가한 뒤 완료 처리했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt`에 현재 시각 활성 구간 creator list 회귀 테스트와, 5개 카테고리 paged query가 빈 페이지에서 빈 rows를 반환하는 회귀 테스트를 RED로 추가했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateService.kt`에서 `getAssignedCreators()``currentTime`을 한 번만 잡아 count/items 조회에 공유하도록 수정했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt`에서 creator list 쿼리를 `assignedAt <= now < unassignedAt` 반열린 구간으로 바꾸고, 5개 paged calculate 메서드가 사전 조회 `creatorIds`가 빈 리스트면 즉시 `emptyList()`를 반환하도록 보강했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt`는 변경된 repository 시그니처에 맞춰 현재 시각 인자를 검증하도록 갱신했다.
- 실행/확인 결과:
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldGetAssignedCreatorsOnlyWithinCurrentAssignmentWindow" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldReturnEmptyRowsWhenPagedCreatorSelectionIsEmptyAcrossAllCategories"` → 1차 실행 실패, 수정 후 재실행 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest"` → 성공
- `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"` → 성공
- `./gradlew build` → 성공
- `lsp_diagnostics` on changed Kotlin files → 불가 (현재 환경에 `.kt`용 LSP 서버 미구성)

View File

@@ -0,0 +1,658 @@
SET @schema_name := DATABASE();
SET @agent_creator_relation_table_exists := (
SELECT COUNT(1)
FROM information_schema.tables
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
);
SET @create_agent_creator_relation_table_sql := IF(
@agent_creator_relation_table_exists = 0,
'CREATE TABLE agent_creator_relation (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT ''PK'',
agent_id BIGINT NOT NULL COMMENT ''에이전트 회원 ID (member.id 참조)'',
creator_id BIGINT NOT NULL COMMENT ''크리에이터 회원 ID (member.id 참조)'',
assigned_at TIMESTAMP NOT NULL COMMENT ''소속 시작 시각'',
unassigned_at TIMESTAMP NULL DEFAULT NULL COMMENT ''소속 종료 시각(NULL이면 현재 활성 소속)'',
active_creator_key TINYINT GENERATED ALWAYS AS (
CASE
WHEN unassigned_at IS NULL THEN 1
ELSE NULL
END
) STORED,
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''생성 시각'',
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ''수정 시각'',
PRIMARY KEY (id),
UNIQUE KEY uk_agent_creator_relation_creator_active (creator_id, active_creator_key),
KEY idx_agent_creator_relation_agent_id (agent_id),
KEY idx_agent_creator_relation_creator_id (creator_id),
KEY idx_agent_creator_relation_agent_unassigned_at (agent_id, unassigned_at),
KEY idx_agent_creator_relation_creator_assigned_at (creator_id, assigned_at),
KEY idx_agent_creator_relation_creator_unassigned_at (creator_id, unassigned_at),
CONSTRAINT fk_agent_creator_relation_agent_id FOREIGN KEY (agent_id) REFERENCES member (id),
CONSTRAINT fk_agent_creator_relation_creator_id FOREIGN KEY (creator_id) REFERENCES member (id),
CONSTRAINT chk_agent_creator_relation_period CHECK (unassigned_at IS NULL OR assigned_at < unassigned_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''에이전트-크리에이터 소속 관계''',
'SELECT ''agent_creator_relation already exists'' AS message'
);
PREPARE create_agent_creator_relation_table_stmt FROM @create_agent_creator_relation_table_sql;
EXECUTE create_agent_creator_relation_table_stmt;
DEALLOCATE PREPARE create_agent_creator_relation_table_stmt;
SET @agent_creator_relation_has_assigned_at := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND column_name = 'assigned_at'
);
SET @alter_agent_creator_relation_add_assigned_at_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_has_assigned_at = 0,
'ALTER TABLE agent_creator_relation ADD COLUMN assigned_at TIMESTAMP NOT NULL COMMENT ''소속 시작 시각'' AFTER creator_id',
'SELECT ''agent_creator_relation.assigned_at already exists'' AS message'
);
PREPARE alter_agent_creator_relation_add_assigned_at_stmt FROM @alter_agent_creator_relation_add_assigned_at_sql;
EXECUTE alter_agent_creator_relation_add_assigned_at_stmt;
DEALLOCATE PREPARE alter_agent_creator_relation_add_assigned_at_stmt;
SET @agent_creator_relation_has_unassigned_at := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND column_name = 'unassigned_at'
);
SET @alter_agent_creator_relation_add_unassigned_at_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_has_unassigned_at = 0,
'ALTER TABLE agent_creator_relation ADD COLUMN unassigned_at TIMESTAMP NULL DEFAULT NULL COMMENT ''소속 종료 시각(NULL이면 현재 활성 소속)'' AFTER assigned_at',
'SELECT ''agent_creator_relation.unassigned_at already exists'' AS message'
);
PREPARE alter_agent_creator_relation_add_unassigned_at_stmt FROM @alter_agent_creator_relation_add_unassigned_at_sql;
EXECUTE alter_agent_creator_relation_add_unassigned_at_stmt;
DEALLOCATE PREPARE alter_agent_creator_relation_add_unassigned_at_stmt;
SET @agent_creator_relation_has_active_creator_key := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND column_name = 'active_creator_key'
);
SET @alter_agent_creator_relation_add_active_creator_key_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_has_active_creator_key = 0,
'ALTER TABLE agent_creator_relation ADD COLUMN active_creator_key TINYINT GENERATED ALWAYS AS (CASE WHEN unassigned_at IS NULL THEN 1 ELSE NULL END) STORED AFTER unassigned_at',
'SELECT ''agent_creator_relation.active_creator_key already exists'' AS message'
);
PREPARE alter_agent_creator_relation_add_active_creator_key_stmt FROM @alter_agent_creator_relation_add_active_creator_key_sql;
EXECUTE alter_agent_creator_relation_add_active_creator_key_stmt;
DEALLOCATE PREPARE alter_agent_creator_relation_add_active_creator_key_stmt;
SET @agent_creator_relation_creator_unique_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND index_name = 'uk_agent_creator_relation_creator_id'
);
SET @drop_agent_creator_relation_creator_unique_sql := IF(
@agent_creator_relation_creator_unique_exists > 0,
'ALTER TABLE agent_creator_relation DROP INDEX uk_agent_creator_relation_creator_id',
'SELECT ''uk_agent_creator_relation_creator_id already dropped'' AS message'
);
PREPARE drop_agent_creator_relation_creator_unique_stmt FROM @drop_agent_creator_relation_creator_unique_sql;
EXECUTE drop_agent_creator_relation_creator_unique_stmt;
DEALLOCATE PREPARE drop_agent_creator_relation_creator_unique_stmt;
SET @agent_creator_relation_creator_active_unique_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND index_name = 'uk_agent_creator_relation_creator_active'
);
SET @add_agent_creator_relation_creator_active_unique_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_creator_active_unique_exists = 0,
'ALTER TABLE agent_creator_relation ADD UNIQUE INDEX uk_agent_creator_relation_creator_active (creator_id, active_creator_key)',
'SELECT ''uk_agent_creator_relation_creator_active already exists'' AS message'
);
PREPARE add_agent_creator_relation_creator_active_unique_stmt FROM @add_agent_creator_relation_creator_active_unique_sql;
EXECUTE add_agent_creator_relation_creator_active_unique_stmt;
DEALLOCATE PREPARE add_agent_creator_relation_creator_active_unique_stmt;
SET @agent_creator_relation_period_check_exists := (
SELECT COUNT(1)
FROM information_schema.table_constraints
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND constraint_name = 'chk_agent_creator_relation_period'
AND constraint_type = 'CHECK'
);
SET @add_agent_creator_relation_period_check_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_period_check_exists = 0,
'ALTER TABLE agent_creator_relation ADD CONSTRAINT chk_agent_creator_relation_period CHECK (unassigned_at IS NULL OR assigned_at < unassigned_at)',
'SELECT ''chk_agent_creator_relation_period already exists'' AS message'
);
PREPARE add_agent_creator_relation_period_check_stmt FROM @add_agent_creator_relation_period_check_sql;
EXECUTE add_agent_creator_relation_period_check_stmt;
DEALLOCATE PREPARE add_agent_creator_relation_period_check_stmt;
SET @agent_creator_relation_agent_unassigned_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND index_name = 'idx_agent_creator_relation_agent_unassigned_at'
);
SET @add_agent_creator_relation_agent_unassigned_index_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_agent_unassigned_index_exists = 0,
'ALTER TABLE agent_creator_relation ADD INDEX idx_agent_creator_relation_agent_unassigned_at (agent_id, unassigned_at)',
'SELECT ''idx_agent_creator_relation_agent_unassigned_at already exists'' AS message'
);
PREPARE add_agent_creator_relation_agent_unassigned_index_stmt FROM @add_agent_creator_relation_agent_unassigned_index_sql;
EXECUTE add_agent_creator_relation_agent_unassigned_index_stmt;
DEALLOCATE PREPARE add_agent_creator_relation_agent_unassigned_index_stmt;
SET @agent_creator_relation_creator_assigned_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND index_name = 'idx_agent_creator_relation_creator_assigned_at'
);
SET @add_agent_creator_relation_creator_assigned_index_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_creator_assigned_index_exists = 0,
'ALTER TABLE agent_creator_relation ADD INDEX idx_agent_creator_relation_creator_assigned_at (creator_id, assigned_at)',
'SELECT ''idx_agent_creator_relation_creator_assigned_at already exists'' AS message'
);
PREPARE add_agent_creator_relation_creator_assigned_index_stmt FROM @add_agent_creator_relation_creator_assigned_index_sql;
EXECUTE add_agent_creator_relation_creator_assigned_index_stmt;
DEALLOCATE PREPARE add_agent_creator_relation_creator_assigned_index_stmt;
SET @agent_creator_relation_creator_unassigned_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_creator_relation'
AND index_name = 'idx_agent_creator_relation_creator_unassigned_at'
);
SET @add_agent_creator_relation_creator_unassigned_index_sql := IF(
@agent_creator_relation_table_exists = 1 AND @agent_creator_relation_creator_unassigned_index_exists = 0,
'ALTER TABLE agent_creator_relation ADD INDEX idx_agent_creator_relation_creator_unassigned_at (creator_id, unassigned_at)',
'SELECT ''idx_agent_creator_relation_creator_unassigned_at already exists'' AS message'
);
PREPARE add_agent_creator_relation_creator_unassigned_index_stmt FROM @add_agent_creator_relation_creator_unassigned_index_sql;
EXECUTE add_agent_creator_relation_creator_unassigned_index_stmt;
DEALLOCATE PREPARE add_agent_creator_relation_creator_unassigned_index_stmt;
SET @agent_settlement_ratio_table_exists := (
SELECT COUNT(1)
FROM information_schema.tables
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
);
SET @create_agent_settlement_ratio_table_sql := IF(
@agent_settlement_ratio_table_exists = 0,
'CREATE TABLE agent_settlement_ratio (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT ''PK'',
member_id BIGINT NOT NULL COMMENT ''에이전트 회원 ID (member.id 참조)'',
settlement_ratio INT NOT NULL COMMENT ''에이전트 정산 비율(%)'',
effective_from TIMESTAMP NOT NULL COMMENT ''비율 시작 시각'',
effective_to TIMESTAMP NULL DEFAULT NULL COMMENT ''비율 종료 시각(NULL이면 현재 활성 비율)'',
active_ratio_key TINYINT GENERATED ALWAYS AS (
CASE
WHEN effective_to IS NULL THEN 1
ELSE NULL
END
) STORED,
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''생성 시각'',
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ''수정 시각'',
PRIMARY KEY (id),
UNIQUE KEY uk_agent_settlement_ratio_member_active (member_id, active_ratio_key),
KEY idx_agent_settlement_ratio_member_effective_to (member_id, effective_to),
KEY idx_agent_settlement_ratio_member_effective_from (member_id, effective_from),
CONSTRAINT fk_agent_settlement_ratio_member_id FOREIGN KEY (member_id) REFERENCES member (id),
CONSTRAINT chk_agent_settlement_ratio_period CHECK (effective_to IS NULL OR effective_from < effective_to)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''에이전트 정산 비율''',
'SELECT ''agent_settlement_ratio already exists'' AS message'
);
PREPARE create_agent_settlement_ratio_table_stmt FROM @create_agent_settlement_ratio_table_sql;
EXECUTE create_agent_settlement_ratio_table_stmt;
DEALLOCATE PREPARE create_agent_settlement_ratio_table_stmt;
SET @agent_settlement_ratio_has_effective_from := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND column_name = 'effective_from'
);
SET @alter_agent_settlement_ratio_add_effective_from_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_has_effective_from = 0,
'ALTER TABLE agent_settlement_ratio ADD COLUMN effective_from TIMESTAMP NOT NULL COMMENT ''비율 시작 시각'' AFTER settlement_ratio',
'SELECT ''agent_settlement_ratio.effective_from already exists'' AS message'
);
PREPARE alter_agent_settlement_ratio_add_effective_from_stmt FROM @alter_agent_settlement_ratio_add_effective_from_sql;
EXECUTE alter_agent_settlement_ratio_add_effective_from_stmt;
DEALLOCATE PREPARE alter_agent_settlement_ratio_add_effective_from_stmt;
SET @agent_settlement_ratio_has_effective_to := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND column_name = 'effective_to'
);
SET @alter_agent_settlement_ratio_add_effective_to_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_has_effective_to = 0,
'ALTER TABLE agent_settlement_ratio ADD COLUMN effective_to TIMESTAMP NULL DEFAULT NULL COMMENT ''비율 종료 시각(NULL이면 현재 활성 비율)'' AFTER effective_from',
'SELECT ''agent_settlement_ratio.effective_to already exists'' AS message'
);
PREPARE alter_agent_settlement_ratio_add_effective_to_stmt FROM @alter_agent_settlement_ratio_add_effective_to_sql;
EXECUTE alter_agent_settlement_ratio_add_effective_to_stmt;
DEALLOCATE PREPARE alter_agent_settlement_ratio_add_effective_to_stmt;
SET @agent_settlement_ratio_has_active_ratio_key := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND column_name = 'active_ratio_key'
);
SET @alter_agent_settlement_ratio_add_active_ratio_key_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_has_active_ratio_key = 0,
'ALTER TABLE agent_settlement_ratio ADD COLUMN active_ratio_key TINYINT GENERATED ALWAYS AS (CASE WHEN effective_to IS NULL THEN 1 ELSE NULL END) STORED AFTER effective_to',
'SELECT ''agent_settlement_ratio.active_ratio_key already exists'' AS message'
);
PREPARE alter_agent_settlement_ratio_add_active_ratio_key_stmt FROM @alter_agent_settlement_ratio_add_active_ratio_key_sql;
EXECUTE alter_agent_settlement_ratio_add_active_ratio_key_stmt;
DEALLOCATE PREPARE alter_agent_settlement_ratio_add_active_ratio_key_stmt;
SET @agent_settlement_ratio_member_unique_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND index_name = 'uk_agent_settlement_ratio_member_id'
);
SET @drop_agent_settlement_ratio_member_unique_sql := IF(
@agent_settlement_ratio_member_unique_exists > 0,
'ALTER TABLE agent_settlement_ratio DROP INDEX uk_agent_settlement_ratio_member_id',
'SELECT ''uk_agent_settlement_ratio_member_id already dropped'' AS message'
);
PREPARE drop_agent_settlement_ratio_member_unique_stmt FROM @drop_agent_settlement_ratio_member_unique_sql;
EXECUTE drop_agent_settlement_ratio_member_unique_stmt;
DEALLOCATE PREPARE drop_agent_settlement_ratio_member_unique_stmt;
SET @agent_settlement_ratio_member_active_unique_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND index_name = 'uk_agent_settlement_ratio_member_active'
);
SET @add_agent_settlement_ratio_member_active_unique_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_member_active_unique_exists = 0,
'ALTER TABLE agent_settlement_ratio ADD UNIQUE INDEX uk_agent_settlement_ratio_member_active (member_id, active_ratio_key)',
'SELECT ''uk_agent_settlement_ratio_member_active already exists'' AS message'
);
PREPARE add_agent_settlement_ratio_member_active_unique_stmt FROM @add_agent_settlement_ratio_member_active_unique_sql;
EXECUTE add_agent_settlement_ratio_member_active_unique_stmt;
DEALLOCATE PREPARE add_agent_settlement_ratio_member_active_unique_stmt;
SET @agent_settlement_ratio_has_deleted_at := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND column_name = 'deleted_at'
);
SET @drop_agent_settlement_ratio_deleted_at_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_has_deleted_at > 0,
'ALTER TABLE agent_settlement_ratio DROP COLUMN deleted_at',
'SELECT ''agent_settlement_ratio.deleted_at already dropped'' AS message'
);
PREPARE drop_agent_settlement_ratio_deleted_at_stmt FROM @drop_agent_settlement_ratio_deleted_at_sql;
EXECUTE drop_agent_settlement_ratio_deleted_at_stmt;
DEALLOCATE PREPARE drop_agent_settlement_ratio_deleted_at_stmt;
SET @agent_settlement_ratio_period_check_exists := (
SELECT COUNT(1)
FROM information_schema.table_constraints
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND constraint_name = 'chk_agent_settlement_ratio_period'
AND constraint_type = 'CHECK'
);
SET @add_agent_settlement_ratio_period_check_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_period_check_exists = 0,
'ALTER TABLE agent_settlement_ratio ADD CONSTRAINT chk_agent_settlement_ratio_period CHECK (effective_to IS NULL OR effective_from < effective_to)',
'SELECT ''chk_agent_settlement_ratio_period already exists'' AS message'
);
PREPARE add_agent_settlement_ratio_period_check_stmt FROM @add_agent_settlement_ratio_period_check_sql;
EXECUTE add_agent_settlement_ratio_period_check_stmt;
DEALLOCATE PREPARE add_agent_settlement_ratio_period_check_stmt;
SET @agent_settlement_ratio_member_effective_to_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND index_name = 'idx_agent_settlement_ratio_member_effective_to'
);
SET @add_agent_settlement_ratio_member_effective_to_index_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_member_effective_to_index_exists = 0,
'ALTER TABLE agent_settlement_ratio ADD INDEX idx_agent_settlement_ratio_member_effective_to (member_id, effective_to)',
'SELECT ''idx_agent_settlement_ratio_member_effective_to already exists'' AS message'
);
PREPARE add_agent_settlement_ratio_member_effective_to_index_stmt FROM @add_agent_settlement_ratio_member_effective_to_index_sql;
EXECUTE add_agent_settlement_ratio_member_effective_to_index_stmt;
DEALLOCATE PREPARE add_agent_settlement_ratio_member_effective_to_index_stmt;
SET @agent_settlement_ratio_member_effective_from_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_ratio'
AND index_name = 'idx_agent_settlement_ratio_member_effective_from'
);
SET @add_agent_settlement_ratio_member_effective_from_index_sql := IF(
@agent_settlement_ratio_table_exists = 1 AND @agent_settlement_ratio_member_effective_from_index_exists = 0,
'ALTER TABLE agent_settlement_ratio ADD INDEX idx_agent_settlement_ratio_member_effective_from (member_id, effective_from)',
'SELECT ''idx_agent_settlement_ratio_member_effective_from already exists'' AS message'
);
PREPARE add_agent_settlement_ratio_member_effective_from_index_stmt FROM @add_agent_settlement_ratio_member_effective_from_index_sql;
EXECUTE add_agent_settlement_ratio_member_effective_from_index_stmt;
DEALLOCATE PREPARE add_agent_settlement_ratio_member_effective_from_index_stmt;
SET @agent_settlement_snapshot_table_exists := (
SELECT COUNT(1)
FROM information_schema.tables
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
);
SET @create_agent_settlement_snapshot_table_sql := IF(
@agent_settlement_snapshot_table_exists = 0,
'CREATE TABLE agent_settlement_snapshot (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT ''PK'',
period_start TIMESTAMP NOT NULL COMMENT ''정산 기간 시작 시각'',
period_end TIMESTAMP NOT NULL COMMENT ''정산 기간 종료 시각'',
settlement_type VARCHAR(50) NOT NULL COMMENT ''정산 유형(LIVE, CONTENT, COMMUNITY, CHANNEL_DONATION, CONTENT_DONATION)'',
agent_id BIGINT NOT NULL COMMENT ''에이전트 회원 ID 스냅샷'',
agent_nickname VARCHAR(255) NOT NULL COMMENT ''에이전트 닉네임 스냅샷'',
creator_id BIGINT NOT NULL COMMENT ''크리에이터 회원 ID 스냅샷'',
creator_nickname VARCHAR(255) NOT NULL COMMENT ''크리에이터 닉네임 스냅샷'',
assignment_id BIGINT NULL COMMENT ''적용된 소속 이력 row ID 스냅샷'',
agent_settlement_ratio_id BIGINT NULL COMMENT ''적용된 에이전트 정산 비율 이력 row ID 스냅샷'',
applied_agent_settlement_ratio INT NULL COMMENT ''적용된 에이전트 정산 비율 스냅샷(creator-level summary에 단일 값으로 귀결될 때만 저장)'',
count INT NOT NULL COMMENT ''건수 스냅샷'',
total_can INT NOT NULL COMMENT ''총 캔 수 스냅샷'',
krw INT NOT NULL COMMENT ''원화 금액 스냅샷'',
fee INT NOT NULL COMMENT ''수수료 스냅샷'',
settlement_amount INT NOT NULL COMMENT ''크리에이터 세전 정산금 스냅샷'',
tax INT NOT NULL COMMENT ''원천세/세금 스냅샷'',
deposit_amount INT NOT NULL COMMENT ''입금액 스냅샷'',
agent_settlement_amount INT NOT NULL COMMENT ''에이전트 정산금 스냅샷'',
finalized_at TIMESTAMP NOT NULL COMMENT ''확정 시각'',
finalized_by_member_id BIGINT NOT NULL COMMENT ''확정한 관리자 회원 ID 스냅샷'',
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''생성 시각'',
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ''수정 시각'',
PRIMARY KEY (id),
UNIQUE KEY uk_agent_settlement_snapshot_period_type_agent_creator (period_start, period_end, settlement_type, agent_id, creator_id),
KEY idx_agent_settlement_snapshot_lookup (agent_id, settlement_type, period_start, period_end),
KEY idx_agent_settlement_snapshot_creator_lookup (creator_id, settlement_type, period_start, period_end),
KEY idx_agent_settlement_snapshot_assignment_id (assignment_id),
KEY idx_agent_settlement_snapshot_ratio_id (agent_settlement_ratio_id),
CONSTRAINT fk_agent_settlement_snapshot_assignment_id FOREIGN KEY (assignment_id) REFERENCES agent_creator_relation (id),
CONSTRAINT fk_agent_settlement_snapshot_ratio_id FOREIGN KEY (agent_settlement_ratio_id) REFERENCES agent_settlement_ratio (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''에이전트 확정 정산 creator-level 스냅샷''',
'SELECT ''agent_settlement_snapshot already exists'' AS message'
);
PREPARE create_agent_settlement_snapshot_table_stmt FROM @create_agent_settlement_snapshot_table_sql;
EXECUTE create_agent_settlement_snapshot_table_stmt;
DEALLOCATE PREPARE create_agent_settlement_snapshot_table_stmt;
SET @agent_settlement_snapshot_has_assignment_id := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND column_name = 'assignment_id'
);
SET @alter_agent_settlement_snapshot_add_assignment_id_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_has_assignment_id = 0,
'ALTER TABLE agent_settlement_snapshot ADD COLUMN assignment_id BIGINT NULL COMMENT ''적용된 소속 이력 row ID 스냅샷'' AFTER creator_nickname',
'SELECT ''agent_settlement_snapshot.assignment_id already exists'' AS message'
);
PREPARE alter_agent_settlement_snapshot_add_assignment_id_stmt FROM @alter_agent_settlement_snapshot_add_assignment_id_sql;
EXECUTE alter_agent_settlement_snapshot_add_assignment_id_stmt;
DEALLOCATE PREPARE alter_agent_settlement_snapshot_add_assignment_id_stmt;
SET @agent_settlement_snapshot_has_ratio_id := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND column_name = 'agent_settlement_ratio_id'
);
SET @alter_agent_settlement_snapshot_add_ratio_id_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_has_ratio_id = 0,
'ALTER TABLE agent_settlement_snapshot ADD COLUMN agent_settlement_ratio_id BIGINT NULL COMMENT ''적용된 에이전트 정산 비율 이력 row ID 스냅샷'' AFTER assignment_id',
'SELECT ''agent_settlement_snapshot.agent_settlement_ratio_id already exists'' AS message'
);
PREPARE alter_agent_settlement_snapshot_add_ratio_id_stmt FROM @alter_agent_settlement_snapshot_add_ratio_id_sql;
EXECUTE alter_agent_settlement_snapshot_add_ratio_id_stmt;
DEALLOCATE PREPARE alter_agent_settlement_snapshot_add_ratio_id_stmt;
SET @agent_settlement_snapshot_unique_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND index_name = 'uk_agent_settlement_snapshot_period_type_agent_creator'
);
SET @add_agent_settlement_snapshot_unique_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_unique_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD UNIQUE INDEX uk_agent_settlement_snapshot_period_type_agent_creator (period_start, period_end, settlement_type, agent_id, creator_id)',
'SELECT ''uk_agent_settlement_snapshot_period_type_agent_creator already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_unique_stmt FROM @add_agent_settlement_snapshot_unique_sql;
EXECUTE add_agent_settlement_snapshot_unique_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_unique_stmt;
SET @agent_settlement_snapshot_lookup_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND index_name = 'idx_agent_settlement_snapshot_lookup'
);
SET @add_agent_settlement_snapshot_lookup_index_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_lookup_index_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD INDEX idx_agent_settlement_snapshot_lookup (agent_id, settlement_type, period_start, period_end)',
'SELECT ''idx_agent_settlement_snapshot_lookup already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_lookup_index_stmt FROM @add_agent_settlement_snapshot_lookup_index_sql;
EXECUTE add_agent_settlement_snapshot_lookup_index_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_lookup_index_stmt;
SET @agent_settlement_snapshot_creator_lookup_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND index_name = 'idx_agent_settlement_snapshot_creator_lookup'
);
SET @add_agent_settlement_snapshot_creator_lookup_index_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_creator_lookup_index_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD INDEX idx_agent_settlement_snapshot_creator_lookup (creator_id, settlement_type, period_start, period_end)',
'SELECT ''idx_agent_settlement_snapshot_creator_lookup already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_creator_lookup_index_stmt FROM @add_agent_settlement_snapshot_creator_lookup_index_sql;
EXECUTE add_agent_settlement_snapshot_creator_lookup_index_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_creator_lookup_index_stmt;
SET @agent_settlement_snapshot_assignment_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND index_name = 'idx_agent_settlement_snapshot_assignment_id'
);
SET @add_agent_settlement_snapshot_assignment_index_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_assignment_index_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD INDEX idx_agent_settlement_snapshot_assignment_id (assignment_id)',
'SELECT ''idx_agent_settlement_snapshot_assignment_id already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_assignment_index_stmt FROM @add_agent_settlement_snapshot_assignment_index_sql;
EXECUTE add_agent_settlement_snapshot_assignment_index_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_assignment_index_stmt;
SET @agent_settlement_snapshot_ratio_index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND index_name = 'idx_agent_settlement_snapshot_ratio_id'
);
SET @add_agent_settlement_snapshot_ratio_index_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_ratio_index_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD INDEX idx_agent_settlement_snapshot_ratio_id (agent_settlement_ratio_id)',
'SELECT ''idx_agent_settlement_snapshot_ratio_id already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_ratio_index_stmt FROM @add_agent_settlement_snapshot_ratio_index_sql;
EXECUTE add_agent_settlement_snapshot_ratio_index_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_ratio_index_stmt;
SET @agent_settlement_snapshot_assignment_fk_exists := (
SELECT COUNT(1)
FROM information_schema.table_constraints
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND constraint_name = 'fk_agent_settlement_snapshot_assignment_id'
AND constraint_type = 'FOREIGN KEY'
);
SET @add_agent_settlement_snapshot_assignment_fk_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_assignment_fk_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD CONSTRAINT fk_agent_settlement_snapshot_assignment_id FOREIGN KEY (assignment_id) REFERENCES agent_creator_relation (id)',
'SELECT ''fk_agent_settlement_snapshot_assignment_id already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_assignment_fk_stmt FROM @add_agent_settlement_snapshot_assignment_fk_sql;
EXECUTE add_agent_settlement_snapshot_assignment_fk_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_assignment_fk_stmt;
SET @agent_settlement_snapshot_ratio_fk_exists := (
SELECT COUNT(1)
FROM information_schema.table_constraints
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot'
AND constraint_name = 'fk_agent_settlement_snapshot_ratio_id'
AND constraint_type = 'FOREIGN KEY'
);
SET @add_agent_settlement_snapshot_ratio_fk_sql := IF(
@agent_settlement_snapshot_table_exists = 1 AND @agent_settlement_snapshot_ratio_fk_exists = 0,
'ALTER TABLE agent_settlement_snapshot ADD CONSTRAINT fk_agent_settlement_snapshot_ratio_id FOREIGN KEY (agent_settlement_ratio_id) REFERENCES agent_settlement_ratio (id)',
'SELECT ''fk_agent_settlement_snapshot_ratio_id already exists'' AS message'
);
PREPARE add_agent_settlement_snapshot_ratio_fk_stmt FROM @add_agent_settlement_snapshot_ratio_fk_sql;
EXECUTE add_agent_settlement_snapshot_ratio_fk_stmt;
DEALLOCATE PREPARE add_agent_settlement_snapshot_ratio_fk_stmt;
SET @agent_settlement_snapshot_source_detail_table_exists := (
SELECT COUNT(1)
FROM information_schema.tables
WHERE table_schema = @schema_name
AND table_name = 'agent_settlement_snapshot_source_detail'
);
SET @create_agent_settlement_snapshot_source_detail_table_sql := IF(
@agent_settlement_snapshot_source_detail_table_exists = 0,
'CREATE TABLE agent_settlement_snapshot_source_detail (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT ''PK'',
snapshot_id BIGINT NOT NULL COMMENT ''summary snapshot FK'',
assignment_id BIGINT NULL COMMENT ''적용된 소속 이력 row ID'',
agent_settlement_ratio_id BIGINT NULL COMMENT ''적용된 에이전트 정산 비율 이력 row ID'',
applied_agent_settlement_ratio INT NULL COMMENT ''적용된 에이전트 정산 비율'',
count INT NOT NULL COMMENT ''source subtotal 건수'',
total_can INT NOT NULL COMMENT ''source subtotal 총 캔 수'',
krw INT NOT NULL COMMENT ''source subtotal 원화 금액'',
fee INT NOT NULL COMMENT ''source subtotal 수수료'',
settlement_amount INT NOT NULL COMMENT ''source subtotal 크리에이터 세전 정산금'',
tax INT NOT NULL COMMENT ''source subtotal 세금'',
deposit_amount INT NOT NULL COMMENT ''source subtotal 입금액'',
agent_settlement_amount INT NOT NULL COMMENT ''source subtotal 에이전트 정산금'',
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''생성 시각'',
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ''수정 시각'',
PRIMARY KEY (id),
KEY idx_agent_settlement_snapshot_source_detail_snapshot_id (snapshot_id),
KEY idx_agent_settlement_snapshot_source_detail_assignment_id (assignment_id),
KEY idx_agent_settlement_snapshot_source_detail_ratio_id (agent_settlement_ratio_id),
CONSTRAINT fk_agent_settlement_snapshot_source_detail_snapshot_id FOREIGN KEY (snapshot_id) REFERENCES agent_settlement_snapshot (id),
CONSTRAINT fk_agent_settlement_snapshot_source_detail_assignment_id FOREIGN KEY (assignment_id) REFERENCES agent_creator_relation (id),
CONSTRAINT fk_agent_settlement_snapshot_source_detail_ratio_id FOREIGN KEY (agent_settlement_ratio_id) REFERENCES agent_settlement_ratio (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''에이전트 확정 정산 source provenance detail''',
'SELECT ''agent_settlement_snapshot_source_detail already exists'' AS message'
);
PREPARE create_agent_settlement_snapshot_source_detail_table_stmt FROM @create_agent_settlement_snapshot_source_detail_table_sql;
EXECUTE create_agent_settlement_snapshot_source_detail_table_stmt;
DEALLOCATE PREPARE create_agent_settlement_snapshot_source_detail_table_stmt;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,313 @@
# 관리자 에이전트 정산 상세 조회 설계
## 문서 목적
- 관리자 페이지에서 에이전트 목록을 보고, 특정 에이전트 상세 화면에서 소속 크리에이터와 에이전트별 정산 현황을 조회할 수 있도록 백엔드 read API 설계를 고정한다.
- 기존 `partner/agent/calculate` 계산 로직은 최대한 재사용하고, `ADMIN` 전용 조회 진입점만 별도로 추가한다.
## 요구사항 정리
- 관리자 화면에는 에이전트 리스트가 필요하다.
- 에이전트 닉네임
- 에이전트에 속한 크리에이터 수
- 관리자 화면에는 크리에이터 검색이 필요하다.
- 특정 에이전트에 크리에이터를 소속시키기 위한 검색
- 관리자 화면에는 특정 에이전트에 현재 소속된 크리에이터 목록이 필요하다.
- 에이전트 소속 해제를 위한 목록
- 관리자 화면에는 특정 에이전트 기준 정산 상세가 필요하다.
- 라이브 정산 현황
- 콘텐츠 판매 정산 현황
- 커뮤니티 정산 현황
- 채널 후원 정산 현황
- 콘텐츠 후원 정산 현황
## 범위와 해석
- 이번 설계는 **B안: 에이전트 목록 → 에이전트 상세** 흐름을 기준으로 한다.
- 에이전트 목록 화면에서는 요약 정보만 제공한다.
- 실제 정산 데이터는 에이전트 상세 화면에서 조회한다.
- 기존 `assignment`, `ratio`, `settlement/finalize` 쓰기 기능은 유지하고, 이번 범위에서는 관리자용 read API만 추가한다.
## 현재 코드 기준 확인 사항
- 관리자 전용 에이전트 관련 컨트롤러는 이미 존재한다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorController.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioController.kt`
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotController.kt`
- 에이전트 본인 전용 정산 조회 컨트롤러도 이미 존재한다.
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateController.kt`
- 따라서 현재 부족한 것은 정산 계산 로직이 아니라, `ADMIN`이 특정 `agentId`를 지정해 같은 데이터를 읽는 관리자 전용 read API 레이어다.
## 권장 아키텍처
### 1. 관리자 전용 read 진입점 추가
- 신규 패키지: `kr.co.vividnext.sodalive.admin.partner.agent.read`
- 신규 구성
- `AdminAgentReadController`
- `AdminAgentReadService`
- `AdminAgentReadQueryRepository`
- 역할
- 에이전트 목록 조회
- 크리에이터 검색 조회
- 특정 에이전트 소속 크리에이터 목록 조회
- 특정 에이전트 기준 5종 정산 조회
### 2. 기존 도메인 계산 로직 재사용
- 기존 계산/집계 로직은 계속 `kr.co.vividnext.sodalive.partner.agent.calculate` 아래에 둔다.
- 관리자용 read 서비스는 기존 `AgentCalculateService`, `AgentCalculateQueryRepository`, snapshot 조회 경로를 재사용한다.
- 현재 `AgentCalculateService`의 정산 조회 메서드는 이미 `agentId`를 직접 받으므로, 관리자 read 서비스는 이 메서드를 그대로 호출한다.
- 최종안은 **정산 계산 로직은 `partner.agent.calculate`에 유지하고, ADMIN은 별도 read 서비스에서 기존 agentId 기반 조회 메서드를 재사용하는 구조**다.
### 3. 쓰기와 읽기 축 분리
- 쓰기 축
- `admin.partner.agent.assignment`
- `admin.partner.agent.ratio`
- `admin.partner.agent.settlement`
- 읽기 축
- `admin.partner.agent.read`
- `partner.agent.calculate`
- 이렇게 나누면 “관리자 권한으로 읽는다”와 “정산 계산 규칙을 제공한다”의 책임이 섞이지 않는다.
## 화면 흐름 기준 API 설계
### 1. 에이전트 목록 API
- 목적: 관리자 화면의 첫 진입 리스트
- 권장 경로: `GET /admin/partner/agent/list`
- 권한: `ADMIN`
- 요청 파라미터
- `pageable`
- 응답 필드
- `agentId`
- `agentNickname`
- `assignedCreatorCount`
- `liveAgentSettlementAmount`
- `contentAgentSettlementAmount`
- `communityAgentSettlementAmount`
- `contentDonationAgentSettlementAmount`
- `channelDonationAgentSettlementAmount`
- `totalCount`
- 조회 기준
- `Member.role == AGENT`
- 현재 활성 소속 크리에이터 수만 집계
- 활성 소속 판정은 현재 코드의 assignment window 규칙과 동일하게 현재 시각 기준 `assignedAt <= now < unassignedAt(or null)`를 따른다.
- 정산 합계는 별도 날짜 입력 없이 **현재 월 기준**으로 계산한다.
- 기준 시간대: `Asia/Seoul`
- 시작 시각: 현재 월 1일 `00:00:00`
- 종료 시각: 다음 달 1일 `00:00:00` 직전까지 포함되는 배타 상한 방식
- 실제 조회 구간은 위 KST 월 경계를 UTC `LocalDateTime`으로 변환해 DB의 UTC 저장 시각과 비교한다.
- 각 합계 값의 의미는 해당 기간의 상세 조회 응답 `total.agentSettlementAmount`와 동일하다.
- 해당 월에 정산 내역이 없더라도 에이전트 목록에서는 제외하지 않고, 5종 합계를 모두 `0`으로 내려준다.
### 2. 크리에이터 검색 API
- 목적: 특정 에이전트에 크리에이터를 소속시키기 전 검색
- 권장 경로: `GET /admin/partner/agent/creator/search`
- 권한: `ADMIN`
- 요청 파라미터
- `search_word`
- `pageable`
- 응답 필드
- `creatorId`
- `creatorNickname`
- `currentAgentId` nullable
- `currentAgentNickname` nullable
- 동작 원칙
- 기존 `AdminMemberController.searchCreator()` 검색 관례를 따른다.
- 검색 결과에 현재 활성 소속 agent 정보를 붙여서, 이미 다른 agent 소속인지 운영자가 바로 판단할 수 있게 한다.
### 3. 특정 에이전트 소속 크리에이터 목록 API
- 목적: 상세 화면의 소속 크리에이터 탭
- 권장 경로: `GET /admin/partner/agent/{agentId}/creator/list`
- 권한: `ADMIN`
- 응답 필드
- `creatorId`
- `creatorNickname`
- `assignedAt`
- 참고
- 현재 `GetAgentAssignedCreatorResponse``creatorId`, `creatorNickname`만 제공한다.
- 관리자 상세 화면에서는 운영 판단을 위해 `assignedAt`이 같이 내려가는 편이 자연스럽다.
### 4. 특정 에이전트 정산 상세 API 5종
- 목적: 에이전트 상세 화면의 정산 탭
- 권한: `ADMIN`
- 권장 경로
- `GET /admin/partner/agent/{agentId}/calculate/live-by-creator`
- `GET /admin/partner/agent/{agentId}/calculate/content-by-creator`
- `GET /admin/partner/agent/{agentId}/calculate/community-by-creator`
- `GET /admin/partner/agent/{agentId}/calculate/channel-donation-by-creator`
- `GET /admin/partner/agent/{agentId}/calculate/content-donation-by-creator`
- 공통 요청 파라미터
- `startDateStr`
- `endDateStr`
- `pageable`
- 응답 원칙
- 기존 AGENT 전용 응답 계약을 가능한 그대로 유지한다.
- `totalCount`, `total`, `items` 구조 유지
- 각 item의 집계 필드 유지
- `count`
- `totalCan`
- `krw`
- `fee`
- `settlementAmount`
- `agentSettlementAmount`
- 차이점
- 기존 AGENT API는 로그인 principal에서 `agentId`를 얻는다.
- 관리자 API는 path variable `agentId`를 받는다.
## 데이터 모델/응답 설계
### 1. 에이전트 목록 응답 DTO
- 신규 DTO 필요
- DTO 명
- `GetAdminAgentListResponse`
- `GetAdminAgentListItem`
- 필수 필드
- response: `totalCount: Int`, `items: List<GetAdminAgentListItem>`
- item:
- `agentId: Long`
- `agentNickname: String`
- `assignedCreatorCount: Int`
- `liveAgentSettlementAmount: Int`
- `contentAgentSettlementAmount: Int`
- `communityAgentSettlementAmount: Int`
- `contentDonationAgentSettlementAmount: Int`
- `channelDonationAgentSettlementAmount: Int`
### 2. 크리에이터 검색 응답 DTO
- 신규 DTO 필요
- 기존 `admin/member` 검색 응답을 그대로 쓰기보다, 현재 활성 agent 정보를 함께 주는 전용 DTO가 필요하다.
- DTO 명
- `SearchAdminAgentAssignableCreatorResponse`
- `SearchAdminAgentAssignableCreatorItem`
- 필수 필드
- response: `totalCount: Int`, `items: List<SearchAdminAgentAssignableCreatorItem>`
- item: `creatorId: Long`, `creatorNickname: String`, `currentAgentId: Long?`, `currentAgentNickname: String?`
### 3. 관리자용 소속 크리에이터 목록 DTO
- 기존 `GetAgentAssignedCreatorResponse`를 그대로 재사용하기보다는 관리자용 항목에 `assignedAt`을 포함한 전용 DTO가 적합하다.
- DTO 명
- `GetAdminAgentAssignedCreatorResponse`
- `GetAdminAgentAssignedCreatorItem`
- 필수 필드
- response: `totalCount: Int`, `items: List<GetAdminAgentAssignedCreatorItem>`
- item: `creatorId: Long`, `creatorNickname: String`, `assignedAt: LocalDateTime`
### 4. 관리자용 정산 상세 DTO
- 가능하면 기존 아래 DTO를 그대로 재사용한다.
- `GetAgentSettlementByCreatorResponse`
- `GetAgentChannelDonationSettlementByCreatorResponse`
- 이유
- 화면 주체만 ADMIN으로 바뀌고 데이터 shape는 동일하기 때문이다.
- DTO까지 갈라지면 정산 계약이 중복될 가능성이 높다.
## 서비스 설계
### 1. `AdminAgentReadService`
- 책임
- 에이전트 목록 조회
- 크리에이터 검색 조회
- 에이전트 소속 크리에이터 목록 조회
- 에이전트 정산 상세 조회 진입
- 내부 동작
- 목록/검색/소속 목록은 전용 read query를 사용한다.
- 에이전트 목록 조회 시 현재 월 기준 시작/종료 시각을 서비스에서 계산해 전용 read query에 전달한다.
- 정산 상세는 기존 `AgentCalculateService`의 agentId 기반 public 조회 메서드를 사용한다.
### 2. 공통 정산 read 메서드 분리
- 현재 `AgentCalculateService`의 핵심 정산 조회 메서드는 이미 `agentId` 파라미터 중심 public 메서드다.
- 따라서 아래 방향으로 정리한다.
- controller는 AGENT/ADMIN 별도로 유지
- ADMIN read 서비스는 기존 `AgentCalculateService` public 메서드를 직접 호출한다.
- 기대 효과
- 정산 계산 규칙 중복 제거
- snapshot 우선 조회 / live 계산 fallback 규칙 재사용
## Repository / Query 방향
### 1. 에이전트 목록용 조회 추가
- 필요 기능
- AGENT role member 목록
- 각 agent의 현재 활성 creator count
- 각 agent의 현재 월 기준 5종 정산 합계
- 구현 방식
- `AdminAgentReadQueryRepository`에서 전용 projection query를 제공한다.
- 기본 에이전트 목록과 활성 creator count는 기존 Querydsl projection query로 조회한다.
- 현재 월 5종 정산 합계는 각 목록 item마다 기존 `AgentCalculateQueryRepository` total 조회 메서드를 재사용해 채운다.
- 정산 row가 없는 agent도 목록에 남겨야 하므로 기존 total 조회 응답의 `0` 기본값을 그대로 사용한다.
### 2. 크리에이터 검색용 조회 추가
- 필요 기능
- creator nickname 기준 검색
- 현재 활성 소속 agent nullable join
- 구현 방식
- `AdminAgentReadQueryRepository`에서 전용 검색 query를 제공한다.
### 3. 소속 크리에이터 목록 조회 추가
- 기존 `AgentCalculateQueryRepository.getAssignedCreators()`를 재사용할 수 있다.
- 다만 `assignedAt`을 응답에 내려야 하면 projection을 확장하거나 관리자 전용 projection을 추가해야 한다.
## 인증/예외 처리 원칙
- 새 관리자 read 엔드포인트는 모두 `@PreAuthorize("hasRole('ADMIN')")`를 사용한다.
- `agentId`가 존재하지 않거나 role이 `AGENT`가 아니면 `SodaException(messageKey = ...)`로 실패한다.
- `creator/search`는 기존 `admin/member/search` 관례처럼 최소 검색어 길이 검증을 적용한다.
- 정산 계산식, snapshot 우선 전략, assignment/ratio history 적용 규칙은 기존 `partner.agent.calculate` 동작을 그대로 사용한다.
## 테스트 설계
### 1. 컨트롤러 테스트
- `ADMIN` 접근 성공
- 익명 사용자 접근 실패
- `AGENT` 또는 일반 사용자 접근 실패
### 2. 서비스/리포지토리 테스트
- 에이전트 목록에서 닉네임과 현재 활성 creator count가 맞는지 검증
- 크리에이터 검색 결과에 현재 agent 소속 정보가 올바르게 붙는지 검증
- 특정 agent 소속 크리에이터 목록이 현재 활성 구간 기준으로만 내려오는지 검증
### 3. 정산 parity 테스트
- 동일 기간, 동일 `agentId`에 대해
- AGENT 전용 조회 응답
- ADMIN 전용 조회 응답
- 두 결과의 `totalCount`, `total`, `items`가 동일한지 검증
- 대상 5종
- live
- content
- community
- channel donation
- content donation
### 4. 설계 대비 구현 계획 누락 체크리스트
- [x] 관리자 컨트롤러 테스트에 `ADMIN` 접근 성공, 익명 접근 실패, `AGENT` 또는 일반 사용자 접근 실패 시나리오가 모두 포함되어 있는지 확인한다.
- [x] 동일 기간, 동일 `agentId` 기준으로 AGENT 전용 응답과 ADMIN 전용 응답의 `totalCount`, `total`, `items` parity를 5종 모두 검증하는 테스트가 구현 계획에 포함되어 있는지 확인한다.
- [x] 구현 계획의 검증 기록 단계에 `무엇을/왜/어떻게`, 실제 실행 명령과 결과, 후속 수정 시 누적 기록 및 `정정` 추가 원칙이 명시되어 있는지 확인한다.
- [x] `/admin/partner/agent/list`가 별도 날짜 입력 없이 현재 월 기준 5종 정산 합계를 계산하도록 구현 계획에 반영되어 있는지 확인한다.
- [x] 에이전트 목록 응답 DTO와 리스트 조회 테스트에 `live/content/community/contentDonation/channelDonation` 5종 `agentSettlementAmount` summary 필드가 모두 반영되어 있는지 확인한다.
- [x] 해당 월에 정산 내역이 없는 에이전트도 목록에서 제외하지 않고 5종 합계를 `0`으로 표기하는 쿼리/응답/테스트 계획이 포함되어 있는지 확인한다.
- [x] 리스트 summary 값이 같은 월 기준 상세 조회 응답의 `total.agentSettlementAmount`와 일치하는지 검증하는 회귀 테스트가 구현 계획에 포함되어 있는지 확인한다.
## 구현 시 유의사항
- 관리자용 정산 API를 새로 만든다고 해서 계산 query를 복제하지 않는다.
- 현재 assignment 활성 판정은 시간창 규칙을 반드시 동일하게 사용한다.
- 정산 응답 계약은 기존 agent 응답과 불필요하게 분기하지 않는다.
- 목록 화면은 요약 정보만 제공하고, 상세 정산은 상세 화면에서만 조회한다.
- 목록의 5종 합계는 상세 API의 기간 필터와 별개로, 현재 월 기준 요약값이라는 의미를 문서와 코드에서 동일하게 유지한다.
- 해당 월에 정산 내역이 없더라도 에이전트 행은 유지하고 금액은 `0`으로 표기한다.
## 최종 설계 결론
- 이번 기능은 “새 정산 엔진 추가”가 아니라 “기존 agent 정산 계산을 ADMIN에서 조회할 수 있게 read API를 보강”하는 작업으로 본다.
- 관리자 페이지 흐름은 아래로 고정한다.
1. `/admin/partner/agent/list`로 에이전트 목록과 현재 월 기준 5종 정산 합계 조회
2. 상세 화면에서 `/admin/partner/agent/{agentId}/creator/list`로 현재 소속 크리에이터 조회
3. 필요 시 `/admin/partner/agent/creator/search`로 크리에이터 검색 후 기존 assignment API로 소속 지정
4. 상세 화면에서 `/admin/partner/agent/{agentId}/calculate/*` 5종으로 정산 현황 조회
- 목록의 5종 합계는 상세 페이지 이동 포인트용 현재 월 summary이며, 상세 화면의 날짜 범위 조회와 역할을 구분한다.
- 현재 월에 정산 내역이 없는 에이전트도 목록에 포함되며, 합계는 0원으로 표시한다.
- 이 설계를 기준으로 다음 단계에서는 구현 계획 문서를 작성한다.
## 검증 기록
- 1차 설계
- 무엇을: 관리자용 에이전트 목록/상세 read API 구조와 기존 agent 계산 로직 재사용 범위를 문서로 고정했다.
- 왜: 현재 코드에는 관리자용 assignment/ratio/finalize는 있지만, 관리자용 agent 정산 상세 조회 API는 없어 read 레이어 설계가 먼저 필요했기 때문이다.
- 어떻게:
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorController.kt` 확인
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioController.kt` 확인
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotController.kt` 확인
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateController.kt` 확인
- `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateService.kt` 확인
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/member/AdminMemberController.kt` 확인
- 결과: 관리자용 read API 부재와 기존 계산 로직 재사용 가능성을 문서에 반영했다.

View File

@@ -0,0 +1,21 @@
- [x] `admin/partner/agent/**` 현재 구현과 테스트 범위 확인
- [x] `partner/agent/**` 현재 구현과 테스트 범위 확인
- [x] 관련 DDL/기획 문서에서 요구사항과 변경 배경 추적
- [x] git history와 GitHub 메타데이터에서 이전 결정/경고 사항 확인
- [x] 제외 대상 2건을 제외하고 남는 실질 이슈만 판정
## 검증 기록
### 1차 컨텍스트 리뷰
- 무엇을: 에이전트 권한 및 정산 기능의 최근 구현/수정 이력, 현재 코드, QA 문서, 관련 테스트를 교차 검토해 남아 있는 실질 결함이 있는지 확인했다.
- 왜: 최근 수정으로 일부 validation 이슈는 닫혔지만, event-time 이력 모델과 finalized snapshot 정책이 실제 조회 동작까지 일관되게 반영되는지 재확인이 필요했기 때문이다.
- 어떻게:
- `GIT_MASTER=1 git status`, `GIT_MASTER=1 git log -30 --oneline`, `GIT_MASTER=1 git log --oneline <merge-base>..HEAD`로 브랜치 상태와 최근 변경 맥락을 확인했다.
- `docs/20260408_에이전트권한및정산기능추가.md`, `docs/20260409_partner_agent_assignment_ratio_ddl.sql`, `docs/20260410_에이전트정산기능QA.md`를 읽어 요구사항/후속 수정/제외 대상 2건을 확인했다.
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/**`, `src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/**`, 관련 테스트 파일을 읽어 assignment, ratio, calculate, snapshot 로직을 대조했다.
- `./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.*" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"`를 실행해 관련 테스트를 검증했다.
- 실행/확인 결과:
- 관련 코드/문서 검토 결과, `AgentCalculateQueryRepository.getAssignedCreatorTotalCount/getAssignedCreators``assignedAt` 현재 시점 조건 없이 `unassignedAt is null`만 사용함을 확인했다.
- `docs/20260410_에이전트정산기능QA.md:35`에 "미래 assignedAt만 가진 예약 소속은 현재 목록에 노출되지 않아야 한다" 요구사항이 명시되어 있음을 확인했다.
- `src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentAssignedCreatorFutureWindowQaTest.kt`의 첫 테스트가 실제로 실패해 위 요구사항 누락이 재현됨을 확인했다.
- `gh` 명령은 현재 환경에 설치되어 있지 않아 GitHub PR/이슈 메타데이터 조회는 불가했다 (`zsh:1: command not found: gh`).

Some files were not shown because too many files have changed in this diff Show More