- 6 Depth 단가 체계 정의 (매입→표준원가→판매→견적→수주→매출) - 전체 단가 흐름도 및 테이블별 저장 위치 매핑 - 고객그룹 차등가, 상태관리, 리비전 관리 정리 - Depth 기반 버전관리 개선방향 4Phase 기획 추가
406 lines
16 KiB
Markdown
406 lines
16 KiB
Markdown
# SAM 단가 정책 (Pricing Policy)
|
||
|
||
> **작성일**: 2025-12-08
|
||
> **최종 갱신**: 2026-03-19
|
||
> **상태**: 설계 확정 + 개선방향 기획 (Depth 버전관리)
|
||
|
||
---
|
||
|
||
## 1. 개요
|
||
|
||
### 1.1 목적
|
||
|
||
SAM 시스템 내 모든 단가의 **유형 정의**, **계산 공식**, **흐름 추적**, **버전 관리**를 명확히 한다.
|
||
|
||
### 1.2 핵심 원칙
|
||
|
||
| 원칙 | 설명 |
|
||
|------|------|
|
||
| **Depth 기반 흐름** | 매입단가 → 표준원가 → 판매단가 → 견적단가 → 수주단가 → 매출단가 |
|
||
| **실제원가 우선** | 수입검사 입고단가 > 표준원가 |
|
||
| **시점 기반 유효성** | `effective_from` ~ `effective_to` 기간 내 유효 |
|
||
| **리비전 추적** | 모든 변경 사항 이력 보관 (`price_revisions`) |
|
||
| **확정 후 불변** | `finalized` 상태는 수정/삭제 불가 |
|
||
| **단가 복제 원칙** | 하위 단계(견적→수주→매출)로 복제된 단가는 원본과 독립 |
|
||
|
||
### 1.3 단가 유형 정의 (6 Depth)
|
||
|
||
```
|
||
Depth 0 매입단가 prices.purchase_price 공급업체로부터의 구매가
|
||
Depth 1 표준원가 (매입단가 + 가공비) × LOSS 제조 기준 원가
|
||
Depth 2 판매단가 prices.sales_price 표준원가 + 마진, 반올림 적용
|
||
Depth 3 견적단가 quote_items.unit_price BOM 산출 또는 수동 입력
|
||
Depth 4 수주단가 order_items.unit_price 견적에서 복제 (확정가)
|
||
Depth 5 매출단가 sales.supply_amount 수주에서 복제 (최종가)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 단가 흐름도
|
||
|
||
### 2.1 전체 흐름
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Depth 0: 매입단가 │
|
||
│ prices.purchase_price + prices.processing_cost │
|
||
│ 소스: 공급업체 견적, 수입검사 입고단가 │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
↓ × (1 + LOSS율/100)
|
||
┌──────────────────────┴──────────────────────────────────────┐
|
||
│ Depth 1: 표준원가 │
|
||
│ = (매입단가 + 가공비) × (1 + LOSS율/100) │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
↓ × (1 + 마진율/100) + 반올림
|
||
┌──────────────────────┴──────────────────────────────────────┐
|
||
│ Depth 2: 판매단가 │
|
||
│ prices.sales_price (고객그룹별 차등가 가능) │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
↓ BOM 산출 or 수동 입력
|
||
┌──────────────────────┴──────────────────────────────────────┐
|
||
│ Depth 3: 견적단가 │
|
||
│ quote_items.unit_price (BOM 부품별 판매단가 합산 = 완제품가) │
|
||
│ quotes.total_amount = 재료비 + 노무비 + 시공비 - 할인 │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
↓ convertToOrder() 복제
|
||
┌──────────────────────┴──────────────────────────────────────┐
|
||
│ Depth 4: 수주단가 │
|
||
│ order_items.unit_price (견적단가 복제) │
|
||
│ + supply_amount, tax_amount, total_amount 재계산 │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
↓ createFromOrder/Shipment 복제
|
||
┌──────────────────────┴──────────────────────────────────────┐
|
||
│ Depth 5: 매출단가 │
|
||
│ sales.supply_amount, tax_amount, total_amount (수주 복제) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 단가 저장 위치
|
||
|
||
| Depth | 테이블 | 단가 컬럼 | 금액 컬럼 |
|
||
|:-----:|--------|----------|----------|
|
||
| 0 | `prices` | `purchase_price`, `processing_cost` | — |
|
||
| 1 | (계산값) | — | `(purchase_price + processing_cost) × (1 + loss_rate)` |
|
||
| 2 | `prices` | `sales_price` | — |
|
||
| 3 | `quote_items` | `unit_price` | `total_price` |
|
||
| 3 | `quotes` | — | `material_cost`, `labor_cost`, `install_cost`, `total_amount` |
|
||
| 4 | `order_items` | `unit_price` | `supply_amount`, `tax_amount`, `total_amount` |
|
||
| 4 | `orders` | — | `supply_amount`, `tax_amount`, `total_amount` |
|
||
| 5 | `sales` | — | `supply_amount`, `tax_amount`, `total_amount` |
|
||
|
||
---
|
||
|
||
## 3. 단가 계산 공식
|
||
|
||
### 3.1 표준원가 (Depth 1)
|
||
|
||
```
|
||
표준원가 = (매입단가 + 가공비) × (1 + LOSS율/100)
|
||
|
||
예시: (10,000 + 2,000) × 1.05 = 12,600원
|
||
```
|
||
|
||
### 3.2 판매단가 (Depth 2)
|
||
|
||
```
|
||
판매단가 = 반올림(표준원가 × (1 + 마진율/100), 반올림단위, 반올림규칙)
|
||
|
||
예시: round(12,600 × 1.25 / 100) × 100 = 15,800원
|
||
```
|
||
|
||
| 반올림규칙 | 설명 | 15,750 (단위 100) |
|
||
|-----------|------|------------------|
|
||
| `round` | 반올림 | 15,800 |
|
||
| `ceil` | 올림 | 15,800 |
|
||
| `floor` | 버림 | 15,700 |
|
||
|
||
### 3.3 원가 조회 우선순위
|
||
|
||
| 순위 | 소스 | 조건 |
|
||
|:----:|------|------|
|
||
| 1 | `material_receipts.purchase_price_excl_vat` | 자재만, 최근 입고 기준 |
|
||
| 2 | `prices.purchase_price` | 해당 일자 유효 단가 |
|
||
| 3 | NULL | 단가 미등록 → 경고 반환 |
|
||
|
||
### 3.4 견적단가 (Depth 3) — BOM 산출
|
||
|
||
```
|
||
견적 품목 금액 = 계산수량 × 단가
|
||
견적 소계 = 재료비 + 노무비 + 시공비
|
||
견적 총액 = 소계 - 할인금액
|
||
```
|
||
|
||
BOM 자동산출 시 `EstimatePriceService`가 items + item_details + prices 조인으로 판매단가 조회.
|
||
|
||
### 3.5 수주단가 (Depth 4)
|
||
|
||
```
|
||
공급가액 = 수량 × 단가 - 할인금액
|
||
세액 = 공급가액 × 10%
|
||
총액 = 공급가액 + 세액
|
||
```
|
||
|
||
### 3.6 매출단가 (Depth 5)
|
||
|
||
수주의 `supply_amount`, `tax_amount`, `total_amount`를 그대로 복제.
|
||
|
||
---
|
||
|
||
## 4. 고객그룹 차등가
|
||
|
||
### 4.1 구조
|
||
|
||
`client_groups` 테이블:
|
||
|
||
| 필드 | 설명 |
|
||
|------|------|
|
||
| `group_code` | 그룹 코드 |
|
||
| `group_name` | 그룹명 |
|
||
| `price_rate` | 가격 배율 (1.0=기본, 0.9=10%할인, 1.1=10%인상) |
|
||
|
||
### 4.2 조회 우선순위
|
||
|
||
```
|
||
1순위: prices WHERE client_group_id = [거래처의 그룹ID] → 그룹 전용 단가
|
||
2순위: prices WHERE client_group_id IS NULL → 기본 단가
|
||
```
|
||
|
||
`Price::getCurrentPrice($tenantId, $itemTypeCode, $itemId, $clientGroupId)` 메서드가 이 로직을 처리한다.
|
||
|
||
---
|
||
|
||
## 5. 상태 관리
|
||
|
||
### 5.1 상태 전이
|
||
|
||
```
|
||
draft ──→ active ──→ finalized
|
||
(초안) (활성) (확정, 불변)
|
||
│
|
||
└──→ inactive (비활성)
|
||
```
|
||
|
||
### 5.2 상태별 권한
|
||
|
||
| 상태 | 조회 | 수정 | 삭제 | 확정 |
|
||
|------|:----:|:----:|:----:|:----:|
|
||
| `draft` | ✅ | ✅ | ✅ | ✅ |
|
||
| `active` | ✅ | ✅ | ✅ | ✅ |
|
||
| `inactive` | ✅ | ✅ | ✅ | ❌ |
|
||
| `finalized` | ✅ | ❌ | ❌ | ❌ |
|
||
|
||
### 5.3 확정 규칙
|
||
|
||
- `sales_price` 또는 `purchase_price` 중 하나 이상 존재해야 확정 가능
|
||
- 확정 시: `is_final = true`, `status = 'finalized'`, `finalized_at/by` 기록
|
||
- 확정 후: 수정 필요 시 **새 단가 레코드 생성** (기존 단가의 `effective_to` 자동 설정)
|
||
|
||
---
|
||
|
||
## 6. 리비전 관리
|
||
|
||
### 6.1 리비전 생성 시점
|
||
|
||
| 이벤트 | 리비전 | 설명 |
|
||
|--------|:------:|------|
|
||
| 최초 등록 | ✅ | `revision_number = 1`, `before_snapshot = NULL` |
|
||
| 수정 | ✅ | `revision_number++`, 변경 전/후 기록 |
|
||
| 삭제 | ✅ | soft delete, 삭제 전 스냅샷 |
|
||
| 확정 | ✅ | 확정 시점 스냅샷 |
|
||
|
||
### 6.2 기간 중복 자동 처리
|
||
|
||
```
|
||
기존: 2025-01-01 ~ NULL (무기한)
|
||
신규: 2025-06-01 ~ NULL
|
||
|
||
→ 기존의 effective_to를 2025-05-31로 자동 설정
|
||
→ 신규 단가 등록
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 비즈니스 규칙
|
||
|
||
| 규칙 | 설명 |
|
||
|------|------|
|
||
| **R1** | `effective_from`은 필수 |
|
||
| **R2** | `effective_from` ≤ `effective_to` |
|
||
| **R3** | 동일 품목+고객그룹+적용시작일 중복 불가 |
|
||
| **R4** | 확정된 단가는 수정/삭제 불가 |
|
||
| **R5** | 마진율: 0% 이상 (상한 없음) |
|
||
| **R6** | LOSS율: 0~100% |
|
||
| **R7** | 반올림단위: 1, 10, 100, 1000 중 택1 |
|
||
| **R8** | Soft Delete 적용 (삭제 후 리비전에서 조회 가능) |
|
||
|
||
---
|
||
|
||
## 8. API 엔드포인트
|
||
|
||
| Method | Endpoint | 설명 |
|
||
|--------|----------|------|
|
||
| GET | `/api/v1/pricing` | 단가 목록 |
|
||
| GET | `/api/v1/pricing/{id}` | 단가 상세 (리비전 10개 포함) |
|
||
| POST | `/api/v1/pricing` | 단가 등록 |
|
||
| PUT | `/api/v1/pricing/{id}` | 단가 수정 (`change_reason` 선택) |
|
||
| DELETE | `/api/v1/pricing/{id}` | 단가 삭제 |
|
||
| POST | `/api/v1/pricing/{id}/finalize` | 단가 확정 |
|
||
| POST | `/api/v1/pricing/by-items` | 품목별 단가 현황 |
|
||
| GET | `/api/v1/pricing/{id}/revisions` | 변경 이력 |
|
||
| POST | `/api/v1/pricing/cost` | 원가 조회 |
|
||
| GET | `/api/v1/pricing/stats` | 단가 통계 |
|
||
| DELETE | `/api/v1/pricing/bulk` | 일괄 삭제 |
|
||
|
||
---
|
||
|
||
## 9. 핵심 코드 위치
|
||
|
||
| 구분 | 파일 | 역할 |
|
||
|------|------|------|
|
||
| 모델 | `api/app/Models/Products/Price.php` | `getCurrentPrice()`, `calculateSalesPrice()`, `getSalesPriceByItemCode()` |
|
||
| 모델 | `api/app/Models/Products/PriceRevision.php` | 변경 이력 (Immutable) |
|
||
| 서비스 | `api/app/Services/PricingService.php` | 단가 CRUD, 리비전, 확정, 원가 조회 |
|
||
| 서비스 | `api/app/Services/Quote/EstimatePriceService.php` | BOM 자동산출 시 부품 단가 조회 |
|
||
| 서비스 | `api/app/Services/Quote/QuoteService.php` | 견적→수주 전환 (`convertToOrder`) |
|
||
| 모델 | `api/app/Models/Orders/OrderItem.php` | `createFromQuoteItem()`, 금액 재계산 |
|
||
| 모델 | `api/app/Models/Tenants/Sale.php` | `createFromOrder()`, `createFromShipment()` |
|
||
|
||
---
|
||
|
||
## 10. 개선방향: Depth 기반 단가 버전관리
|
||
|
||
> **상태**: 기획 단계
|
||
> **배경**: 현재 원자재 가격 인상 시 하위 Depth(판매단가, 견적단가)에 대한 영향 분석이 수동으로 이루어지고 있다. Depth 개념을 도입하여 단가 변경의 **상향 전파(Cascade)** 와 **영향 분석(Impact Analysis)** 을 체계화한다.
|
||
|
||
### 10.1 현재 한계
|
||
|
||
| 문제 | 설명 |
|
||
|------|------|
|
||
| **단절된 추적** | 매입단가 변경 시 어떤 판매단가/견적에 영향이 있는지 추적 불가 |
|
||
| **수동 갱신** | 원자재 인상 → 판매단가 수동 재계산 → 견적 재작성 필요 |
|
||
| **스냅샷 부재** | 견적/수주 시점의 Depth 0~2 단가를 역추적할 수 없음 |
|
||
| **일괄 변경 불가** | 특정 공급업체 자재 10% 인상 시 관련 판매단가 일괄 갱신 불가 |
|
||
|
||
### 10.2 개선 방향
|
||
|
||
#### Phase 1: 단가 연쇄 관계 추적
|
||
|
||
`prices` 테이블에 Depth 관계를 기록하여, 하위 Depth 단가가 어떤 상위 Depth 단가에서 파생되었는지 추적한다.
|
||
|
||
```
|
||
신규 필드 (prices 테이블):
|
||
price_depth TINYINT — 0(매입), 2(판매) 등
|
||
parent_price_id BIGINT — 이 단가의 원천 단가 ID (NULL=최상위)
|
||
price_version VARCHAR(20) — 단가 버전 (예: 'v2026-Q1')
|
||
```
|
||
|
||
**효과**: 매입단가(Depth 0) 변경 시 → 해당 품목의 판매단가(Depth 2) 목록을 즉시 조회 가능.
|
||
|
||
#### Phase 2: 변경 영향 분석 API
|
||
|
||
매입단가 변경 전 영향 범위를 분석하는 API를 제공한다.
|
||
|
||
```
|
||
POST /api/v1/pricing/impact-analysis
|
||
Body: { "price_id": 123, "new_purchase_price": 12000 }
|
||
|
||
Response:
|
||
{
|
||
"affected_sales_prices": [
|
||
{ "id": 456, "item_name": "가이드레일", "current": 15800, "projected": 17400, "diff": "+10.1%" }
|
||
],
|
||
"affected_quotes": [
|
||
{ "id": 789, "quote_number": "KD-SC-260301-01", "status": "draft", "impact": "+52,000원" }
|
||
],
|
||
"affected_orders": [
|
||
{ "id": 101, "order_number": "ORD-260215-001", "status": "confirmed", "impact": "변경 불가 (확정)" }
|
||
]
|
||
}
|
||
```
|
||
|
||
#### Phase 3: 단가 버전 그룹 관리
|
||
|
||
분기/반기 단위로 단가 버전을 묶어 관리한다.
|
||
|
||
```
|
||
신규 테이블: price_versions
|
||
id, tenant_id
|
||
version_code VARCHAR(20) — 'v2026-Q1', 'v2026-H1'
|
||
version_name VARCHAR(100) — '2026년 1분기 단가'
|
||
base_version_id BIGINT — 이전 버전 ID (NULL=최초)
|
||
effective_from DATE — 적용 시작일
|
||
status ENUM — draft/active/archived
|
||
change_summary JSON — { total_items: 150, avg_increase: 3.2% }
|
||
created_by, created_at
|
||
```
|
||
|
||
**워크플로우**:
|
||
|
||
```
|
||
1. 신규 버전 생성 (draft)
|
||
→ 이전 버전의 모든 단가를 복사
|
||
2. 변경 대상 품목의 매입단가 수정
|
||
→ 판매단가 자동 재계산 (Cascade)
|
||
3. 영향 분석 리포트 생성
|
||
→ 변경 전후 비교, 마진 변화 시각화
|
||
4. 승인 → 버전 활성화 (active)
|
||
→ effective_from 일자부터 신규 단가 적용
|
||
5. 이전 버전 자동 종료 (archived)
|
||
```
|
||
|
||
#### Phase 4: 견적/수주 단가 스냅샷
|
||
|
||
견적/수주 생성 시점의 Depth 0~2 단가를 `calculation_inputs` JSON에 보존한다.
|
||
|
||
```json
|
||
{
|
||
"price_snapshot": {
|
||
"version": "v2026-Q1",
|
||
"captured_at": "2026-03-15",
|
||
"items": [
|
||
{
|
||
"item_id": 100,
|
||
"item_name": "가이드레일",
|
||
"depth_0": { "purchase_price": 10000, "processing_cost": 2000, "loss_rate": 5 },
|
||
"depth_2": { "sales_price": 15800, "margin_rate": 25 },
|
||
"depth_3": { "unit_price": 15800, "quantity": 2.5, "total": 39500 }
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**효과**: 수주 완료 후 원가 변동이 발생해도, 해당 수주 시점의 원가 구조를 완전히 역추적 가능.
|
||
|
||
### 10.3 기대 효과
|
||
|
||
| 항목 | 현재 | 개선 후 |
|
||
|------|------|--------|
|
||
| 원자재 인상 반영 | 수동 (1~2일) | 자동 Cascade (즉시) |
|
||
| 영향 범위 파악 | 불가능 | Impact Analysis API |
|
||
| 단가 이력 비교 | 개별 리비전만 | 버전 간 일괄 비교 |
|
||
| 견적 원가 역추적 | 불가능 | 스냅샷 보존 |
|
||
| 분기 단가 갱신 | 품목별 수동 | 버전 복사 + 일괄 변경 |
|
||
|
||
### 10.4 구현 우선순위
|
||
|
||
| Phase | 내용 | 난이도 | 우선순위 |
|
||
|:-----:|------|:------:|:-------:|
|
||
| 1 | 단가 연쇄 관계 (parent_price_id) | 낮음 | 🔴 필수 |
|
||
| 2 | 변경 영향 분석 API | 중간 | 🟡 중요 |
|
||
| 3 | 단가 버전 그룹 관리 | 높음 | 🟢 권장 |
|
||
| 4 | 견적/수주 스냅샷 보존 | 낮음 | 🟡 중요 |
|
||
|
||
---
|
||
|
||
## 11. 관련 문서
|
||
|
||
- [품목 정책](item-policy.md) — 품목 유형 체계 (FG, PT, SM, RM, CS)
|
||
- [견적 시스템](../features/quotes/README.md) — BOM 산출, 견적 생성 흐름
|
||
- [API 개발 규칙](../dev/standards/api-rules.md) — Service-First 패턴
|
||
- [DB 스키마](../system/database/README.md) — 테이블 관계
|
||
|
||
---
|
||
|
||
**최종 업데이트**: 2026-03-19
|