Files
sam-docs/dev/dev_plans/price-version-management-plan.md
김보곤 659a33c37a docs: [pricing] 단가 버전 관리 시스템 기획서 추가
- price_versions / price_version_items 테이블 설계
- 버전 생명주기 (draft→review→approved→active→archived)
- 일괄 단가 변경, 영향 분석, 버전 비교 기능 설계
- 4 Phase 구현 계획 (기반→핵심→분석+UI→고도화)
- 기존 시스템 하위 호환성 보장 전략
2026-03-20 08:17:14 +09:00

18 KiB

단가 버전 관리 시스템 기획

작성일: 2026-03-20 상태: 기획 관련 정책: 단가 정책 §10 개선방향


1. 배경

1.1 현재 시스템

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

1.2 현재 한계

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

2. 목표

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

3. 시스템 설계

3.1 신규 테이블

price_versions (단가 버전)

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 (버전별 품목 단가)

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 테이블 추가 컬럼

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 버전 생성 (이전 버전 복사)

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

5.2 일괄 단가 변경

// 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 영향 분석

// 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)

// 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 버전 비교

// 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단계: 이후 단가 변경은 버전 단위로 관리 권장 (개별 수정도 허용)

관련 문서


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