# 기간별 단가 설정 시스템 구현 가이드

## 개요

**선택항목 상세입력(selected-items-detail-input)** 컴포넌트를 활용하여 기간별 단가를 설정하는 범용 시스템입니다.

## 데이터베이스 설계

### 1. 마이그레이션 실행

```bash
# 마이그레이션 파일 위치
db/migrations/999_add_period_price_columns_to_customer_item_mapping.sql

# 실행 (로컬)
npm run migrate:local

# 또는 수동 실행
psql -U your_user -d erp_db -f db/migrations/999_add_period_price_columns_to_customer_item_mapping.sql
```

### 2. 추가된 컬럼들

| 컬럼명 | 타입 | 설명 | 사진 항목 |
|--------|------|------|-----------|
| `start_date` | DATE | 기간 시작일 | ✅ 시작일 DatePicker |
| `end_date` | DATE | 기간 종료일 | ✅ 종료일 DatePicker |
| `discount_type` | VARCHAR(50) | 할인 방식 | ✅ 할인율/할인금액 Select |
| `discount_value` | NUMERIC(15,2) | 할인율 또는 할인금액 | ✅ 숫자 입력 |
| `rounding_type` | VARCHAR(50) | 반올림 방식 | ✅ 반올림/절삭/올림 Select |
| `rounding_unit_value` | VARCHAR(50) | 반올림 단위 | ✅ 1원/10원/100원/1,000원 Select |
| `calculated_price` | NUMERIC(15,2) | 계산된 최종 단가 | ✅ 계산 결과 표시 |
| `is_base_price` | BOOLEAN | 기준단가 여부 | ✅ 기준단가 Checkbox |

## 화면 편집기 설정 방법

### Step 1: 선택항목 상세입력 컴포넌트 추가

1. 화면 편집기에서 "선택항목 상세입력" 컴포넌트를 캔버스에 드래그앤드롭
2. 컴포넌트 ID: `customer-item-price-periods`

### Step 2: 데이터 소스 설정

- **원본 데이터 테이블**: `item_info` (품목 정보)
- **저장 대상 테이블**: `customer_item_mapping`
- **데이터 소스 ID**: URL 파라미터에서 자동 설정 (Button 컴포넌트가 전달)

### Step 3: 표시할 원본 데이터 컬럼 설정

이전 화면(품목 선택 모달)에서 전달받은 품목 정보를 표시:

```
컬럼1: item_code (품목코드)
컬럼2: item_name (품목명)
컬럼3: spec (규격)
```

### Step 4: 필드 그룹 2개 생성

#### 그룹 1: 거래처 품목/품명 관리 (group_customer)

| 필드명 | 라벨 | 타입 | 설명 |
|--------|------|------|------|
| `customer_item_code` | 거래처 품번 | text | 거래처에서 사용하는 품번 |
| `customer_item_name` | 거래처 품명 | text | 거래처에서 사용하는 품명 |

#### 그룹 2: 기간별 단가 설정 (group_period_price)

| 필드명 | 라벨 | 타입 | 자동 채우기 | 설명 |
|--------|------|------|-------------|------|
| `start_date` | 시작일 | date | - | 단가 적용 시작일 |
| `end_date` | 종료일 | date | - | 단가 적용 종료일 (NULL이면 무기한) |
| `current_unit_price` | 단가 | number | `item_info.standard_price` | 기본 단가 (품목에서 자동 채우기) |
| `currency_code` | 통화 | code/category | - | 통화 코드 (KRW, USD 등) |
| `discount_type` | 할인 방식 | code/category | - | 할인율없음/할인율(%)/할인금액 |
| `discount_value` | 할인값 | number | - | 할인율(5) 또는 할인금액 |
| `rounding_type` | 반올림 방식 | code/category | - | 반올림없음/반올림/절삭/올림 |
| `rounding_unit_value` | 반올림 단위 | code/category | - | 1원/10원/100원/1,000원 |
| `calculated_price` | 최종 단가 | number | - | 계산된 최종 단가 (읽기 전용) |
| `is_base_price` | 기준단가 | checkbox | - | 기준단가 여부 |

### Step 5: 그룹별 표시 항목 설정 (DisplayItems)

**그룹 2 (기간별 단가 설정)의 표시 설정:**

```
1. [필드] start_date | 라벨: "" | 형식: date | 빈 값: 기본값 (미설정)
2. [텍스트] " ~ "
3. [필드] end_date | 라벨: "" | 형식: date | 빈 값: 기본값 (무기한)
4. [텍스트] " | "
5. [필드] calculated_price | 라벨: "" | 형식: currency | 빈 값: 기본값 (계산 중)
6. [텍스트] " "
7. [필드] currency_code | 라벨: "" | 형식: text | 빈 값: 기본값 (KRW)
8. [조건] is_base_price가 true이면 → [배지] "기준단가" (variant: default)
```

**렌더링 예시:**
```
2024-01-01 ~ 2024-06-30 | 50,000 KRW [기준단가]
2024-07-01 ~ 무기한 | 55,000 KRW
```

## 데이터 흐름

### 1. 품목 선택 모달 (이전 화면)

```tsx
// TableList 컴포넌트에서 품목 선택
<Button
  onClick={() => {
    const selectedItems = tableData.filter(item => selectedRowIds.includes(item.id));
    
    // modalDataStore에 데이터 저장
    useModalDataStore.getState().setData("item_info", selectedItems);
    
    // 다음 화면으로 이동 (dataSourceId 전달)
    router.push("/screen/period-price-settings?dataSourceId=item_info");
  }}
>
  다음
</Button>
```

### 2. 기간별 단가 설정 화면

```tsx
// 선택항목 상세입력 컴포넌트가 자동으로 처리
// 1. URL 파라미터에서 dataSourceId 읽기
// 2. modalDataStore에서 item_info 데이터 가져오기
// 3. 사용자가 그룹별로 여러 개의 기간별 단가 입력
// 4. 저장 버튼 클릭 시 customer_item_mapping 테이블에 저장
```

### 3. 저장 데이터 구조

**하나의 품목(item_id = "ITEM001")에 대해 3개의 기간별 단가를 입력한 경우:**

```sql
-- customer_item_mapping 테이블에 3개의 행으로 저장
INSERT INTO customer_item_mapping (
  customer_id, item_id, 
  customer_item_code, customer_item_name,
  start_date, end_date, 
  current_unit_price, currency_code,
  discount_type, discount_value,
  rounding_type, rounding_unit_value,
  calculated_price, is_base_price
) VALUES
-- 첫 번째 기간 (기준단가)
('CUST001', 'ITEM001', 
 'CUST-A-001', '실리콘 고무 시트',
 '2024-01-01', '2024-06-30',
 50000, 'KRW',
 '할인율없음', 0,
 '반올림', '100원',
 50000, true),

-- 두 번째 기간
('CUST001', 'ITEM001',
 'CUST-A-001', '실리콘 고무 시트',
 '2024-07-01', '2024-12-31',
 50000, 'KRW',
 '할인율(%)', 5,
 '절삭', '1원',
 47500, false),

-- 세 번째 기간 (무기한)
('CUST001', 'ITEM001',
 'CUST-A-001', '실리콘 고무 시트',
 '2025-01-01', NULL,
 50000, 'KRW',
 '할인금액', 3000,
 '올림', '1000원',
 47000, false);
```

## 계산 로직 (선택사항)

단가 계산을 자동화하려면 프론트엔드에서 `calculated_price`를 자동 계산:

```typescript
const calculatePrice = (
  basePrice: number, 
  discountType: string, 
  discountValue: number,
  roundingType: string,
  roundingUnit: string
): number => {
  let price = basePrice;
  
  // 1단계: 할인 적용
  if (discountType === "할인율(%)") {
    price = price * (1 - discountValue / 100);
  } else if (discountType === "할인금액") {
    price = price - discountValue;
  }
  
  // 2단계: 반올림 적용
  const unitMap: Record<string, number> = {
    "1원": 1,
    "10원": 10,
    "100원": 100,
    "1,000원": 1000,
  };
  
  const unit = unitMap[roundingUnit] || 1;
  
  if (roundingType === "반올림") {
    price = Math.round(price / unit) * unit;
  } else if (roundingType === "절삭") {
    price = Math.floor(price / unit) * unit;
  } else if (roundingType === "올림") {
    price = Math.ceil(price / unit) * unit;
  }
  
  return price;
};

// 필드 변경 시 자동 계산
useEffect(() => {
  const calculatedPrice = calculatePrice(
    basePrice,
    discountType,
    discountValue,
    roundingType,
    roundingUnit
  );
  
  // calculated_price 필드 업데이트
  handleFieldChange(itemId, groupId, entryId, "calculated_price", calculatedPrice);
}, [basePrice, discountType, discountValue, roundingType, roundingUnit]);
```

## 백엔드 API 구현 (필요시)

### 기간별 단가 조회

```typescript
// GET /api/customer-item/price-periods?customer_id=CUST001&item_id=ITEM001
router.get("/price-periods", async (req, res) => {
  const { customer_id, item_id } = req.query;
  const companyCode = req.user!.companyCode;
  
  const query = `
    SELECT * FROM customer_item_mapping
    WHERE customer_id = $1
      AND item_id = $2
      AND company_code = $3
    ORDER BY start_date ASC
  `;
  
  const result = await pool.query(query, [customer_id, item_id, companyCode]);
  
  return res.json({ success: true, data: result.rows });
});
```

### 기간별 단가 저장

```typescript
// POST /api/customer-item/price-periods
router.post("/price-periods", async (req, res) => {
  const { items } = req.body; // 선택항목 상세입력 컴포넌트에서 전달
  const companyCode = req.user!.companyCode;
  
  const client = await pool.connect();
  
  try {
    await client.query("BEGIN");
    
    for (const item of items) {
      // item.fieldGroups.group_period_price 배열의 각 항목을 INSERT
      const periodPrices = item.fieldGroups.group_period_price || [];
      
      for (const periodPrice of periodPrices) {
        const query = `
          INSERT INTO customer_item_mapping (
            company_code, customer_id, item_id,
            customer_item_code, customer_item_name,
            start_date, end_date,
            current_unit_price, currency_code,
            discount_type, discount_value,
            rounding_type, rounding_unit_value,
            calculated_price, is_base_price
          ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
        `;
        
        await client.query(query, [
          companyCode,
          item.originalData.customer_id,
          item.originalData.item_id,
          periodPrice.customer_item_code,
          periodPrice.customer_item_name,
          periodPrice.start_date,
          periodPrice.end_date || null,
          periodPrice.current_unit_price,
          periodPrice.currency_code,
          periodPrice.discount_type,
          periodPrice.discount_value,
          periodPrice.rounding_type,
          periodPrice.rounding_unit_value,
          periodPrice.calculated_price,
          periodPrice.is_base_price
        ]);
      }
    }
    
    await client.query("COMMIT");
    
    return res.json({ success: true, message: "기간별 단가가 저장되었습니다." });
  } catch (error) {
    await client.query("ROLLBACK");
    console.error("기간별 단가 저장 실패:", error);
    return res.status(500).json({ success: false, error: "저장 실패" });
  } finally {
    client.release();
  }
});
```

## 사용 시나리오 예시

### 시나리오 1: 거래처별 단가 관리

1. 거래처 선택 모달 → 거래처 선택 → 다음
2. 품목 선택 모달 → 품목 여러 개 선택 → 다음
3. **기간별 단가 설정 화면**
   - 품목1 (실리콘 고무 시트)
     - **그룹1 추가**: 거래처 품번: CUST-A-001, 품명: 실리콘 시트
     - **그룹2 추가**: 2024-01-01 ~ 2024-06-30, 50,000원 (기준단가)
     - **그룹2 추가**: 2024-07-01 ~ 무기한, 할인율 5% → 47,500원
   - 품목2 (스테인리스 판)
     - **그룹1 추가**: 거래처 품번: CUST-A-002, 품명: SUS304 판
     - **그룹2 추가**: 2024-01-01 ~ 무기한, 150,000원 (기준단가)
4. 저장 버튼 클릭 → customer_item_mapping 테이블에 4개 행 저장

### 시나리오 2: 단순 단가 입력

필드 그룹을 사용하지 않고 단일 입력도 가능:

```
그룹 없이 필드 정의:
- customer_item_code
- customer_item_name
- current_unit_price
- currency_code

→ 각 품목당 1개의 행만 저장
```

## 장점

### 1. 범용성
- 기간별 단가뿐만 아니라 **모든 숫자 계산 시나리오**에 적용 가능
- 견적서, 발주서, 판매 단가, 구매 단가 등

### 2. 유연성
- 필드 그룹으로 자유롭게 섹션 구성
- 표시 항목 설정으로 UI 커스터마이징

### 3. 데이터 무결성
- 1:N 관계로 여러 기간별 데이터 관리
- 기간 중복 체크는 백엔드에서 처리

### 4. 사용자 경험
- 품목별로 여러 개의 기간별 단가를 손쉽게 입력
- 입력 완료 후 작은 카드로 요약 표시

## 다음 단계

1. **마이그레이션 실행** (999_add_period_price_columns_to_customer_item_mapping.sql)
2. **화면 편집기에서 설정** (위 Step 1~5 참고)
3. **백엔드 API 구현** (저장/조회 엔드포인트)
4. **계산 로직 추가** (선택사항: 자동 계산)
5. **테스트** (품목 선택 → 기간별 단가 입력 → 저장 → 조회)

## 참고 자료

- 선택항목 상세입력 컴포넌트: `frontend/lib/registry/components/selected-items-detail-input/`
- 타입 정의: `frontend/lib/registry/components/selected-items-detail-input/types.ts`
- 설정 패널: `SelectedItemsDetailInputConfigPanel.tsx`

