From 828b452186dda45f873a032c2d7363115ff95026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Mon, 16 Mar 2026 17:40:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[bending]=20=EC=A0=88=EA=B3=A1=ED=92=88?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=20=EA=B3=84=ED=9A=8D=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README.md: 전체 개요, 메뉴 구조, 작업 순서 - step1-데이터분석.md: 레거시 매핑 + options 확장 스키마 - step2-API.md: 엔드포인트 설계 (docs 규칙 준수) - step3-MNG화면.md: Blade+HTMX 화면 구성 (3타입별 폼) - step4-React연동.md: 견적 이미지 + 운영 화면 계획 --- dev/dev_plans/bending-management/README.md | 390 +++++++++++++ .../bending-management/step1-데이터분석.md | 282 ++++++++++ dev/dev_plans/bending-management/step2-API.md | 525 ++++++++++++++++++ .../bending-management/step3-MNG화면.md | 524 +++++++++++++++++ .../bending-management/step4-React연동.md | 89 +++ 5 files changed, 1810 insertions(+) create mode 100644 dev/dev_plans/bending-management/README.md create mode 100644 dev/dev_plans/bending-management/step1-데이터분석.md create mode 100644 dev/dev_plans/bending-management/step2-API.md create mode 100644 dev/dev_plans/bending-management/step3-MNG화면.md create mode 100644 dev/dev_plans/bending-management/step4-React연동.md diff --git a/dev/dev_plans/bending-management/README.md b/dev/dev_plans/bending-management/README.md new file mode 100644 index 0000000..cab0be7 --- /dev/null +++ b/dev/dev_plans/bending-management/README.md @@ -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 +``` + + diff --git a/dev/dev_plans/bending-management/step1-데이터분석.md b/dev/dev_plans/bending-management/step1-데이터분석.md new file mode 100644 index 0000000..ae1104b --- /dev/null +++ b/dev/dev_plans/bending-management/step1-데이터분석.md @@ -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) + - [ ] 회귀 테스트 결과 diff --git a/dev/dev_plans/bending-management/step2-API.md b/dev/dev_plans/bending-management/step2-API.md new file mode 100644 index 0000000..30a6cbe --- /dev/null +++ b/dev/dev_plans/bending-management/step2-API.md @@ -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` 무변경 diff --git a/dev/dev_plans/bending-management/step3-MNG화면.md b/dev/dev_plans/bending-management/step3-MNG화면.md new file mode 100644 index 0000000..0be37b7 --- /dev/null +++ b/dev/dev_plans/bending-management/step3-MNG화면.md @@ -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` 테이블 기반 동적 메뉴 시스템. +`` 컴포넌트로 렌더링됨. + +#### 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(서버사이드)이므로 ``로 직접 호출 시 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 + +전개도 + + +@if($fileId) + 전개도 +@else +
이미지 없음
+@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 + +
+ @if($imageFileId) + 전개도 + @else +
이미지 없음
+ @endif + + + +
+``` + +**클립보드 붙여넣기 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 저장 +- 목록에서 이미지 호버 시 확대 팝업 diff --git a/dev/dev_plans/bending-management/step4-React연동.md b/dev/dev_plans/bending-management/step4-React연동.md new file mode 100644 index 0000000..61d0b53 --- /dev/null +++ b/dev/dev_plans/bending-management/step4-React연동.md @@ -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 검증 후 별도 문서로 작성 예정 +- 현재 문서는 견적 이미지 연동 범위만 정의