# 품목 등록/수정 백엔드 API 수정 요청

> 프론트엔드 품목관리 기능 개발 중 발견된 백엔드 수정 필요 사항 정리
>
> **최종 업데이트**: 2025-12-09

---

## 🟢 [핵심] field_key 통일 - source_column 매핑 방식 채택

### 상태: 🟡 백엔드 작업 진행 중

### 발견일: 2025-12-06

### 요약

| 항목 | 내용 |
|------|------|
| **근본 원인** | 품목기준관리 / 품목관리 API 요청서 분리로 키값 불일치 |
| **해결 방향** | `source_column` 매핑으로 백엔드에서 변환 처리 |
| **백엔드 작업** | `item_fields`에 `source_column` 컬럼 추가 ✅, Edit 응답 형식 수정 진행 중 |
| **프론트 작업** | 백엔드 완료 후 하드코딩 매핑 제거 예정 |

### 2025-12-09 백엔드 대화 결론

1. **등록/상세/리스트**: ✅ `field_key` 기준으로 문제없음
2. **수정 (Edit)**: ⏳ 조회 시 `98_unit` 형식으로 응답 예정 (백엔드 수정 중)
3. **source_column**: 백엔드 내부 변환용, 프론트에 노출 안됨

### 참고 문서

- 백엔드 설계서: `~/Desktop/코브라브릿지백엔드문서/item-master-integration.md`
- sam-api 커밋: `bf92b37` (품목 마스터 소스 매핑 기능 추가)

### 현재 문제

```
품목기준관리 field_key    프론트엔드 변환      백엔드 API 응답
─────────────────────    ─────────────────    ─────────────────
Part_type            →   part_type        →   part_type (불일치!)
Installation_type_1  →   installation_type →  저장 안됨
side_dimensions_horizontal → side_spec_width → 저장 안됨
```

**두 개의 요청서가 따로 놀면서** 백엔드에서 각각 다르게 구현 → 키 불일치 발생

### 이상적인 구조

```
품목기준관리 (field_key 정의)
         │
         ▼
    ┌────────────┐
    │ field_key  │ ← Single Source of Truth
    └────────────┘
         │
    ┌────┴────┬────────┬────────┐
    ▼         ▼        ▼        ▼
  등록       수정      상세     리스트

  (전부 동일한 field_key로 저장/조회/렌더링)
```

### 기대 효과

1. **단위 필드 혼란 해결**: field_key가 "unit"이면 그게 단위 (명확!)
2. **필드 타입 자동 인식**: 품목기준관리 field_type 보고 자동 렌더링
3. **누락 데이터 분석 용이**: field_key 하나만 확인하면 끝
4. **디버깅 속도 향상**: API 응답 = 폼 데이터 (변환 없음)

### 수정 요청

1. **품목기준관리 field_key를 기준**으로 API 요청/응답 키 통일
2. 동적 필드는 `attributes` JSON에 field_key 그대로 저장
3. 조회 시에도 field_key 그대로 응답

### 영향 범위

- 품목관리 전체 (등록/수정/상세/리스트)
- 모든 품목 유형 (FG, PT, SM, RM, CS)

### 우선순위

🔴 **최우선** - 이 구조 개선 후 아래 개별 이슈들 대부분 자동 해결

---

## 1. 소모품(CS) 등록 시 규격(specification) 저장 안됨

### 상태: 🔴 수정 필요

### 발견일: 2025-12-06

### 파일 위치
`/app/Http/Requests/Item/ItemStoreRequest.php` - rules() 메서드 (Line 14-42)

### 현재 문제
- `specification` 필드가 validation rules에 없음
- Laravel FormRequest는 rules에 정의되지 않은 필드를 `$request->validated()` 결과에서 제외
- 프론트엔드에서 `specification: "테스트"` 값을 보내도 백엔드에서 무시됨
- DB에 규격 값이 null로 저장됨

### 프론트엔드 확인 사항
- `DynamicItemForm`에서 `97_specification` → `spec` → `specification`으로 정상 변환됨
- API 요청 payload에 `specification` 필드 포함 확인됨
- 백엔드 `ItemsService.createMaterial()`에서 `$data['specification']` 참조하나 값이 없음

### 수정 요청
```php
// /app/Http/Requests/Item/ItemStoreRequest.php

public function rules(): array
{
    return [
        // 필수 필드
        'code' => 'required|string|max:50',
        'name' => 'required|string|max:255',
        'product_type' => 'required|string|in:FG,PT,SM,RM,CS',
        'unit' => 'required|string|max:20',

        // 선택 필드
        'category_id' => 'nullable|integer|exists:categories,id',
        'description' => 'nullable|string',
        'specification' => 'nullable|string|max:255',  // ✅ 추가 필요

        // ... 나머지 기존 필드들 ...
    ];
}
```

### 영향 범위
- 소모품(CS) 등록
- 원자재(RM) 등록 (해당 시)
- 부자재(SM) 등록 (해당 시)

---

## 2. Material(SM, RM, CS) 수정 시 material_code 중복 에러

### 상태: 🔴 수정 필요

### 발견일: 2025-12-06

### 현재 문제
- Material 수정 시 `material_code` 중복 체크 에러 발생
- 케이스 1: 값을 변경하지 않아도 자기 자신과 중복 체크됨
- 케이스 2: 소프트 삭제된 품목의 코드와도 중복 체크됨
- 에러 메시지: `Duplicate entry '알루미늄-옵션2-2' for key 'materials.materials_material_code_unique'`

### 원인
1. UPDATE 시 자기 자신의 ID를 제외하지 않음
2. 소프트 삭제(`deleted_at`)된 레코드도 unique 체크에 포함됨

### 수정 요청
```php
// Material 수정 Request 파일 (MaterialUpdateRequest.php 또는 해당 파일)
use Illuminate\Validation\Rule;

public function rules(): array
{
    return [
        // ... 기존 필드들 ...

        // ✅ 수정 시 자기 자신 제외 + 소프트삭제 제외하고 unique 체크
        'material_code' => [
            'required',
            'string',
            'max:255',
            Rule::unique('materials', 'material_code')
                ->whereNull('deleted_at')      // 소프트삭제된 건 제외
                ->ignore($this->route('id')),  // 자기 자신 제외
        ],

        // ... 나머지 필드들 ...
    ];
}
```

### 영향 범위
- 원자재(RM) 수정
- 부자재(SM) 수정
- 소모품(CS) 수정

### 비고
- 현재 수정 자체가 불가능하여 수정 후 데이터 검증이 어려움
- 이 이슈 해결 후 수정 기능 재검증 필요

---

## 3. Material(SM, RM) 수정 시 규격(specification) 로딩 안됨

### 상태: 🔴 확인 필요

### 발견일: 2025-12-06

### 현재 문제
- SM(부자재), RM(원자재) 수정 페이지 진입 시 규격 값이 표시 안됨
- 1회 수정 후 다시 수정 페이지 진입 시 규격 값 없음
- CS(소모품)과 동일한 문제로 추정

### 원인 추정
- 백엔드에서 `options` 배열이 제대로 저장되지 않거나 반환되지 않음
- SM/RM은 `standard_*` 필드 조합으로 `specification`을 생성하고, 이 값을 `options` 배열에도 저장
- 수정 페이지에서는 `options` 배열을 읽어서 폼에 표시
- `options`가 null이면 규격 필드들이 빈 값으로 표시됨

### 확인 요청
```php
// Material 조회 API 응답에서 options 필드 확인 필요
// GET /api/v1/items/{id}?item_type=MATERIAL

// 응답 예시 (정상)
{
    "id": 396,
    "name": "썬더볼트",
    "specification": "부자재2-2",
    "options": [
        {"label": "standard_1", "value": "부자재2"},
        {"label": "standard_2", "value": "2"}
    ]  // ✅ options 배열이 있어야 함
}

// 응답 예시 (문제)
{
    "id": 396,
    "name": "썬더볼트",
    "specification": "부자재2-2",
    "options": null  // ❌ options가 null이면 규격 로딩 불가
}
```

### 수정 요청
1. Material 저장 시 `options` 배열 정상 저장 확인
2. Material 조회 시 `options` 필드 반환 확인
3. `options`가 JSON 컬럼이라면 정상적인 JSON 형식으로 저장되는지 확인

### 영향 범위
- 원자재(RM) 수정
- 부자재(SM) 수정

---

## 4. Material(SM, RM, CS) 비고(remarks) 저장 안됨

### 상태: 🔴 수정 필요

### 발견일: 2025-12-06

### 현재 문제
- 소모품(CS), 원자재(RM), 부자재(SM) 등록/수정 시 비고(remarks)가 저장되지 않음
- 프론트에서 `note` → `remarks`로 정상 변환하여 전송
- 백엔드 Service에서 `$data['remarks']` 참조하지만 값이 없음

### 원인 분석
- **프론트엔드**: `note` → `remarks` 변환 ✅ (`materialTransform.ts:115`)
- **백엔드 Service**: `'remarks' => $data['remarks'] ?? null` ✅ (`ItemsService.php:301`)
- **백엔드 Model**: `remarks` 컬럼 존재 ✅ (`Material.php:31`)
- **백엔드 Request**: `remarks` validation rule 없음 ❌ **누락**

### 수정 요청
```php
// /app/Http/Requests/Item/ItemStoreRequest.php
// /app/Http/Requests/Item/ItemUpdateRequest.php

public function rules(): array
{
    return [
        // 기존 필드들...
        'description' => 'nullable|string',

        // ✅ 추가 필요
        'remarks' => 'nullable|string',  // 비고 필드

        // ... 나머지 필드들 ...
    ];
}
```

### 영향 범위
- 소모품(CS) 등록/수정
- 원자재(RM) 등록/수정
- 부자재(SM) 등록/수정

### 비고
- 1번 이슈(specification)와 동일한 원인: Request validation 누락
- 함께 처리하면 효율적

---

## 5. 품목기준관리 옵션 필드 식별자 필요 (장기 개선)

### 상태: 🟡 개선 권장

### 발견일: 2025-12-06

### 현재 문제
- 품목기준관리에서 옵션 필드의 `field_key`를 자유롭게 입력 가능
- 프론트엔드는 `standard_*`, `option_*` 패턴으로 옵션 필드를 식별
- 패턴에 맞지 않는 field_key (예: `st_2`)는 규격(specification) 조합에서 누락됨
- 결과: 부자재(SM)의 규격값이 저장되지 않음

### 임시 해결 (프론트엔드)
- 품목기준관리에서 field_key를 `standard_*` 패턴으로 통일
- 예: `st_2` → `standard_2`

### 근본 해결 요청 (백엔드)
```php
// 품목기준관리 API 응답에 옵션 필드 여부 표시

// 현재 응답
{
    "field_key": "st_2",
    "field_type": "select",
    "field_name": "규격옵션"
}

// 개선 요청
{
    "field_key": "st_2",
    "field_type": "select",
    "field_name": "규격옵션",
    "is_spec_option": true  // ✅ 규격 조합용 옵션 필드인지 표시
}
```

### 기대 효과
- 프론트엔드가 field_key 패턴에 의존하지 않음
- `is_spec_option: true`인 필드만 규격 조합에 사용
- 새로운 field_key 패턴이 추가되어도 프론트 수정 불필요

### 영향 범위
- 원자재(RM) 등록/수정
- 부자재(SM) 등록/수정
- 향후 추가되는 Material 유형

---

## 6. 조립부품(PT-ASSEMBLY) 필드 저장 안됨 - fillable 누락

### 상태: 🔴 수정 필요

### 발견일: 2025-12-06

### 현재 문제
- 조립부품 등록 후 상세보기/수정 페이지에서 데이터가 제대로 표시되지 않음
- **프론트엔드는 데이터를 정상 전송하고 있음** ✅
- **백엔드에서 조립부품 필드들이 저장되지 않음** ❌

### 원인 분석

#### 프론트엔드 전송 데이터 (정상)
```javascript
// DynamicItemForm/index.tsx - handleFormSubmit()
const submitData = {
    ...convertedData,           // 폼 데이터 (installation_type, assembly_type 등 포함)
    product_type: 'PT',
    part_type: 'ASSEMBLY',      // ✅ 명시적 추가
    bending_diagram: base64,    // ✅ 캔버스 이미지
    bom: [...],                 // ✅ BOM 데이터
};
```

#### 백엔드 저장 로직 (문제)
```php
// ItemsService.php - createProduct()
private function createProduct(array $data, int $tenantId, int $userId): Product
{
    $payload = $data;
    // ... 기본 필드만 설정
    return Product::create($payload);  // Mass Assignment
}
```

#### Product 모델 fillable (누락 필드 있음)
```php
// Product.php
protected $fillable = [
    'tenant_id', 'code', 'name', 'unit', 'category_id',
    'product_type',
    'attributes', 'description',        // ✅ attributes JSON 있음
    'part_type',                         // ✅ 있음
    'bending_diagram', 'bending_details', // ✅ 있음
    // ❌ installation_type 없음
    // ❌ assembly_type 없음
    // ❌ side_spec_width 없음
    // ❌ side_spec_height 없음
    // ❌ length 없음
];
```

### 필드별 저장 상태

| 필드 | 프론트 전송 | fillable | 저장 여부 |
|------|------------|----------|----------|
| `part_type` | ✅ | ✅ 컬럼 | ✅ 저장됨 |
| `bending_diagram` | ✅ | ✅ 컬럼 | ⚠️ 파일 업로드 별도 처리 |
| `installation_type` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `assembly_type` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `side_spec_width` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `side_spec_height` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `length` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `bom` | ✅ | ⚠️ 별도 처리 | ❓ 확인 필요 |

### 수정 요청

#### 방법 1: attributes JSON에 저장 (권장)
```php
// ItemsService.php - createProduct() 수정

private function createProduct(array $data, int $tenantId, int $userId): Product
{
    // 조립부품 전용 필드들을 attributes JSON으로 묶기
    $assemblyFields = ['installation_type', 'assembly_type', 'side_spec_width', 'side_spec_height', 'length'];
    $attributes = $data['attributes'] ?? [];

    foreach ($assemblyFields as $field) {
        if (isset($data[$field])) {
            $attributes[$field] = $data[$field];
            unset($data[$field]);  // payload에서 제거
        }
    }

    $payload = $data;
    $payload['tenant_id'] = $tenantId;
    $payload['created_by'] = $userId;
    $payload['attributes'] = !empty($attributes) ? $attributes : null;
    // ... 나머지 동일

    return Product::create($payload);
}
```

#### 방법 2: 컬럼 추가 + fillable 등록
```php
// Product.php - fillable에 추가
protected $fillable = [
    // 기존 필드들...
    'installation_type',    // ✅ 추가
    'assembly_type',        // ✅ 추가
    'side_spec_width',      // ✅ 추가
    'side_spec_height',     // ✅ 추가
    'length',               // ✅ 추가 (또는 assembly_length)
];

// + migration으로 컬럼 추가 필요
```

### 프론트엔드 대응 (완료)
- 프론트에서 `data.xxx` 또는 `data.attributes.xxx` 둘 다에서 값을 찾도록 수정 완료
- 상세보기: `items/[id]/page.tsx` - `mapApiResponseToItemMaster` 함수
- 수정페이지: `items/[id]/edit/page.tsx` - `mapApiResponseToFormData` 함수

### 영향 범위
- 조립부품(PT-ASSEMBLY) 등록/조회/수정
- 설치유형, 마감, 측면규격, 길이 모든 필드

### 우선순위
🔴 **높음** - 조립부품 등록 기능이 완전히 동작하지 않음

---

## 7. 파일 업로드 API 500 에러 - ApiResponse 클래스 네임스페이스 불일치

### 상태: 🔴 수정 필요

### 발견일: 2025-12-06

### 현재 문제
- 품목 파일 업로드 (전개도, 시방서, 인정서) 시 500 에러 발생
- 절곡부품, 조립부품 모두 동일한 에러
- 파일이 업로드되지 않음

### 에러 로그
```
[2025-12-06 17:28:22] DEV.ERROR: Class "App\Http\Responses\ApiResponse" not found
{"exception":"[object] (Error(code: 0): Class \"App\\Http\\Responses\\ApiResponse\" not found
at /home/webservice/api/app/Http/Controllers/Api/V1/ItemsFileController.php:31)
```

### 원인 분석
**네임스페이스 불일치**

| 위치 | 네임스페이스 |
|------|-------------|
| `ItemsFileController.php` (Line 7) | `use App\Http\Responses\ApiResponse` ❌ |
| 실제 파일 위치 | `App\Helpers\ApiResponse` ✅ |

### 파일 위치
`/app/Http/Controllers/Api/V1/ItemsFileController.php` (Line 7)

### 현재 코드
```php
<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Http\Requests\ItemsFileUploadRequest;
use App\Http\Responses\ApiResponse;  // ❌ 잘못된 경로
// ...
```

### 수정 요청
```php
<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Http\Requests\ItemsFileUploadRequest;
use App\Helpers\ApiResponse;  // ✅ 올바른 경로
// ...
```

### 영향 범위
- 절곡부품(PT-BENDING) 전개도 업로드
- 조립부품(PT-ASSEMBLY) 전개도 업로드
- 제품(FG) 시방서 업로드
- 제품(FG) 인정서 업로드

### 우선순위
🔴 **긴급** - 모든 파일 업로드 기능이 동작하지 않음 (한 줄 수정으로 해결 가능)

---

## 수정 완료 내역

> 수정 완료된 항목은 아래로 이동

(아직 없음)

---

## 참고 사항

### 관련 파일 (프론트엔드)
- `src/app/[locale]/(protected)/items/create/page.tsx` - 품목 등록 페이지
- `src/app/[locale]/(protected)/items/[id]/edit/page.tsx` - 품목 수정 페이지
- `src/components/items/DynamicItemForm/index.tsx` - 동적 폼 컴포넌트

### 관련 파일 (백엔드)
- `/app/Http/Controllers/Api/V1/ItemsController.php` - 품목 API 컨트롤러
- `/app/Services/ItemsService.php` - 품목 서비스 레이어
- `/app/Http/Requests/Item/ItemStoreRequest.php` - 등록 요청 검증
- `/app/Http/Requests/Item/ItemUpdateRequest.php` - 수정 요청 검증
- `/app/Models/Materials/Material.php` - Material 모델
