docs: [plans] BOM 트리 3단계 React 구현 요청서 작성 (프론트엔드 전달용)

This commit is contained in:
김보곤
2026-03-18 16:43:41 +09:00
parent 83b2c697a8
commit 9588e777dc

View File

@@ -1,112 +1,264 @@
# 서비스 BOM 트리 3단계 구조 개선
# BOM 트리 3단계 구조 개선 — React 구현 요청
> **작성일**: 2026-03-18
> **상태**: 계획
> **요청자**: API/MNG 개발팀
> **대상**: React 프론트엔드
> **상태**: MNG 구현 완료, React 구현 대기
---
## 1. 개요
### 1.1 목적
품목관리 > 제품상세 화면의 BOM 트리를 2단계(FG → PT 플랫 리스트)에서 3단계(FG → 카테고리 → PT)로 개선한다.
서비스(React) 품목관리 > 제품상세의 BOM 트리를 MNG와 동일한 3단계 계층 구조로 개선한다.
### 1.1 현재 상태
### 1.2 현재 상태
| 항목 | 상태 | 설명 |
|------|------|------|
| **BOM 데이터 category 필드** | ✅ 완료 | `items.bom` JSON에 `category` 필드 포함 |
| **MNG BOM 트리 3단계** | ✅ 완료 | MNG 품목관리에서 3단계 구조 표시 |
| **React BOM 트리** | ❌ 2단계 | 플랫 테이블로 17건 나열 |
| 항목 | MNG (백오피스) | React (서비스) |
|------|-------------|--------------|
| BOM 트리 구조 | ✅ 3단계 (FG → 카테고리 → PT) | ❌ 2단계 (FG → PT) |
| 카테고리 그룹 | 주자재, 모터, 제어기, 절곡품, 부자재, 검사비 | 없음 (플랫 리스트) |
| 접힘/펼침 | ✅ chevron 토글 | ✅ 토글 있음 |
### 1.2 대상 페이지
### 1.3 목표 구조
`/production/screen-production/{code}?mode=view&type=FG&id={id}`
### 1.3 대상 컴포넌트
`src/components/items/ItemDetailClient.tsx` — 557~611줄 "부품 구성 (BOM)" 섹션
---
## 2. 현재 vs 목표 UI
### 현재 (2단계 — 플랫 테이블)
```
현재 (2단계):
▼ FG KSS02 스크린 SUS마감 벽면형
PT 스크린 실리카 x10.65
PT 모터 150K(S) (220V) x1
PT 제어기 노출형 x1
PT 케이스 500*380 x3.22
...17건 플랫 리스트
부품 구성 (BOM) 총 17개 품목
┌────┬──────────────────────┬──────────────────┬────────┬──────┐
│ 번호 │ 품목코드 │ 품목명 │ 수량 │ 단위 │
├────┼──────────────────────┼──────────────────┼────────┼──────┤
1 │ EST-RAW-슬랫-방화 │ 스크린 실리카 │ +10.65 │ m²
2 │ EST-MOTOR-220V-150K │ 모터 150K(S) x1 │ EA │
3 │ EST-CTRL-노출형 │ 제어기 노출형 │ x1 │ EA │
│ 4 │ BD-케이스-500*380 │ 케이스 500*380 │ +3.22 │ m │
│ 5 │ BD-케이스용 연기차단...│ 케이스용 연기차단재│ +3.22 │ m │
│ .. │ ... │ ... │ ... │ ... │
│ 17 │ EST-INSPECTION │ 검사비 │ x1 │ EA │
└────┴──────────────────────┴──────────────────┴────────┴──────┘
```
개선 후 (3단계):
▼ FG KSS02 스크린 SUS마감 벽면형
▼ 주자재 (1건)
PT 스크린 실리카 x10.65
▼ 모터 (1건)
PT 모터 150K(S) (220V) x1
▼ 제어기 (1건)
PT 제어기 노출형 x1
▼ 절곡품 (9건)
PT 케이스 500*380 x3.22
PT 케이스용 연기차단재 x3.22
...
▼ 부자재 (4건)
PT 감기샤프트 5인치 6m x1
...
▼ 검사비 (1건)
PT 검사비 x1
### 목표 (3단계 — 카테고리 그룹 접힘/펼침)
```
부품 구성 (BOM) 총 17개 품목 (6개 그룹)
▼ 주자재 (1건)
│ EST-RAW-슬랫-방화 스크린 실리카 x10.65 m²
▼ 모터 (1건)
│ EST-MOTOR-220V-150K 모터 150K(S) (220V) x1 EA
▼ 제어기 (1건)
│ EST-CTRL-노출형 제어기 노출형 x1 EA
▼ 절곡품 (9건)
│ BD-케이스-500*380 케이스 500*380 x3.22 m
│ BD-케이스용 연기차단... 케이스용 연기차단재 x3.22 m
│ BD-마구리-505*385 마구리 505*385 x1 EA
│ ...
▶ 부자재 (4건) ← 접힌 상태
▼ 검사비 (1건)
│ EST-INSPECTION 검사비 x1 EA
```
---
## 2. 구현 범위
## 3. BOM 데이터 구조 (API 변경 없음)
### 2.1 백엔드 (API)
현재 API 응답의 `item.bom` 배열에 이미 `category` 필드가 포함되어 있다.
**API 수정 불필요** — React에서 `category`로 그룹화하면 된다.
BOM 데이터에 이미 `category` 필드가 포함되어 있으므로, API 응답에서 카테고리 그룹 노드를 생성하면 된다.
**MNG 구현 참고**: `mng/app/Services/ItemManagementService.php``buildBomNode()` 메서드
```
핵심 로직:
1. BOM 항목에 category 필드가 있으면 카테고리별 그룹화
2. 가상 카테고리 노드(item_type='CAT') 생성
3. 각 카테고리 노드 하위에 실제 PT 품목 배치
```
**변경 대상**: API의 BOM 트리 응답 엔드포인트 (또는 React에서 프론트엔드 그룹화)
### 2.2 프론트엔드 (React)
**대상 페이지**: `/production/screen-production/{code}?mode=view&type=FG&id={id}`
**변경 방식 (2가지 선택지)**:
| 방식 | 설명 | 장점 | 단점 |
|------|------|------|------|
| A. API 응답 변경 | API에서 3단계 트리 반환 | React 수정 최소화 | API 하위 호환성 확인 필요 |
| B. React 그룹화 | BOM 데이터의 category 필드로 프론트에서 그룹화 | API 변경 불필요 | React 컴포넌트 수정 필요 |
**권장: B 방식** — BOM 데이터에 이미 `category`가 있으므로 React에서 그룹화하는 것이 API 영향 없이 안전하다.
---
## 3. BOM 데이터 구조
`items.bom` JSON 필드에 저장된 카테고리 정보:
### 3.1 item.bom 배열 예시
```json
[
{"child_item_id": 15657, "child_item_code": "EST-RAW-슬랫-방화", "quantity": 10.65, "unit": "m²", "category": "주자재"},
{"child_item_id": 15627, "child_item_code": "EST-MOTOR-220V-150K", "quantity": 1, "unit": "EA", "category": "모터"},
{"child_item_id": 15578, "child_item_code": "BD-케이스-500*380", "quantity": 3.22, "unit": "m", "category": "절곡품"},
...
{
"child_item_id": 15657,
"child_item_code": "EST-RAW-슬랫-방화",
"quantity": 10.65,
"unit": "m²",
"category": "주자재"
},
{
"child_item_id": 15627,
"child_item_code": "EST-MOTOR-220V-150K",
"quantity": 1,
"unit": "EA",
"category": "모터"
},
{
"child_item_id": 15578,
"child_item_code": "BD-케이스-500*380",
"quantity": 3.22,
"unit": "m",
"category": "절곡품"
}
]
```
`category` 필드 값: `주자재`, `모터`, `제어기`, `절곡품`, `부자재`, `검사비`
### 3.2 category 값 목록 (순서대로)
| category 값 | 설명 | 대표 아이템 |
|-------------|------|------------|
| `주자재` | 주요 원자재 | 스크린 실리카 |
| `모터` | 모터류 | 모터 150K, 400K |
| `제어기` | 제어 장치 | 제어기 노출형/매입형 |
| `절곡품` | 절곡 가공 부품 | 케이스, 가이드레일, L-BAR |
| `부자재` | 보조 자재 | 샤프트, 각파이프, 앵글 |
| `검사비` | 검사 비용 | 검사비 |
---
## 4. 관련 파일
## 4. 구현 가이드
| 프로젝트 | 파일 | 역할 |
|---------|------|------|
| MNG | `app/Services/ItemManagementService.php` | 3단계 트리 구현 (참고용) |
| React | BOM 트리 컴포넌트 | 개선 대상 |
| API | BOM 조회 엔드포인트 | 방식 A 선택 시 변경 |
### 4.1 그룹화 로직 (핵심)
```typescript
// item.bom 배열을 category별로 그룹화
const groupedBom = useMemo(() => {
if (!item.bom || item.bom.length === 0) return {};
const groups: Record<string, typeof item.bom> = {};
for (const line of item.bom) {
const cat = line.category || '기타';
if (!groups[cat]) groups[cat] = [];
groups[cat].push(line);
}
return groups;
}, [item.bom]);
// 카테고리 순서 보장 (선택)
const categoryOrder = ['주자재', '모터', '제어기', '절곡품', '부자재', '검사비'];
const sortedCategories = Object.keys(groupedBom).sort((a, b) => {
const ia = categoryOrder.indexOf(a);
const ib = categoryOrder.indexOf(b);
return (ia === -1 ? 999 : ia) - (ib === -1 ? 999 : ib);
});
```
### 4.2 접힘/펼침 상태 관리
```typescript
// 카테고리별 접힘 상태 (기본: 모두 펼침)
const [collapsedCategories, setCollapsedCategories] = useState<Set<string>>(new Set());
const toggleCategory = (category: string) => {
setCollapsedCategories(prev => {
const next = new Set(prev);
if (next.has(category)) next.delete(category);
else next.add(category);
return next;
});
};
```
### 4.3 렌더링 구조
```tsx
{sortedCategories.map(category => {
const items = groupedBom[category];
const isCollapsed = collapsedCategories.has(category);
return (
<div key={category}>
{/* 카테고리 헤더 (클릭으로 접기/펼치기) */}
<div
className="flex items-center gap-2 py-2 px-3 bg-gray-50 hover:bg-gray-100 cursor-pointer rounded"
onClick={() => toggleCategory(category)}
>
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
<span className="text-sm font-semibold text-gray-700">{category}</span>
<Badge variant="outline" className="text-xs">{items.length}</Badge>
</div>
{/* 품목 리스트 (접힌 상태면 숨김) */}
{!isCollapsed && (
<Table>
<TableBody>
{items.map((line, index) => (
<TableRow key={line.id || index} className="border-l-2 border-gray-200 ml-4">
<TableCell>
<code className="text-xs bg-gray-100 px-2 py-1 rounded">{line.childItemCode}</code>
</TableCell>
<TableCell>{line.childItemName}</TableCell>
<TableCell className="text-right">{line.quantity}</TableCell>
<TableCell>{line.unit}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</div>
);
})}
```
---
## 5. 수정 대상 파일
| 파일 | 수정 내용 |
|------|----------|
| `src/components/items/ItemDetailClient.tsx` | 557~611줄 BOM 섹션을 카테고리 그룹 UI로 교체 |
### 5.1 현재 코드 (교체 대상: 557~611줄)
```tsx
{/* BOM 정보 - 절곡 부품은 제외 */}
{(item.itemType === 'FG' || (item.itemType === 'PT' && item.partType !== 'BENDING')) && item.bom && item.bom.length > 0 && (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Package className="h-5 w-5" />
부품 구성 (BOM)
</CardTitle>
<Badge variant="outline" className="bg-blue-50 text-blue-700">
{item.bom.length} 품목
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="border rounded-lg overflow-hidden">
<Table>
...플랫 테이블...
</Table>
</div>
</CardContent>
</Card>
)}
```
---
## 6. 참고: MNG 구현 (동작 확인 가능)
MNG(admin.codebridge-x.com) > 품목관리에서 FG 품목 선택 시 BOM 탭에서 3단계 트리를 확인할 수 있다.
**MNG 구현 파일:**
- `mng/app/Services/ItemManagementService.php``buildBomNode()` 메서드
- `mng/resources/views/item-management/index.blade.php``renderBomTree()` JS 함수
**MNG 핵심 로직:** BOM 데이터의 `category` 필드가 있으면 가상 카테고리 노드(`item_type='CAT'`)를 생성하여 중간 계층으로 삽입한다. React에서는 이를 프론트엔드에서 처리하면 된다.
---
## 7. 체크리스트
- [ ] `item.bom` 배열의 `category` 필드로 그룹화
- [ ] 카테고리별 접힘/펼침 토글 구현
- [ ] 카테고리 헤더에 건수 Badge 표시
- [ ] `category` 없는 항목은 "기타"로 분류
- [ ] 절곡품 Badge 표시 유지 (`line.isBending`)
- [ ] 기존 BOM 조건 유지 (FG 또는 PT 비절곡품만 표시)
---