Files
sodalive-backend-spring-boot/docs/20260408_에이전트권한및정산기능추가.md

71 KiB

에이전트 권한 및 정산 기능 추가 작업 계획

요구사항 상세 분석

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_relationassignedAt/unassignedAt 기반 이력형 소속 모델로 전환
    • agent_settlement_ratioeffectiveFrom/effectiveTo 기반 이력형 비율 모델로 전환
    • 관리자 소속 지정/해제 API를 시간 경계 기반으로 수정
    • 관리자 비율 생성/수정/조회 API를 이력형 비율 기준으로 수정
    • AGENT 정산 조회 쿼리를 거래 시점(event time)의 소속/비율을 기준으로 계산하도록 수정
    • 확정 정산 스냅샷 저장 모델 및 관리자 확정 API 추가
    • finalized 기간 조회는 스냅샷 우선, 미확정 기간 조회는 live 계산 유지
    • 위 구조에 필요한 테스트, DDL 문서, 계획 문서 갱신
  • 제외 범위
    • 기존 과거 데이터의 완전 복원 보장
    • 이벤트소싱 도입
    • 프론트엔드 화면 개편

current-state 설계의 한계

  • 현재 AgentCreatorRelationagent, creator만 저장하고 remove 시 hard-delete 하므로 과거 소속 이력을 보존하지 못한다.
  • 현재 AgentSettlementRatiodeletedAt 기반 현재 활성 행 조회에 의존하므로 특정 거래 시점의 비율을 안정적으로 재현하지 못한다.
  • 현재 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 축으로 분리

작업 체크리스트

  • 기존 MemberRole.AGENT 사용 범위를 점검하고, 관리자 화면/API에서 에이전트 계정을 식별할 수 있는 조회 경로를 확정한다.
  • kr.co.vividnext.sodalive.partner.agent 패키지 아래에 에이전트-크리에이터 소속 전용 엔티티/리포지토리를 추가한다.
  • 소속 엔티티에 creator_id 유니크 제약과 agent/creator role 검증 로직을 추가해 "한 크리에이터는 하나의 에이전트에만 소속" 규칙을 보장한다.
  • 관리자 전용 크리에이터 소속 지정 API를 추가한다.
  • 관리자 전용 크리에이터 소속 해제 API를 추가한다.
  • 에이전트 정산 비율 전용 엔티티/리포지토리를 추가하고, 에이전트당 단일 비율만 유지되도록 한다.
  • 관리자 전용 에이전트 정산 비율 생성/수정/조회 API를 추가한다.
  • 1차 구현 범위의 assignment/ratio 컨트롤러/서비스 테스트를 추가해 role 검증, 중복 소속 방지, 누락 엔티티, pageable 위임을 검증한다.
  • 신규 assignment/ratio 테이블 생성을 위한 DDL 문서를 docs/20260409_partner_agent_assignment_ratio_ddl.sql에 추가한다.
  • 에이전트 본인의 소속 크리에이터 목록 조회 API를 추가한다.
  • /agent/calculate/creator/list가 현재 시각 기준 assignedAt <= now < unassignedAt 활성 구간의 크리에이터만 노출하도록 보강한다.
  • 라이브 현황용 agent 전용 Query/DTO/응답을 추가하고, settlementAmount 기준 agentSettlementAmount를 계산한다.
  • 콘텐츠 판매 현황용 agent 전용 Query/DTO/응답을 추가하고, 기존 콘텐츠 정산 비율(audioContent.settlementRatio 또는 CreatorSettlementRatio.contentSettlementRatio)을 재사용한다.
  • 커뮤니티 현황용 agent 전용 Query/DTO/응답을 추가하고, CreatorSettlementRatio.communitySettlementRatio 기반 정산 후 agent 금액을 계산한다.
  • 채널후원 현황용 agent 전용 Query/DTO/응답을 추가하고, 기존 ChannelDonationSettlementCalculator 결과의 settlementAmount 기준으로 agent 금액을 계산한다.
  • 콘텐츠후원 현황용 agent 전용 Query/DTO/응답을 추가하고, 기존 콘텐츠후원 계산 결과의 settlementAmount 기준으로 agent 금액을 계산한다.
  • 5개 현황 응답 모두에 totalCount + total + items 구조를 맞추고, item/total 양쪽에 agentSettlementAmount를 포함한다.
  • AGENT 계정이 자신에게 소속된 크리에이터 데이터만 조회하도록 @AuthenticationPrincipal + relation 기반 필터링을 구현한다.
  • ADMIN/AGENT 권한 오류, 잘못된 role 요청, 중복 소속 요청, 비소속 데이터 조회 차단에 대한 예외 처리를 추가한다.
  • 컨트롤러/서비스/쿼리 리포지토리 테스트를 추가해 소속 제약, 권한 제약, 정산 계산식, total 합계를 검증한다.
  • ./gradlew test, ./gradlew build로 최종 검증한다.
  • agent_creator_relationassignedAt, unassignedAt를 추가하고 current-state 단일 row 모델을 append-only 이력 모델로 전환한다.
  • 관리자 소속 지정 API가 assignedAt을 받아 활성 기간 중복을 검증하도록 수정한다.
  • 관리자 소속 해제 API가 hard-delete 대신 unassignedAt 종료 처리로 변경되도록 수정한다.
  • agent_settlement_ratioeffectiveFrom, effectiveTo를 추가하고 단일 현재 row 갱신 모델을 append-only 이력 모델로 전환한다.
  • 관리자 비율 생성/수정 API가 effectiveFrom을 받아 기존 활성 row 종료 + 신규 row 추가로 동작하도록 수정한다.
  • 관리자 비율 생성/수정 API가 effectiveFrom backdate, 동일 시각 입력, 기존 ratio history와 겹치는 시점을 거절하도록 검증을 보강한다.
  • 관리자 비율 생성/수정 API가 settlementRatio를 0..100 범위로 검증하도록 보강한다.
  • agent_settlement_ratio DDL에 MySQL 생성 컬럼 + UNIQUE 인덱스로 active row 단일성을 보장하고, effective_from < effective_to 기간 무결성 제약을 추가한다.
  • agent_creator_relation DDL에 MySQL 생성 컬럼 + UNIQUE 인덱스로 active row 단일성을 보장하고, assigned_at < unassigned_at 기간 무결성 제약을 추가한다.
  • 관리자 소속/비율 쓰기 경로가 MemberRepository.findByIdForUpdate(...) 기반 비관적 락과 unique violation 대응 패턴으로 직렬화되도록 보강한다.
  • AGENT 정산 조회가 거래 시점 기준의 소속 이력과 비율 이력을 조인하도록 AgentCalculateQueryRepository를 수정한다.
  • 기간 중 소속 변경 또는 비율 변경이 있는 경우 결과가 올바르게 분리/집계되는 테스트를 추가한다.
  • AGENT 정산 조회의 paged query가 사전 조회된 creatorIds가 빈 페이지일 때 전체 결과로 fallback하지 않고 빈 rows/items를 반환하도록 보강한다.
  • AGENT 정산 조회에서 agent_settlement_ratio 이력이 없으면 agent 정산금을 0% 대신 10% 기본값으로 계산하도록 수정한다.
  • 확정 정산 스냅샷 엔티티/리포지토리/관리자 API를 추가한다.
  • 확정 정산 스냅샷이 소속/비율 foreign key뿐 아니라 적용 비율과 계산 결과 숫자값을 함께 저장하도록 구현한다.
    • 정정(2026-04-09): 현재 구현은 appliedAgentSettlementRatio와 계산 결과 숫자값은 저장하지만, 설계 초안에 명시한 assignmentId, agentSettlementRatioId는 아직 스냅샷/DDL에 포함하지 않았다. 따라서 이 항목은 엄밀히는 부분 충족 상태로 본다.
    • AgentSettlementSnapshotagent_settlement_snapshot DDL에 assignmentId, agentSettlementRatioId 컬럼을 추가한다.
    • AgentCalculateQueryRepository와 snapshot 생성용 query DTO에 거래 시점 기준 assignmentId, agentSettlementRatioId projection을 추가한다.
    • AdminAgentSettlementSnapshotService와 관련 테스트를 갱신해 finalize 시점에 foreign key + 적용 비율 + 계산 숫자값이 함께 저장되도록 보완한다.
    • 참고: 위 보완은 creator-period summary가 단일 소속/단일 비율 이력 row로 귀결되는 경우의 추적성은 복구하지만, 기간 중 복수 history row가 섞인 summary의 완전 provenance까지 보장하지는 않는다. 그 수준의 감사 추적이 필요하면 별도 snapshot source detail 테이블이 추가로 필요하다.
    • agent_settlement_snapshot_source_detail(가칭) DDL을 추가해 summary를 구성한 원천 source row별 provenance를 별도 저장한다.
    • finalize가 raw source row를 기준으로 source detail과 creator-period summary를 같은 트랜잭션 안에서 함께 저장하도록 보강한다.
    • source detail이 1건인 summary만 assignmentId, agentSettlementRatioId, appliedAgentSettlementRatio를 채우고, mixed-period summary는 null로 유지하는 규칙을 테스트로 고정한다.
  • finalized 기간 조회는 스냅샷 우선, 미확정 기간 조회는 live 계산을 사용하도록 분기한다.
  • 신규 이력/스냅샷 구조에 맞는 DDL 문서를 추가 또는 기존 DDL 문서를 확장한다.
  • 기존 계획 문서 하단 검증 기록에 이력형 전환과 스냅샷 도입 구현/검증 결과를 누적한다.

세부 구현 메모

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 입력 무결성 차단부터 먼저 수정한다.

  • src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AgentSettlementRatioServiceTest.kt에 아래 RED 테스트를 추가한다.
    • active row보다 과거 effectiveFrom으로 create/update 요청 시 예외가 발생한다.
    • active row와 같은 effectiveFrom으로 create/update 요청 시 예외가 발생한다.
    • active row가 없더라도 기존 closed history와 겹치는 effectiveFrom이면 예외가 발생한다.
  • src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt에서 effectiveFrom backdate / same-time / history overlap을 거절하도록 검증을 추가한다.
  • src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/ratio/AgentSettlementRatioRepository.kt에 history overlap 판별용 조회 메서드를 추가한다.
  • ratio 서비스 테스트를 재실행해 backdate 차단이 먼저 보장되는지 확인한다.

5-2. 관리자 소속/비율 쓰기 경로를 직렬화한다.

  • src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.kt에서 agent 대상 MemberRepository.findByIdForUpdate(...)를 사용하도록 수정한다.
  • src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/assignment/AdminAgentCreatorService.kt에서 creator 대상 MemberRepository.findByIdForUpdate(...)를 사용하도록 수정한다.
  • 두 서비스 모두 saveAndFlush + DataIntegrityViolationException 대응 패턴으로 unique violation을 사용자 예외로 변환하도록 보강한다.
  • 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/엔티티에 맞춘다.

  • docs/20260409_partner_agent_assignment_ratio_ddl.sql 기준으로 agent_creator_relation, agent_settlement_ratio, agent_settlement_snapshot 최종 스키마가 구현 코드와 일치하는지 다시 점검한다.
  • 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의 최종 컬럼 구조와 충돌하지 않는지 확인한다.
  • generated column(active_creator_key, active_ratio_key)은 JPA 쓰기 대상에서 제외하고, 서비스/리포지토리 로직이 해당 컬럼 없이도 동작하는지 확인한다.

5-4. snapshot traceability를 query → service → entity 순서로 연결한다.

  • src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/AgentSettlementSnapshot.ktassignmentId, agentSettlementRatioId 필드를 추가한다.
  • src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt와 snapshot 생성용 query DTO에 거래 시점 기준 assignmentId, agentSettlementRatioId projection을 추가한다.
  • docs/20260409_partner_agent_assignment_ratio_ddl.sqlagent_settlement_snapshot_source_detail(가칭) 테이블을 추가하고, snapshot_id, assignment_id, agent_settlement_ratio_id, source subtotal 컬럼, 조회 인덱스/FK를 정의한다.
  • src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/settlement/snapshot/**에 source detail 엔티티/리포지토리 파일을 추가한다.
  • src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.ktraw source row 기준의 source detail과 creator-period summary를 함께 저장하도록 바꾼다.
  • creator-period summary가 단일 source row로 귀결될 때만 assignmentId, agentSettlementRatioId, appliedAgentSettlementRatio를 summary에 채우고, mixed-period summary는 null로 저장하도록 매핑 규칙을 고정한다.
  • src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotServiceTest.kt에 아래 검증을 추가한다.
    • 단일 source summary는 summary row와 detail row가 같은 assignmentId, agentSettlementRatioId, appliedAgentSettlementRatio를 가진다.
    • mixed-period summary는 summary의 assignmentId, agentSettlementRatioId, appliedAgentSettlementRationull이고, detail row 여러 건으로 provenance를 복원할 수 있다.
    • detail 합계가 summary 숫자값과 일치한다.

5-5. 최종 회귀 검증을 수행한다.

  • ./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.assignment.*"
  • ./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.ratio.*"
  • ./gradlew test --tests "kr.co.vividnext.sodalive.admin.partner.agent.settlement.*"
  • ./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.*"
  • ./gradlew test
  • ./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.sqlagent_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.ktpartnerAgentMessages 맵을 추가하고 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, AdminAgentCreatorServiceadmin.partner.agent.assignment로 이동하고, relation/request DTO/repository는 partner.agent.assignment에 유지했다.
    • AdminAgentSettlementRatioController, AdminAgentSettlementRatioServiceadmin.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.AdminAgentSettlementRatioControllerClassNotFoundException으로 미존재 확인

5차 수정

  • 무엇을: agent_creator_relationassignedAt/unassignedAt 기반 append-only 이력 모델로 전환하고, 관리자 소속 지정/해제 API를 시간 경계 기반 계약으로 수정했다.
  • 왜: 기존 current-state + hard-delete 구조로는 과거 소속 이력을 보존할 수 없고, 소속 해제 후 재배정 같은 운영 시나리오를 안전하게 표현할 수 없기 때문이다.
  • 어떻게:
    • src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/assignment/AgentCreatorRelation.ktassignedAt, 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.sqlagent_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_ratioeffectiveFrom/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.kteffectiveFrom/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.ktPOST /admin/partner/agent/settlement/finalize를 추가하고, 인증 관리자 member.idfinalizedByMemberId로 전달하도록 맞췄다.
    • 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.sqlagent_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 범위 메모를 기존 체크리스트에 후속 작업으로 추가했다.
  • 왜: 현재 AdminAgentSettlementRatioServiceeffectiveFrom의 시간순 검증 없이 활성 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.ktagentSettlementRatio = 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.ktsettlementRatio = -1 생성, settlementRatio = 101 수정 요청이 common.error.invalid_request를 던지고 memberRepository/repository가 호출되지 않는다는 RED 테스트 2건을 추가했다.
    • src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/ratio/AdminAgentSettlementRatioService.ktvalidateSettlementRatio(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 서버 미구성)