- price_versions / price_version_items 테이블 설계 - 버전 생명주기 (draft→review→approved→active→archived) - 일괄 단가 변경, 영향 분석, 버전 비교 기능 설계 - 4 Phase 구현 계획 (기반→핵심→분석+UI→고도화) - 기존 시스템 하위 호환성 보장 전략
475 lines
18 KiB
Markdown
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
|