# Guide #50 — 정산 메뉴 백엔드 개선 + UI 재설계

> **작성일**: 2026-03-24
> **대상 모듈**: `partner-api` (Spring Boot) + `partner-ui` (Vue 3)
> **선행 조건**: Guide #48 (Partner API 완성) 완료

---

## 1. 현황 및 문제 분석

### 1.1 현재 화면 (스크린샷 기반)

```
┌──────────────────────────────────────────────────────────────────┐
│  정산 잔액                                                        │
│  미실현/실현/출금가능 잔액 현황                                      │
│                                                                  │
│  ┌─ ID:2 ──────┐  ┌─ ID:3 ──────┐  ┌─ ID:4 ──────┐             │
│  │ 미실현    25 │  │ 미실현  12.5│  │ 미실현    25 │             │
│  │ 실현       5 │  │ 실현      0 │  │ 실현       0 │             │
│  │ 출금완료   - │  │ 출금완료  - │  │ 출금완료   - │             │
│  │ 출금가능   - │  │ 출금가능  - │  │ 출금가능  - │             │
│  │ [쉐어출금] │  │ [쉐어출금] │  │ [쉐어출금] │             │
│  └──────────────┘  └──────────────┘  └──────────────┘             │
│                                                                  │
│  ┌─ ID:6 ──────┐                                                 │
│  │ 미실현    10 │                                                 │
│  │ 실현       0 │                                                 │
│  │ 출금완료   - │                                                 │
│  │ 출금가능   - │                                                 │
│  │ [쉐어출금] │                                                 │
│  └──────────────┘                                                 │
└──────────────────────────────────────────────────────────────────┘
```

### 1.2 문제점 5가지

| # | 문제 | 원인 | 영향 |
|---|------|------|------|
| ① | **카드 헤더가 "ID:2", "ID:3"** — 통화/네트워크명 없음 | 백엔드가 raw entity 반환 (`currencyId`, `networkId`만 포함) | 사용자가 어떤 코인인지 알 수 없음 |
| ② | **"출금완료", "출금가능"이 전부 `-`** | `totalWithdrawn`이 0이면 `-` 표시, `withdrawableBalance` 필드가 백엔드에 없음 | 출금 가능 금액을 확인 불가 |
| ③ | **네트워크 구분 없음** | currency_id만으로 카드 분리 — BSC USDT / PLG USDT가 별도 카드인데 둘 다 "USDT"로 표시될 예정 | 어떤 체인의 USDT인지 혼동 |
| ④ | **상단 요약 영역 없음** | 전체 미실현/실현/출금가능 합계를 한눈에 볼 수 없음 | 정산 현황 파악 불가 |
| ⑤ | **일별 수수료 / 실현 내역도 동일 문제** | `SettlementDailyFee`, `SettlementRealization` entity 그대로 반환 → currencyCode/networkCode 없음 | 모든 정산 화면에서 ID만 표시 |

### 1.3 원인: admin-api vs partner-api 비교

| 항목 | admin-api (✅ 정상) | partner-api (❌ 문제) |
|------|---------------------|----------------------|
| 정산 잔액 응답 | `SettlementBalanceResponse` DTO (JOIN) | `SettlementBalance` entity (raw) |
| 일별 수수료 응답 | `SettlementDailyFeeResponse` DTO (JOIN) | `SettlementDailyFee` entity (raw) |
| 실현 내역 응답 | `SettlementRealizationDetailResponse` DTO (JOIN) | `SettlementRealization` entity (raw) |
| Mapper | `SettlementSearchMapper` — JOIN currencies, networks, partners | `PartnerSettlementMapper` — JOIN 없음 |

---

## 2. 백엔드 수정 (partner-api)

### 2.1 새 Response DTO 3개 생성

#### `PartnerSettlementBalanceResponse.java`

```java
package com.cryptoments.partnerapi.dto.response;

/**
 * 파트너 정산 잔액 응답.
 */
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PartnerSettlementBalanceResponse {

    /** PK */
    private Long id;

    /** 통화 ID */
    private Long currencyId;

    /** 통화 심볼 (USDT, USDC 등) */
    private String currencySymbol;

    /** 네트워크 ID */
    private Long networkId;

    /** 네트워크 심볼 (BSC, POLYGON, TRON) */
    private String networkSymbol;

    /** 미실현 잔액 — 수수료 발생했으나 MASTER에 있음 */
    private BigDecimal unrealizedBalance;

    /** 실현 잔액 — SETTLEMENT 이체 완료 */
    private BigDecimal realizedBalance;

    /** 누적 출금액 */
    private BigDecimal totalWithdrawn;

    /** 출금 가능 금액 = realizedBalance - totalWithdrawn (가상 필드) */
    private BigDecimal withdrawableBalance;
}
```

> **핵심**: `withdrawableBalance`는 DB에 없는 **계산 필드**. SQL에서 `(sb.realized_balance - sb.total_withdrawn) AS withdrawable_balance`로 산출.

#### `PartnerDailyFeeResponse.java`

```java
package com.cryptoments.partnerapi.dto.response;

/**
 * 파트너 일별 수수료 응답.
 */
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PartnerDailyFeeResponse {

    /** PK */
    private Long id;

    /** 정산일 */
    private LocalDate settlementDate;

    /** 통화 심볼 */
    private String currencySymbol;

    /** 네트워크 심볼 */
    private String networkSymbol;

    /** 참여 유형 */
    private String participantType;

    /** 총 입금액 */
    private BigDecimal totalDepositAmount;

    /** 총 수수료 */
    private BigDecimal totalFeeAmount;

    /** 쉐어 비율 */
    private BigDecimal shareRate;

    /** 수수료 수익 (= totalFeeAmount × shareRate) */
    private BigDecimal shareAmount;

    /** 상태 (UNREALIZED / REALIZED) */
    private String status;

    /** 생성일 */
    private LocalDateTime createdAt;
}
```

#### `PartnerRealizationResponse.java`

```java
package com.cryptoments.partnerapi.dto.response;

/**
 * 파트너 정산 실현 응답.
 */
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PartnerRealizationResponse {

    /** PK */
    private Long id;

    /** 통화 심볼 */
    private String currencySymbol;

    /** 네트워크 심볼 */
    private String networkSymbol;

    /** 기간 시작 */
    private LocalDate periodStart;

    /** 기간 종료 */
    private LocalDate periodEnd;

    /** 총 수수료 */
    private BigDecimal totalFeeAmount;

    /** 총 쉐어 */
    private BigDecimal totalShareAmount;

    /** TX Hash */
    private String txHash;

    /** 상태 */
    private String status;

    /** 완료일 */
    private LocalDateTime completedAt;

    /** 실패 사유 */
    private String failedReason;

    /** 생성일 */
    private LocalDateTime createdAt;
}
```

### 2.2 Mapper 수정 — `PartnerSettlementMapper.java`

3개 쿼리 모두 **JOIN + 계산 필드** 추가:

```java
@Mapper
public interface PartnerSettlementMapper {

    /**
     * 파트너 정산 잔액 (JOIN + withdrawableBalance 계산).
     */
    @Select("SELECT sb.id, sb.currency_id, sb.network_id," +
            "  c.symbol AS currency_symbol," +
            "  bn.chain_symbol AS network_symbol," +
            "  sb.unrealized_balance," +
            "  sb.realized_balance," +
            "  sb.total_withdrawn," +
            "  (sb.realized_balance - sb.total_withdrawn) AS withdrawable_balance" +
            " FROM settlement_balances sb" +
            " LEFT JOIN currencies c ON sb.currency_id = c.id" +
            " LEFT JOIN blockchain_networks bn ON sb.network_id = bn.id" +
            " WHERE sb.participant_partner_id = #{partnerId}" +
            " ORDER BY sb.currency_id, sb.network_id")
    List<PartnerSettlementBalanceResponse> findPartnerBalances(@Param("partnerId") Long partnerId);

    /**
     * 일별 수수료 (JOIN currencies + networks).
     */
    @Select("<script>" +
            "SELECT sdf.id, sdf.settlement_date, sdf.participant_type," +
            "  c.symbol AS currency_symbol," +
            "  bn.chain_symbol AS network_symbol," +
            "  sdf.total_deposit_amount, sdf.total_fee_amount," +
            "  sdf.share_rate, sdf.share_amount, sdf.status, sdf.created_at" +
            " FROM settlement_daily_fees sdf" +
            " LEFT JOIN currencies c ON sdf.currency_id = c.id" +
            " LEFT JOIN blockchain_networks bn ON sdf.network_id = bn.id" +
            " WHERE (sdf.participant_partner_id = #{partnerId}" +
            "        OR sdf.source_partner_id = #{partnerId})" +
            " <if test='from != null'>AND sdf.settlement_date &gt;= #{from}</if>" +
            " <if test='to != null'>AND sdf.settlement_date &lt;= #{to}</if>" +
            " <if test='currencyId != null'>AND sdf.currency_id = #{currencyId}</if>" +
            " <if test='networkId != null'>AND sdf.network_id = #{networkId}</if>" +
            "</script>")
    XPage<PartnerDailyFeeResponse> searchDailyFees(XPagination pagination,
                                                    @Param("partnerId") Long partnerId,
                                                    @Param("from") String from,
                                                    @Param("to") String to,
                                                    @Param("currencyId") Long currencyId,
                                                    @Param("networkId") Long networkId,
                                                    Class<?> cls);

    /**
     * 실현 내역 (JOIN currencies + networks).
     */
    @Select("<script>" +
            "SELECT sr.id, sr.period_start, sr.period_end," +
            "  c.symbol AS currency_symbol," +
            "  bn.chain_symbol AS network_symbol," +
            "  sr.total_fee_amount, sr.total_share_amount," +
            "  sr.tx_hash, sr.status, sr.completed_at," +
            "  sr.failed_reason, sr.created_at" +
            " FROM settlement_realizations sr" +
            " LEFT JOIN currencies c ON sr.currency_id = c.id" +
            " LEFT JOIN blockchain_networks bn ON sr.network_id = bn.id" +
            " WHERE sr.partner_id = #{partnerId}" +
            " <if test='from != null'>AND sr.created_at &gt;= #{from}</if>" +
            " <if test='to != null'>AND sr.created_at &lt; DATE_ADD(#{to}, INTERVAL 1 DAY)</if>" +
            " <if test='currencyId != null'>AND sr.currency_id = #{currencyId}</if>" +
            " <if test='status != null'>AND sr.status = #{status}</if>" +
            "</script>")
    XPage<PartnerRealizationResponse> searchRealizations(XPagination pagination,
                                                          @Param("partnerId") Long partnerId,
                                                          @Param("from") String from,
                                                          @Param("to") String to,
                                                          @Param("currencyId") Long currencyId,
                                                          @Param("status") String status,
                                                          Class<?> cls);
}
```

### 2.3 Service 반환타입 변경 — `PartnerSettlementService.java`

```java
// Before (raw entity)
public List<SettlementBalance> getBalance(Long partnerId) { ... }
public XPage<SettlementDailyFee> getDailyFees(...) { ... }
public XPage<SettlementRealization> getRealizations(...) { ... }

// After (DTO)
public List<PartnerSettlementBalanceResponse> getBalance(Long partnerId) { ... }
public XPage<PartnerDailyFeeResponse> getDailyFees(...) { ... }
public XPage<PartnerRealizationResponse> getRealizations(...) { ... }
```

### 2.4 Controller 반환타입 변경 — `PartnerSettlementController.java`

```java
// Before
@GetMapping("/balance")
public List<SettlementBalance> getSettlementBalance() { ... }

@GetMapping("/daily-fees")
public XPage<SettlementDailyFee> getDailyFees(...) { ... }

@GetMapping("/realizations")
public XPage<SettlementRealization> getRealizations(...) { ... }

// After
@GetMapping("/balance")
public List<PartnerSettlementBalanceResponse> getSettlementBalance() { ... }

@GetMapping("/daily-fees")
public XPage<PartnerDailyFeeResponse> getDailyFees(...) { ... }

@GetMapping("/realizations")
public XPage<PartnerRealizationResponse> getRealizations(...) { ... }
```

---

## 3. 프론트엔드 수정 (partner-ui)

### 3.1 TypeScript 타입 변경 — `settlement.ts`

```typescript
// ── Before ──
export interface SettlementBalance {
  id: number
  currencyId: number
  currencyCode?: string        // ← 백엔드가 안 줌
  unrealizedBalance?: number
  realizedBalance?: number
  withdrawnBalance?: number
  withdrawableBalance?: number  // ← 백엔드가 안 줌
}

// ── After ──
export interface SettlementBalance {
  id: number
  currencyId: number
  networkId: number
  /** 통화 심볼: USDT, USDC */
  currencySymbol: string
  /** 네트워크 심볼: BSC, POLYGON, TRON */
  networkSymbol: string
  /** 미실현 잔액 */
  unrealizedBalance: number
  /** 실현 잔액 */
  realizedBalance: number
  /** 누적 출금액 */
  totalWithdrawn: number
  /** 출금 가능 = realized - withdrawn */
  withdrawableBalance: number
}

export interface SettlementDailyFee {
  id: number
  settlementDate: string
  currencySymbol: string
  networkSymbol: string
  participantType: string
  totalDepositAmount: number
  totalFeeAmount: number
  shareRate: number
  shareAmount: number
  status: string
  createdAt: string
}

export interface SettlementRealization {
  id: number
  currencySymbol: string
  networkSymbol: string
  periodStart: string
  periodEnd: string
  totalFeeAmount: number
  totalShareAmount: number
  txHash?: string
  status: string
  completedAt?: string
  failedReason?: string
  createdAt: string
}
```

### 3.2 정산 잔액 화면 재설계 — `BalanceView.vue`

**목표 UI (PCR-5020 설계서 준수 + 개선)**:

```
┌─────────────────────────────────────────────────────────────────────────┐
│  📊 정산 잔액                                                           │
│  미실현/실현/출금가능 잔액 현황                                            │
│                                                                         │
│  ┌──── 📋 전체 요약 ──────────────────────────────────────────────┐      │
│  │  총 미실현        총 실현         총 출금완료      총 출금가능   │      │
│  │  72.50 USD       5.00 USD       500.00 USD      -495.00 USD  │      │
│  └───────────────────────────────────────────────────────────────┘      │
│                                                                         │
│  ┌─ BSC · USDT ────────────┐  ┌─ POLYGON · USDT ──────────────┐       │
│  │                          │  │                                │       │
│  │  미실현 잔액      25.00  │  │  미실현 잔액          12.50   │       │
│  │  실현 잔액         5.00  │  │  실현 잔액              0.00   │       │
│  │  출금 완료         0.00  │  │  출금 완료              0.00   │       │
│  │  ─────────────────────── │  │  ──────────────────────────── │       │
│  │  출금 가능 ✅      5.00  │  │  출금 가능 ✅           0.00   │       │
│  │                          │  │                                │       │
│  │  [쉐어 출금 요청 →]      │  │  [쉐어 출금 요청 →]            │       │
│  └──────────────────────────┘  └────────────────────────────────┘       │
│                                                                         │
│  ┌─ TRON · USDT ───────────┐  ┌─ BSC · USDC ──────────────────┐       │
│  │                          │  │                                │       │
│  │  미실현 잔액      25.00  │  │  미실현 잔액          10.00   │       │
│  │  실현 잔액         0.00  │  │  실현 잔액              0.00   │       │
│  │  출금 완료         0.00  │  │  출금 완료            500.00   │       │
│  │  ─────────────────────── │  │  ──────────────────────────── │       │
│  │  출금 가능 ✅      0.00  │  │  출금 가능 ✅       -500.00   │       │
│  │                          │  │                                │       │
│  │  [쉐어 출금 요청 →]      │  │  [쉐어 출금 요청 →]            │       │
│  └──────────────────────────┘  └────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 변경 포인트

| 항목 | Before | After |
|------|--------|-------|
| 카드 헤더 | `ID:2` | `BSC · USDT` (networkSymbol · currencySymbol) |
| 출금가능 | `-` (표시 안됨) | `withdrawableBalance` (백엔드 계산) |
| 상단 요약 | 없음 | 전체 합계 4칸 카드 |
| 출금 버튼 | 무조건 활성 | `withdrawableBalance > 0`일 때만 활성 |
| 금액 포맷 | 소수점 미통일 | 소수점 2자리 (스테이블코인) |
| 쉐어 출금 링크 | 단순 라우터 이동 | `currencyId`, `networkId`를 query param으로 전달 |

#### 전체 코드

```vue
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { settlementService } from '@/api/services/settlement.service'
import type { SettlementBalance } from '@/api/types/settlement'
import PageHeader from '@/components/common/PageHeader.vue'
import AmountDisplay from '@/components/common/AmountDisplay.vue'
import LoadingSkeleton from '@/components/common/LoadingSkeleton.vue'
import EmptyState from '@/components/common/EmptyState.vue'

const loading = ref(false)
const balances = ref<SettlementBalance[]>([])

/** 상단 요약: 전체 합계 */
const summary = computed(() => ({
  unrealized: balances.value.reduce((s, b) => s + (b.unrealizedBalance ?? 0), 0),
  realized: balances.value.reduce((s, b) => s + (b.realizedBalance ?? 0), 0),
  withdrawn: balances.value.reduce((s, b) => s + (b.totalWithdrawn ?? 0), 0),
  withdrawable: balances.value.reduce((s, b) => s + (b.withdrawableBalance ?? 0), 0),
}))

async function fetchData() {
  loading.value = true
  try {
    balances.value = await settlementService.getBalance()
  } finally {
    loading.value = false
  }
}

/** 카드 헤더 라벨: "BSC · USDT" */
function cardLabel(b: SettlementBalance): string {
  const net = b.networkSymbol ?? `NET:${b.networkId}`
  const cur = b.currencySymbol ?? `CUR:${b.currencyId}`
  return `${net} · ${cur}`
}

onMounted(fetchData)
</script>

<template>
  <div class="space-y-6">
    <PageHeader title="정산 잔액" description="미실현/실현/출금가능 잔액 현황" />

    <LoadingSkeleton v-if="loading" :rows="3" />
    <EmptyState v-else-if="!balances.length" message="정산 잔액 데이터가 없습니다." />

    <template v-else>
      <!-- ① 상단 요약 카드 -->
      <Card>
        <CardContent class="pt-6">
          <h3 class="mb-4 text-sm font-semibold text-muted-foreground">전체 요약</h3>
          <div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
            <div>
              <p class="text-xs text-muted-foreground">총 미실현</p>
              <p class="text-lg font-semibold">
                <AmountDisplay :amount="summary.unrealized" />
              </p>
            </div>
            <div>
              <p class="text-xs text-muted-foreground">총 실현</p>
              <p class="text-lg font-semibold">
                <AmountDisplay :amount="summary.realized" />
              </p>
            </div>
            <div>
              <p class="text-xs text-muted-foreground">총 출금완료</p>
              <p class="text-lg font-semibold">
                <AmountDisplay :amount="summary.withdrawn" />
              </p>
            </div>
            <div>
              <p class="text-xs text-muted-foreground">총 출금가능</p>
              <p class="text-lg font-semibold text-primary">
                <AmountDisplay :amount="summary.withdrawable" />
              </p>
            </div>
          </div>
        </CardContent>
      </Card>

      <!-- ② 통화/네트워크별 카드 -->
      <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
        <Card v-for="b in balances" :key="`${b.networkId}-${b.currencyId}`">
          <CardContent class="pt-6 space-y-3">
            <h3 class="text-base font-semibold">{{ cardLabel(b) }}</h3>

            <div class="flex justify-between text-sm">
              <span class="text-muted-foreground">미실현 잔액</span>
              <AmountDisplay :amount="b.unrealizedBalance" :currency="b.currencySymbol" />
            </div>
            <div class="flex justify-between text-sm">
              <span class="text-muted-foreground">실현 잔액</span>
              <AmountDisplay :amount="b.realizedBalance" :currency="b.currencySymbol" />
            </div>
            <div class="flex justify-between text-sm">
              <span class="text-muted-foreground">출금 완료</span>
              <AmountDisplay :amount="b.totalWithdrawn" :currency="b.currencySymbol" />
            </div>
            <div class="flex justify-between text-sm font-medium border-t pt-2">
              <span class="text-muted-foreground">출금 가능</span>
              <span :class="(b.withdrawableBalance ?? 0) > 0 ? 'text-primary' : 'text-muted-foreground'">
                <AmountDisplay :amount="b.withdrawableBalance" :currency="b.currencySymbol" />
              </span>
            </div>

            <router-link
              :to="{
                path: '/partner/settlement/withdraw',
                query: { currencyId: b.currencyId, networkId: b.networkId }
              }"
            >
              <Button
                size="sm"
                class="mt-2 w-full"
                :disabled="!b.withdrawableBalance || b.withdrawableBalance <= 0"
              >
                쉐어 출금 요청
              </Button>
            </router-link>
          </CardContent>
        </Card>
      </div>
    </template>
  </div>
</template>
```

### 3.3 일별 수수료 화면 수정 — `FeesView.vue`

**변경 사항:**

| 항목 | Before | After |
|------|--------|-------|
| 통화 컬럼 | `currencyCode` (null) | `currencySymbol` + `networkSymbol` |
| 필드 매핑 | `feeDate`, `role`, `txCount` (서버에 없는 필드) | `settlementDate`, `participantType`, `totalFeeAmount` |

```typescript
// columns 변경
const columns: Column<SettlementDailyFee>[] = [
  { key: 'settlementDate', label: '날짜' },
  { key: 'networkSymbol', label: '네트워크' },
  { key: 'currencySymbol', label: '통화' },
  { key: 'participantType', label: '역할' },
  { key: 'totalDepositAmount', label: '총 입금액', class: 'text-right' },
  { key: 'totalFeeAmount', label: '총 수수료', class: 'text-right' },
  { key: 'shareRate', label: '쉐어율', class: 'text-right' },
  { key: 'shareAmount', label: '수수료 수익', class: 'text-right' },
  { key: 'status', label: '상태' },
]
```

### 3.4 실현 내역 화면 수정 — `RealizationsView.vue`

```typescript
const columns: Column<SettlementRealization>[] = [
  { key: 'periodStart', label: '기간' },        // periodStart ~ periodEnd 표시
  { key: 'networkSymbol', label: '네트워크' },
  { key: 'currencySymbol', label: '통화' },
  { key: 'totalShareAmount', label: '실현 금액', class: 'text-right' },
  { key: 'txHash', label: 'TX Hash' },           // 블록 익스플로러 링크
  { key: 'status', label: '상태' },
  { key: 'completedAt', label: '실현일' },
]
```

### 3.5 쉐어 출금 화면 개선 — `WithdrawView.vue`

**변경 사항:**
- URL query에서 `currencyId`, `networkId` 자동 선택
- 정산 잔액 데이터 로딩 → 출금 가능 금액 표시

```vue
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { settlementService } from '@/api/services/settlement.service'
import type { SettlementBalance } from '@/api/types/settlement'
// ...기존 import

const route = useRoute()
const router = useRouter()

// query param에서 초기값 로드
const networkCurrency = ref({
  networkId: route.query.networkId ? Number(route.query.networkId) : null,
  currencyId: route.query.currencyId ? Number(route.query.currencyId) : null,
})

// 정산 잔액 로딩 → 선택한 통화/네트워크의 출금 가능 금액 표시
const balances = ref<SettlementBalance[]>([])

const selectedBalance = computed(() =>
  balances.value.find(
    b => b.currencyId === networkCurrency.value.currencyId
      && b.networkId === networkCurrency.value.networkId
  )
)

onMounted(async () => {
  balances.value = await settlementService.getBalance()
})
</script>

<template>
  <!-- ... 기존 폼 ... -->

  <!-- 출금 가능 금액 표시 (추가) -->
  <div v-if="selectedBalance" class="rounded-md bg-muted p-3 text-sm">
    <div class="flex justify-between">
      <span class="text-muted-foreground">출금 가능 잔액</span>
      <span class="font-medium">
        {{ selectedBalance.withdrawableBalance?.toFixed(2) ?? '0.00' }}
        {{ selectedBalance.currencySymbol }}
      </span>
    </div>
  </div>

  <!-- ... 나머지 폼 ... -->
</template>
```

---

## 4. 작업 순서

### Phase A: 백엔드 (IntelliJ)

| # | 작업 | 파일 |
|---|------|------|
| A1 | DTO 3개 생성 | `partner-api/dto/response/PartnerSettlementBalanceResponse.java` |
| | | `partner-api/dto/response/PartnerDailyFeeResponse.java` |
| | | `partner-api/dto/response/PartnerRealizationResponse.java` |
| A2 | Mapper 쿼리 수정 (JOIN 추가) | `partner-api/mapper/PartnerSettlementMapper.java` |
| A3 | Service 반환타입 변경 | `partner-api/service/PartnerSettlementService.java` |
| A4 | Controller 반환타입 변경 | `partner-api/controller/PartnerSettlementController.java` |
| A5 | 컴파일 확인 | `./gradlew :partner-api:compileJava` |

### Phase B: 프론트엔드 (VS Code)

| # | 작업 | 파일 |
|---|------|------|
| B1 | TypeScript 타입 수정 | `src/api/types/settlement.ts` |
| B2 | 정산 잔액 화면 재설계 | `src/views/partner/settlement/BalanceView.vue` |
| B3 | 일별 수수료 화면 수정 | `src/views/partner/settlement/FeesView.vue` |
| B4 | 실현 내역 화면 수정 | `src/views/partner/settlement/RealizationsView.vue` |
| B5 | 쉐어 출금 화면 개선 | `src/views/partner/settlement/WithdrawView.vue` |

### Phase C: 검증

| # | 작업 |
|---|------|
| C1 | partner-api 서버 재시작 후 `/api/partner/settlement/balance` 호출 → JSON에 `currencySymbol`, `networkSymbol`, `withdrawableBalance` 포함 확인 |
| C2 | partner-ui에서 정산 잔액 화면 → 카드 헤더가 "BSC · USDT" 형태 확인 |
| C3 | 출금가능 금액이 숫자로 표시되는지 확인 (더 이상 `-` 아님) |
| C4 | 쉐어 출금 버튼 클릭 시 currencyId/networkId가 query param으로 전달되는지 확인 |
| C5 | 일별 수수료 / 실현 내역 화면에서 네트워크/통화 컬럼이 심볼로 표시되는지 확인 |

---

## 5. 체크리스트

- [ ] **A1** — PartnerSettlementBalanceResponse DTO 생성 (withdrawableBalance 가상 필드 포함)
- [ ] **A1** — PartnerDailyFeeResponse DTO 생성
- [ ] **A1** — PartnerRealizationResponse DTO 생성
- [ ] **A2** — PartnerSettlementMapper: findPartnerBalances → JOIN + 계산 필드
- [ ] **A2** — PartnerSettlementMapper: searchDailyFees → JOIN
- [ ] **A2** — PartnerSettlementMapper: searchRealizations → JOIN
- [ ] **A3** — PartnerSettlementService 반환타입 3개 변경
- [ ] **A4** — PartnerSettlementController 반환타입 3개 변경
- [ ] **A5** — `./gradlew :partner-api:compileJava` 성공
- [ ] **B1** — settlement.ts 타입 3개 수정 (currencySymbol, networkSymbol 필드)
- [ ] **B2** — BalanceView.vue: 상단 요약 + 카드 헤더 + 출금가능 금액 + 조건부 버튼
- [ ] **B3** — FeesView.vue: 컬럼 매핑 수정 (settlementDate, networkSymbol 등)
- [ ] **B4** — RealizationsView.vue: 컬럼 매핑 수정
- [ ] **B5** — WithdrawView.vue: query param 자동 선택 + 출금 가능 잔액 표시
- [ ] **C1~C5** — 전체 동작 확인
