# [FIX-2025-12-16] options vs item_details 중복 저장 버그

## 문제 현상

- 품목 수정 시 `bending_details` 값이 111 → 1110으로 저장했는데, 다시 조회하면 111로 표시됨
- 입력한 최신값이 아닌 이전에 저장된 값이 표시되는 문제

## 근본 원인

### 백엔드 저장 구조

```
┌─────────────────────────────────────────────────────────────┐
│                    백엔드 저장 구조                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  [동적 필드] ─────────────────────▶ options (JSON)         │
│  (품목기준관리에서 생성)              [{label, value}, ...]  │
│                                                             │
│  [고정 필드] ─────────────────────▶ item_details (컬럼)    │
│  (하드코딩: 시방서, 인정서, 전개도 등)   bending_details 등   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### 문제 발생 지점

**백엔드 `ItemService.php`의 `getKnownFields()` (85-109줄)**

```php
private function getKnownFields(): array
{
    // 1. 기본 고정 필드 (items 테이블 컬럼)
    $baseFields = [
        'id', 'tenant_id', 'item_type', 'code', 'name', 'unit', ...
    ];

    // 2. ItemField에서 source_table='items' AND storage_type='column' 인 것만 조회
    $columnFields = ItemField::where('source_table', 'items')  // ⚠️ items만!
        ->where('storage_type', 'column')
        ...

    // ❌ item_details 테이블 컬럼 누락!
    // ❌ SystemFields 상수 미사용!
}
```

**결과**: `bending_details` 등 `item_details` 컬럼이 "알려진 필드"로 인식되지 않음

### 데이터 흐름

```
저장 시:
  요청: { bending_details: 111 }
  → item_details.bending_details = 111  (extractDetailData로 저장)
  → options: [{label: "bending_details", value: "111"}]  (동적 필드로 잘못 인식)

수정 시 (값을 1110으로 변경):
  요청: { bending_details: 1110 }
  → item_details.bending_details = 1110  (최신값)
  → options: [{label: "bending_details", value: "1110"}]  (기존 값 덮어쓰기)

⚠️ 문제: options 병합 시 이전 값이 남아있는 경우가 있음

조회 시 (프론트엔드):
  API 응답: {
    bending_details: 1110,  // ← item_details에서 가져온 최신값
    options: [{label: "bending_details", value: "111"}]  // ← 첫 저장 시 값!
  }

  프론트엔드 매핑:
  1. formData.bending_details = 1110 (API 응답에서)
  2. options 순회: formData.bending_details = "111" ← 덮어쓰기! ❌
```

## 해결 방법

### A. 프론트엔드 수정 (즉시 적용) ✅

**파일**: `src/app/[locale]/(protected)/items/[id]/edit/page.tsx`

**변경 내용**:

1. `excludeKeys`에 `'details'` 추가
2. `details` 객체 펼치기 로직 추가 (최신값 매핑)
3. `options` 순회 시 `item_details` 필드 제외

```typescript
// 1. details는 아래에서 펼쳐서 추가
const excludeKeys = [..., 'details'];

// 2. details 객체가 있으면 펼쳐서 추가 (item_details 테이블 필드)
const details = (data as Record<string, unknown>).details;
if (details && typeof details === 'object') {
  const detailExcludeKeys = ['id', 'item_id', 'created_at', 'updated_at'];
  Object.entries(details).forEach(([key, value]) => {
    if (!detailExcludeKeys.includes(key) && value !== null && value !== undefined) {
      formData[key] = value;
    }
  });
}

// 3. options 순회 시 item_details 필드 제외
const detailsFieldsInOptions = [
  'files', 'bending_details', 'bending_diagram',
  'specification_file', 'certification_file',
];
if (data.options && Array.isArray(data.options)) {
  data.options.forEach((opt) => {
    if (opt.label && opt.value && !detailsFieldsInOptions.includes(opt.label)) {
      formData[opt.label] = opt.value;
    }
  });
}
```

### B. 백엔드 수정 (근본 해결, 권장)

**파일**: `/app/Services/ItemService.php` - `getKnownFields()`

**수정 요청**:

```php
// 방안 A: extractDetailData() 필드들을 knownFields에 추가
private function getKnownFields(): array
{
    $baseFields = [...];

    // item_details 테이블 필드 추가
    $detailFields = [
        'bending_diagram', 'bending_details',
        'specification_file', 'certification_file', ...
    ];

    return array_unique(array_merge($baseFields, $columnFields, $detailFields, $apiFields));
}

// 방안 B: SystemFields 상수 활용
use App\Constants\SystemFields;

private function getKnownFields(): array
{
    return array_unique(array_merge(
        $baseFields,
        $columnFields,
        SystemFields::getAllReservedKeysForGroup(SystemFields::GROUP_ITEM_MASTER),
        $apiFields
    ));
}
```

## 관련 파일

| 파일 | 역할 |
|------|------|
| `sam-api/app/Services/ItemService.php` | getKnownFields(), extractDetailData() |
| `sam-api/app/Constants/SystemFields.php` | 시스템 필드 정의 (bending_details 포함) |
| `sam-api/app/Models/Items/ItemDetail.php` | item_details 테이블 모델 |
| `sam-next/.../edit/page.tsx` | mapApiResponseToFormData() |

## 영향 범위

- `bending_details` (전개도 상세)
- `bending_diagram` (전개도 이미지)
- `specification_file` (시방서)
- `certification_file` (인정서)
- `files` (첨부파일)
- 기타 `item_details` 테이블의 고정 컬럼들

## 향후 계획

- 전개도 상세 입력 수치를 품목기준관리에서 동적 필드로 등록하는 형태로 변경 예정
- 그때는 `options`에 저장되는 것이 정상
- 현재는 하드코딩된 고정 필드이므로 `item_details`에만 저장되어야 함

## 참고

- 유사 버그: `files` 필드도 같은 문제가 있었음 (2025-12-15 수정)
- `[GUIDE] radix-ui-select-controlled-mode-bug.md` - Radix UI Select 관련 버그