Files
sam-docs/rules/pricing-policy.md
권혁성 0ace50b006 docs: [종합정비] 구조 재편 — Phase 0+2+4 통합
- Phase 0: INDEX.md 전면 재작성, CLAUDE.md→INDEX.md 통합 삭제
- Phase 0: front/→guides/ 이관(5개 파일), changes/ D7 포맷 통일(3개)
- Phase 0: guides/ai-config-설정.md→ai-config-settings.md D3 통일
- Phase 2: architecture/+specs/→system/ 이관(6개 이동, 4개 폐기)
- Phase 2: 13개 파일 경로 참조 수정 (specs/→system/, architecture/→system/)
- Phase 4: 7개 파일 11개 교차참조 깨진 링크 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:03:04 +09:00

402 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 단가 정책 (Pricing Policy)
> **작성일**: 2025-12-08
> **상태**: 설계 확정
> **관련 요청**: `docs/front/[API-2025-12-08] pricing-api-enhancement-request.md`
---
## 1. 개요
### 1.1 목적
- 품목(제품/자재)의 **표준단가** 관리
- **실제 입고단가** 기반 원가 계산
- 단가 변경 **이력(리비전)** 추적
- 고객그룹별 **차등 판매가** 지원
### 1.2 핵심 원칙
| 원칙 | 설명 |
|------|------|
| **실제원가 우선** | 수입검사 입고단가 > 표준원가 |
| **시점 기반 유효성** | effective_from ~ effective_to 기간 내 유효 |
| **리비전 추적** | 모든 변경 사항 이력 보관 |
| **확정 후 불변** | finalized 상태는 수정 불가 |
---
## 2. 테이블 구조
### 2.1 ERD 개요
```
┌─────────────────┐ ┌─────────────────────┐
│ prices │──────<│ price_revisions │
│ (단가 마스터) │ │ (변경 이력) │
└────────┬────────┘ └─────────────────────┘
│ item_type_code + item_id
┌────┴────┐
│ │
┌───▼───┐ ┌───▼─────┐
│products│ │materials│
└───────┘ └────┬────┘
│ material_id
┌─────────────────┐
│material_receipts│
│ (실제 입고단가) │
└─────────────────┘
```
### 2.2 prices 테이블 (단가 마스터)
```sql
CREATE TABLE prices (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
-- 품목 연결 --
item_type_code VARCHAR(20) NOT NULL COMMENT '품목유형 (PRODUCT/MATERIAL)',
item_id BIGINT UNSIGNED NOT NULL COMMENT '품목 ID',
client_group_id BIGINT UNSIGNED NULL COMMENT '고객그룹 ID (NULL=기본가)',
-- 원가 정보 --
purchase_price DECIMAL(15,4) NULL COMMENT '매입단가 (표준원가)',
processing_cost DECIMAL(15,4) NULL COMMENT '가공비',
loss_rate DECIMAL(5,2) NULL COMMENT 'LOSS율 (%)',
-- 판매가 정보 --
margin_rate DECIMAL(5,2) NULL COMMENT '마진율 (%)',
sales_price DECIMAL(15,4) NULL COMMENT '판매단가',
rounding_rule ENUM('round','ceil','floor') DEFAULT 'round' COMMENT '반올림 규칙',
rounding_unit INT DEFAULT 1 COMMENT '반올림 단위 (1,10,100,1000)',
-- 메타 정보 --
supplier VARCHAR(255) NULL COMMENT '공급업체',
effective_from DATE NOT NULL COMMENT '적용 시작일',
effective_to DATE NULL COMMENT '적용 종료일',
note TEXT NULL COMMENT '비고',
-- 상태 관리 --
status ENUM('draft','active','inactive','finalized') DEFAULT 'draft' COMMENT '상태',
is_final BOOLEAN DEFAULT FALSE COMMENT '최종 확정 여부',
finalized_at DATETIME NULL COMMENT '확정 일시',
finalized_by BIGINT UNSIGNED NULL COMMENT '확정자 ID',
-- 감사 컬럼 --
created_by BIGINT UNSIGNED NULL COMMENT '생성자 ID',
updated_by BIGINT UNSIGNED NULL COMMENT '수정자 ID',
deleted_by BIGINT UNSIGNED NULL COMMENT '삭제자 ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL COMMENT 'Soft Delete',
-- 인덱스 --
INDEX idx_prices_tenant (tenant_id),
INDEX idx_prices_item (tenant_id, item_type_code, item_id),
INDEX idx_prices_effective (tenant_id, effective_from, effective_to),
INDEX idx_prices_status (tenant_id, status),
UNIQUE idx_prices_unique (tenant_id, item_type_code, item_id, client_group_id, effective_from, deleted_at)
) COMMENT='단가 마스터';
```
### 2.3 price_revisions 테이블 (변경 이력)
```sql
CREATE TABLE price_revisions (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
price_id BIGINT UNSIGNED NOT NULL COMMENT '단가 ID',
-- 리비전 정보 --
revision_number INT NOT NULL COMMENT '리비전 번호',
changed_at DATETIME NOT NULL COMMENT '변경 일시',
changed_by BIGINT UNSIGNED NOT NULL COMMENT '변경자 ID',
change_reason VARCHAR(500) NULL COMMENT '변경 사유',
-- 변경 스냅샷 (JSON) --
before_snapshot JSON NULL COMMENT '변경 전 데이터',
after_snapshot JSON NOT NULL COMMENT '변경 후 데이터',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 인덱스 --
INDEX idx_revisions_price (price_id),
INDEX idx_revisions_tenant (tenant_id),
UNIQUE idx_revisions_unique (price_id, revision_number),
FOREIGN KEY (price_id) REFERENCES prices(id) ON DELETE CASCADE
) COMMENT='단가 변경 이력';
```
### 2.4 material_receipts 테이블 (기존 - 실제 입고단가)
```sql
-- 기존 테이블, purchase_price_excl_vat 필드 활용
-- 수입검사 시 실제 입고 단가 입력
```
---
## 3. 원가 계산 정책
### 3.1 원가 조회 우선순위
```
┌─────────────────────────────────────────────────────────┐
│ 원가 조회 로직 │
├─────────────────────────────────────────────────────────┤
│ 1순위: 수입검사 입고단가 │
│ material_receipts.purchase_price_excl_vat │
│ (가장 최근 입고 기준) │
│ │
│ 2순위: 표준원가 │
│ prices.purchase_price │
│ (해당 일자에 유효한 단가) │
│ │
│ 3순위: NULL (단가 미등록) │
│ → 경고 메시지 반환 │
└─────────────────────────────────────────────────────────┘
```
### 3.2 원가 계산 공식
```
총원가 = (매입단가 + 가공비) × (1 + LOSS율/100)
예시:
- 매입단가: 10,000원
- 가공비: 2,000원
- LOSS율: 5%
- 총원가 = (10,000 + 2,000) × 1.05 = 12,600원
```
### 3.3 품목 유형별 원가 적용
| 품목 유형 | 원가 소스 | 설명 |
|----------|----------|------|
| **MATERIAL** (자재) | material_receipts 우선 | 실제 입고단가 사용 |
| **PRODUCT** (제품) | prices 테이블 | 표준원가 사용 |
---
## 4. 판매가 계산 정책
### 4.1 판매가 계산 공식
```
판매단가 = 반올림(총원가 × (1 + 마진율/100), 반올림단위, 반올림규칙)
예시:
- 총원가: 12,600원
- 마진율: 25%
- 반올림단위: 100
- 반올림규칙: round
- 판매단가 = round(12,600 × 1.25, 100) = round(15,750, 100) = 15,800원
```
### 4.2 반올림 규칙
| 규칙 | 설명 | 예시 (단위: 100) |
|------|------|-----------------|
| `round` | 반올림 | 15,750 → 15,800 |
| `ceil` | 올림 | 15,701 → 15,800 |
| `floor` | 버림 | 15,799 → 15,700 |
### 4.3 고객그룹별 차등가
```
┌─────────────────────────────────────────────────────────┐
│ 판매가 조회 우선순위 │
├─────────────────────────────────────────────────────────┤
│ 1순위: 고객그룹별 특별가 │
│ prices WHERE client_group_id = [고객의 그룹ID] │
│ │
│ 2순위: 기본 판매가 │
│ prices WHERE client_group_id IS NULL │
└─────────────────────────────────────────────────────────┘
```
---
## 5. 상태 관리
### 5.1 단가 상태 (status)
```
┌──────────┐ 등록 ┌──────────┐ 활성화 ┌──────────┐
│ draft │ ──────────> │ active │ ─────────── │ finalized│
│ (초안) │ │ (활성) │ 확정 │ (확정) │
└──────────┘ └────┬─────┘ └──────────┘
│ 비활성화
┌──────────┐
│ inactive │
│ (비활성) │
└──────────┘
```
### 5.2 상태별 권한
| 상태 | 조회 | 수정 | 삭제 | 확정 |
|------|------|------|------|------|
| `draft` | ✅ | ✅ | ✅ | ✅ |
| `active` | ✅ | ✅ | ✅ | ✅ |
| `inactive` | ✅ | ✅ | ✅ | ❌ |
| `finalized` | ✅ | ❌ | ❌ | ❌ |
### 5.3 확정 (Finalize) 규칙
- **확정 조건**: status가 `draft` 또는 `active`일 때만 가능
- **확정 효과**:
- `is_final = true`
- `status = 'finalized'`
- `finalized_at = NOW()`
- `finalized_by = 현재 사용자`
- **확정 후**: 수정/삭제 불가, 조회만 가능
---
## 6. 리비전 관리
### 6.1 리비전 생성 시점
| 이벤트 | 리비전 생성 | 설명 |
|--------|-----------|------|
| 최초 등록 | ✅ | revision_number = 1, before_snapshot = NULL |
| 수정 | ✅ | revision_number++, 변경 전/후 기록 |
| 삭제 | ✅ | soft delete, 삭제 전 상태 기록 |
| 확정 | ✅ | 확정 시점 스냅샷 기록 |
### 6.2 스냅샷 JSON 구조
```json
{
"purchase_price": 10000,
"processing_cost": 2000,
"loss_rate": 5.0,
"margin_rate": 25.0,
"sales_price": 15800,
"effective_from": "2025-01-01",
"effective_to": null,
"status": "active",
"supplier": "ABC공급"
}
```
---
## 7. API 엔드포인트
### 7.1 엔드포인트 목록
| Method | Endpoint | 설명 | 우선순위 |
|--------|----------|------|---------|
| GET | `/api/v1/pricing` | 단가 목록 조회 | 🔴 필수 |
| GET | `/api/v1/pricing/{id}` | 단가 상세 조회 | 🔴 필수 |
| POST | `/api/v1/pricing` | 단가 등록 | 🔴 필수 |
| PUT | `/api/v1/pricing/{id}` | 단가 수정 | 🔴 필수 |
| DELETE | `/api/v1/pricing/{id}` | 단가 삭제 | 🔴 필수 |
| GET | `/api/v1/pricing/by-items` | 품목별 단가 현황 | 🔴 필수 |
| GET | `/api/v1/pricing/{id}/revisions` | 변경 이력 조회 | 🟡 중요 |
| POST | `/api/v1/pricing/{id}/finalize` | 단가 확정 | 🟢 권장 |
| GET | `/api/v1/pricing/cost` | 원가 조회 (계산) | 🟡 중요 |
### 7.2 원가 조회 API
```
GET /api/v1/pricing/cost?item_type=MATERIAL&item_id=123&date=2025-01-15
```
**Response:**
```json
{
"success": true,
"data": {
"item_type": "MATERIAL",
"item_id": 123,
"cost_source": "receipt", // "receipt" | "standard" | "not_found"
"purchase_price": 10500,
"receipt_id": 456, // cost_source가 receipt일 때
"receipt_date": "2025-01-10",
"price_id": null // cost_source가 standard일 때
}
}
```
---
## 8. 비즈니스 규칙
### 8.1 검증 규칙
| 규칙 | 설명 |
|------|------|
| **R1** | effective_from은 필수 |
| **R2** | effective_from ≤ effective_to (종료일이 있을 경우) |
| **R3** | 동일 품목+고객그룹+적용시작일 조합은 중복 불가 |
| **R4** | 확정된 단가는 수정/삭제 불가 |
| **R5** | 마진율은 0~100% 범위 |
| **R6** | LOSS율은 0~100% 범위 |
| **R7** | 반올림단위는 1, 10, 100, 1000 중 하나 |
### 8.2 기간 중복 처리
```
기존: 2025-01-01 ~ NULL (무기한)
신규: 2025-06-01 ~ NULL
→ 기존 단가의 effective_to를 2025-05-31로 자동 설정
→ 신규 단가 등록
```
### 8.3 삭제 정책
- **Soft Delete** 적용
- 삭제 시 `deleted_at`, `deleted_by` 기록
- 삭제된 단가도 리비전 이력에서 조회 가능
---
## 9. 기존 시스템 호환
### 9.1 price_histories 테이블 처리
| 상태 | 설명 |
|------|------|
| **현재** | 기존 price_histories 데이터 유지 |
| **전환 기간** | 양쪽 테이블 병행 운영 |
| **전환 완료 후** | price_histories deprecated |
### 9.2 데이터 마이그레이션
```sql
-- 기존 데이터를 prices 테이블로 마이그레이션
INSERT INTO prices (
tenant_id, item_type_code, item_id, client_group_id,
purchase_price, effective_from, effective_to,
status, created_by, created_at
)
SELECT
tenant_id, item_type_code, item_id, client_group_id,
price as purchase_price, started_at, ended_at,
'active', created_by, created_at
FROM price_histories
WHERE deleted_at IS NULL;
```
---
## 10. 관련 문서
- [API 개발 규칙](../standards/api-rules.md)
- [데이터베이스 스키마](../system/database/README.md)
- [프론트엔드 요청서](../front/[API-2025-12-08]%20pricing-api-enhancement-request.md)
---
**최종 업데이트**: 2025-12-08