feat: [bending] 절곡품 관리 기능 개발 계획서 추가
- README.md: 전체 개요, 메뉴 구조, 작업 순서 - step1-데이터분석.md: 레거시 매핑 + options 확장 스키마 - step2-API.md: 엔드포인트 설계 (docs 규칙 준수) - step3-MNG화면.md: Blade+HTMX 화면 구성 (3타입별 폼) - step4-React연동.md: 견적 이미지 + 운영 화면 계획
This commit is contained in:
390
dev/dev_plans/bending-management/README.md
Normal file
390
dev/dev_plans/bending-management/README.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# 절곡품 관리 기능 개발 계획서
|
||||
|
||||
> **시작일**: 2026-03-16
|
||||
> **위치**: MNG 생산관리 > 절곡품 관리 (신규 메뉴)
|
||||
> **목표**: 경동기업(5130) 수준의 절곡품 마스터 관리 + 전개도 데이터 + 이미지 관리
|
||||
> **원칙**: 기존 BendingInfoBuilder/PrefixResolver 보존, items.options 확장 방식
|
||||
|
||||
---
|
||||
|
||||
## 배경
|
||||
|
||||
SAM은 절곡품의 "계산과 조합"(BendingInfoBuilder/PrefixResolver)은 잘 되어 있지만,
|
||||
"관리와 시각화"가 빠져 있다. 경동기업(5130) `guiderail/list.php` 수준의 관리 화면을 MNG에 구현한다.
|
||||
|
||||
**갭 분석**: `docs/dev/dev_plans/bending-parts-analysis.md` 참조
|
||||
|
||||
---
|
||||
|
||||
## MNG 현재 구조
|
||||
|
||||
### 생산관리 메뉴 (sidebar-static.blade.php)
|
||||
|
||||
```
|
||||
생산 관리 (production-group)
|
||||
├─ 품목기준 필드 관리 ✅ (구현됨)
|
||||
├─ 견적수식 관리 ✅ (구현됨)
|
||||
├─ 제품 관리 (준비중)
|
||||
├─ 자재 관리 (준비중)
|
||||
├─ BOM 관리 (준비중)
|
||||
├─ 카테고리 관리 (준비중)
|
||||
└─ 절곡품 관리 ← 🆕 추가 대상
|
||||
├─ 기초관리 (개별 부품 CRUD)
|
||||
└─ 절곡품 (모델별 조합 관리)
|
||||
```
|
||||
|
||||
### 기존 절곡 관련 코드 (MNG)
|
||||
|
||||
| 파일 | 역할 | 변경 여부 |
|
||||
|------|------|----------|
|
||||
| `views/documents/partials/bending-worklog.blade.php` | 절곡 작업일지 렌더링 | 무변경 |
|
||||
| `views/documents/partials/bending-inspection-data.blade.php` | 절곡 중간검사 | 무변경 |
|
||||
|
||||
---
|
||||
|
||||
## 작업 순서
|
||||
|
||||
```
|
||||
Step 1 (DB분석) → Step 2 (API) → Step 3 (MNG 화면) → Step 4 (React 연동)
|
||||
레거시 매핑 options 확장 Blade + HTMX 견적 이미지
|
||||
+ 데이터 정리 + 엔드포인트 + 메뉴 등록
|
||||
```
|
||||
|
||||
상세 계획: 아래 문서 참조
|
||||
|
||||
| 문서 | 내용 |
|
||||
|------|------|
|
||||
| `step1-데이터분석.md` | 레거시 매핑 + options 확장 |
|
||||
| `step2-API.md` | API 엔드포인트 + 컨트롤러 설계 |
|
||||
| `step3-MNG화면.md` | Blade 뷰 + HTMX + 메뉴 등록 |
|
||||
| `step4-React연동.md` | 견적 페이지 이미지 컴포넌트 |
|
||||
|
||||
---
|
||||
|
||||
## 참조 문서
|
||||
|
||||
| 문서 | 경로 | 용도 |
|
||||
|------|------|------|
|
||||
| 갭 분석 | `dev_plans/bending-parts-analysis.md` | 요구사항 기준 |
|
||||
| API 규칙 | `standards/api-rules.md` | API 네이밍/응답 |
|
||||
| options 정책 | `standards/options-column-policy.md` | JSON 컬럼 설계 |
|
||||
| 품목 정책 | `rules/item-policy.md` | BD 코드 체계 |
|
||||
| Phase 2 | `dev_plans/integrated-phase-2.md` | 절곡 설계 |
|
||||
| Phase 3 | `dev_plans/integrated-phase-3.md` | 절곡 검사 |
|
||||
|
||||
## 프로토타입
|
||||
|
||||
| 위치 | 설명 |
|
||||
|------|------|
|
||||
| `SAM/work/절곡/index.html` | 사이드바 + iframe 전체 구조 |
|
||||
| `SAM/work/절곡/base.html` | 기초관리 목록 (참고용) |
|
||||
| `SAM/work/절곡/base-form.html` | 등록/수정 폼 + 절곡 테이블 (참고용) |
|
||||
| `SAM/work/절곡/products.html` | 절곡품 탭 목록 (참고용) |
|
||||
| `SAM/work/절곡/product-form.html` | 절곡품 등록/수정 (참고용) |
|
||||
|
||||
|
||||
|
||||
# 절곡품 관리 — 전체 흐름도
|
||||
|
||||
---
|
||||
|
||||
## 1. 시스템 전체 구조
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ SAM 절곡품 관리 시스템 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ MNG │ │ API │ │ React │ │
|
||||
│ │ (샘플용) │────→│ (핵심) │←────│ (운영용) │ │
|
||||
│ │ Blade │ │ Laravel │ │ Next.js │ │
|
||||
│ └──────────┘ └─────┬────┘ └──────────┘ │
|
||||
│ │ │
|
||||
│ ┌────┴────┐ │
|
||||
│ │ samdb │ │
|
||||
│ │ items │ ← item_category = 'BENDING' │
|
||||
│ │ files │ ← field_key = 'bending_diagram' │
|
||||
│ └─────────┘ │
|
||||
│ │ │
|
||||
│ ┌────┴────┐ │
|
||||
│ │ R2 │ ← Cloudflare (이미지 저장) │
|
||||
│ └─────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 데이터 구조 (2계층)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ [1계층] 기초관리 — 개별 부품 (items 테이블) │
|
||||
│ ════════════════════════════════════════ │
|
||||
│ │
|
||||
│ items (item_category = 'BENDING') │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ id: 100 │ │
|
||||
│ │ code: BD-가이드레일-KSS01-SUS-120*70 │ │
|
||||
│ │ name: 가이드레일 KSS01 SUS 120*70 │ │
|
||||
│ │ options: { │ │
|
||||
│ │ item_name: "마감재" ← 부품 품명 │ │
|
||||
│ │ item_sep: "스크린" ← 대분류 │ │
|
||||
│ │ item_bending: "가이드레일" ← 중분류 │ │
|
||||
│ │ material: "SUS 1.2T" ← 재질 │ │
|
||||
│ │ model_name: "KSS01" ← 소속 모델 │ │
|
||||
│ │ model_UA: "인정" ← 인정여부 │ │
|
||||
│ │ item_spec: "120*70" ← 규격 │ │
|
||||
│ │ rail_width: 70 ← 레일폭 │ │
|
||||
│ │ bendingData: [ ← 전개도 데이터 │ │
|
||||
│ │ {no:1, input:10, rate:"", sum:10, ...}, │ │
|
||||
│ │ {no:2, input:11, rate:"", sum:21, ...}, │ │
|
||||
│ │ ... │ │
|
||||
│ │ ] │ │
|
||||
│ │ + 케이스전용: exit_direction, box_width, ... │ │
|
||||
│ │ } │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ ↑ 265건 (레거시) + α │
|
||||
│ │
|
||||
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
|
||||
│ │
|
||||
│ [2계층] 절곡품 — 모델별 부품 조합 │
|
||||
│ ════════════════════════════════ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ 가이드레일 모델: KSS01 벽면형 SUS마감 │ │
|
||||
│ │ │ │
|
||||
│ │ components (부품 조합): │ │
|
||||
│ │ ┌─────┬───────────┬──────────┬────┬────────┐ │ │
|
||||
│ │ │순서 │ 부품명 │ 재질 │수량│전개폭합│ │ │
|
||||
│ │ ├─────┼───────────┼──────────┼────┼────────┤ │ │
|
||||
│ │ │ 1 │ 마감재 │ SUS 1.2T│ 2 │ 203 │ ──→ item:100│
|
||||
│ │ │ 2 │ 본체 │ EGI 1.55│ 1 │ 296 │ ──→ item:101│
|
||||
│ │ │ 3 │ 벽면형-C │ EGI 1.55│ 1 │ 144 │ ──→ item:102│
|
||||
│ │ │ 4 │ 벽면형-D │ EGI 1.55│ 1 │ 144 │ ──→ item:103│
|
||||
│ │ └─────┴───────────┴──────────┴────┴────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ 재질별 폭합: SUS 1.2T → 406 | EGI 1.55T → 398 │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ ↑ 가이드레일 20건 + 케이스 + 하단마감재 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 3가지 타입 비교
|
||||
|
||||
```
|
||||
┌─────────────────┬─────────────────┬─────────────────┐
|
||||
│ 가이드레일 │ 케이스 │ 하단마감재 │
|
||||
├─────────────────┼─────────────────┼─────────────────┤
|
||||
│ │ │ │
|
||||
│ 모델: KSS01 │ 모델: ❌ 없음 │ 모델: KSS01 │
|
||||
│ 마감: SUS/EGI │ 마감: ❌ 없음 │ 마감: SUS/EGI │
|
||||
│ 형상: 벽면/측면 │ 형상: ❌ 없음 │ 형상: ❌ 없음 │
|
||||
│ 대분류: 스크린/철재│ 대분류: ❌ │ 대분류: 스크린/철재│
|
||||
│ 인정: 인정/비인정 │ 인정: ❌ │ 인정: 인정/비인정 │
|
||||
│ │ │ │
|
||||
│ 규격: 120×70 │ 규격: 650×550 │ 규격: 60×40 │
|
||||
│ 레일폭: 70 │ 전면밑: 50 │ │
|
||||
│ │ 레일폭: 75 │ │
|
||||
│ │ 점검구: 후면 │ │
|
||||
│ │ │ │
|
||||
│ 파트: 3~5개 │ 파트: 5개 │ 파트: 1개 │
|
||||
│ ┌─────────────┐│ ┌─────────────┐│ ┌─────────────┐│
|
||||
│ │본체상부 ││ │상부덮개 ││ │하단마감 ││
|
||||
│ │본체하부 ││ │전면 ││ │(단일 파트) ││
|
||||
│ │마감재 ││ │점검구 ││ └─────────────┘│
|
||||
│ │(+C형,D형) ││ │린텔 ││ │
|
||||
│ └─────────────┘│ │후면코너 ││ │
|
||||
│ │ └─────────────┘│ │
|
||||
├─────────────────┼─────────────────┼─────────────────┤
|
||||
│ 재질별 폭합 │ 재질별 폭합 │ 재질별 폭합 │
|
||||
│ SUS: 406 │ EGI: 2652 │ SUS: 193 │
|
||||
│ EGI: 398 │ │ │
|
||||
└─────────────────┴─────────────────┴─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 전개도 테이블 구조 (1개 부품)
|
||||
|
||||
```
|
||||
레거시 5130 화면과 동일한 구조:
|
||||
|
||||
┌────────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
|
||||
│ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│ 번호 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│ 입력 │ 10 │ 11 │ 110 │ 30 │ 15 │ 15 │ 15 │ ← 치수 입력
|
||||
│ │[색상]│ │ │ │ │[색상]│ │ ← 파란 배경
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│ 연신율 │ │ │ -1 │ -1 │ -1 │ │ │ ← 절곡 방향
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│연신율후 │ 10 │ 11 │ 109 │ 29 │ 14 │ 15 │ 15 │ ← input + rate
|
||||
│ │ │ │(-1) │(-1) │(-1) │ │ │ (rate=-1 → -1mm)
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│ 합계 │ 10 │ 21 │ 130 │ 159 │ 173 │ 188 │ 203 │ ← 보정후 누적합
|
||||
│ │[색상]│ │ │ │ │[색상]│ │ ← 노란 배경
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│ 음영 │ ■■ │ │ │ │ │ ■■ │ │ ← 색상 마킹
|
||||
├────────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||
│ A각 │ │ │ │ A각 │ │ │ │ ← A각 표시
|
||||
└────────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
|
||||
|
||||
연신율 보정 규칙:
|
||||
rate = "" → 보정 없음 (input 그대로)
|
||||
rate = "-1" → input - 1mm (하향 절곡)
|
||||
rate = "1" → input + 1mm (상향 절곡)
|
||||
|
||||
합계 = 보정후 값의 누적합
|
||||
폭합계 = 마지막 합계값 (이 예시: 203)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. JSON 저장 구조 (options.bendingData)
|
||||
|
||||
```
|
||||
레거시 (별도 배열 5개) SAM (객체 배열 1개)
|
||||
──────────────────── ────────────────────
|
||||
inputList: [10,11,110...] bendingData: [
|
||||
bendingrateList: ["","","-1"...] { no:1, input:10, rate:"",
|
||||
sumList: [10,21,130...] sum:10, color:true, aAngle:false },
|
||||
colorList: [true,false,false...] { no:2, input:11, rate:"",
|
||||
AList: [false,false,false...] sum:21, color:false, aAngle:false },
|
||||
{ no:3, input:110, rate:"-1",
|
||||
→ 5개 배열 동기화 필요 sum:130, color:false, aAngle:false },
|
||||
→ 열 추가/삭제 시 5개 다 조작 ...
|
||||
]
|
||||
→ 1개 배열만 관리
|
||||
→ 열 추가 = 객체 1개 push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 화면 흐름도
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ MNG 사이드바 │
|
||||
│ │
|
||||
│ 생산 관리 │
|
||||
│ ├─ 품목기준 필드 관리 │
|
||||
│ ├─ 견적수식 관리 │
|
||||
│ └─ 🆕 절곡품 관리 │
|
||||
│ ├─ 기초관리 ─────────────────┐ │
|
||||
│ └─ 절곡품 ──────────────┐ │ │
|
||||
│ │ │ │
|
||||
└─────────────────────────────┼────┼───────────────────────────┘
|
||||
│ │
|
||||
┌───────────────────┘ └───────────────────┐
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ 절곡품 목록 │ │ 기초관리 목록 │
|
||||
│ │ │ │
|
||||
│ [가이드레일] [케이스] │ │ 265건 테이블 │
|
||||
│ [하단마감재] │ │ 필터: 대분류/인정/ │
|
||||
│ │ │ 그룹/품명/검색 │
|
||||
│ 필터 + 테이블 │ │ │
|
||||
│ │ │ 행 클릭 ──→ 상세 │
|
||||
│ 행 클릭 ──→ 상세 │ │ [+등록] ──→ 등록 │
|
||||
│ [+등록] ──→ 등록 │ └───────────┬──────────┘
|
||||
└───────────┬──────────┘ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ 절곡품 등록/수정 │ │ 기초관리 등록/수정 │
|
||||
│ │ │ │
|
||||
│ ┌──────────┬───────┐ │ │ ┌──────────┬───────┐ │
|
||||
│ │ 기본정보 │ 이미지 │ │ │ │ 기본정보 │ 이미지 │ │
|
||||
│ │ (타입별) │ 업로드 │ │ │ │ 대분류 │ 업로드 │ │
|
||||
│ ├──────────┤ 검색어 │ │ │ │ 그룹/품명 │ 검색어 │ │
|
||||
│ │ 파트 탭 │ │ │ │ │ 재질/규격 │ │ │
|
||||
│ │ [1][2][3] │ │ │ │ ├──────────┤ │ │
|
||||
│ │ │ │ │ │ │ 절곡 테이블│ │ │
|
||||
│ │ 절곡테이블│ │ │ │ │ (단일) │ │ │
|
||||
│ │ (파트별) │ │ │ │ ├──────────┤ │ │
|
||||
│ ├──────────┤ │ │ │ │ 재질별폭합│ │ │
|
||||
│ │ 재질별폭합│ │ │ │ └──────────┴───────┘ │
|
||||
│ └──────────┴───────┘ │ └──────────────────────┘
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 엔드포인트 흐름
|
||||
|
||||
```
|
||||
MNG / React
|
||||
│
|
||||
├── GET /api/v1/bending-items ← 기초관리 목록
|
||||
├── GET /api/v1/bending-items/filters ← 필터 옵션
|
||||
├── GET /api/v1/bending-items/{id} ← 상세
|
||||
├── POST /api/v1/bending-items ← 등록
|
||||
├── PUT /api/v1/bending-items/{id} ← 수정
|
||||
├── DELETE /api/v1/bending-items/{id} ← 삭제
|
||||
│
|
||||
├── GET /api/v1/guiderail-models ← 절곡품 모델 목록
|
||||
├── GET /api/v1/guiderail-models/{id} ← 모델 상세 (부품조합)
|
||||
├── POST /api/v1/guiderail-models ← 모델 등록
|
||||
├── PUT /api/v1/guiderail-models/{id} ← 모델 수정
|
||||
├── DELETE /api/v1/guiderail-models/{id} ← 모델 삭제
|
||||
│
|
||||
├── POST /api/v1/items/{id}/files ← 이미지 업로드 (기존)
|
||||
├── GET /api/v1/items/{id}/files ← 이미지 목록 (기존)
|
||||
└── GET /api/v1/files/{id}/view ← 이미지 표시 (기존)
|
||||
|
||||
※ 이미지는 기존 ItemsFileController 재사용
|
||||
※ field_key: 'bending_diagram'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 작업 순서
|
||||
|
||||
```
|
||||
Step 1 Step 2 Step 3 Step 4
|
||||
데이터 분석 API 구현 MNG 화면 React 화면
|
||||
━━━━━━━━ ━━━━━━━━ ━━━━━━━━ ━━━━━━━━
|
||||
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│레거시 265건│ │Controller│ │기초관리 │ │견적 이미지│
|
||||
│SAM 170건 │──→ │Service │──→ │ 목록/등록 │──→ │GuiderailP│
|
||||
│매핑 테이블 │ │FormReq │ │절곡품 │ │review │
|
||||
│ │ │Resource │ │ 목록/등록 │ │ │
|
||||
│options 확장│ │ │ │ │ │절곡품 │
|
||||
│artisan cmd│ │이미지: │ │메뉴 등록 │ │관리 화면 │
|
||||
│ │ │기존 재사용│ │(tinker) │ │(본 화면) │
|
||||
│회귀 테스트 │ │ │ │ │ │ │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
API 프로젝트 API 프로젝트 MNG 프로젝트 React 프로젝트
|
||||
(샘플 확인용) (운영용)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 레거시 → SAM 대응표
|
||||
|
||||
```
|
||||
레거시 (5130) SAM
|
||||
━━━━━━━━━━━━━ ━━━━━
|
||||
chandj.bending (265건) → items (item_category='BENDING') + options
|
||||
chandj.guiderail (20건) → guiderail-models API (신규 저장 구조)
|
||||
guiderail/list.php → MNG /bending/products (절곡품 목록)
|
||||
bending CRUD → MNG /bending/base (기초관리)
|
||||
put_guiderail_image.php → 기존 ItemsFileController (R2)
|
||||
fetch_guiderail_detail.php → React GuiderailPreview
|
||||
drawingTool.js (Canvas) → 2차 구현 (1차는 이미지 업로드만)
|
||||
inputList[] (별도 배열) → bendingData[] (객체 배열)
|
||||
bendingrateList[] → bendingData[].rate
|
||||
sumList[] → bendingData[].sum
|
||||
colorList[] → bendingData[].color
|
||||
AList[] → bendingData[].aAngle
|
||||
```
|
||||
|
||||
|
||||
282
dev/dev_plans/bending-management/step1-데이터분석.md
Normal file
282
dev/dev_plans/bending-management/step1-데이터분석.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Step 1: 데이터 분석 + options 확장
|
||||
|
||||
> **프로젝트**: API (`sam/api`)
|
||||
> **선행 조건**: 없음
|
||||
> **참조**: `standards/options-column-policy.md`, `rules/item-policy.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 레거시 데이터 매핑
|
||||
|
||||
### 1-1. 레거시 데이터 현황 (chandj DB)
|
||||
|
||||
**테이블별 건수**:
|
||||
|
||||
| 테이블 | 건수 | 설명 |
|
||||
|--------|------|------|
|
||||
| `bending` | 265건 (활성) | 개별 절곡품 부품 |
|
||||
| `guiderail` | 20건 (활성) | 모델별 부품 조합 (7개 모델 + 비인정 1개) |
|
||||
| `bendingfee` | 82건 | 절곡 단가 |
|
||||
| `bendingmap` | 9건 | 절곡 매핑 |
|
||||
| `etcbending` | 2건 | 기타 절곡 |
|
||||
| `bending_work_log` | 8건 | 작업 로그 |
|
||||
|
||||
**bending 분류 분포** (265건):
|
||||
|
||||
| 대분류 | 중분류 | 건수 |
|
||||
|--------|--------|------|
|
||||
| 스크린 | 가이드레일 | 41 |
|
||||
| 스크린 | 케이스 | 24 |
|
||||
| 스크린 | 하단마감재 | 6 |
|
||||
| 스크린 | 마구리 | 4 |
|
||||
| 스크린 | L-BAR | 2 |
|
||||
| 철재 | 케이스 | 136 |
|
||||
| 철재 | 가이드레일 | 30 |
|
||||
| 철재 | (미분류) | 9 |
|
||||
| 철재 | 마구리 | 8 |
|
||||
| 철재 | 하단마감재 | 5 |
|
||||
|
||||
**bending 테이블 전체 컬럼** (25개):
|
||||
|
||||
| 컬럼 | 타입 | 설명 | options 키 |
|
||||
|------|------|------|-----------|
|
||||
| `num` | int PK | 번호 | `legacy_bending_num` |
|
||||
| `is_deleted` | tinyint | 삭제여부 | SAM 자체 관리 |
|
||||
| `item_sep` | varchar(14) | 대분류 (스크린/철재) | `item_sep` |
|
||||
| `model_UA` | varchar(15) | 인정여부 | `model_UA` |
|
||||
| `item_bending` | varchar(40) | 중분류 | `item_bending` |
|
||||
| `itemName` | varchar(50) | 품명 | `item_name` |
|
||||
| `material` | varchar(25) | 재질 | `material` |
|
||||
| `parentnum` | varchar(12) | 부모 참조 | `parent_num` |
|
||||
| `registration_date` | date | 등록일 | `registration_date` |
|
||||
| `imgdata` | text | 이미지 파일경로 | `image_path` |
|
||||
| `inputList` | text | 치수 JSON | `bendingData[].input` |
|
||||
| `bendingrateList` | text | 연신율 JSON | `bendingData[].rate` |
|
||||
| `sumList` | text | 합계 JSON | `bendingData[].sum` |
|
||||
| `colorList` | text | 색상마킹 JSON | `bendingData[].color` |
|
||||
| `AList` | text | A각 JSON | `bendingData[].aAngle` |
|
||||
| `memo` | text | 비고 | `memo` |
|
||||
| `update_log` | text | 수정 이력 | SAM 자체 관리 (updated_at) |
|
||||
| `item_spec` | varchar(50) | 규격 | `item_spec` |
|
||||
| `widthsum` | int | 폭합계 | 계산값 (bendingData 마지막 sum) |
|
||||
| `author` | varchar(20) | 작성자 | `author` |
|
||||
| `search_keyword` | varchar(50) | 검색어 | `search_keyword` |
|
||||
| `exit_direction` | varchar(20) | 점검구 방향 (케이스) | `exit_direction` |
|
||||
| `front_bottom_width` | varchar(5) | 전면부 밑 치수 (케이스) | `front_bottom_width` |
|
||||
| `rail_width` | varchar(5) | 레일폭 | `rail_width` |
|
||||
| `box_width` | varchar(5) | 케이스 너비 | `box_width` |
|
||||
| `box_height` | varchar(5) | 케이스 높이 | `box_height` |
|
||||
|
||||
**전개도 JSON 현황**: 265건 전부 inputList/bendingrateList/sumList/colorList/imgdata 보유 (크기: 30~50 bytes/필드)
|
||||
|
||||
**guiderail 모델 목록** (20건):
|
||||
|
||||
| 모델 | 인정 | 형태 | 레일폭 | 레일길이 | 마감 | 제품 |
|
||||
|------|------|------|--------|---------|------|------|
|
||||
| KSS01 | 인정 | 벽면/측면 | 70/120 | 120 | SUS | 스크린 |
|
||||
| KSS02 | 인정 | 벽면/측면 | 70/120 | 120 | SUS | 스크린 |
|
||||
| KSE01 | 인정 | 벽면/측면 | 70/120 | 120 | EGI/SUS | 스크린 |
|
||||
| KWE01 | 인정 | 벽면/측면 | 70/120 | 120 | EGI/SUS | 스크린 |
|
||||
| KTE01 | 인정 | 벽면/측면 | 75/125 | 130 | EGI/SUS | 철재 |
|
||||
| KQTS01 | 인정 | 벽면/측면 | 75/125 | 130 | SUS | 철재 |
|
||||
| KDSS01 | 인정 | 벽면 | 150 | 150 | SUS | 스크린 |
|
||||
| 스크린비인정 | 비인정 | 벽면 | 70 | 130 | SUS | 스크린 |
|
||||
|
||||
**guiderail 테이블 전체 컬럼** (15개):
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `num` | int PK | 번호 |
|
||||
| `is_deleted` | varchar(2) | 삭제여부 |
|
||||
| `registration_date` | date | 등록일 |
|
||||
| `model_UA` | varchar(10) | 인정여부 |
|
||||
| `check_type` | varchar(20) | 형상 (벽면형/측면형) |
|
||||
| `model_name` | varchar(15) | 모델명 |
|
||||
| `author` | varchar(50) | 작성자 |
|
||||
| `remark` | text | 비고 |
|
||||
| `update_log` | text | 수정 이력 |
|
||||
| `rail_width` | varchar(10) | 레일폭 |
|
||||
| `rail_length` | varchar(10) | 레일길이(높이) |
|
||||
| `finishing_type` | varchar(10) | 마감 (SUS마감/EGI마감) |
|
||||
| `bending_components` | mediumtext | 부품 조합 JSON |
|
||||
| `firstitem` | varchar(15) | 대분류 (스크린/철재) |
|
||||
| `search_keyword` | varchar(50) | 검색어 |
|
||||
| `material_summary` | text | 재질별 폭합 |
|
||||
|
||||
### 1-1b. 추가 파악 필요
|
||||
|
||||
| 작업 | 대상 | 비고 |
|
||||
|------|------|------|
|
||||
| BendingInfoBuilder.php 지원 모델 추출 | 코드 분석 | 기존 로직 파악 |
|
||||
| PrefixResolver.php PREFIX 규칙 추출 | 코드 분석 | 기존 로직 파악 |
|
||||
| bendingfee 82건 구조 | 절곡 단가 — SAM 연동 필요 여부 | |
|
||||
| bendingmap 9건 구조 | 매핑 용도 확인 | |
|
||||
|
||||
### 1-2. SAM BD 품목 현황 (170건)
|
||||
|
||||
**패턴별 분류**:
|
||||
|
||||
| 패턴 | 건수 | 예시 | 파싱 |
|
||||
|------|------|------|------|
|
||||
| A) `BD-PREFIX-LEN` | 112 | BD-RS-30, BD-CF-35 | prefix/length 자동 추출 |
|
||||
| B) `BD-L-BAR-모델-규격` | 5 | BD-L-BAR-KSS01-17*60 | 모델+규격 추출 가능 |
|
||||
| C) `BD-가이드레일-모델-재질-규격` | 21 | BD-가이드레일-KSS01-SUS-120*70 | 모델+재질+규격 추출 가능 |
|
||||
| D) `BD-마구리-규격` | 10 | BD-마구리-655*505 | 규격 추출 가능 |
|
||||
| E) `BD-케이스-규격` | 11 | BD-케이스-650*550 | 규격 추출 가능 |
|
||||
| F) `BD-하단마감재-모델-재질-규격` | 10 | BD-하단마감재-KSS01-SUS-60*40 | 모델+재질+규격 추출 가능 |
|
||||
| G) `BD-보강평철-규격` | 1 | BD-보강평철-50 | 규격 추출 가능 |
|
||||
|
||||
**A) PREFIX-LEN 112건 상세**:
|
||||
|
||||
| PREFIX | 용도 | 길이 종류 | 건수 |
|
||||
|--------|------|----------|------|
|
||||
| RS | 가이드레일 마감재(벽면) SUS | 24,30,35,40,43 | 5 |
|
||||
| SS | 가이드레일 마감재(측면) SUS | 30,35,40,43 | 4 |
|
||||
| SU | 가이드레일 마감재(측면) SUS2 | 30,35,40,43 | 4 |
|
||||
| RM | 가이드레일 본체(벽면) | 12,24,30,35,40,42,43 | 7 |
|
||||
| SM | 가이드레일 본체(측면) | 02,24,30,35,40,43 | 6 |
|
||||
| RC | 가이드레일 C형(벽면) | 12,24,30,35,40,42,43 | 7 |
|
||||
| RD | 가이드레일 D형(벽면) | 12,24,30,35,40,42,43 | 7 |
|
||||
| SC | 가이드레일 C형(측면) | 24,30,35,40,43 | 5 |
|
||||
| SD | 가이드레일 D형(측면) | 24,30,35,40,43 | 5 |
|
||||
| RT | 가이드레일 본체(벽면/철재) | 30,43 | 2 |
|
||||
| ST | 가이드레일 본체(측면/철재) | 43 | 1 |
|
||||
| BS | 하단마감재(스크린) SUS | 12,24,30,35,40,42,43 | 7 |
|
||||
| BE | 하단마감재(스크린) EGI | 30,40 | 2 |
|
||||
| TS | 하단마감재(철재) SUS | 40,43 | 2 |
|
||||
| CF | 케이스 전면부 | 12,24,30,35,40,41 | 6 |
|
||||
| CL | 케이스 린텔부 | 12,24,30,35,40,41 | 6 |
|
||||
| CP | 케이스 점검구 | 12,24,30,35,40,41 | 6 |
|
||||
| CB | 케이스 후면코너부 | 12,24,30,35,40,41 | 6 |
|
||||
| GI | 연기차단재 | 24,30,35,40,43,53,54,83,84 | 9 |
|
||||
| HH | 보강평철 | 30,40 | 2 |
|
||||
| LA | L-Bar | 30,40 | 2 |
|
||||
| XX | 하부BASE/상부덮개/마구리(공용) | 12,24,30,35,40,41,43 | 7 |
|
||||
| YY | 별도마감 | 30,35,40,43 | 4 |
|
||||
|
||||
**options 채워진 상태**:
|
||||
|
||||
| 상태 | 건수 | 비고 |
|
||||
|------|------|------|
|
||||
| options 완전 (prefix+length) | 22 | 13% |
|
||||
| options 있지만 불완전 | 90 | PREFIX-LEN 중 일부 |
|
||||
| options 비어있음 (`{}`) | 58 | 한글 패턴 전부 |
|
||||
|
||||
### 1-3. 매핑 테이블 작성
|
||||
|
||||
```
|
||||
레거시 bending (부품 단위) SAM items (품목 단위)
|
||||
───────────────────────── ─────────────────────
|
||||
num:100 마감재 SUS 120*70 ↔ BD-가이드레일-KSS01-SUS-120*70 (한글 패턴)
|
||||
num:101 본체 EGI 120*70 ↔ BD-RM-30 (PREFIX-LEN — 길이 기준)
|
||||
※ 부품 단위 vs 길이 단위 구조 차이
|
||||
```
|
||||
|
||||
**핵심 결정사항**:
|
||||
- BD-한글 패턴(58건)을 BD-PREFIX-LEN으로 통일할지
|
||||
- 레거시 265건 중 SAM에 없는 항목 → 신규 생성 범위
|
||||
- 매핑 애매한 항목 → 사업부 확인 목록
|
||||
|
||||
---
|
||||
|
||||
## 2. options 확장
|
||||
|
||||
### 2-1. 확장 스키마
|
||||
|
||||
**기존 키 (보존)**:
|
||||
```json
|
||||
{
|
||||
"source": "bending_item_seeder",
|
||||
"lot_managed": true,
|
||||
"consumption_method": "auto",
|
||||
"production_source": "self_produced",
|
||||
"input_tracking": true,
|
||||
"prefix": "RS",
|
||||
"length_code": "30",
|
||||
"length_mm": 3000
|
||||
}
|
||||
```
|
||||
|
||||
**추가 키**:
|
||||
```json
|
||||
{
|
||||
// --- 기본 속성 ---
|
||||
"item_name": "마감재", // 품명 (레거시 itemName — items.name과 별도 보존)
|
||||
"item_sep": "스크린", // 대분류 (스크린/철재)
|
||||
"item_bending": "가이드레일", // 중분류 (가이드레일/케이스/하단마감재/마구리/L-BAR)
|
||||
"item_spec": "120*70", // 규격
|
||||
"material": "SUS 1.2T", // 재질
|
||||
"model_name": "KSS01", // 모델명
|
||||
"model_UA": "인정", // 인정여부
|
||||
"search_keyword": "", // 검색 키워드
|
||||
"rail_width": 70, // 레일폭
|
||||
"registration_date": "2025-07-19", // 등록일
|
||||
"author": "개발자", // 작성자
|
||||
"memo": "", // 비고
|
||||
"parent_num": null, // 부모 절곡품 참조 (조합 관계)
|
||||
|
||||
// --- 케이스 전용 ---
|
||||
"exit_direction": "후면 점검구", // 점검구 방향 (후면/양면/밑면)
|
||||
"front_bottom_width": 50, // 전면부 밑 치수 (mm)
|
||||
"box_width": 650, // 케이스 너비 (mm)
|
||||
"box_height": 550, // 케이스 높이 (mm)
|
||||
|
||||
// --- 전개도 데이터 (인덱스 기반 객체 배열) ---
|
||||
"bendingData": [
|
||||
{ "no": 1, "input": 10, "rate": "", "sum": 10, "color": true, "aAngle": false },
|
||||
{ "no": 2, "input": 11, "rate": "", "sum": 21, "color": false, "aAngle": false }
|
||||
// ... 열 단위로 모든 속성을 하나의 객체에 통합
|
||||
],
|
||||
|
||||
// --- 이미지/추적 ---
|
||||
"image_path": "", // 전개도 이미지 경로
|
||||
"legacy_bending_num": null // 레거시 추적용
|
||||
}
|
||||
```
|
||||
|
||||
### 2-2. 마이그레이션 순서
|
||||
|
||||
```
|
||||
1단계: 기존 148건 prefix/length 채우기
|
||||
→ BD-PREFIX-LEN 패턴에서 자동 추출
|
||||
|
||||
2단계: 레거시 속성 입력
|
||||
→ 매핑 테이블 기반 item_sep/item_bending/material 등
|
||||
|
||||
3단계: 전개도 JSON 입력
|
||||
→ 레거시 inputList/bendingrateList/sumList/colorList
|
||||
```
|
||||
|
||||
### 2-3. artisan command
|
||||
|
||||
```bash
|
||||
# 1단계
|
||||
php artisan bending:fill-options --dry-run # 미리보기
|
||||
php artisan bending:fill-options # 실행
|
||||
|
||||
# 2단계
|
||||
php artisan bending:import-legacy --dry-run
|
||||
php artisan bending:import-legacy
|
||||
|
||||
# 3단계 (2단계에 포함 가능)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 회귀 테스트 (필수)
|
||||
|
||||
| 테스트 | 확인 내용 | 판정 기준 |
|
||||
|--------|----------|----------|
|
||||
| BendingInfoBuilder | 견적 BOM 계산 결과 | 변경 전/후 동일 |
|
||||
| PrefixResolver | BD 코드 자동 결정 | 변경 전/후 동일 |
|
||||
| 작업지시서 절곡 섹션 | GuideRailSection 렌더링 | 정상 표시 |
|
||||
| 절곡 검사 | inspection-config API | 정상 응답 |
|
||||
| 견적→수주→작업지시 | 전체 흐름 1건 | 오류 없음 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 산출물
|
||||
|
||||
- [ ] 매핑 테이블 (레거시 num ↔ SAM item_id)
|
||||
- [ ] artisan command (bending:fill-options, bending:import-legacy)
|
||||
- [ ] 회귀 테스트 결과
|
||||
525
dev/dev_plans/bending-management/step2-API.md
Normal file
525
dev/dev_plans/bending-management/step2-API.md
Normal file
@@ -0,0 +1,525 @@
|
||||
# Step 2: API 엔드포인트
|
||||
|
||||
> **프로젝트**: API (`sam/api`)
|
||||
> **선행 조건**: Step 1 완료
|
||||
> **참조**: `standards/api-rules.md`, `standards/options-column-policy.md`, `rules/item-policy.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 설계 방침
|
||||
|
||||
### 기존 규칙 준수 사항
|
||||
|
||||
| 규칙 | 적용 |
|
||||
|------|------|
|
||||
| URL prefix | `/api/v1/` |
|
||||
| 응답 형식 | `ApiResponse::handle()` → `{success, message, data}` |
|
||||
| Controller | FormRequest 타입힌트 → Service 호출만 |
|
||||
| Service | `extends Service`, `tenantId()`, `apiUserId()` 사용 |
|
||||
| i18n 메시지 | `__('message.bending_item.created')` 패턴 |
|
||||
| 멀티테넌시 | `BelongsToTenant` 글로벌 스코프 |
|
||||
| Audit 로그 | `audit_logs` 테이블 자동 기록 |
|
||||
| SoftDeletes | 기본 적용 |
|
||||
| options | `'array'` 캐스트, `getOption()`/`setOption()` 헬퍼 |
|
||||
| Validation | FormRequest 클래스, 컨트롤러에서 직접 validate() 금지 |
|
||||
|
||||
### 기존 Item 구조와의 관계
|
||||
|
||||
```
|
||||
기존 구조:
|
||||
ItemsController → ItemsService → items 테이블
|
||||
item_type: FG(완제품), PT(부품), SM(부자재), RM(원자재), CS(소모품)
|
||||
item_category: 'BENDING' (절곡품 구분)
|
||||
|
||||
절곡품 API 방향:
|
||||
→ 기존 ItemsController 무변경
|
||||
→ 별도 BendingItemController 생성 (items 테이블을 item_category='BENDING'으로 필터)
|
||||
→ 절곡품 전용 필터/검색/전개도 데이터 관리
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 엔드포인트 설계
|
||||
|
||||
### 2-1. 절곡품 기초관리 (개별 부품)
|
||||
|
||||
| Method | Path | 설명 | 비고 |
|
||||
|--------|------|------|------|
|
||||
| GET | `/api/v1/bending-items` | 목록 (필터/검색/페이지네이션) | |
|
||||
| GET | `/api/v1/bending-items/filters` | 필터 옵션 (분류/재질/모델 distinct) | 캐시 10분 |
|
||||
| GET | `/api/v1/bending-items/{id}` | 상세 (options 전체) | |
|
||||
| POST | `/api/v1/bending-items` | 등록 | |
|
||||
| PUT | `/api/v1/bending-items/{id}` | 수정 | |
|
||||
| DELETE | `/api/v1/bending-items/{id}` | 삭제 (soft delete) | |
|
||||
| ~~이미지~~ | 기존 `ItemsFileController` 사용 | `field_key: 'bending_diagram'` | 별도 엔드포인트 불필요 |
|
||||
|
||||
**필터 파라미터** (GET /api/v1/bending-items):
|
||||
```
|
||||
?item_sep=스크린 # 대분류
|
||||
&item_bending=가이드레일 # 중분류
|
||||
&material=SUS # 재질 (부분 매칭)
|
||||
&model_UA=인정 # 인정여부
|
||||
&search=KSS01 # 통합 검색 (이름/검색어/규격)
|
||||
&page=1&size=50 # 페이지네이션 (size — api-rules 기준)
|
||||
```
|
||||
|
||||
### 2-2. 절곡품 모델 관리 (조합)
|
||||
|
||||
| Method | Path | 설명 | 비고 |
|
||||
|--------|------|------|------|
|
||||
| GET | `/api/v1/guiderail-models` | 모델 목록 (타입별) | ?type=가이드레일 |
|
||||
| GET | `/api/v1/guiderail-models/{id}` | 모델 상세 (부품 조합 + 재질별 폭합) | |
|
||||
| POST | `/api/v1/guiderail-models` | 모델 등록 | |
|
||||
| PUT | `/api/v1/guiderail-models/{id}` | 모델 수정 | |
|
||||
| DELETE | `/api/v1/guiderail-models/{id}` | 모델 삭제 (soft delete) | |
|
||||
|
||||
---
|
||||
|
||||
## 3. 구현 파일 구조
|
||||
|
||||
### Controller
|
||||
|
||||
```
|
||||
app/Http/Controllers/Api/V1/
|
||||
├─ BendingItemController.php ← 신규
|
||||
└─ GuiderailModelController.php ← 신규
|
||||
```
|
||||
|
||||
```php
|
||||
// BendingItemController.php
|
||||
class BendingItemController extends Controller
|
||||
{
|
||||
public function __construct(private BendingItemService $service) {}
|
||||
|
||||
public function index(BendingItemIndexRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(fn() =>
|
||||
$this->service->list($request->validated())
|
||||
);
|
||||
}
|
||||
|
||||
public function store(BendingItemStoreRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(fn() =>
|
||||
$this->service->create($request->validated()),
|
||||
__('message.bending_item.created')
|
||||
);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(fn() =>
|
||||
$this->service->find($id)
|
||||
);
|
||||
}
|
||||
|
||||
public function update(BendingItemUpdateRequest $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(fn() =>
|
||||
$this->service->update($id, $request->validated()),
|
||||
__('message.bending_item.updated')
|
||||
);
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(fn() =>
|
||||
$this->service->delete($id),
|
||||
__('message.bending_item.deleted')
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service
|
||||
|
||||
```
|
||||
app/Services/
|
||||
├─ BendingItemService.php ← 신규
|
||||
└─ GuiderailModelService.php ← 신규
|
||||
```
|
||||
|
||||
```php
|
||||
// BendingItemService.php
|
||||
class BendingItemService extends Service
|
||||
{
|
||||
public function list(array $params): LengthAwarePaginator
|
||||
{
|
||||
return Item::where('item_category', 'BENDING')
|
||||
->when($params['item_sep'] ?? null, fn($q, $v) =>
|
||||
$q->where('options->item_sep', $v))
|
||||
->when($params['item_bending'] ?? null, fn($q, $v) =>
|
||||
$q->where('options->item_bending', $v))
|
||||
->when($params['material'] ?? null, fn($q, $v) =>
|
||||
$q->where('options->material', 'like', "%{$v}%"))
|
||||
->when($params['model_UA'] ?? null, fn($q, $v) =>
|
||||
$q->where('options->model_UA', $v))
|
||||
->when($params['search'] ?? null, fn($q, $v) =>
|
||||
$q->where(fn($q2) => $q2
|
||||
->where('name', 'like', "%{$v}%")
|
||||
->orWhere('options->search_keyword', 'like', "%{$v}%")
|
||||
->orWhere('options->item_spec', 'like', "%{$v}%")))
|
||||
->orderByDesc('id')
|
||||
->paginate($params['size'] ?? 50);
|
||||
}
|
||||
|
||||
public function create(array $data): Item
|
||||
{
|
||||
$options = $this->buildOptions($data);
|
||||
$item = Item::create([
|
||||
'tenant_id' => $this->tenantId(),
|
||||
'item_type' => 'PT',
|
||||
'item_category' => 'BENDING',
|
||||
'code' => $data['code'],
|
||||
'name' => $data['name'],
|
||||
'options' => $options,
|
||||
'created_by' => $this->apiUserId(),
|
||||
]);
|
||||
// audit log 자동 기록
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): Item
|
||||
{
|
||||
$item = Item::findOrFail($id);
|
||||
// setOption()으로 개별 키 업데이트 (기존 키 보존)
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, ['code', 'name'])) {
|
||||
$item->$key = $value;
|
||||
} else {
|
||||
$item->setOption($key, $value);
|
||||
}
|
||||
}
|
||||
$item->updated_by = $this->apiUserId();
|
||||
$item->save();
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function buildOptions(array $data): array
|
||||
{
|
||||
$options = [];
|
||||
$optionKeys = [
|
||||
'item_name', 'item_sep', 'item_bending', 'item_spec',
|
||||
'material', 'model_name', 'model_UA', 'search_keyword',
|
||||
'rail_width', 'registration_date', 'author', 'memo',
|
||||
'parent_num', 'exit_direction', 'front_bottom_width',
|
||||
'box_width', 'box_height', 'bendingData', 'image_path',
|
||||
];
|
||||
foreach ($optionKeys as $key) {
|
||||
if (isset($data[$key])) {
|
||||
$options[$key] = $data[$key];
|
||||
}
|
||||
}
|
||||
return $options ?: null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### FormRequest
|
||||
|
||||
```
|
||||
app/Http/Requests/Api/V1/
|
||||
├─ BendingItemIndexRequest.php ← 신규
|
||||
├─ BendingItemStoreRequest.php ← 신규
|
||||
├─ BendingItemUpdateRequest.php ← 신규
|
||||
├─ GuiderailModelStoreRequest.php ← 신규
|
||||
└─ GuiderailModelUpdateRequest.php← 신규
|
||||
```
|
||||
|
||||
```php
|
||||
// BendingItemStoreRequest.php
|
||||
class BendingItemStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'required|string|max:100|unique:items,code',
|
||||
'name' => 'required|string|max:200',
|
||||
'item_name' => 'required|string|max:50',
|
||||
'item_sep' => 'required|in:스크린,철재',
|
||||
'item_bending' => 'required|string',
|
||||
'material' => 'required|string',
|
||||
'model_UA' => 'nullable|in:인정,비인정',
|
||||
'item_spec' => 'nullable|string',
|
||||
'model_name' => 'nullable|string',
|
||||
'search_keyword' => 'nullable|string',
|
||||
'rail_width' => 'nullable|integer',
|
||||
'memo' => 'nullable|string',
|
||||
// 케이스 전용
|
||||
'exit_direction' => 'nullable|string',
|
||||
'front_bottom_width' => 'nullable|integer',
|
||||
'box_width' => 'nullable|integer',
|
||||
'box_height' => 'nullable|integer',
|
||||
// 전개도 데이터
|
||||
'bendingData' => 'nullable|array',
|
||||
'bendingData.*.no' => 'required|integer',
|
||||
'bendingData.*.input' => 'required|numeric',
|
||||
'bendingData.*.rate' => 'nullable|string',
|
||||
'bendingData.*.sum' => 'required|numeric',
|
||||
'bendingData.*.color' => 'required|boolean',
|
||||
'bendingData.*.aAngle' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Resource
|
||||
|
||||
```
|
||||
app/Http/Resources/Api/V1/
|
||||
├─ BendingItemResource.php ← 신규
|
||||
└─ GuiderailModelResource.php ← 신규
|
||||
```
|
||||
|
||||
```php
|
||||
// BendingItemResource.php
|
||||
class BendingItemResource extends JsonResource
|
||||
{
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
// options → 최상위로 풀어서 노출
|
||||
'item_name' => $this->getOption('item_name'),
|
||||
'item_sep' => $this->getOption('item_sep'),
|
||||
'item_bending' => $this->getOption('item_bending'),
|
||||
'item_spec' => $this->getOption('item_spec'),
|
||||
'material' => $this->getOption('material'),
|
||||
'model_name' => $this->getOption('model_name'),
|
||||
'model_UA' => $this->getOption('model_UA'),
|
||||
'search_keyword' => $this->getOption('search_keyword'),
|
||||
'rail_width' => $this->getOption('rail_width'),
|
||||
'registration_date' => $this->getOption('registration_date'),
|
||||
'author' => $this->getOption('author'),
|
||||
'memo' => $this->getOption('memo'),
|
||||
// 케이스 전용
|
||||
'exit_direction' => $this->getOption('exit_direction'),
|
||||
'front_bottom_width' => $this->getOption('front_bottom_width'),
|
||||
'box_width' => $this->getOption('box_width'),
|
||||
'box_height' => $this->getOption('box_height'),
|
||||
// 전개도
|
||||
'bendingData' => $this->getOption('bendingData'),
|
||||
'image_path' => $this->getOption('image_path'),
|
||||
// 계산값
|
||||
'width_sum' => $this->getWidthSum(),
|
||||
'bend_count' => $this->getBendCount(),
|
||||
'has_image' => !empty($this->getOption('image_path')),
|
||||
// 메타
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
|
||||
private function getWidthSum(): ?int
|
||||
{
|
||||
$data = $this->getOption('bendingData', []);
|
||||
if (empty($data)) return null;
|
||||
return (int) end($data)['sum'] ?? null;
|
||||
}
|
||||
|
||||
private function getBendCount(): int
|
||||
{
|
||||
$data = $this->getOption('bendingData', []);
|
||||
return count(array_filter($data, fn($d) => ($d['rate'] ?? '') !== ''));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 라우트
|
||||
|
||||
```php
|
||||
// routes/api.php (v1 그룹 내부에 추가)
|
||||
Route::prefix('v1')->middleware(['auth:sanctum'])->group(function () {
|
||||
// ... 기존 라우트 유지 ...
|
||||
|
||||
// 절곡품 기초관리
|
||||
Route::apiResource('bending-items', BendingItemController::class);
|
||||
Route::get('bending-items/filters', [BendingItemController::class, 'filters']);
|
||||
Route::post('bending-items/{id}/image', [BendingItemController::class, 'uploadImage']);
|
||||
Route::delete('bending-items/{id}/image', [BendingItemController::class, 'deleteImage']);
|
||||
|
||||
// 절곡품 모델 (가이드레일 조합)
|
||||
Route::apiResource('guiderail-models', GuiderailModelController::class);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 응답 형식
|
||||
|
||||
### 목록 응답 (GET /api/v1/bending-items)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": null,
|
||||
"data": {
|
||||
"data": [
|
||||
{
|
||||
"id": 123,
|
||||
"code": "BD-가이드레일-KSS01-SUS-120*70",
|
||||
"name": "가이드레일 KSS01 SUS 120*70",
|
||||
"item_name": "마감재",
|
||||
"item_sep": "스크린",
|
||||
"item_bending": "가이드레일",
|
||||
"item_spec": "120*70",
|
||||
"material": "SUS 1.2T",
|
||||
"model_name": "KSS01",
|
||||
"model_UA": "인정",
|
||||
"width_sum": 203,
|
||||
"bend_count": 3,
|
||||
"has_image": true
|
||||
}
|
||||
],
|
||||
"current_page": 1,
|
||||
"total": 170,
|
||||
"per_page": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 모델 상세 응답 (GET /api/v1/guiderail-models/{id})
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": null,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"model_name": "KSS01",
|
||||
"check_type": "벽면형",
|
||||
"rail_width": 70,
|
||||
"rail_length": 120,
|
||||
"finishing_type": "SUS마감",
|
||||
"item_sep": "스크린",
|
||||
"model_UA": "인정",
|
||||
"components": [
|
||||
{
|
||||
"order": 1,
|
||||
"name": "1번(마감재)",
|
||||
"material": "SUS 1.2T",
|
||||
"qty": 2,
|
||||
"bending_item_id": 100,
|
||||
"sum_total": 203,
|
||||
"bendingData": [...]
|
||||
}
|
||||
],
|
||||
"material_summary": {
|
||||
"SUS 1.2T": 406,
|
||||
"EGI 1.55T": 398
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 이미지 처리 (Cloudflare R2)
|
||||
|
||||
### 기존 파일 시스템 구조
|
||||
|
||||
SAM API는 **Cloudflare R2** (S3 호환)로 파일을 관리한다. 절곡품 이미지도 동일한 구조를 따른다.
|
||||
|
||||
```
|
||||
기존 구조:
|
||||
FileStorageService.php → Storage::disk('r2')->put()
|
||||
FileStorageController → POST /api/v1/files/upload (임시)
|
||||
ItemsFileController → POST /api/v1/items/{id}/files (품목 전용)
|
||||
File 모델 → files 테이블 (메타데이터)
|
||||
|
||||
경로 패턴:
|
||||
임시: {tenant_id}/temp/{year}/{month}/{stored_name}
|
||||
확정: {tenant_id}/items/{year}/{month}/{stored_name}
|
||||
```
|
||||
|
||||
### 절곡품 이미지 업로드 방안
|
||||
|
||||
**기존 `ItemsFileController` 재사용** (별도 이미지 컨트롤러 불필요):
|
||||
|
||||
```php
|
||||
// 이미 존재하는 엔드포인트 활용
|
||||
POST /api/v1/items/{id}/files ← 절곡품 이미지 업로드
|
||||
GET /api/v1/items/{id}/files ← 이미지 목록
|
||||
DELETE /api/v1/items/{id}/files/{fileId} ← 이미지 삭제
|
||||
|
||||
// field_key로 절곡품 이미지 구분
|
||||
field_key: 'bending_diagram' ← 전개도 이미지
|
||||
```
|
||||
|
||||
### R2 설정 (이미 구성됨)
|
||||
|
||||
```php
|
||||
// config/filesystems.php
|
||||
'r2' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('R2_ACCESS_KEY_ID'),
|
||||
'secret' => env('R2_SECRET_ACCESS_KEY'),
|
||||
'region' => 'auto',
|
||||
'bucket' => 'sam',
|
||||
'endpoint' => env('R2_ENDPOINT'),
|
||||
'use_path_style_endpoint' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 이미지 조회
|
||||
|
||||
```php
|
||||
// File 모델의 download() 메서드로 스트리밍
|
||||
GET /api/v1/files/{id}/view ← 인라인 표시 (브라우저)
|
||||
GET /api/v1/files/{id}/download ← 다운로드
|
||||
```
|
||||
|
||||
### 주의사항
|
||||
|
||||
- ❌ 별도 이미지 엔드포인트 생성 불필요 — `ItemsFileController` 재사용
|
||||
- ❌ 로컬 `storage/app/public/bending/` 직접 저장 금지 — R2 사용
|
||||
- ✅ `field_key: 'bending_diagram'`으로 전개도 이미지 식별
|
||||
- ✅ `files` 테이블에 메타데이터 자동 관리 (tenant_id, file_path, mime_type 등)
|
||||
- ✅ options에는 `image_path` 대신 `file_id` 참조 또는 `field_key`로 조회
|
||||
|
||||
---
|
||||
|
||||
## 6. options 상수 정의
|
||||
|
||||
```php
|
||||
// Item 모델에 추가 (또는 별도 상수 클래스)
|
||||
class Item extends Model
|
||||
{
|
||||
// 절곡품 options 키 상수
|
||||
const OPTION_ITEM_NAME = 'item_name';
|
||||
const OPTION_ITEM_SEP = 'item_sep';
|
||||
const OPTION_ITEM_BENDING = 'item_bending';
|
||||
const OPTION_ITEM_SPEC = 'item_spec';
|
||||
const OPTION_MATERIAL = 'material';
|
||||
const OPTION_MODEL_NAME = 'model_name';
|
||||
const OPTION_MODEL_UA = 'model_UA';
|
||||
const OPTION_SEARCH_KEYWORD = 'search_keyword';
|
||||
const OPTION_RAIL_WIDTH = 'rail_width';
|
||||
const OPTION_BENDING_DATA = 'bendingData';
|
||||
const OPTION_IMAGE_PATH = 'image_path';
|
||||
const OPTION_EXIT_DIRECTION = 'exit_direction';
|
||||
const OPTION_BOX_WIDTH = 'box_width';
|
||||
const OPTION_BOX_HEIGHT = 'box_height';
|
||||
const OPTION_FRONT_BOTTOM_WIDTH = 'front_bottom_width';
|
||||
const OPTION_MEMO = 'memo';
|
||||
const OPTION_AUTHOR = 'author';
|
||||
const OPTION_REGISTRATION_DATE = 'registration_date';
|
||||
const OPTION_PARENT_NUM = 'parent_num';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 주의사항
|
||||
|
||||
- ✅ 기존 `ItemsController` / `ItemsService` 무변경
|
||||
- ✅ items 테이블 스키마 무변경 — options JSON만 활용
|
||||
- ✅ `item_category = 'BENDING'` 필터로 기존 items API 영향 없음
|
||||
- ✅ `setOption()`으로 개별 키 업데이트 — 기존 키 보존
|
||||
- ✅ `ApiResponse::handle()` 사용 — 직접 JSON 반환 금지
|
||||
- ✅ FormRequest에서만 유효성 검증 — 컨트롤러 validate() 금지
|
||||
- ✅ i18n 메시지 키 사용 — 직접 문자열 금지
|
||||
- ✅ SoftDeletes 적용
|
||||
- ⚠️ `BendingInfoBuilder` / `PrefixResolver` 무변경
|
||||
524
dev/dev_plans/bending-management/step3-MNG화면.md
Normal file
524
dev/dev_plans/bending-management/step3-MNG화면.md
Normal file
@@ -0,0 +1,524 @@
|
||||
# Step 3: MNG 관리 화면 (Blade + HTMX)
|
||||
|
||||
> **프로젝트**: MNG (`sam/mng`)
|
||||
> **선행 조건**: Step 2 (API 엔드포인트) 완료
|
||||
> **참조**: 프로토타입 `SAM/work/절곡/`, MNG 기존 Blade 패턴
|
||||
|
||||
---
|
||||
|
||||
## 1. 메뉴 구조
|
||||
|
||||
### 생산관리 하위에 추가
|
||||
|
||||
```
|
||||
생산 관리 — DB menus 테이블 (동적 메뉴)
|
||||
├─ 품목기준 필드 관리 ✅
|
||||
├─ 견적수식 관리 ✅
|
||||
├─ 제품 관리 (준비중)
|
||||
├─ 자재 관리 (준비중)
|
||||
├─ BOM 관리 (준비중)
|
||||
├─ 카테고리 관리 (준비중)
|
||||
└─ 🆕 절곡품 관리 ← tinker로 menus 테이블에 추가
|
||||
├─ 기초관리 (/bending/base) ← 개별 부품 CRUD
|
||||
└─ 절곡품 (/bending/products) ← 모델별 조합 관리
|
||||
```
|
||||
|
||||
### 메뉴 등록 방법
|
||||
|
||||
⚠️ **시더 실행 금지** — tinker로 수동 등록
|
||||
⚠️ **sidebar-static.blade.php 사용 안 함** — 현재 레이아웃은 동적 사이드바(`partials/sidebar.blade.php`) 사용
|
||||
|
||||
MNG 사이드바는 DB `menus` 테이블 기반 동적 메뉴 시스템.
|
||||
`<x-sidebar.menu-tree :menus="$mainMenus" />` 컴포넌트로 렌더링됨.
|
||||
|
||||
#### tinker로 메뉴 추가 (서버에서 실행)
|
||||
|
||||
```bash
|
||||
ssh sam-server "cd /home/webservice/mng && php artisan tinker --execute=\"
|
||||
// 1. 생산관리 부모 메뉴 ID 확인
|
||||
\\\$parent = App\\\\Models\\\\Commons\\\\Menu::withoutGlobalScopes()
|
||||
->where('tenant_id', 1)
|
||||
->where('name', '생산 관리')
|
||||
->first();
|
||||
echo 'parent_id: ' . \\\$parent->id;
|
||||
|
||||
// 2. 현재 최대 sort_order 확인
|
||||
\\\$maxSort = App\\\\Models\\\\Commons\\\\Menu::withoutGlobalScopes()
|
||||
->where('parent_id', \\\$parent->id)
|
||||
->max('sort_order') ?? 0;
|
||||
|
||||
// 3. 절곡품 관리 그룹 메뉴 추가 (폴더)
|
||||
\\\$bending = App\\\\Models\\\\Commons\\\\Menu::create([
|
||||
'tenant_id' => 1,
|
||||
'parent_id' => \\\$parent->id,
|
||||
'name' => '절곡품 관리',
|
||||
'url' => null,
|
||||
'icon' => 'tools',
|
||||
'sort_order' => \\\$maxSort + 1,
|
||||
'is_active' => true,
|
||||
'options' => ['section' => 'main'],
|
||||
]);
|
||||
echo 'bending group id: ' . \\\$bending->id;
|
||||
|
||||
// 4. 하위 메뉴 추가
|
||||
App\\\\Models\\\\Commons\\\\Menu::create([
|
||||
'tenant_id' => 1,
|
||||
'parent_id' => \\\$bending->id,
|
||||
'name' => '기초관리',
|
||||
'url' => '/bending/base',
|
||||
'icon' => 'database',
|
||||
'sort_order' => 1,
|
||||
'is_active' => true,
|
||||
'options' => ['section' => 'main', 'route_name' => 'bending.base.index'],
|
||||
]);
|
||||
|
||||
App\\\\Models\\\\Commons\\\\Menu::create([
|
||||
'tenant_id' => 1,
|
||||
'parent_id' => \\\$bending->id,
|
||||
'name' => '절곡품',
|
||||
'url' => '/bending/products',
|
||||
'icon' => 'stack',
|
||||
'sort_order' => 2,
|
||||
'is_active' => true,
|
||||
'options' => ['section' => 'main', 'route_name' => 'bending.products.index'],
|
||||
]);
|
||||
|
||||
echo 'Done!';
|
||||
\""
|
||||
```
|
||||
|
||||
#### 확인용 SQL (phpMyAdmin)
|
||||
|
||||
```sql
|
||||
-- 생산관리 하위 메뉴 확인
|
||||
SELECT id, parent_id, name, url, sort_order, is_active
|
||||
FROM menus
|
||||
WHERE tenant_id = 1
|
||||
AND parent_id = (SELECT id FROM menus WHERE name = '생산 관리' AND tenant_id = 1 LIMIT 1)
|
||||
ORDER BY sort_order;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 라우트
|
||||
|
||||
```php
|
||||
// routes/web.php
|
||||
|
||||
// 파일 뷰어 (R2 이미지 스트리밍 — MNG 세션 인증)
|
||||
Route::get('/files/{id}/view', [FileViewController::class, 'show'])->name('files.view');
|
||||
|
||||
Route::prefix('bending')->name('bending.')->group(function () {
|
||||
// 기초관리
|
||||
Route::get('/base', [BendingBaseController::class, 'index'])->name('base.index');
|
||||
Route::get('/base/create', [BendingBaseController::class, 'create'])->name('base.create');
|
||||
Route::post('/base', [BendingBaseController::class, 'store'])->name('base.store');
|
||||
Route::get('/base/{id}', [BendingBaseController::class, 'show'])->name('base.show');
|
||||
Route::get('/base/{id}/edit', [BendingBaseController::class, 'edit'])->name('base.edit');
|
||||
Route::put('/base/{id}', [BendingBaseController::class, 'update'])->name('base.update');
|
||||
Route::delete('/base/{id}', [BendingBaseController::class, 'destroy'])->name('base.destroy');
|
||||
|
||||
// 절곡품 (모델)
|
||||
Route::get('/products', [BendingProductController::class, 'index'])->name('products.index');
|
||||
Route::get('/products/create', [BendingProductController::class, 'create'])->name('products.create');
|
||||
Route::post('/products', [BendingProductController::class, 'store'])->name('products.store');
|
||||
Route::get('/products/{id}', [BendingProductController::class, 'show'])->name('products.show');
|
||||
Route::get('/products/{id}/edit', [BendingProductController::class, 'edit'])->name('products.edit');
|
||||
Route::put('/products/{id}', [BendingProductController::class, 'update'])->name('products.update');
|
||||
Route::delete('/products/{id}', [BendingProductController::class, 'destroy'])->name('products.destroy');
|
||||
});
|
||||
```
|
||||
|
||||
### 파일 뷰어 (R2 이미지 프록시)
|
||||
|
||||
MNG는 Blade(서버사이드)이므로 `<img src="/api/v1/files/{id}/view">`로 직접 호출 시 sanctum 인증 문제 발생.
|
||||
MNG 세션 인증으로 R2 파일을 스트리밍하는 프록시 라우트 필요.
|
||||
|
||||
```php
|
||||
// FileViewController.php
|
||||
class FileViewController extends Controller
|
||||
{
|
||||
public function show(int $id)
|
||||
{
|
||||
$file = File::findOrFail($id);
|
||||
$stream = Storage::disk('r2')->readStream($file->file_path);
|
||||
|
||||
return response()->stream(function () use ($stream) {
|
||||
fpassthru($stream);
|
||||
if (is_resource($stream)) fclose($stream);
|
||||
}, 200, [
|
||||
'Content-Type' => $file->mime_type,
|
||||
'Content-Disposition' => 'inline',
|
||||
'Cache-Control' => 'private, max-age=3600',
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Blade에서 사용**:
|
||||
```html
|
||||
<!-- 전개도 이미지 표시 -->
|
||||
<img src="{{ route('files.view', $file->id) }}" alt="전개도">
|
||||
|
||||
<!-- 이미지 없을 때 fallback -->
|
||||
@if($fileId)
|
||||
<img src="{{ route('files.view', $fileId) }}" alt="전개도" class="max-w-full rounded">
|
||||
@else
|
||||
<div class="text-gray-400 text-center py-8">이미지 없음</div>
|
||||
@endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 화면 구성
|
||||
|
||||
### 3-1. 기초관리 목록 (`/bending/base`)
|
||||
|
||||
**프로토타입 참고**: `work/절곡/base.html`
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 절곡 바라시 기초자료 [+ 신규 등록] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 필터: │
|
||||
│ [전체|스크린|철재] [전체|인정|비인정] [그룹▼] [품명▼] [검색] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ NO│등록일│대분류│인정│절곡물분류│품명│규격│이미지│재질│ │
|
||||
│ │ │ │ │ │ │ │ │ │... │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 265건 (1~15) [< 1 2 3 ... >] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**테이블 컬럼**: NO, 등록일, 대분류, 인정, 절곡물분류, 품명, 규격, 이미지, 재질, 폭합계, 절곡횟수, 역방향, A각, 폭합, 작성, 검색어, 비고, 작업
|
||||
|
||||
**HTMX 인터랙션**:
|
||||
- 필터 토글 → `hx-get="/bending/base"` → 테이블 교체
|
||||
- 검색 입력 → `hx-trigger="keyup changed delay:300ms"`
|
||||
- 행 클릭 → 상세 페이지 이동
|
||||
|
||||
### 3-2. 기초관리 등록/수정 (`/bending/base/create`, `/bending/base/{id}/edit`)
|
||||
|
||||
**프로토타입 참고**: `work/절곡/base-form.html`
|
||||
|
||||
```
|
||||
┌───────────────────────────────────┬──────────────────┐
|
||||
│ [기본 정보] │ [형상 이미지] │
|
||||
│ 등록일 | 대분류 | 인정 │ 이미지 업로드 │
|
||||
│ 그룹 | 품명 | 재질 │ 이미지 미리보기 │
|
||||
│ 폭합 | 규격 | 작성자 | 비고 │ 품목검색어 │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [케이스 전용] (그룹=케이스 시) │ │
|
||||
│ 점검구방향 | 너비 | 높이 | 전면밑 | 레일폭 │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [절곡 입력 테이블] ★핵심 │ │
|
||||
│ 번호 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ │
|
||||
│ 입력 │ │ │ │ │ │ │ │
|
||||
│ 연신율│ │ │ │ │ │ │ │
|
||||
│ 연신율후│ │ │ │ │ │ │ │
|
||||
│ 합계 │ │ │ │ │ │ │ │
|
||||
│ 음영 │☐ │☐ │☐ │☐ │☐ │☐ │ │
|
||||
│ A각 │☐ │☐ │☐ │☐ │☐ │☐ │ │
|
||||
│ [비우기] [열추가] [열삭제] │ │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [재질별 폭합] │ │
|
||||
│ 재질 | 폭합계 │ │
|
||||
└───────────────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
**JS 동작 (필수)**:
|
||||
- 입력 시 합계 자동계산
|
||||
- 연신율 입력 시 연신율후 자동계산: rate="-1" → input-1mm, rate="1" → input+1mm, rate="" → input 그대로 (절곡 1회당 고정 1mm 보정)
|
||||
- 열 추가/삭제 동적 DOM
|
||||
- 그룹 변경 시 케이스 전용 필드 토글
|
||||
- 폭합 필드 자동 업데이트
|
||||
- 조회 모드: 입력 비활성화
|
||||
|
||||
### 3-3. 절곡품 목록 (`/bending/products`)
|
||||
|
||||
**프로토타입 참고**: `work/절곡/products.html`
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 절곡품 관리 [+ 신규 등록] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ [가이드레일 20] [케이스 30] [하단마감재 11] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 필터: (탭별 다른 필터) │
|
||||
│ 가이드레일: [대분류] [인정] [모델▼] [검색] │
|
||||
│ 케이스: [대분류] [인정] [점검구형태] [검색] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ (탭별 다른 테이블 컬럼) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**탭별 컬럼**:
|
||||
- 가이드레일: 번호, 등록일, 대분류, 인정, 제품코드, 검색어, 가로X세로, 형상, 마감, 소요자재량, 형태, 작성, 비고
|
||||
- 케이스: 번호, 등록일, 박스(가로X세로), 점검구형태, 전면부밑면, 레일폭, 소요자재량, 검색어, 형태, 작성, 비고
|
||||
- 하단마감재: 번호, 등록일, 대분류, 인정, 제품코드, 가로X세로, 검색어, 마감형태, 소요자재량, 형태, 작성, 비고
|
||||
|
||||
### 3-4. 절곡품 등록/수정 (`/bending/products/create`, `/bending/products/{id}/edit`)
|
||||
|
||||
**프로토타입 참고**: `work/절곡/product-form.html`
|
||||
|
||||
타입별로 폼 헤더가 다름 — 아래 3가지 구분:
|
||||
|
||||
#### 가이드레일 폼
|
||||
|
||||
```
|
||||
┌───────────────────────────────────┬──────────────────┐
|
||||
│ [기본 정보] │ [형상 이미지] │
|
||||
│ 등록일 | 작성자 | 비고 │ 이미지 업로드 │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [가이드레일 정보] │ 품목검색어 │
|
||||
│ 가로(폭) × 세로(높이) │ │
|
||||
│ 대분류: ○스크린 ○철재 │ │
|
||||
│ 인정: ○인정 ○비인정 │ │
|
||||
│ 모델: [KSS01 ▼] │ │
|
||||
│ 마감: [SUS마감 ▼] │ │
|
||||
│ 형상: [벽면형 ▼] │ │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [절곡 입력] ★핵심 │ │
|
||||
│ 파트 탭: [본체상부] [본체하부] [마감재] │
|
||||
│ (파트별 절곡 테이블) │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [재질별 폭합] │ │
|
||||
│ 재질 | 폭합계 │ │
|
||||
└───────────────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
#### 케이스 폼
|
||||
|
||||
```
|
||||
┌───────────────────────────────────┬──────────────────┐
|
||||
│ [기본 정보] │ [형상 이미지] │
|
||||
│ 등록일 | 작성자 │ 이미지 업로드 │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [케이스 정보] │ 품목검색어 │
|
||||
│ 가로(폭) × 세로(높이) │ 비고 │
|
||||
│ 전면밑: [50] | 레일폭: [75] │ │
|
||||
│ 점검구: ○양면 ○밑면 ○후면 │ │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [절곡 입력] ★핵심 │ │
|
||||
│ 파트 탭: [상부덮개] [전면] [점검구] [린텔] [후면코너] │
|
||||
│ (파트별 절곡 테이블) │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [재질별 폭합] │ │
|
||||
│ EGI 1.55T | 2652 │ │
|
||||
└───────────────────────────────────┴──────────────────┘
|
||||
```
|
||||
※ 케이스는 대분류/인정/모델/마감 필드 **없음** — 규격+점검구형태로만 구분
|
||||
|
||||
#### 하단마감재 폼
|
||||
|
||||
```
|
||||
┌───────────────────────────────────┬──────────────────┐
|
||||
│ [기본 정보] │ [형상 이미지] │
|
||||
│ 등록일 | 작성자 | 비고 │ 이미지 업로드 │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [하단마감재 정보] │ 품목검색어 │
|
||||
│ 가로(폭) × 세로(높이) │ │
|
||||
│ 대분류: ○스크린 ○철재 │ │
|
||||
│ 인정: ○인정 ○비인정 │ │
|
||||
│ 모델: [KSS01 ▼] │ │
|
||||
│ 마감: [SUS마감 ▼] │ │
|
||||
│ (형상 필드 없음) │ │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [절곡 입력] ★핵심 │ │
|
||||
│ 파트 1개 (하단마감재 단일) │
|
||||
├───────────────────────────────────┤ │
|
||||
│ [재질별 폭합] │ │
|
||||
│ 재질 | 폭합계 │ │
|
||||
└───────────────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
**타입별 폼 차이 요약**:
|
||||
|
||||
| 필드 | 가이드레일 | 케이스 | 하단마감재 |
|
||||
|------|-----------|--------|----------|
|
||||
| 등록일/작성자/비고 | ✅ | ✅ | ✅ |
|
||||
| 가로×세로 | ✅ | ✅ | ✅ |
|
||||
| 대분류 (스크린/철재) | ✅ | ❌ | ✅ |
|
||||
| 인정/비인정 | ✅ | ❌ | ✅ |
|
||||
| 모델 | ✅ | ❌ | ✅ |
|
||||
| 마감 (SUS/EGI) | ✅ | ❌ | ✅ |
|
||||
| 형상 (벽면/측면) | ✅ | ❌ | ❌ |
|
||||
| 전면밑/레일폭 | ❌ | ✅ | ❌ |
|
||||
| 점검구 형태 | ❌ | ✅ | ❌ |
|
||||
| 파트 수 | 3~5 | 5 | 1 |
|
||||
| 품목검색어 | ✅ | ✅ | ✅ |
|
||||
| 재질별 폭합 | ✅ | ✅ | ✅ |
|
||||
|
||||
**파트 구성**:
|
||||
- 가이드레일: 3~5파트 (본체 상부, 본체 하부, 마감재, ...)
|
||||
- 케이스: 5파트 (상부덮개, 전면, 점검구, 린텔, 후면코너)
|
||||
- 하단마감재: 1파트
|
||||
|
||||
---
|
||||
|
||||
## 4. Blade 파일 구조
|
||||
|
||||
```
|
||||
resources/views/bending/
|
||||
├─ base/
|
||||
│ ├─ index.blade.php ← 기초관리 목록
|
||||
│ ├─ form.blade.php ← 등록/수정/조회 (mode 분기)
|
||||
│ └─ partials/
|
||||
│ ├─ table.blade.php ← HTMX 갱신 대상
|
||||
│ ├─ filters.blade.php ← 필터 영역
|
||||
│ └─ bend-table.blade.php ← 절곡 입력 테이블 (재사용)
|
||||
├─ products/
|
||||
│ ├─ index.blade.php ← 절곡품 탭 목록
|
||||
│ ├─ form.blade.php ← 등록/수정
|
||||
│ └─ partials/
|
||||
│ ├─ tab-guiderail.blade.php ← 가이드레일 탭 테이블
|
||||
│ ├─ tab-case.blade.php ← 케이스 탭 테이블
|
||||
│ ├─ tab-bottom.blade.php ← 하단마감재 탭 테이블
|
||||
│ └─ filters-*.blade.php ← 탭별 필터
|
||||
└─ components/
|
||||
└─ bend-input-table.blade.php ← 절곡 입력 테이블 공용 컴포넌트
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 기존 MNG 패턴 준수
|
||||
|
||||
| 항목 | 기존 패턴 | 적용 |
|
||||
|------|----------|------|
|
||||
| 레이아웃 | `layouts/app.blade.php` 상속 | `@extends('layouts.app')` |
|
||||
| 사이드바 | `partials/sidebar.blade.php` (동적 DB 메뉴) | tinker로 `menus` 테이블에 추가 |
|
||||
| HTMX | 기존 페이지 패턴 참고 | `hx-get`, `hx-target`, `hx-trigger` |
|
||||
| Tailwind | 기존 클래스 패턴 | 동일 스타일 사용 |
|
||||
| 테이블 | 기존 목록 페이지 참고 | 정렬/페이지네이션 동일 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 주의사항
|
||||
|
||||
### 아키텍처
|
||||
- ✅ **MNG는 샘플 확인용** — 실제 운영 화면은 React
|
||||
- ✅ **MNG/React 모두 동일한 API 엔드포인트 호출** (`/api/v1/bending-items`, `/api/v1/guiderail-models`)
|
||||
- ✅ MNG에서 API 연동 검증 후 React 화면 구현으로 진행
|
||||
- ❌ MNG에서 Eloquent 직접 DB 접근 금지 — 반드시 API 통해 접근
|
||||
|
||||
### 메뉴/사이드바
|
||||
- ⚠️ 메뉴 시더 실행 금지
|
||||
- ⚠️ sidebar-static.blade.php 사용 안 함 — 동적 메뉴(DB `menus` 테이블) 사용
|
||||
- ✅ tinker로 `menus` 테이블에 직접 추가
|
||||
|
||||
### 기존 코드 보호
|
||||
- ⚠️ 기존 bending-worklog.blade.php 무변경
|
||||
- ⚠️ 기존 bending-inspection-data.blade.php 무변경
|
||||
- ⚠️ BendingInfoBuilder / PrefixResolver 무변경
|
||||
|
||||
---
|
||||
|
||||
## 7. 형상 이미지 구현 전략 (단계별)
|
||||
|
||||
### 1차: 이미지 업로드만
|
||||
|
||||
MNG는 샘플 확인용이므로 1차에서는 **파일 업로드 + 미리보기**만 구현.
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ [형상 이미지] │
|
||||
│ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ 미리보기 │ │
|
||||
│ │ (없으면 │ │
|
||||
│ │ placeholder)│ │
|
||||
│ └────────────┘ │
|
||||
│ │
|
||||
│ [파일 선택] │ ← input[type=file] accept="image/*"
|
||||
│ [Ctrl+V 붙여넣기]│ ← 클립보드 이미지 지원
|
||||
│ 품목검색어: [___] │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
**구현 범위**:
|
||||
|
||||
| 기능 | 1차 | 2차 |
|
||||
|------|-----|-----|
|
||||
| 파일 업로드 (`input[type=file]`) | ✅ | ✅ |
|
||||
| 이미지 미리보기 | ✅ | ✅ |
|
||||
| Ctrl+V 클립보드 붙여넣기 | ✅ | ✅ |
|
||||
| R2 저장 (API files 엔드포인트) | ✅ | ✅ |
|
||||
| 기존 이미지 표시 (`/files/{id}/view`) | ✅ | ✅ |
|
||||
| Canvas 그리기 도구 | ❌ | ✅ |
|
||||
| 호버 시 확대 팝업 | ❌ | ✅ |
|
||||
|
||||
**1차 업로드 흐름**:
|
||||
```
|
||||
[파일 선택] or [Ctrl+V]
|
||||
→ 미리보기 표시 (FileReader → img.src)
|
||||
→ 폼 저장 시 FormData로 API 전송
|
||||
→ API가 R2에 저장 → file_id 반환
|
||||
→ bending_base_data.image_file_id에 저장
|
||||
```
|
||||
|
||||
**Blade 이미지 업로드 컴포넌트**:
|
||||
```html
|
||||
<!-- 1차: 단순 업로드 + 미리보기 -->
|
||||
<div class="border-2 border-dashed border-gray-300 rounded-lg p-4">
|
||||
@if($imageFileId)
|
||||
<img src="{{ route('files.view', $imageFileId) }}"
|
||||
class="max-w-full rounded mb-2" alt="전개도">
|
||||
@else
|
||||
<div class="text-gray-400 text-center py-8">이미지 없음</div>
|
||||
@endif
|
||||
|
||||
<input type="file" name="image" accept="image/*"
|
||||
onchange="previewImage(this)" class="mt-2">
|
||||
<img id="image-preview" class="hidden max-w-full rounded mt-2">
|
||||
</div>
|
||||
```
|
||||
|
||||
**클립보드 붙여넣기 JS**:
|
||||
```javascript
|
||||
document.addEventListener('paste', function(e) {
|
||||
const items = e.clipboardData?.items;
|
||||
if (!items) return;
|
||||
for (const item of items) {
|
||||
if (item.type.startsWith('image/')) {
|
||||
const file = item.getAsFile();
|
||||
const dt = new DataTransfer();
|
||||
dt.items.add(file);
|
||||
document.querySelector('input[name="image"]').files = dt.files;
|
||||
previewImage(document.querySelector('input[name="image"]'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function previewImage(input) {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const preview = document.getElementById('image-preview');
|
||||
preview.src = e.target.result;
|
||||
preview.classList.remove('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
```
|
||||
|
||||
### 2차: Canvas 그리기 도구 추가 (React 화면과 함께)
|
||||
|
||||
레거시 `5130/js/imageEditor.js` (Fabric.js 기반, 511줄) 기반으로 Canvas 에디터 통합.
|
||||
React 화면 구현 시 함께 진행 — MNG에는 필요 시에만 백포트.
|
||||
|
||||
**레거시 Canvas 에디터 파일 위치**:
|
||||
|
||||
| 파일 | 위치 | 용도 |
|
||||
|------|------|------|
|
||||
| `imageEditor.js` | `5130/js/imageEditor.js` | Fabric.js Canvas 에디터 (511줄) |
|
||||
| `drawLib.js` | `5130/js/drawLib.js` | Pure Canvas 대안 (272줄) |
|
||||
| `drawingModule.js` | `5130/js/drawingModule.js` | 독립 모달 포함 (966줄) |
|
||||
| `imageHandler.js` | `5130/guiderail/js/imageHandler.js` | 이미지 검색/호버 팝업 |
|
||||
|
||||
**2차 추가 기능**:
|
||||
- [그리기] 버튼 → Canvas 모달 (Poly/Free/Line/Text/Eraser)
|
||||
- 직각 고정 모드
|
||||
- 그린 이미지 → Base64 → API 저장
|
||||
- 목록에서 이미지 호버 시 확대 팝업
|
||||
89
dev/dev_plans/bending-management/step4-React연동.md
Normal file
89
dev/dev_plans/bending-management/step4-React연동.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Step 4: React 견적 화면 이미지 연동
|
||||
|
||||
> **프로젝트**: React (`sam/react`)
|
||||
> **선행 조건**: Step 2 (API), Step 3 (MNG에서 데이터 등록 후)
|
||||
> **참조**: 기존 GuideRailSection 컴포넌트
|
||||
|
||||
---
|
||||
|
||||
## 1. 목적
|
||||
|
||||
견적 페이지(`/sales/quote-management/new`)에서 가이드레일 모델 선택 시
|
||||
전개도 이미지 + 부품 조합 테이블을 표시한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 흐름
|
||||
|
||||
```
|
||||
제품 선택 → BOM 계산 (BendingInfoBuilder)
|
||||
→ product_code, finish_material 확정
|
||||
→ 가이드레일 모델 결정
|
||||
→ 텍스트만 표시 ❌ 이미지 없음
|
||||
```
|
||||
|
||||
## 3. 목표 흐름
|
||||
|
||||
```
|
||||
제품 선택 → BOM 계산 (기존 그대로)
|
||||
→ 모델 확정
|
||||
→ GET /api/guiderail-models/{id} 호출 🆕
|
||||
→ GuiderailPreview 컴포넌트 렌더링 🆕
|
||||
├─ 전개도 이미지
|
||||
└─ 부품 조합 테이블 (부품명/재질/수량/전개폭)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 구현 사항
|
||||
|
||||
### 4-1. GuiderailPreview 컴포넌트
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 가이드레일: KSS01 벽면형 | 인정 | SUS마감 | 70×120 │
|
||||
├──────────────────────┬──────────────────────────────┤
|
||||
│ 전개도 이미지 │ 부품 조합 │
|
||||
│ ┌────────────────┐ │ # │ 부품 │ 재질 │ 수량 │
|
||||
│ │ │ │ 1 │ 마감재 │ SUS │ 2 │
|
||||
│ │ (이미지) │ │ 2 │ 본체 │ EGI │ 1 │
|
||||
│ │ │ │ 3 │ C형 │ EGI │ 1 │
|
||||
│ └────────────────┘ │ 4 │ D형 │ EGI │ 1 │
|
||||
└──────────────────────┴──────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4-2. 삽입 위치
|
||||
|
||||
견적 페이지에서 BOM 결과 표시 영역 하단 (기존 레이아웃 무변경)
|
||||
|
||||
### 4-3. 데이터 흐름
|
||||
|
||||
```
|
||||
BOM 계산 결과 → product_code + finish_material
|
||||
→ API 호출: GET /api/guiderail-models?model={code}&check_type={형상}
|
||||
→ 응답: image_url + components + material_summary
|
||||
→ GuiderailPreview 렌더링
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 주의사항
|
||||
|
||||
- 기존 견적 계산 로직 무변경
|
||||
- 기존 GuideRailSection (작업지시서용) 무변경 — 별도 컴포넌트
|
||||
- 이미지 없는 모델: 텍스트만 표시 (graceful degradation)
|
||||
- 모바일 반응형 처리
|
||||
|
||||
---
|
||||
|
||||
## 6. 범위 (추후 확정)
|
||||
|
||||
| 영역 | 설명 | 시점 |
|
||||
|------|------|------|
|
||||
| 견적 이미지 연동 | GuiderailPreview 컴포넌트 | Step 1~3 완료 후 |
|
||||
| 절곡품 관리 화면 | React 버전 CRUD (MNG 대체) | MNG 샘플 검증 후 |
|
||||
|
||||
- MNG는 **샘플 확인용** — API 연동 검증이 목적
|
||||
- **실제 운영 화면은 React**에서 구현 (MNG와 동일한 API 호출)
|
||||
- React 화면 상세 설계는 MNG 검증 후 별도 문서로 작성 예정
|
||||
- 현재 문서는 견적 이미지 연동 범위만 정의
|
||||
Reference in New Issue
Block a user