# 복리후생비 현황 섹션 개발 계획

> **작성일**: 2026-01-22
> **목적**: CEO 대시보드 복리후생비 현황 섹션 완성 (4개 카드 + 모달 API 연동)
> **기준 문서**: `api/app/Swagger/v1/WelfareApi.php`
> **상태**: 🔄 진행중 (Serena ID: welfare-section-state)

---

## 📍 현재 진행 상태

| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | Phase 2 - 프론트엔드 연동 완료 |
| **다음 작업** | 검증 및 테스트 |
| **진행률** | 6/6 (100%) |
| **마지막 업데이트** | 2026-01-22 |

---

## 1. 개요

### 1.1 배경
CEO 대시보드의 복리후생비 현황 섹션은 4개의 카드로 구성됩니다:
1. **당해년도 복리후생비 한도** - 연간 총 한도
2. **{분기} 복리후생비 총 한도** - 분기별 한도
3. **{분기} 복리후생비 잔여한도** - 분기별 남은 한도
4. **{분기} 복리후생비 사용금액** - 분기별 사용 금액

현재 상태:
- ✅ 섹션 UI 컴포넌트: 완료 (`WelfareSection.tsx`)
- ✅ 카드 데이터 API: 완료 (`/api/v1/welfare/summary`)
- ✅ 프론트엔드 Hook: 완료 (`useWelfare()`)
- ⚠️ 모달 상세 데이터: Mock 사용 중 (API 연동 필요)

### 1.2 기준 원칙
```
┌─────────────────────────────────────────────────────────────────┐
│  🎯 핵심 원칙                                                    │
├─────────────────────────────────────────────────────────────────┤
│  1. API-First: 백엔드 API 완성 후 프론트엔드 연동                 │
│  2. 기존 패턴 준수: WelfareService 확장                          │
│  3. Mock 데이터 구조 유지: 기존 모달 설정 형식 호환               │
└─────────────────────────────────────────────────────────────────┘
```

### 1.3 변경 승인 정책

| 분류 | 예시 | 승인 |
|------|------|------|
| ✅ 즉시 가능 | 모달 설정 Mock → API 변환, Transformer 추가 | 불필요 |
| ⚠️ 컨펌 필요 | 새 API 엔드포인트 추가, 서비스 메서드 추가 | **필수** |
| 🔴 금지 | expense_accounts 테이블 구조 변경 | 별도 협의 |

### 1.4 준수 규칙
- `docs/quickstart/quick-start.md` - 빠른 시작 가이드
- `docs/standards/quality-checklist.md` - 품질 체크리스트
- `api/CLAUDE.md` - SAM API Development Rules

---

## 2. 대상 범위

### 2.1 Phase 1: API 개발 (Backend)

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1.1 | 모달 상세 데이터 API 개발 | ✅ | `/api/v1/welfare/detail` |
| 1.2 | Swagger 문서 업데이트 | ✅ | WelfareApi.php |

### 2.2 Phase 2: 프론트엔드 연동

| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 2.1 | 타입 정의 추가 | ✅ | WelfareDetailApiResponse (types.ts) |
| 2.2 | API 함수 추가 | ✅ | useWelfareDetail hook (useCEODashboard.ts) |
| 2.3 | Transformer 추가 | ✅ | transformWelfareDetailResponse (transformers.ts) |
| 2.4 | 모달 설정 동적 생성 | ✅ | CEODashboard.tsx 연동 + fallback |

---

## 3. 작업 절차

### 3.1 단계별 절차

```
Step 1: API 개발 (Backend)
├── WelfareService에 getDetail() 메서드 추가
├── WelfareController에 detail() 액션 추가
├── routes/api.php에 라우트 등록
└── Swagger 문서 작성

Step 2: 프론트엔드 연동
├── types.ts에 WelfareDetailApiResponse 추가
├── useCEODashboard.ts에 fetchWelfareDetail 추가
├── transformers.ts에 transformWelfareDetailResponse 추가
└── welfareConfigs.ts를 API 응답 기반으로 수정
```

---

## 4. 핵심 참조 코드 (인라인)

### 4.1 DetailModalConfig 타입 정의

**파일**: `react/src/components/business/CEODashboard/types.ts` (라인 414-426)

```typescript
// 상세 모달 전체 설정 타입
export interface DetailModalConfig {
  title: string;
  summaryCards: SummaryCardData[];
  barChart?: BarChartConfig;
  pieChart?: PieChartConfig;
  horizontalBarChart?: HorizontalBarChartConfig;
  comparisonSection?: ComparisonSectionConfig;
  referenceTable?: ReferenceTableConfig;
  referenceTables?: ReferenceTableConfig[];
  calculationCards?: CalculationCardsConfig;
  quarterlyTable?: QuarterlyTableConfig;
  table?: TableConfig;
}
```

### 4.2 관련 서브 타입 정의

```typescript
// 요약 카드 타입 (라인 249-255)
export interface SummaryCardData {
  label: string;
  value: string | number;
  isComparison?: boolean;
  isPositive?: boolean;
  unit?: string;
}

// 막대 차트 설정 타입 (라인 265-271)
export interface BarChartConfig {
  title: string;
  data: BarChartDataItem[];
  dataKey: string;
  xAxisKey: string;
  color?: string;
}

// 도넛 차트 설정 타입 (라인 282-285)
export interface PieChartConfig {
  title: string;
  data: PieChartDataItem[];
}

// 도넛 차트 데이터 아이템 (라인 274-279)
export interface PieChartDataItem {
  name: string;
  value: number;
  percentage: number;
  color: string;
}

// 테이블 설정 타입 (라인 332-342)
export interface TableConfig {
  title: string;
  columns: TableColumnConfig[];
  data: Record<string, unknown>[];
  filters?: TableFilterConfig[];
  showTotal?: boolean;
  totalLabel?: string;
  totalValue?: string | number;
  totalColumnKey?: string;
  footerSummary?: FooterSummaryItem[];
}

// 계산 카드 섹션 설정 타입 (라인 391-395)
export interface CalculationCardsConfig {
  title: string;
  subtitle?: string;
  cards: CalculationCardItem[];
}

// 계산 카드 아이템 타입 (라인 383-388)
export interface CalculationCardItem {
  label: string;
  value: number;
  unit?: string;
  operator?: '+' | '=' | '-' | '×';
}

// 분기별 테이블 설정 타입 (라인 408-411)
export interface QuarterlyTableConfig {
  title: string;
  rows: QuarterlyTableRow[];
}

// 분기별 테이블 행 타입 (라인 398-405)
export interface QuarterlyTableRow {
  label: string;
  q1?: number | string;
  q2?: number | string;
  q3?: number | string;
  q4?: number | string;
  total?: number | string;
}
```

### 4.3 현재 Mock 데이터 구조 (welfareConfigs.ts 전체)

**파일**: `react/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts`

```typescript
import type { DetailModalConfig } from '../types';

export function getWelfareModalConfig(calculationType: 'fixed' | 'ratio'): DetailModalConfig {
  // 계산 방식에 따른 조건부 calculationCards 생성
  const calculationCards = calculationType === 'fixed'
    ? {
        // 직원당 정액 금액/월 방식
        title: '복리후생비 계산',
        subtitle: '직원당 정액 금액/월 200,000원',
        cards: [
          { label: '직원 수', value: 20, unit: '명' },
          { label: '연간 직원당 월급 금액', value: 2400000, unit: '원', operator: '×' as const },
          { label: '당해년도 복리후생비 총 한도', value: 48000000, unit: '원', operator: '=' as const },
        ],
      }
    : {
        // 연봉 총액 비율 방식
        title: '복리후생비 계산',
        subtitle: '연봉 총액 기준 비율 20.5%',
        cards: [
          { label: '연봉 총액', value: 1000000000, unit: '원' },
          { label: '비율', value: 20.5, unit: '%', operator: '×' as const },
          { label: '당해년도 복리후생비 총 한도', value: 205000000, unit: '원', operator: '=' as const },
        ],
      };

  return {
    title: '복리후생비 상세',

    // 1. 요약 카드 (8개)
    summaryCards: [
      // 1행: 당해년도 기준
      { label: '당해년도 복리후생비 계정', value: 3123000, unit: '원' },
      { label: '당해년도 복리후생비 한도', value: 600000, unit: '원' },
      { label: '당해년도 복리후생비 사용', value: 6000000, unit: '원' },
      { label: '당해년도 잔여한도', value: 0, unit: '원' },
      // 2행: 분기 기준
      { label: '1사분기 복리후생비 총 한도', value: 3123000, unit: '원' },
      { label: '1사분기 복리후생비 잔여한도', value: 6000000, unit: '원' },
      { label: '1사분기 복리후생비 사용금액', value: 6000000, unit: '원' },
      { label: '1사분기 복리후생비 초과 금액', value: 6000000, unit: '원' },
    ],

    // 2. 월별 사용 추이 (막대 차트)
    barChart: {
      title: '월별 복리후생비 사용 추이',
      data: [
        { name: '1월', value: 1500000 },
        { name: '2월', value: 1800000 },
        { name: '3월', value: 2200000 },
        { name: '4월', value: 1900000 },
        { name: '5월', value: 2100000 },
        { name: '6월', value: 1700000 },
      ],
      dataKey: 'value',
      xAxisKey: 'name',
      color: '#60A5FA',
    },

    // 3. 항목별 사용 비율 (도넛 차트)
    pieChart: {
      title: '항목별 사용 비율',
      data: [
        { name: '식비', value: 55000000, percentage: 55, color: '#FBBF24' },
        { name: '건강검진', value: 25000000, percentage: 5, color: '#60A5FA' },
        { name: '경조사비', value: 10000000, percentage: 10, color: '#F87171' },
        { name: '기타', value: 10000000, percentage: 30, color: '#34D399' },
      ],
    },

    // 4. 일별 사용 내역 (테이블)
    table: {
      title: '일별 복리후생비 사용 내역',
      columns: [
        { key: 'no', label: 'No.', align: 'center' },
        { key: 'cardName', label: '카드명', align: 'left' },
        { key: 'user', label: '사용자', align: 'center' },
        { key: 'date', label: '사용일자', align: 'center', format: 'date' },
        { key: 'store', label: '가맹점명', align: 'left' },
        { key: 'amount', label: '사용금액', align: 'right', format: 'currency' },
        { key: 'usageType', label: '사용항목', align: 'center' },
      ],
      data: [
        { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1000000, usageType: '식비' },
        { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1200000, usageType: '건강검진' },
        { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1500000, usageType: '경조사비' },
        { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 1300000, usageType: '기타' },
        { cardName: '카드명', user: '홍길동', date: '2025-12-12 12:12', store: '가맹점명', amount: 6000000, usageType: '식비' },
      ],
      filters: [
        {
          key: 'usageType',
          options: [
            { value: 'all', label: '전체' },
            { value: '식비', label: '식비' },
            { value: '건강검진', label: '건강검진' },
            { value: '경조사비', label: '경조사비' },
            { value: '기타', label: '기타' },
          ],
          defaultValue: 'all',
        },
        {
          key: 'sortOrder',
          options: [
            { value: 'latest', label: '최신순' },
            { value: 'oldest', label: '등록순' },
            { value: 'amountDesc', label: '금액 높은순' },
            { value: 'amountAsc', label: '금액 낮은순' },
          ],
          defaultValue: 'latest',
        },
      ],
      showTotal: true,
      totalLabel: '합계',
      totalValue: 11000000,
      totalColumnKey: 'amount',
    },

    // 5. 복리후생비 계산 (조건부 - calculationType에 따라)
    calculationCards,

    // 6. 분기별 현황 테이블
    quarterlyTable: {
      title: '복리후생비 현황',
      rows: [
        { label: '한도금액', q1: 12000000, q2: 12000000, q3: 12000000, q4: 12000000, total: 48000000 },
        { label: '이월금액', q1: 0, q2: '', q3: '', q4: '', total: '' },
        { label: '사용금액', q1: 1000000, q2: '', q3: '', q4: '', total: '' },
        { label: '잔여한도', q1: 11000000, q2: '', q3: '', q4: '', total: '' },
        { label: '초과금액', q1: '', q2: '', q3: '', q4: '', total: '' },
      ],
    },
  };
}
```

### 4.4 expense_accounts 테이블 스키마

**파일**: `api/database/migrations/2026_01_21_103734_create_expense_accounts_table.php`

```sql
CREATE TABLE expense_accounts (
  id                BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  tenant_id         BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',

  -- 비용 유형
  account_type      VARCHAR(50) NOT NULL COMMENT '계정 유형: welfare, entertainment, etc.',
  sub_type          VARCHAR(50) NULL COMMENT '세부 유형: meal, gift, etc.',

  -- 비용 정보
  expense_date      DATE NOT NULL COMMENT '지출일',
  amount            DECIMAL(15,2) DEFAULT 0 COMMENT '금액',
  description       VARCHAR(500) NULL COMMENT '비용 내역',
  receipt_no        VARCHAR(100) NULL COMMENT '증빙번호',

  -- 거래처 정보
  vendor_id         BIGINT UNSIGNED NULL COMMENT '거래처 ID',
  vendor_name       VARCHAR(200) NULL COMMENT '거래처명 (직접 입력)',

  -- 카드/결제 정보
  payment_method    VARCHAR(50) NULL COMMENT '결제수단: card, cash, transfer',
  card_no           VARCHAR(50) NULL COMMENT '카드 마지막 4자리',

  -- 감사 컬럼
  created_by        BIGINT UNSIGNED NULL COMMENT '등록자',
  updated_by        BIGINT UNSIGNED NULL COMMENT '수정자',
  deleted_by        BIGINT UNSIGNED NULL COMMENT '삭제자',

  created_at        TIMESTAMP NULL,
  updated_at        TIMESTAMP NULL,
  deleted_at        TIMESTAMP NULL,

  -- 인덱스
  INDEX idx_tenant_type_date (tenant_id, account_type, expense_date),
  INDEX idx_tenant_date (tenant_id, expense_date),

  -- 외래키
  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (vendor_id) REFERENCES clients(id) ON DELETE SET NULL
);
```

**account_type 값**:
- `welfare` - 복리후생비
- `entertainment` - 접대비

**sub_type 값** (welfare의 경우):
- `meal` - 식비
- `health_check` - 건강검진
- `congratulation` - 경조사비
- `other` - 기타

---

## 5. API → 모달 설정 변환 매핑

### 5.1 API 응답 스키마 (제안)

```typescript
// 백엔드 API 응답: GET /api/v1/welfare/detail
interface WelfareDetailApiResponse {
  // 요약 카드 데이터
  summary: {
    annual_account: number;      // 당해년도 복리후생비 계정
    annual_limit: number;        // 당해년도 복리후생비 한도
    annual_used: number;         // 당해년도 복리후생비 사용
    annual_remaining: number;    // 당해년도 잔여한도
    quarterly_limit: number;     // 분기 복리후생비 총 한도
    quarterly_remaining: number; // 분기 복리후생비 잔여한도
    quarterly_used: number;      // 분기 복리후생비 사용금액
    quarterly_exceeded: number;  // 분기 복리후생비 초과 금액
  };

  // 월별 사용 추이
  monthly_usage: {
    month: number;    // 1-12
    amount: number;
  }[];

  // 항목별 분포
  category_distribution: {
    category: string;   // meal, health_check, congratulation, other
    label: string;      // 식비, 건강검진, 경조사비, 기타
    amount: number;
    ratio: number;      // 백분율 (0-100)
  }[];

  // 일별 사용 내역
  transactions: {
    id: number;
    card_name: string;
    user_name: string;
    expense_date: string;     // YYYY-MM-DD HH:mm
    vendor_name: string;
    amount: number;
    sub_type: string;
    sub_type_label: string;
  }[];

  // 계산 정보
  calculation: {
    type: 'fixed' | 'ratio';
    employee_count: number;
    monthly_amount?: number;     // fixed 방식
    total_salary?: number;       // ratio 방식
    ratio?: number;              // ratio 방식 (%)
    annual_limit: number;
  };

  // 분기별 현황
  quarterly: {
    quarter: number;    // 1-4
    limit: number;
    carryover: number;
    used: number;
    remaining: number;
    exceeded: number;
  }[];
}
```

### 5.2 변환 매핑 테이블

| API 필드 | DetailModalConfig 필드 | 변환 로직 |
|----------|----------------------|----------|
| `summary.annual_account` | `summaryCards[0].value` | 직접 매핑 |
| `summary.annual_limit` | `summaryCards[1].value` | 직접 매핑 |
| `summary.annual_used` | `summaryCards[2].value` | 직접 매핑 |
| `summary.annual_remaining` | `summaryCards[3].value` | 직접 매핑 |
| `summary.quarterly_limit` | `summaryCards[4].value` | 라벨에 분기 동적 삽입 |
| `summary.quarterly_remaining` | `summaryCards[5].value` | 라벨에 분기 동적 삽입 |
| `summary.quarterly_used` | `summaryCards[6].value` | 라벨에 분기 동적 삽입 |
| `summary.quarterly_exceeded` | `summaryCards[7].value` | 라벨에 분기 동적 삽입 |
| `monthly_usage[]` | `barChart.data[]` | `{ name: '${month}월', value: amount }` |
| `category_distribution[]` | `pieChart.data[]` | 색상 매핑 추가 필요 |
| `transactions[]` | `table.data[]` | 필드명 camelCase 변환 |
| `calculation` | `calculationCards` | type에 따라 분기 |
| `quarterly[]` | `quarterlyTable.rows[]` | 행/열 피벗 변환 |

### 5.3 색상 매핑 (카테고리별)

```typescript
const CATEGORY_COLORS: Record<string, string> = {
  meal: '#FBBF24',           // 식비 - 노란색
  health_check: '#60A5FA',   // 건강검진 - 파란색
  congratulation: '#F87171', // 경조사비 - 빨간색
  other: '#34D399',          // 기타 - 초록색
};
```

---

## 6. 상세 작업 내용

### 6.1 Phase 1: API 개발

#### 1.1 WelfareService 확장

**파일**: `api/app/Services/WelfareService.php`

**추가할 메서드**:
```php
/**
 * 복리후생비 상세 정보 조회 (모달용)
 */
public function getDetail(
    ?string $calculationType = 'fixed',
    ?int $fixedAmountPerMonth = 200000,
    ?float $ratio = 0.05,
    ?int $year = null,
    ?int $quarter = null
): array {
    // 1. 요약 데이터 조회
    // 2. 월별 사용 추이 조회
    // 3. 항목별 분포 조회
    // 4. 일별 사용 내역 조회
    // 5. 계산 정보 생성
    // 6. 분기별 현황 조회
}
```

**필요한 쿼리**:
```php
// 월별 사용 추이
DB::table('expense_accounts')
    ->select(DB::raw('MONTH(expense_date) as month'), DB::raw('SUM(amount) as amount'))
    ->where('tenant_id', $tenantId)
    ->where('account_type', 'welfare')
    ->whereYear('expense_date', $year)
    ->whereNull('deleted_at')
    ->groupBy(DB::raw('MONTH(expense_date)'))
    ->orderBy('month')
    ->get();

// 항목별 분포
DB::table('expense_accounts')
    ->select('sub_type', DB::raw('SUM(amount) as amount'))
    ->where('tenant_id', $tenantId)
    ->where('account_type', 'welfare')
    ->whereBetween('expense_date', [$startDate, $endDate])
    ->whereNull('deleted_at')
    ->groupBy('sub_type')
    ->get();

// 일별 사용 내역
DB::table('expense_accounts')
    ->select('id', 'card_no', 'created_by', 'expense_date', 'vendor_name', 'amount', 'sub_type')
    ->where('tenant_id', $tenantId)
    ->where('account_type', 'welfare')
    ->whereBetween('expense_date', [$startDate, $endDate])
    ->whereNull('deleted_at')
    ->orderByDesc('expense_date')
    ->get();
```

#### 1.2 WelfareController 확장

**파일**: `api/app/Http/Controllers/Api/V1/WelfareController.php`

**추가할 메서드**:
```php
/**
 * 복리후생비 상세 조회 (모달용)
 */
public function detail(Request $request): JsonResponse
{
    $calculationType = $request->query('calculation_type', 'fixed');
    $fixedAmountPerMonth = $request->query('fixed_amount_per_month')
        ? (int) $request->query('fixed_amount_per_month')
        : 200000;
    $ratio = $request->query('ratio')
        ? (float) $request->query('ratio')
        : 0.05;
    $year = $request->query('year') ? (int) $request->query('year') : null;
    $quarter = $request->query('quarter') ? (int) $request->query('quarter') : null;

    return ApiResponse::handle(function () use ($calculationType, $fixedAmountPerMonth, $ratio, $year, $quarter) {
        return $this->welfareService->getDetail(
            $calculationType,
            $fixedAmountPerMonth,
            $ratio,
            $year,
            $quarter
        );
    }, __('message.fetched'));
}
```

#### 1.3 라우트 등록

**파일**: `api/routes/api.php`

```php
Route::prefix('welfare')->group(function () {
    Route::get('/summary', [WelfareController::class, 'summary']);
    Route::get('/detail', [WelfareController::class, 'detail']);  // 추가
});
```

### 6.2 Phase 2: 프론트엔드 연동

#### 2.1 타입 정의 추가

**파일**: `react/src/lib/api/dashboard/types.ts`

```typescript
// Welfare Detail API 응답 타입
export interface WelfareDetailApiResponse {
  summary: {
    annual_account: number;
    annual_limit: number;
    annual_used: number;
    annual_remaining: number;
    quarterly_limit: number;
    quarterly_remaining: number;
    quarterly_used: number;
    quarterly_exceeded: number;
  };
  monthly_usage: {
    month: number;
    amount: number;
  }[];
  category_distribution: {
    category: string;
    label: string;
    amount: number;
    ratio: number;
  }[];
  transactions: {
    id: number;
    card_name: string;
    user_name: string;
    expense_date: string;
    vendor_name: string;
    amount: number;
    sub_type: string;
    sub_type_label: string;
  }[];
  calculation: {
    type: 'fixed' | 'ratio';
    employee_count: number;
    monthly_amount?: number;
    total_salary?: number;
    ratio?: number;
    annual_limit: number;
  };
  quarterly: {
    quarter: number;
    limit: number;
    carryover: number;
    used: number;
    remaining: number;
    exceeded: number;
  }[];
}
```

#### 2.2 API 함수 추가

**파일**: `react/src/hooks/useCEODashboard.ts`

```typescript
export async function fetchWelfareDetail(
  options: {
    calculationType?: 'fixed' | 'ratio';
    fixedAmountPerMonth?: number;
    ratio?: number;
    year?: number;
    quarter?: number;
  }
): Promise<WelfareDetailApiResponse> {
  const params = new URLSearchParams();
  if (options.calculationType) params.append('calculation_type', options.calculationType);
  if (options.fixedAmountPerMonth) params.append('fixed_amount_per_month', options.fixedAmountPerMonth.toString());
  if (options.ratio) params.append('ratio', options.ratio.toString());
  if (options.year) params.append('year', options.year.toString());
  if (options.quarter) params.append('quarter', options.quarter.toString());

  return fetchApi<WelfareDetailApiResponse>(`welfare/detail?${params.toString()}`);
}
```

#### 2.3 Transformer 추가

**파일**: `react/src/lib/api/dashboard/transformers.ts`

```typescript
const CATEGORY_COLORS: Record<string, string> = {
  meal: '#FBBF24',
  health_check: '#60A5FA',
  congratulation: '#F87171',
  other: '#34D399',
};

export function transformWelfareDetailToModalConfig(
  api: WelfareDetailApiResponse,
  quarter: number
): DetailModalConfig {
  const quarterLabel = `${quarter}사분기`;

  return {
    title: '복리후생비 상세',

    summaryCards: [
      { label: '당해년도 복리후생비 계정', value: api.summary.annual_account, unit: '원' },
      { label: '당해년도 복리후생비 한도', value: api.summary.annual_limit, unit: '원' },
      { label: '당해년도 복리후생비 사용', value: api.summary.annual_used, unit: '원' },
      { label: '당해년도 잔여한도', value: api.summary.annual_remaining, unit: '원' },
      { label: `${quarterLabel} 복리후생비 총 한도`, value: api.summary.quarterly_limit, unit: '원' },
      { label: `${quarterLabel} 복리후생비 잔여한도`, value: api.summary.quarterly_remaining, unit: '원' },
      { label: `${quarterLabel} 복리후생비 사용금액`, value: api.summary.quarterly_used, unit: '원' },
      { label: `${quarterLabel} 복리후생비 초과 금액`, value: api.summary.quarterly_exceeded, unit: '원' },
    ],

    barChart: {
      title: '월별 복리후생비 사용 추이',
      data: api.monthly_usage.map(m => ({ name: `${m.month}월`, value: m.amount })),
      dataKey: 'value',
      xAxisKey: 'name',
      color: '#60A5FA',
    },

    pieChart: {
      title: '항목별 사용 비율',
      data: api.category_distribution.map(c => ({
        name: c.label,
        value: c.amount,
        percentage: c.ratio,
        color: CATEGORY_COLORS[c.category] || '#9CA3AF',
      })),
    },

    table: {
      title: '일별 복리후생비 사용 내역',
      columns: [
        { key: 'no', label: 'No.', align: 'center' },
        { key: 'cardName', label: '카드명', align: 'left' },
        { key: 'user', label: '사용자', align: 'center' },
        { key: 'date', label: '사용일자', align: 'center', format: 'date' },
        { key: 'store', label: '가맹점명', align: 'left' },
        { key: 'amount', label: '사용금액', align: 'right', format: 'currency' },
        { key: 'usageType', label: '사용항목', align: 'center' },
      ],
      data: api.transactions.map((t, i) => ({
        no: i + 1,
        cardName: t.card_name,
        user: t.user_name,
        date: t.expense_date,
        store: t.vendor_name,
        amount: t.amount,
        usageType: t.sub_type_label,
      })),
      filters: [
        {
          key: 'usageType',
          options: [
            { value: 'all', label: '전체' },
            { value: '식비', label: '식비' },
            { value: '건강검진', label: '건강검진' },
            { value: '경조사비', label: '경조사비' },
            { value: '기타', label: '기타' },
          ],
          defaultValue: 'all',
        },
        {
          key: 'sortOrder',
          options: [
            { value: 'latest', label: '최신순' },
            { value: 'oldest', label: '등록순' },
            { value: 'amountDesc', label: '금액 높은순' },
            { value: 'amountAsc', label: '금액 낮은순' },
          ],
          defaultValue: 'latest',
        },
      ],
      showTotal: true,
      totalLabel: '합계',
      totalValue: api.transactions.reduce((sum, t) => sum + t.amount, 0),
      totalColumnKey: 'amount',
    },

    calculationCards: api.calculation.type === 'fixed'
      ? {
          title: '복리후생비 계산',
          subtitle: `직원당 정액 금액/월 ${(api.calculation.monthly_amount || 0).toLocaleString()}원`,
          cards: [
            { label: '직원 수', value: api.calculation.employee_count, unit: '명' },
            { label: '연간 직원당 월급 금액', value: (api.calculation.monthly_amount || 0) * 12, unit: '원', operator: '×' },
            { label: '당해년도 복리후생비 총 한도', value: api.calculation.annual_limit, unit: '원', operator: '=' },
          ],
        }
      : {
          title: '복리후생비 계산',
          subtitle: `연봉 총액 기준 비율 ${api.calculation.ratio}%`,
          cards: [
            { label: '연봉 총액', value: api.calculation.total_salary || 0, unit: '원' },
            { label: '비율', value: api.calculation.ratio || 0, unit: '%', operator: '×' },
            { label: '당해년도 복리후생비 총 한도', value: api.calculation.annual_limit, unit: '원', operator: '=' },
          ],
        },

    quarterlyTable: {
      title: '복리후생비 현황',
      rows: [
        {
          label: '한도금액',
          q1: api.quarterly.find(q => q.quarter === 1)?.limit || '',
          q2: api.quarterly.find(q => q.quarter === 2)?.limit || '',
          q3: api.quarterly.find(q => q.quarter === 3)?.limit || '',
          q4: api.quarterly.find(q => q.quarter === 4)?.limit || '',
          total: api.quarterly.reduce((sum, q) => sum + q.limit, 0),
        },
        {
          label: '이월금액',
          q1: api.quarterly.find(q => q.quarter === 1)?.carryover || '',
          q2: api.quarterly.find(q => q.quarter === 2)?.carryover || '',
          q3: api.quarterly.find(q => q.quarter === 3)?.carryover || '',
          q4: api.quarterly.find(q => q.quarter === 4)?.carryover || '',
          total: '',
        },
        {
          label: '사용금액',
          q1: api.quarterly.find(q => q.quarter === 1)?.used || '',
          q2: api.quarterly.find(q => q.quarter === 2)?.used || '',
          q3: api.quarterly.find(q => q.quarter === 3)?.used || '',
          q4: api.quarterly.find(q => q.quarter === 4)?.used || '',
          total: api.quarterly.reduce((sum, q) => sum + q.used, 0),
        },
        {
          label: '잔여한도',
          q1: api.quarterly.find(q => q.quarter === 1)?.remaining || '',
          q2: api.quarterly.find(q => q.quarter === 2)?.remaining || '',
          q3: api.quarterly.find(q => q.quarter === 3)?.remaining || '',
          q4: api.quarterly.find(q => q.quarter === 4)?.remaining || '',
          total: '',
        },
        {
          label: '초과금액',
          q1: api.quarterly.find(q => q.quarter === 1)?.exceeded || '',
          q2: api.quarterly.find(q => q.quarter === 2)?.exceeded || '',
          q3: api.quarterly.find(q => q.quarter === 3)?.exceeded || '',
          q4: api.quarterly.find(q => q.quarter === 4)?.exceeded || '',
          total: '',
        },
      ],
    },
  };
}
```

#### 2.4 모달 설정 동적 생성

**파일**: `react/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts`

```typescript
import type { DetailModalConfig } from '../types';
import { fetchWelfareDetail, transformWelfareDetailToModalConfig } from '@/lib/api/dashboard';

// 기존 Mock 함수 (fallback용)
export function getWelfareModalConfigMock(calculationType: 'fixed' | 'ratio'): DetailModalConfig {
  // ... 기존 Mock 코드 유지
}

// 새로운 API 기반 함수
export async function getWelfareModalConfigFromApi(
  options: {
    calculationType: 'fixed' | 'ratio';
    fixedAmountPerMonth?: number;
    ratio?: number;
    year?: number;
    quarter?: number;
  }
): Promise<DetailModalConfig> {
  try {
    const apiData = await fetchWelfareDetail(options);
    return transformWelfareDetailToModalConfig(apiData, options.quarter || getCurrentQuarter());
  } catch (error) {
    console.error('[Welfare] Failed to fetch detail, using mock data:', error);
    return getWelfareModalConfigMock(options.calculationType);
  }
}

function getCurrentQuarter(): number {
  return Math.ceil((new Date().getMonth() + 1) / 3);
}
```

---

## 7. 컨펌 대기 목록

| # | 항목 | 변경 내용 | 영향 범위 | 상태 |
|---|------|----------|----------|------|
| 1 | 새 API 엔드포인트 | `GET /api/v1/welfare/detail` 추가 | api | ✅ 완료 |
| 2 | WelfareService 확장 | getDetail() 메서드 추가 | api | ✅ 완료 |

---

## 8. 변경 이력

| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2026-01-22 | - | 문서 초안 작성 | - | - |
| 2026-01-22 | - | 자기완결성 보완 (타입, 스키마, 매핑 추가) | - | - |
| 2026-01-22 | Phase 1.1 | getDetail() 메서드 추가 | WelfareService.php | ✅ |
| 2026-01-22 | Phase 1.1 | detail() 액션 추가 | WelfareController.php | ✅ |
| 2026-01-22 | Phase 1.1 | /welfare/detail 라우트 추가 | routes/api.php | ✅ |
| 2026-01-22 | Phase 1.2 | Swagger 스키마 및 엔드포인트 추가 | WelfareApi.php | ✅ |
| 2026-01-22 | Phase 2.1 | WelfareDetailApiResponse 타입 추가 | types.ts | ✅ |
| 2026-01-22 | Phase 2.2 | useWelfareDetail hook 추가 | useCEODashboard.ts | ✅ |
| 2026-01-22 | Phase 2.3 | transformWelfareDetailResponse 추가 | transformers.ts | ✅ |
| 2026-01-22 | Phase 2.4 | 모달 설정 API 연동 + fallback | CEODashboard.tsx, welfareConfigs.ts | ✅ |

---

## 9. 참고 문서

- **빠른 시작**: `docs/quickstart/quick-start.md`
- **품질 체크리스트**: `docs/standards/quality-checklist.md`
- **API 규칙**: `api/CLAUDE.md` (SAM API Development Rules)
- **Swagger 가이드**: `docs/guides/swagger-guide.md`

---

## 10. 세션 및 메모리 관리 정책 (Serena Optimized)

### 10.1 세션 시작 시 (Load Strategy)
```javascript
// 순차적 로드
read_memory("welfare-section-state")     // 1. 상태 파악
read_memory("welfare-section-snapshot")  // 2. 사고 흐름 복구
```

### 10.2 작업 중 관리 (Context Defense)
| 컨텍스트 잔량 | Action | 내용 |
|--------------|--------|------|
| **30% 이하** | 🛠 **Snapshot** | `write_memory("welfare-section-snapshot", "코드변경+논의요약")` |
| **20% 이하** | 🧹 **Context Purge** | `write_memory("welfare-section-active-symbols", "주요 수정 파일/함수")` |
| **10% 이하** | 🛑 **Stop & Save** | 최종 상태 저장 후 세션 교체 권고 |

### 10.3 Serena 메모리 구조
- `welfare-section-state`: { phase, progress, next_step, last_decision }
- `welfare-section-snapshot`: 현재까지의 논의 및 코드 변경점 요약
- `welfare-section-active-symbols`: 현재 수정 중인 파일/심볼 리스트

---

## 11. 검증 결과

> 작업 완료 후 이 섹션에 검증 결과 추가

### 11.1 테스트 케이스

| 입력값 | 예상 결과 | 실제 결과 | 상태 |
|--------|----------|----------|------|
| 모달 클릭 (fixed) | 정액 방식 계산 표시 | - | ⏳ |
| 모달 클릭 (ratio) | 비율 방식 계산 표시 | - | ⏳ |
| 분기 변경 (Q1→Q2) | 해당 분기 데이터 표시 | - | ⏳ |

### 11.2 성공 기준 달성 현황

| 기준 | 달성 | 비고 |
|------|------|------|
| 4개 카드 데이터 실시간 반영 | ✅ | API 연동 완료 상태 |
| 모달 상세 데이터 API 연동 | ✅ | Backend API + Frontend hook 완료 |
| Mock 데이터 제거 | ✅ | API 우선, Mock fallback 유지 |

---

## 12. 자기완결성 점검 결과

### 12.1 체크리스트 검증

| # | 검증 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1 | 작업 목적이 명확한가? | ✅ | 모달 데이터 API 연동 |
| 2 | 성공 기준이 정의되어 있는가? | ✅ | 11.2 참조 |
| 3 | 작업 범위가 구체적인가? | ✅ | 2. 대상 범위 참조 |
| 4 | 의존성이 명시되어 있는가? | ✅ | Phase 1 → Phase 2 순서 |
| 5 | 참고 파일 경로가 정확한가? | ✅ | 4. 핵심 참조 코드 인라인 |
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 6. 상세 작업 내용 |
| 7 | 검증 방법이 명시되어 있는가? | ✅ | 11.1 테스트 케이스 |
| 8 | 모호한 표현이 없는가? | ✅ | 구체적 코드 스니펫 포함 |

### 12.2 새 세션 시뮬레이션 테스트

| 질문 | 답변 가능 | 참조 섹션 |
|------|:--------:|----------|
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 |
| Q2. 어디서부터 시작해야 하는가? | ✅ | 3.1 단계별 절차 |
| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 6. 상세 작업 내용 |
| Q4. DetailModalConfig 구조는? | ✅ | 4.1, 4.2 타입 정의 |
| Q5. Mock 데이터 구조는? | ✅ | 4.3 현재 Mock 데이터 |
| Q6. DB 테이블 스키마는? | ✅ | 4.4 expense_accounts |
| Q7. API → 모달 변환 방법은? | ✅ | 5. 변환 매핑 |
| Q8. 작업 완료 확인 방법은? | ✅ | 11. 검증 결과 |
| Q9. 막혔을 때 참고 문서는? | ✅ | 9. 참고 문서 |

**결과**: 9/9 통과 → ✅ 자기완결성 확보

### 12.3 보완 이력

| 날짜 | 항목 | 원본 | 보완 내용 |
|------|------|------|----------|
| 2026-01-22 | DetailModalConfig | 없음 | 타입 정의 전체 인라인 |
| 2026-01-22 | Mock 데이터 | 없음 | welfareConfigs.ts 전체 인라인 |
| 2026-01-22 | DB 스키마 | 없음 | expense_accounts 테이블 구조 |
| 2026-01-22 | 변환 매핑 | 없음 | API → 모달 매핑 테이블 및 코드 |

---

*이 문서는 /sc:plan 스킬로 생성되었습니다.*