docs(agent-read): 관리자 에이전트 조회 문서를 추가한다

This commit is contained in:
2026-04-10 19:53:18 +09:00
parent 37f2e3d45a
commit 576498ffb6
2 changed files with 1950 additions and 0 deletions

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 부재와 기존 계산 로직 재사용 가능성을 문서에 반영했다.