- 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>
14 KiB
14 KiB
단가 정책 (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 테이블 (단가 마스터)
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 테이블 (변경 이력)
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 테이블 (기존 - 실제 입고단가)
-- 기존 테이블, 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 = truestatus = 'finalized'finalized_at = NOW()finalized_by = 현재 사용자
- 확정 후: 수정/삭제 불가, 조회만 가능
6. 리비전 관리
6.1 리비전 생성 시점
| 이벤트 | 리비전 생성 | 설명 |
|---|---|---|
| 최초 등록 | ✅ | revision_number = 1, before_snapshot = NULL |
| 수정 | ✅ | revision_number++, 변경 전/후 기록 |
| 삭제 | ✅ | soft delete, 삭제 전 상태 기록 |
| 확정 | ✅ | 확정 시점 스냅샷 기록 |
6.2 스냅샷 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:
{
"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 데이터 마이그레이션
-- 기존 데이터를 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. 관련 문서
최종 업데이트: 2025-12-08