--- source: Phase 0 + 프론트엔드 가이드 통합 분석 section: BP-MES 백엔드 개발 로드맵 (개정판) created: 2025-11-13 updated: 2025-11-13 bp_mes_phase: Phase 0 완료, Phase 1 준비 related: - ITEM_MANAGEMENT_MIGRATION_GUIDE.md (프론트엔드 가이드) - PHASE_0_FINAL_REPORT.md - api_gap_validation_report.md tags: [backend, roadmap, api, database, laravel] --- # BP-MES 백엔드 개발 로드맵 V2 **작성일:** 2025-11-13 **기반:** Next.js 15 프론트엔드 가이드 + Phase 0 분석 **대상:** 백엔드 개발자 (프론트엔드 작업 제외) **프레임워크:** Laravel 12 + PHP 8.2+ --- ## 📋 Executive Summary ### 변경 사항 - ✅ **프론트엔드 가이드 통합:** Next.js 15 마이그레이션 가이드 기반 API 요구사항 반영 - ✅ **우선순위 재조정:** 프론트엔드 80% 물리적 페이지 → 백엔드 고정 필드 우선 - ✅ **선택적 확장:** 동적 템플릿 시스템 2차 목표로 후순위 - ⚠️ **프론트엔드 작업 제외:** 백엔드 API 개발에만 집중 ### 핵심 방향 1. **하이브리드 전략:** 고정 필드 API (우선) + 메타데이터 API (선택) 2. **RESTful 설계:** 프론트 가이드 엔드포인트 스펙 준수 3. **BOM 계산 강화:** quantityFormula, 계층 구조, tree API 4. **파일 관리:** 절곡도, 시방서, 인정서 업로드 --- ## 🎯 프론트엔드 요구사항 분석 ### 1. 데이터 구조 (프론트엔드 가이드 기준) #### 1.1 ItemMaster 필드 구성 **공통 필드 (ALL):** ``` 필수: - id, itemCode, itemName, itemType, unit - specification, isActive 분류: - category1, category2, category3 가격: - purchasePrice, salesPrice, marginRate - processingCost, laborCost, installCost BOM: - bom[] (BOMLine 배열) - bomCategories[] 메타: - safetyStock, leadTime, isVariableSize - revisions[] - createdAt, updatedAt ``` **FG (제품) 전용:** ``` - productCategory: 'SCREEN' | 'STEEL' - lotAbbreviation: string (예: "KD") - note: string ``` **PT (부품) 전용:** ``` 기본: - partType: 'ASSEMBLY' | 'BENDING' | 'PURCHASED' - partUsage: 'GUIDE_RAIL' | 'BOTTOM_FINISH' | 'CASE' | 'DOOR' | 'BRACKET' | 'GENERAL' 조립 부품: - installationType, assemblyType - sideSpecWidth, sideSpecHeight, assemblyLength - guideRailModelType, guideRailModel (가이드레일) 절곡품: - bendingDiagram: string (이미지 URL) - bendingDetails: BendingDetail[] (전개도 데이터) - material, length, bendingLength ``` **인증 정보 (FG/PT):** ``` - certificationNumber - certificationStartDate, certificationEndDate - specificationFile, specificationFileName - certificationFile, certificationFileName ``` --- #### 1.2 BOMLine 구조 ``` 필수: - id, childItemCode, childItemName - quantity, unit, unitPrice - note 핵심: - quantityFormula: string // 🔴 "W * 2", "H + 100", "G/1000*1.02" 절곡품: - isBending: boolean - bendingDiagram: string - bendingDetails: BendingDetail[] ``` **백엔드 요구:** - ✅ quantityFormula 저장 (text 컬럼) - ✅ 계산 로직 (수식 파싱 및 실행) --- #### 1.3 BendingDetail 구조 ```typescript { id: string no: number // 순서 input: number // 입력값 elongation: number // 연신율 (-1 기본) calculated: number // 계산 후 값 sum: number // 합계 shaded: boolean // 음영 여부 aAngle?: number // A각 } ``` **백엔드 요구:** - ✅ JSON 컬럼으로 저장 - ⚠️ 계산 로직: 프론트 vs 백엔드 협의 필요 --- ### 2. API 엔드포인트 (프론트엔드 가이드) #### 2.1 품목 CRUD (필수) ``` GET /api/items Query Parameters: - itemType?: string (FG,PT,SM,RM,CS) - search?: string - category1?: string Response: { data: ItemMaster[] meta: { total, page, perPage } } GET /api/items/:itemCode Response: { data: ItemMaster (BOM 포함) } POST /api/items Body: Partial Response: { data: ItemMaster } PUT /api/items/:itemCode Body: Partial Response: { data: ItemMaster } DELETE /api/items/:itemCode Response: { message: "Deleted" } ``` --- #### 2.2 BOM 관리 (필수) ``` GET /api/items/:itemCode/bom Response: { data: BOMLine[] (flat list) } GET /api/items/:itemCode/bom/tree 🔴 Phase 0에서 부재 확인 Response: { data: { item: ItemMaster children: [ { bomLine: BOMLine item: ItemMaster children: [...] // 재귀 } ] } } POST /api/items/:itemCode/bom Body: Partial Response: { data: BOMLine } PUT /api/items/:itemCode/bom/:lineId Body: Partial Response: { data: BOMLine } DELETE /api/items/:itemCode/bom/:lineId Response: { message: "Deleted" } ``` --- #### 2.3 파일 관리 (필수) ``` POST /api/items/:itemCode/files Body: FormData - type: 'specification' | 'certification' | 'bending_diagram' - file: File Response: { data: { url: string fileName: string type: string } } DELETE /api/items/:itemCode/files/:type Response: { message: "Deleted" } ``` --- #### 2.4 동적 템플릿 (선택적, 2차 목표) ``` GET /api/item-templates GET /api/item-templates/:pageId POST /api/item-templates PUT /api/item-templates/:pageId DELETE /api/item-templates/:pageId ``` **우선순위:** 🟡 중간 (Phase 2-3 이후) --- ## 🔍 Phase 0 Gap 통합 분석 ### 기존 구현 현황 (Phase 0 검증 결과) | Gap | 설명 | 상태 | 영향 | |-----|------|------|------| | #1 | 통합 품목 조회 API | ✅ 해결 | ItemsController 존재 | | #2 | 치수 연결 매핑 | ❌ 미해결 | dimension_mappings 테이블 필요 | | #3 | 실시간 견적 계산 | ⚠️ 부분 | BomCalculationController (설계만) | | #4 | BOM formula 필드 | ❌ 미해결 | product_components.formula 추가 | | #5 | 조건부 BOM | ❌ 미해결 | product_components.condition 추가 | | #6 | options/dimensions | ⚠️ 부분 | Material만, Product 없음 | | #7 | 가격 통합 조회 | ✅ 해결 | PricingService 완성 | --- ### 프론트엔드 가이드 vs Phase 0 Gap | 프론트 요구사항 | Phase 0 Gap | 우선순위 | |----------------|-------------|---------| | ItemMaster 필드 | #6 (options) | 🔴 P0 | | BOMLine.quantityFormula | #4 (formula) | 🔴 P0 | | BOM tree API | 신규 발견 | 🔴 P0 | | 파일 업로드 | 기존 존재 | 🟢 완료 | | 치수 연결 | #2 | 🟡 P1 | | 견적 계산 API | #3 | 🟡 P1 | | 조건부 BOM | #5 | 🟡 P1 | --- ## 🗂️ 데이터베이스 설계 ### 1. products 테이블 확장 **현재 구조:** ```sql -- 기존 필드 (Phase 0 분석) id, tenant_id, item_code, item_name, item_type specification, unit, category1, category2, category3 purchase_price, sales_price attributes (JSON) created_by, updated_by, deleted_at, timestamps ``` **추가 필요 필드 (프론트 가이드 기반):** ```sql -- 공통 is_active BOOLEAN DEFAULT true margin_rate DECIMAL(5,2) processing_cost DECIMAL(10,2) labor_cost DECIMAL(10,2) install_cost DECIMAL(10,2) safety_stock INT lead_time INT is_variable_size BOOLEAN DEFAULT false -- FG 전용 product_category VARCHAR(20) -- SCREEN, STEEL lot_abbreviation VARCHAR(10) note TEXT -- PT 전용 part_type VARCHAR(20) -- ASSEMBLY, BENDING, PURCHASED part_usage VARCHAR(30) -- GUIDE_RAIL, BOTTOM_FINISH, ... installation_type VARCHAR(20) assembly_type VARCHAR(20) side_spec_width VARCHAR(20) side_spec_height VARCHAR(20) assembly_length VARCHAR(20) guide_rail_model_type VARCHAR(50) guide_rail_model VARCHAR(50) -- 절곡품 bending_diagram VARCHAR(255) -- 이미지 URL bending_details JSON -- BendingDetail[] material VARCHAR(50) length VARCHAR(20) bending_length VARCHAR(20) -- 인증 certification_number VARCHAR(50) certification_start_date DATE certification_end_date DATE specification_file VARCHAR(255) specification_file_name VARCHAR(255) certification_file VARCHAR(255) certification_file_name VARCHAR(255) -- 동적 확장 (기존 유지) options JSON -- 🔴 신규 추가 필요 ``` **Migration 작업량:** 2-3일 --- ### 2. product_components 테이블 확장 **현재 구조:** ```sql id, tenant_id parent_product_id, child_product_id (또는 material_id) quantity, unit, unit_price note created_by, updated_by, deleted_at, timestamps ``` **추가 필요 필드:** ```sql -- 핵심 필드 quantity_formula TEXT -- 🔴 "W * 2", "H + 100", "G/1000*1.02" condition TEXT -- 🔴 "MOTOR='Y'", "WIDTH > 3000" -- 절곡품 is_bending BOOLEAN DEFAULT false bending_diagram VARCHAR(255) bending_details JSON ``` **Migration 작업량:** 1일 --- ### 3. 신규 테이블: dimension_mappings ```sql CREATE TABLE dimension_mappings ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, parent_item_code VARCHAR(50) NOT NULL, -- 부모 품목 (예: 세트) child_item_code VARCHAR(50) NOT NULL, -- 자식 품목 (예: 가이드레일) source_dimension VARCHAR(20) NOT NULL, -- 부모 치수명 (예: "W", "H") target_dimension VARCHAR(20) NOT NULL, -- 자식 치수명 (예: "G", "length") mapping_formula VARCHAR(100), -- 변환 공식 (예: "W - 100") is_active BOOLEAN DEFAULT true, created_by BIGINT, updated_by BIGINT, deleted_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), FOREIGN KEY (tenant_id) REFERENCES tenants(id), INDEX idx_parent_item (tenant_id, parent_item_code), INDEX idx_child_item (tenant_id, child_item_code) ); ``` **Migration 작업량:** 1일 --- ### 4. 신규 테이블: item_templates (선택적) ```sql CREATE TABLE item_templates ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, page_name VARCHAR(100) NOT NULL, item_type VARCHAR(10) NOT NULL, -- FG, PT, SM, RM, CS sections JSON NOT NULL, -- ItemSection[] is_active BOOLEAN DEFAULT true, absolute_path VARCHAR(255), created_by BIGINT, updated_by BIGINT, deleted_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), FOREIGN KEY (tenant_id) REFERENCES tenants(id), INDEX idx_item_type (tenant_id, item_type, is_active) ); ``` **우선순위:** 🟡 Phase 2-3 (선택적) --- ## 🔧 백엔드 개발 작업 분류 ### Priority 0 (즉시 시작, 1-2주) #### Task 1.1: products 테이블 Migration - [ ] Migration 파일 작성 - [ ] 프론트 가이드 필드 추가 (공통 + FG + PT + 인증) - [ ] options JSON 컬럼 추가 - [ ] Rollback 스크립트 준비 **예상 시간:** 1일 --- #### Task 1.2: product_components 테이블 Migration - [ ] quantity_formula TEXT 컬럼 추가 - [ ] condition TEXT 컬럼 추가 - [ ] is_bending, bending_diagram, bending_details 추가 - [ ] Rollback 스크립트 **예상 시간:** 0.5일 --- #### Task 1.3: ItemsController 확장 - [ ] GET /api/items - 필터링 (itemType, search, category1) - [ ] GET /api/items/:itemCode - BOM 포함 응답 - [ ] POST /api/items - 유형별 검증 (FG/PT/SM/RM/CS) - [ ] PUT /api/items/:itemCode - [ ] DELETE /api/items/:itemCode (Soft Delete) **예상 시간:** 3일 --- #### Task 1.4: BOMController 신규 개발 - [ ] GET /api/items/:itemCode/bom - flat list - [ ] GET /api/items/:itemCode/bom/tree - 계층 구조 (재귀) - [ ] POST /api/items/:itemCode/bom - [ ] PUT /api/items/:itemCode/bom/:lineId - [ ] DELETE /api/items/:itemCode/bom/:lineId **예상 시간:** 3-4일 --- #### Task 1.5: FileController 확장 - [ ] POST /api/items/:itemCode/files - type: specification, certification, bending_diagram - 파일 검증 (확장자, 크기) - Storage 저장 (local or S3) - URL 반환 - [ ] DELETE /api/items/:itemCode/files/:type **예상 시간:** 2일 --- ### Priority 1 (2주차, 1-2주) #### Task 2.1: dimension_mappings 테이블 - [ ] Migration 작성 - [ ] DimensionMapping 모델 생성 - [ ] API: GET/POST/PUT/DELETE /api/dimension-mappings **예상 시간:** 2일 --- #### Task 2.2: BOM 계산 로직 강화 - [ ] quantityFormula 파싱 (수식 계산) - [ ] 변수 지원 (W, H, G, length 등) - [ ] 에러 핸들링 (잘못된 수식) - [ ] POST /api/quotes/calculate - 입력: itemCode + dimensions (W, H) - 출력: BOM 전개 + 수량 계산 + 원가 **예상 시간:** 3-4일 --- #### Task 2.3: 조건부 BOM - [ ] condition 필드 파싱 (조건 평가) - [ ] 조건부 BOM 필터링 로직 - [ ] API 통합 **예상 시간:** 2-3일 --- ### Priority 2 (3-4주차, 선택적) #### Task 3.1: 동적 템플릿 시스템 - [ ] item_templates 테이블 - [ ] ItemTemplateController - [ ] GET/POST/PUT/DELETE /api/item-templates **예상 시간:** 3-4일 --- #### Task 3.2: 성능 최적화 - [ ] BOM tree 조회 성능 (N+1 문제 해결) - [ ] Eager Loading - [ ] Cache 전략 (Redis) - [ ] 인덱싱 최적화 **예상 시간:** 2-3일 --- ## 📅 백엔드 개발 타임라인 ### Week 1-2: 핵심 API 개발 (P0) ``` Day 1-2: ✅ products 테이블 Migration ✅ product_components Migration ✅ Migration 실행 및 검증 Day 3-5: ✅ ItemsController 확장 - GET /api/items (필터링) - GET /api/items/:itemCode (BOM 포함) - POST/PUT/DELETE Day 6-9: ✅ BOMController 신규 - CRUD API - Tree 구조 조회 (재귀) Day 10-12: ✅ FileController 확장 - 파일 업로드 (3가지 타입) - 파일 삭제 - Storage 연동 Day 13-14: ✅ 통합 테스트 ✅ Postman Collection 작성 ``` --- ### Week 3-4: BOM 계산 및 매핑 (P1) ``` Day 15-16: ✅ dimension_mappings 테이블 ✅ DimensionMappingController Day 17-20: ✅ BOM 계산 로직 - quantityFormula 파싱 - 변수 지원 (W, H, G) - POST /api/quotes/calculate Day 21-23: ✅ 조건부 BOM - condition 필드 파싱 - 조건 평가 로직 Day 24-28: ✅ 통합 테스트 ✅ 성능 테스트 ✅ 버그 수정 ``` --- ### Week 5-6: 선택적 확장 (P2) ``` Day 29-32: 🎯 동적 템플릿 시스템 (선택) - item_templates 테이블 - Template API Day 33-35: 🎯 성능 최적화 - Eager Loading - Cache - 인덱싱 Day 36-42: 🎯 문서화 🎯 배포 준비 ``` --- ## 🧪 테스트 전략 ### Unit Test (PHPUnit) ```php // tests/Unit/Services/BomCalculationServiceTest.php class BomCalculationServiceTest extends TestCase { /** @test */ public function it_calculates_formula_with_variables() { $service = new BomCalculationService(); $formula = "W * 2 + H"; $variables = ['W' => 3500, 'H' => 2000]; $result = $service->evaluateFormula($formula, $variables); $this->assertEquals(9000, $result); // 3500*2 + 2000 } /** @test */ public function it_builds_bom_tree_recursively() { $item = Product::factory()->create(); // ... $tree = $service->buildBomTree($item->item_code); $this->assertArrayHasKey('children', $tree); } } ``` --- ### API Test (Feature Test) ```php // tests/Feature/Api/ItemsControllerTest.php class ItemsControllerTest extends TestCase { /** @test */ public function it_lists_items_with_filtering() { $response = $this->getJson('/api/items?itemType=FG&search=test'); $response->assertStatus(200) ->assertJsonStructure([ 'data' => [ '*' => ['id', 'itemCode', 'itemName', 'itemType'] ], 'meta' => ['total', 'page', 'perPage'] ]); } /** @test */ public function it_creates_item_with_validation() { $data = [ 'itemName' => 'Test Product', 'itemType' => 'FG', 'unit' => 'EA', // ... ]; $response = $this->postJson('/api/items', $data); $response->assertStatus(201) ->assertJsonFragment(['itemName' => 'Test Product']); } } ``` --- ### Postman Collection ```json { "info": { "name": "BP-MES Items API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "품목 목록 조회", "request": { "method": "GET", "url": "{{baseUrl}}/api/items?itemType=FG&search=&category1=" } }, { "name": "품목 상세 조회", "request": { "method": "GET", "url": "{{baseUrl}}/api/items/:itemCode" } }, { "name": "BOM Tree 조회", "request": { "method": "GET", "url": "{{baseUrl}}/api/items/:itemCode/bom/tree" } } ] } ``` --- ## 📝 API 문서화 (Swagger) ### Swagger 주석 예시 ```php /** * @OA\Get( * path="/api/items", * summary="품목 목록 조회", * tags={"Items"}, * security={{"sanctum":{}}}, * @OA\Parameter( * name="itemType", * in="query", * description="품목 유형 (FG,PT,SM,RM,CS)", * required=false, * @OA\Schema(type="string", enum={"FG","PT","SM","RM","CS"}) * ), * @OA\Parameter( * name="search", * in="query", * description="검색어 (품목명, 품목코드)", * required=false, * @OA\Schema(type="string") * ), * @OA\Response( * response=200, * description="성공", * @OA\JsonContent( * @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/ItemMaster")), * @OA\Property(property="meta", ref="#/components/schemas/PaginationMeta") * ) * ) * ) */ public function index(Request $request) { // ... } ``` --- ## ⚙️ 개발 환경 설정 ### .env 설정 ```bash # Laravel API APP_NAME="BP-MES API" APP_ENV=local APP_DEBUG=true APP_URL=http://localhost:8000 # Database DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=bp_mes DB_USERNAME=postgres DB_PASSWORD= # File Storage FILESYSTEM_DISK=local # FILESYSTEM_DISK=s3 # AWS_ACCESS_KEY_ID= # AWS_SECRET_ACCESS_KEY= # AWS_DEFAULT_REGION= # AWS_BUCKET= # CORS (Next.js 프론트엔드) CORS_ALLOWED_ORIGINS=http://localhost:3000,https://your-frontend-domain.com ``` --- ### CORS 설정 ```php // config/cors.php return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', '*')), 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, ]; ``` --- ## 🚨 주의사항 ### 1. Multi-tenancy **모든 API는 tenant_id 기반 필터링 필수:** ```php // ItemsController.php public function index(Request $request) { $tenantId = auth()->user()->tenant_id; $items = Product::where('tenant_id', $tenantId) ->when($request->itemType, fn($q, $v) => $q->where('item_type', $v)) ->when($request->search, fn($q, $v) => $q->where('item_name', 'like', "%{$v}%")) ->get(); return response()->json(['data' => $items]); } ``` --- ### 2. Soft Delete **삭제는 항상 Soft Delete:** ```php // Product 모델 use Illuminate\Database\Eloquent\SoftDeletes; class Product extends Model { use SoftDeletes, BelongsToTenant; protected $dates = ['deleted_at']; } ``` --- ### 3. Audit Log **변경 이력 기록:** ```php // 품목 수정 시 AuditLog::create([ 'tenant_id' => $tenantId, 'user_id' => auth()->id(), 'action' => 'update', 'model' => 'Product', 'model_id' => $product->id, 'changes' => [ 'before' => $product->getOriginal(), 'after' => $product->getAttributes(), ], ]); ``` --- ### 4. 파일 업로드 검증 ```php // FileController.php public function upload(Request $request, $itemCode) { $request->validate([ 'type' => 'required|in:specification,certification,bending_diagram', 'file' => 'required|file|max:10240|mimes:pdf,jpg,jpeg,png', ]); $type = $request->type; $file = $request->file('file'); // 파일 저장 $path = $file->store("items/{$itemCode}/{$type}", 'public'); $url = Storage::url($path); // DB 업데이트 $item = Product::where('item_code', $itemCode)->firstOrFail(); if ($type === 'specification') { $item->specification_file = $url; $item->specification_file_name = $file->getClientOriginalName(); } elseif ($type === 'certification') { $item->certification_file = $url; $item->certification_file_name = $file->getClientOriginalName(); } elseif ($type === 'bending_diagram') { $item->bending_diagram = $url; } $item->save(); return response()->json([ 'data' => [ 'url' => $url, 'fileName' => $file->getClientOriginalName(), 'type' => $type, ] ]); } ``` --- ## 📚 참고 문서 ### 프로젝트 문서 - **프론트엔드 가이드:** `ITEM_MANAGEMENT_MIGRATION_GUIDE.md` - **Phase 0 보고서:** `PHASE_0_FINAL_REPORT.md` - **Gap 검증:** `api_gap_validation_report.md` - **아키텍처 옵션:** `ARCHITECTURE_OPTIONS_CORE.md` ### Laravel 문서 - [Laravel 12 Documentation](https://laravel.com/docs/12.x) - [Eloquent ORM](https://laravel.com/docs/12.x/eloquent) - [API Resources](https://laravel.com/docs/12.x/eloquent-resources) - [File Storage](https://laravel.com/docs/12.x/filesystem) --- ## 🎯 다음 단계 ### 즉시 시작: 1. **Migration 파일 작성** (products, product_components 확장) 2. **ItemsController 리뷰** (기존 구현 확인) 3. **BOMController 설계** (tree API 구조) ### 협의 필요: - [ ] 절곡품 계산 로직: 프론트 vs 백엔드? - [ ] 파일 저장소: Local vs S3? - [ ] BOM 계산 API 응답 형식 확정 - [ ] quantityFormula 수식 파싱 라이브러리 선택 ### 준비 사항: - [ ] 개발 서버 환경 확인 - [ ] PostgreSQL/MySQL 준비 - [ ] Postman 설치 - [ ] Git branch 전략 --- **작성일:** 2025-11-13 **버전:** 2.0 (프론트엔드 가이드 통합) **다음 업데이트:** Migration 완료 후