docs: [plans] BOM 트리 3단계 React 구현 요청서 작성 (프론트엔드 전달용)
This commit is contained in:
@@ -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 비절곡품만 표시)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user