# 단가 버전 관리 시스템 기획

> **작성일**: 2026-03-20
> **상태**: 기획
> **관련 정책**: [단가 정책](../../rules/pricing-policy.md) §10 개선방향

---

## 1. 배경

### 1.1 현재 시스템

| 기능 | 현재 상태 |
|------|----------|
| 시간 기반 유효기간 (effective_from/to) | ✅ 구현 |
| 개별 리비전 이력 (price_revisions) | ✅ 구현 |
| 고객그룹 차등가 | ✅ 구현 |
| 무기한 단가 자동 종료 | ✅ 구현 |
| 확정 후 불변 (finalized) | ✅ 구현 |

### 1.2 현재 한계

| 문제 | 영향 |
|------|------|
| 품목별 개별 수정만 가능 | 원자재 인상 시 수백 건 수동 변경 |
| 변경 전 영향 분석 불가 | 마진 축소/확대를 사전에 파악 못함 |
| 버전 개념 없음 | "2026년 Q2 단가" 같은 그룹 관리 불가 |
| 이전 단가로 롤백 불가 | 잘못 적용 시 전부 수동 복원 |
| 견적/수주 시점 원가 역추적 불가 | 수주 마진 사후 분석이 어려움 |

---

## 2. 목표

```
분기/반기 단위로 "단가 버전"을 생성하여
  → 이전 버전 복사 → 변경 대상 품목 수정
  → 영향 분석 → 승인 → 예약 적용
  → 이전 버전 자동 보관
```

---

## 3. 시스템 설계

### 3.1 신규 테이블

#### price_versions (단가 버전)

```sql
CREATE TABLE price_versions (
    id                BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id         BIGINT UNSIGNED NOT NULL,

    -- 버전 정보 --
    version_code      VARCHAR(30) NOT NULL COMMENT '버전 코드 (v2026-Q2)',
    version_name      VARCHAR(100) NOT NULL COMMENT '버전명 (2026년 2분기 단가)',
    description       TEXT NULL COMMENT '변경 요약',

    -- 계보 --
    parent_version_id BIGINT UNSIGNED NULL COMMENT '기반 버전 ID',

    -- 적용 일정 --
    effective_from    DATE NOT NULL COMMENT '적용 시작일',
    effective_to      DATE NULL COMMENT '적용 종료일',

    -- 상태 --
    status            ENUM('draft','review','approved','active','archived')
                      DEFAULT 'draft',
    approved_by       BIGINT UNSIGNED NULL,
    approved_at       DATETIME NULL,
    activated_at      DATETIME NULL,
    archived_at       DATETIME NULL,

    -- 변경 통계 (승인 시 계산) --
    summary           JSON NULL COMMENT '변경 요약 통계',
    -- summary 구조:
    -- { total_items: 150, changed_items: 23,
    --   increased: 15, decreased: 5, unchanged: 3,
    --   avg_change_rate: 3.2, max_increase: "가이드레일 +12.5%" }

    -- 감사 --
    created_by        BIGINT UNSIGNED NULL,
    updated_by        BIGINT UNSIGNED NULL,
    created_at        TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at        TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at        TIMESTAMP NULL,

    UNIQUE idx_version_code (tenant_id, version_code, deleted_at),
    INDEX idx_version_status (tenant_id, status)
);
```

#### price_version_items (버전별 품목 단가)

```sql
CREATE TABLE price_version_items (
    id                BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id         BIGINT UNSIGNED NOT NULL,
    version_id        BIGINT UNSIGNED NOT NULL,

    -- 품목 --
    item_type_code    VARCHAR(20) NOT NULL,
    item_id           BIGINT UNSIGNED NOT NULL,
    client_group_id   BIGINT UNSIGNED NULL,

    -- 단가 정보 (prices 테이블과 동일 구조) --
    purchase_price    DECIMAL(15,4) NULL,
    processing_cost   DECIMAL(15,4) NULL,
    loss_rate         DECIMAL(5,2) NULL,
    margin_rate       DECIMAL(5,2) NULL,
    sales_price       DECIMAL(15,4) NULL,
    rounding_rule     ENUM('round','ceil','floor') DEFAULT 'round',
    rounding_unit     INT DEFAULT 1,
    supplier          VARCHAR(255) NULL,

    -- 변경 추적 --
    change_type       ENUM('unchanged','increased','decreased','new','removed')
                      DEFAULT 'unchanged',
    prev_sales_price  DECIMAL(15,4) NULL COMMENT '이전 버전 판매단가',
    change_rate       DECIMAL(8,4) NULL COMMENT '변동률 (%)',

    created_at        TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at        TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (version_id) REFERENCES price_versions(id) ON DELETE CASCADE,
    UNIQUE idx_version_item (version_id, item_type_code, item_id, client_group_id),
    INDEX idx_change_type (version_id, change_type)
);
```

### 3.2 기존 테이블 변경

#### prices 테이블 추가 컬럼

```sql
ALTER TABLE prices
    ADD COLUMN version_id BIGINT UNSIGNED NULL COMMENT '소속 버전 ID' AFTER client_group_id,
    ADD INDEX idx_prices_version (tenant_id, version_id);
```

### 3.3 ERD

```
┌──────────────────┐
│  price_versions  │ ← 버전 마스터
│  (v2026-Q1 등)   │
└────────┬─────────┘
         │ 1:N
    ┌────┴─────────────────┐
    │                      │
    ▼                      ▼
┌──────────────────┐  ┌──────────┐
│price_version_items│  │  prices  │ ← 활성 버전 적용 시 생성/갱신
│ (버전별 품목단가)  │  │(단가마스터)│
└──────────────────┘  └──────────┘
                           │ 1:N
                           ▼
                    ┌──────────────┐
                    │price_revisions│
                    └──────────────┘
```

---

## 4. 버전 생명주기

### 4.1 상태 전이

```
draft ──→ review ──→ approved ──→ active ──→ archived
(작성)    (검토)      (승인)      (적용중)    (보관)

언제든 draft로 돌아갈 수 있음 (active/archived 제외)
```

### 4.2 각 상태별 동작

| 상태 | 설명 | 가능한 작업 |
|------|------|-----------|
| `draft` | 작성 중, 품목 단가 자유 수정 | 품목 추가/수정/삭제, 영향 분석 |
| `review` | 검토 요청, 영향 분석 확정 | 조회, 승인/반려 |
| `approved` | 승인됨, 적용 대기 | 적용 예약, 즉시 적용 |
| `active` | 현재 적용 중인 버전 | 조회만 (수정 불가) |
| `archived` | 새 버전으로 대체됨 | 조회만 (이력 보관) |

### 4.3 워크플로우

```
1. 신규 버전 생성
   └→ 이전 active 버전의 모든 품목 단가를 복사
   └→ status = 'draft'

2. 단가 수정 (draft 상태에서)
   └→ 개별 수정: 특정 품목 매입단가/마진율 등 변경
   └→ 일괄 수정: 품목 유형별 일괄 인상/인하 (예: 원자재 전체 +5%)
   └→ 변경 시 change_type, change_rate 자동 계산

3. 영향 분석 실행
   └→ 변경된 품목 수, 인상/인하 비율
   └→ 기존 미확정 견적에 미치는 영향
   └→ 마진 변동 시뮬레이션

4. 검토 요청 (draft → review)
   └→ 분석 결과를 summary JSON에 저장

5. 승인 (review → approved)
   └→ approved_by, approved_at 기록

6. 적용 (approved → active)
   └→ price_version_items → prices 테이블 반영
   └→ 기존 active 버전 → archived 전환
   └→ 기존 prices의 effective_to 자동 설정
   └→ 새 prices의 effective_from = 버전의 effective_from
```

---

## 5. 핵심 기능

### 5.1 버전 생성 (이전 버전 복사)

```php
// PriceVersionService::createFromVersion($parentVersionId)
1. parent_version의 모든 price_version_items 조회
2. 새 version 생성 (parent_version_id = 원본)
3. 모든 items 복사 (change_type = 'unchanged')
```

### 5.2 일괄 단가 변경

```php
// PriceVersionService::bulkAdjust($versionId, $criteria, $adjustment)
// 예: 원자재(RM) 전체 매입단가 +5%

$criteria = [
    'item_type_code' => 'RM',           // 원자재만
    'supplier' => '(주)한국철강',         // 특정 공급업체 (선택)
];
$adjustment = [
    'type' => 'percentage',              // percentage | fixed
    'field' => 'purchase_price',         // 변경 대상 필드
    'value' => 5.0,                      // +5%
    'recalculate_sales_price' => true,   // 판매단가 자동 재계산
];
```

### 5.3 영향 분석

```php
// PriceVersionService::analyze($versionId)
// 반환 구조:
{
    "version": { "code": "v2026-Q2", "name": "2026년 2분기 단가" },
    "changes": {
        "total_items": 150,
        "changed": 23,
        "increased": 15,
        "decreased": 5,
        "new": 3,
        "removed": 0
    },
    "price_impact": {
        "avg_change_rate": 3.2,
        "max_increase": { "item": "가이드레일 STS304", "rate": 12.5 },
        "max_decrease": { "item": "볼트 M8", "rate": -3.0 }
    },
    "affected_quotes": [
        {
            "quote_id": 789,
            "quote_number": "KD-SC-260301-01",
            "status": "draft",
            "current_total": 1250000,
            "projected_total": 1295000,
            "diff": "+45,000 (+3.6%)"
        }
    ],
    "margin_simulation": {
        "current_avg_margin": 25.3,
        "projected_avg_margin": 23.1,
        "margin_change": -2.2
    }
}
```

### 5.4 버전 적용 (Activation)

```php
// PriceVersionService::activate($versionId)
DB::transaction(function () {
    // 1. 기존 active 버전 → archived
    // 2. price_version_items 각각에 대해:
    //    - 기존 prices의 effective_to 설정 (적용일 -1일)
    //    - 새 prices 레코드 생성 (version_id 포함)
    //    - price_revisions 기록
    // 3. 새 버전 status = 'active', activated_at = now()
});
```

### 5.5 버전 비교

```php
// PriceVersionService::compare($versionA, $versionB)
// 두 버전 간 품목별 단가 차이 리스트
[
    {
        "item_code": "GR-STS304-50",
        "item_name": "가이드레일 STS304 50mm",
        "version_a": { "sales_price": 15800, "margin_rate": 25 },
        "version_b": { "sales_price": 17400, "margin_rate": 25 },
        "diff": { "sales_price": "+1600 (+10.1%)", "margin_rate": "0%" }
    }
]
```

---

## 6. API 엔드포인트

| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | `/api/v1/price-versions` | 버전 목록 |
| POST | `/api/v1/price-versions` | 신규 버전 (빈 버전 또는 복사) |
| GET | `/api/v1/price-versions/{id}` | 버전 상세 + 통계 |
| PUT | `/api/v1/price-versions/{id}` | 버전 메타 수정 (이름, 적용일 등) |
| DELETE | `/api/v1/price-versions/{id}` | 버전 삭제 (draft만) |
| GET | `/api/v1/price-versions/{id}/items` | 버전 내 품목 단가 목록 |
| PUT | `/api/v1/price-versions/{id}/items/{itemId}` | 개별 품목 단가 수정 |
| POST | `/api/v1/price-versions/{id}/bulk-adjust` | 일괄 단가 변경 |
| POST | `/api/v1/price-versions/{id}/analyze` | 영향 분석 |
| POST | `/api/v1/price-versions/{id}/submit-review` | 검토 요청 |
| POST | `/api/v1/price-versions/{id}/approve` | 승인 |
| POST | `/api/v1/price-versions/{id}/activate` | 적용 |
| GET | `/api/v1/price-versions/compare` | 버전 비교 (?a=1&b=2) |
| GET | `/api/v1/price-versions/active` | 현재 적용 중인 버전 |

---

## 7. UI 화면 구성

### 7.1 버전 목록

```
┌──────────────────────────────────────────────────────────────┐
│  단가 버전 관리                                    [+ 신규 버전] │
├──────┬──────────────┬──────────┬────────┬─────────┬─────────┤
│ 상태 │ 버전코드      │ 버전명    │ 적용일  │ 변경품목 │ 평균변동 │
├──────┼──────────────┼──────────┼────────┼─────────┼─────────┤
│🟢활성│ v2026-Q1     │ 1분기단가 │ 01-01  │ —       │ —       │
│🟡승인│ v2026-Q2     │ 2분기단가 │ 04-01  │ 23건    │ +3.2%  │
│⚪작성│ v2026-H2     │ 하반기단가 │ 07-01  │ 작성중   │ —       │
│🔘보관│ v2025-Q4     │ 4분기단가 │ 10-01  │ 18건    │ +1.5%  │
└──────┴──────────────┴──────────┴────────┴─────────┴─────────┘
```

### 7.2 버전 상세 (품목 단가 편집)

```
┌──────────────────────────────────────────────────────────────┐
│  v2026-Q2 | 2026년 2분기 단가 | 상태: 작성중                   │
│  적용 예정일: 2026-04-01                                      │
├──────────────────────────────────────────────────────────────┤
│  [일괄변경▼] [영향분석] [검토요청]                              │
│                                                              │
│  필터: [전체▼] [변경된 항목만 ☐]                               │
├──────┬──────────┬────────┬────────┬────────┬────────┬───────┤
│ 변동 │ 품목명    │ 매입단가 │ 마진율  │ 이전단가 │ 신규단가 │ 변동률 │
├──────┼──────────┼────────┼────────┼────────┼────────┼───────┤
│ 🔺   │ 가이드레일 │ 11,200 │ 25%   │ 15,800 │ 17,400 │+10.1% │
│ 🔺   │ 케이스    │ 8,500  │ 25%   │ 12,300 │ 13,100 │ +6.5% │
│ —    │ 모터 0.4kW│ 45,000 │ 30%   │ 58,500 │ 58,500 │  0%   │
│ 🔻   │ 볼트 M8  │ 120    │ 20%   │ 155    │ 150    │ -3.2% │
└──────┴──────────┴────────┴────────┴────────┴────────┴───────┘
```

### 7.3 영향 분석 결과

```
┌──────────────────────────────────────────────────────────────┐
│  영향 분석 | v2026-Q1 → v2026-Q2                              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  전체 품목: 150건                                             │
│  ├ 인상: 15건 (평균 +5.8%)                                   │
│  ├ 인하:  5건 (평균 -2.1%)                                   │
│  ├ 신규:  3건                                                │
│  └ 변동없음: 127건                                            │
│                                                              │
│  평균 마진율: 25.3% → 23.1% (▼ 2.2%p)                       │
│                                                              │
│  영향받는 미확정 견적: 3건                                     │
│  ├ KD-SC-260301-01 (draft)  1,250,000 → 1,295,000 (+3.6%)  │
│  ├ KD-SC-260305-02 (draft)    890,000 →   912,000 (+2.5%)  │
│  └ KD-SC-260312-01 (sent)     750,000 →   780,000 (+4.0%)  │
│                                                              │
│  ※ 확정/수주전환 완료된 건은 영향 없음 (복제된 단가 사용)       │
│                                                              │
│                                        [닫기] [검토 요청 →]  │
└──────────────────────────────────────────────────────────────┘
```

---

## 8. 구현 계획

### Phase 1: 기반 구조 (1주)

| 작업 | 내용 |
|------|------|
| DB | `price_versions`, `price_version_items` 테이블 생성 |
| DB | `prices` 테이블에 `version_id` 컬럼 추가 |
| Model | PriceVersion, PriceVersionItem 모델 생성 |
| Service | PriceVersionService 기본 CRUD |
| API | 버전 CRUD 엔드포인트 |

### Phase 2: 핵심 기능 (1주)

| 작업 | 내용 |
|------|------|
| 복사 | 이전 버전 기반 신규 버전 생성 (전체 복사) |
| 수정 | 개별/일괄 단가 변경 + change_type/change_rate 자동 계산 |
| 적용 | 버전 활성화 → prices 테이블 반영 + 기존 버전 보관 |
| 비교 | 두 버전 간 품목별 차이 조회 |

### Phase 3: 분석 + UI (1주)

| 작업 | 내용 |
|------|------|
| 분석 | 영향 분석 API (변경 통계, 견적 영향, 마진 시뮬레이션) |
| UI | React 버전 목록/상세/편집 화면 |
| UI | 영향 분석 다이얼로그 |
| UI | 버전 비교 화면 |

### Phase 4: 고도화 (선택)

| 작업 | 내용 |
|------|------|
| 예약 적용 | effective_from 날짜에 자동 활성화 (스케줄러) |
| 결재 연동 | 승인 프로세스를 결재관리와 연동 |
| 견적 스냅샷 | 견적/수주 생성 시 적용된 버전 정보 기록 |
| 알림 | 적용 예정 알림, 승인 요청 알림 |

---

## 9. 기존 시스템과의 호환

### 9.1 하위 호환성

| 기존 기능 | 영향 | 대응 |
|----------|------|------|
| `Price::getCurrentPrice()` | 변경 없음 | version_id 무관하게 effective 기반 조회 유지 |
| `EstimatePriceService` | 변경 없음 | 기존 prices 테이블 조회 로직 그대로 |
| 개별 단가 CRUD | 유지 | 버전에 속하지 않는 개별 단가도 계속 가능 |
| 기존 리비전 | 유지 | 버전 적용 시에도 price_revisions 자동 기록 |

### 9.2 전환 전략

```
1단계: price_versions 테이블 생성, 기존 시스템 영향 없음
2단계: 현재 모든 active 단가를 "v1 (초기 버전)"으로 그룹핑
3단계: 이후 단가 변경은 버전 단위로 관리 권장 (개별 수정도 허용)
```

---

## 관련 문서

- [단가 정책](../../rules/pricing-policy.md) — 6 Depth 단가 체계
- [단가 수정 가이드](../../guides/pricing-modification-guide.md) — 현재 수정 절차
- [견적 시스템](../../features/quotes/README.md) — BOM 산출 흐름

---

**최종 업데이트**: 2026-03-20
