# 단가 버전 관리 시스템 기획 > **작성일**: 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