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

475 lines
18 KiB
Markdown

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