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

14 KiB
Raw Permalink Blame History

단가 정책 (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 = true
    • status = '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