# 품목 관리 API 요구사항 명세서 **작성일**: 2025-11-17 **최종 수정**: 2025-11-17 (v1.2) **대상**: PHP/Laravel 백엔드 API **프론트엔드**: Next.js 15 App Router **상태**: ✅ 프론트엔드 구현 완료, 백엔드 API 대기 중 --- ## 📋 목차 1. [리스트 화면 API (품목 목록 조회)](#1-리스트-화면-api) 2. [품목 등록 화면 필요 데이터](#2-품목-등록-화면-필요-데이터) 3. [품목 등록/수정 시 전송 데이터](#3-품목-등록수정-시-전송-데이터) --- ## 1. 리스트 화면 API ### 1.1 품목 목록 조회 (GET) **엔드포인트**: `GET /api/items` 또는 `GET /api/items/paginated` **참고**: - `/api/items` - 전체 데이터 반환 (클라이언트 사이드 페이지네이션) - `/api/items/paginated` - 서버 사이드 페이지네이션 (권장) #### Request Parameters (Query String) | 파라미터 | 타입 | 필수 | 설명 | 예시 | |---------|------|------|------|------| | `itemType` | string | ❌ | 품목 유형 필터 (FG/PT/SM/RM/CS) | `FG` | | `search` | string | ❌ | 검색어 (품목코드, 품목명, 규격) | `스크린` | | `category1` | string | ❌ | 대분류 필터 | `본체부품` | | `category2` | string | ❌ | 중분류 필터 | `가이드시스템` | | `category3` | string | ❌ | 소분류 필터 | `가이드레일` | | `isActive` | boolean | ❌ | 활성 상태 필터 | `true` | | `page` | integer | ❌ | 페이지 번호 (기본값: 1) | `1` | | `per_page` | integer | ❌ | 페이지당 항목 수 (기본값: 50) | `50` | #### Response Body ```json { "success": true, "data": [ { "id": "1", "itemCode": "KD-FG-001", "itemName": "스크린 제품 A", "itemType": "FG", "unit": "EA", "specification": "2000x2000", "isActive": true, "category1": "본체부품", "category2": "가이드시스템", "category3": null, "purchasePrice": 100000, "salesPrice": 150000, "marginRate": 33.3, "processingCost": null, "laborCost": null, "installCost": null, // 제품(FG) 전용 필드 "productName": "프리미엄 스크린", "productCategory": "SCREEN", "lotAbbreviation": "KD", "note": null, // 부품(PT) 전용 필드 "partType": null, // "ASSEMBLY" | "BENDING" | "PURCHASED" "partUsage": null, // "GUIDE_RAIL" | "BOTTOM_FINISH" | "CASE" | "DOOR" | "BRACKET" | "GENERAL" "installationType": null, // 조립품: "벽면형" | "측면형" "assemblyType": null, // 조립품: "M" | "T" | "C" | "D" | "S" | "U" "assemblyLength": null, // 조립품: "2438" | "3000" | "3500" | "4000" | "4300" "material": null, // 절곡품: "EGI 1.55T" | "EGI 2.0T" | "SUS 1.2T" 등 "length": null, // 절곡품: 길이/목함 (mm) "sideSpecWidth": null, // 조립품: 측면 규격 가로 (mm) "sideSpecHeight": null, // 조립품: 측면 규격 세로 (mm) // 버전 관리 "currentRevision": 0, "isFinal": false, // 메타데이터 "createdAt": "2025-01-10T00:00:00Z", "updatedAt": null } ], "pagination": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 7 } } ``` #### 리스트 화면에서 필수로 표시되는 필드 **데스크톱 테이블 컬럼 (우선순위 순)**: 1. ✅ `id` - 체크박스 및 번호에 사용 2. ✅ `itemCode` - 품목코드 (배경색 표시) 3. ✅ `itemType` - 품목유형 (색상별 Badge) 4. ✅ `partType` - 부품유형 (PT 품목에서 Badge 추가 표시) - `ASSEMBLY` → "조립" (파란색 Badge) - `BENDING` → "절곡" (보라색 Badge) - `PURCHASED` → "구매" (녹색 Badge) 5. ✅ `itemName` - 품목명 6. ✅ `specification` - 규격 7. ✅ `unit` - 단위 (Badge 표시) 8. ✅ `isActive` - 품목 상태 (활성/비활성) **모바일 카드 레이아웃** (lg 미만): - 체크박스 + 품목코드 (코드 형식) - 품목유형 Badge + 부품유형 Badge (PT인 경우) - 품목명 (클릭 가능) - 규격 (있는 경우) - 단위 Badge - 액션 버튼 (조회/수정/삭제) **검색 및 필터링**: - ✅ `itemType` - 탭 및 드롭다운 필터 - ✅ `itemCode`, `itemName`, `specification` - 통합 검색 **통계 카드**: - ✅ 전체 품목 수 - ✅ 품목 유형별 개수 (FG, PT, SM, RM, CS) --- ## 2. 품목 등록 화면 필요 데이터 ### 2.1 공통 마스터 데이터 조회 (GET) 품목 등록 화면 진입 시 필요한 드롭다운 옵션 데이터 **엔드포인트**: `GET /api/items/master-data` #### Response Body ```json { "success": true, "data": { // 단위 목록 "units": ["EA", "SET", "KG", "M", "L", "BOX", "PCS"], // 제품 카테고리 "productCategories": [ { "code": "SCREEN", "label": "스크린" }, { "code": "STEEL", "label": "철재" } ], // 부품 용도 "partUsages": [ { "code": "GUIDE_RAIL", "label": "가이드레일" }, { "code": "BOTTOM_FINISH", "label": "하단마감재" }, { "code": "CASE", "label": "케이스" }, { "code": "DOOR", "label": "도어" }, { "code": "BRACKET", "label": "브라켓" }, { "code": "GENERAL", "label": "일반" } ], // 설치 유형 "installationTypes": ["벽면형", "측면형"], // 조립품 종류 "assemblyTypes": ["M", "T", "C", "D", "S", "U"], // 조립품 길이 "assemblyLengths": ["2438", "3000", "3500", "4000", "4300"], // 재질 목록 (절곡품) "materials": [ "EGI 1.55T", "EGI 2.0T", "SUS 1.2T", "SPHC-SD 1.6T" ], // 분류 체계 "categories": { "본체부품": { "가이드시스템": ["가이드레일", "브라켓"], "케이스": ["상부케이스", "하부케이스"] }, "구조재/부속품": { "볼트/너트": null, "와셔": null }, "철강재": null } } } ``` ### 2.2 품목 상세 조회 (수정 모드용) (GET) **엔드포인트**: `GET /api/items/{itemCode}` **URL 파라미터**: - `itemCode`: 품목 코드 (예: `KD-FG-001`) #### Response Body ```json { "success": true, "data": { // === 공통 필드 === "id": "1", "itemCode": "KD-FG-001", "itemName": "스크린 제품 A", "itemType": "FG", "unit": "EA", "specification": "2000x2000", "isActive": true, // === 분류 === "category1": "본체부품", "category2": "가이드시스템", "category3": null, // === 가격 정보 === "purchasePrice": 100000, "salesPrice": 150000, "marginRate": 33.3, "processingCost": 20000, "laborCost": 15000, "installCost": 10000, // === 제품(FG) 전용 === "productName": "프리미엄 스크린", "productCategory": "SCREEN", "lotAbbreviation": "KD", "note": "비고 내용", // === 부품(PT) 전용 - 조립품 === "partType": "ASSEMBLY", "partUsage": "GUIDE_RAIL", "installationType": "벽면형", "assemblyType": "M", "assemblyLength": "2438", // === 부품(PT) 전용 - 절곡품 === "bendingDiagram": "https://example.com/uploads/bending-diagram.png", "bendingDetails": [ { "id": "bd-1", "no": 1, "input": 100, "elongation": -1, "calculated": 99, "sum": 99, "shaded": false, "aAngle": 90 } ], "material": "EGI 1.55T", "length": "2000", // === 부품(PT) 전용 - 구매품 === "electricOpenerPower": "220V", "electricOpenerCapacity": "300", "motorVoltage": "380V", // === BOM (자재명세서) === "bom": [ { "id": "bom-1", "childItemCode": "KD-PT-001", "childItemName": "가이드레일", "quantity": 2, "unit": "EA", "unitPrice": 35000, "quantityFormula": "H / 1000", "note": "비고" } ], // === 인정 정보 === "certificationNumber": "인정번호-001", "certificationStartDate": "2025-01-01", "certificationEndDate": "2027-12-31", "specificationFile": "https://example.com/uploads/spec.pdf", "specificationFileName": "시방서.pdf", "certificationFile": "https://example.com/uploads/cert.pdf", "certificationFileName": "인정서.pdf", // === 메타데이터 === "safetyStock": 10, "leadTime": 7, "currentRevision": 0, "isFinal": false, "createdAt": "2025-01-10T00:00:00Z", "updatedAt": "2025-01-12T00:00:00Z" } } ``` ### 2.3 BOM 품목 검색 (GET) BOM 추가 시 하위 품목 검색용 - **2개의 분리된 API**로 구현 #### 2.3.1 품목 코드 검색 (자동완성) **엔드포인트**: `GET /api/items/search/codes` **Query Parameters**: - `q`: 검색어 (품목코드) - `limit`: 결과 개수 제한 (기본값: 10) **Response Body**: ```json { "success": true, "data": ["KD-PT-001", "KD-PT-002", "KD-PT-003"] } ``` #### 2.3.2 품목명 검색 (자동완성) **엔드포인트**: `GET /api/items/search/names` **Query Parameters**: - `q`: 검색어 (품목명) - `limit`: 결과 개수 제한 (기본값: 10) **Response Body**: ```json { "success": true, "data": [ { "itemCode": "KD-PT-001", "itemName": "가이드레일" }, { "itemCode": "KD-PT-002", "itemName": "가이드레일 브라켓" } ] } ``` #### 2.3.3 통합 품목 검색 (BOM 추가용) **엔드포인트**: `GET /api/items/search` **Query Parameters**: - `q`: 검색어 (품목코드 또는 품목명) - `itemType`: 품목 유형 필터 (선택) - `limit`: 결과 개수 제한 (기본값: 10) **Response Body**: ```json { "success": true, "data": [ { "itemCode": "KD-PT-001", "itemName": "가이드레일", "itemType": "PT", "partType": "BENDING", "unit": "EA", "specification": "2438mm", "purchasePrice": 35000, "salesPrice": 50000 } ] } ``` --- ## 3. 품목 등록/수정 시 전송 데이터 ### 3.1 품목 등록 (POST) **엔드포인트**: `POST /api/items` **Content-Type**: `multipart/form-data` (파일 업로드 포함 시) #### Request Body ```json { // === 공통 필드 (모든 품목 유형) === "itemCode": "KD-FG-001", // 자동생성 또는 수동입력 "itemName": "스크린 제품 A", "itemType": "FG", // FG/PT/SM/RM/CS "unit": "EA", "specification": "2000x2000", "isActive": true, // === 분류 === "category1": "본체부품", "category2": "가이드시스템", "category3": null, // === 가격 정보 === "purchasePrice": 100000, "salesPrice": 150000, "marginRate": 33.3, // 자동계산 또는 수동입력 "processingCost": 20000, "laborCost": 15000, "installCost": 10000, // === 제품(FG) 전용 필드 === "productName": "프리미엄 스크린", "productCategory": "SCREEN", "lotAbbreviation": "KD", "note": "비고 내용", // === 부품(PT) 전용 필드 - 조립품 === "partType": "ASSEMBLY", // ASSEMBLY/BENDING/PURCHASED "partUsage": "GUIDE_RAIL", "installationType": "벽면형", "assemblyType": "M", "assemblyLength": "2438", // === 부품(PT) 전용 필드 - 절곡품 === "material": "EGI 1.55T", "length": "2000", "bendingLength": "2000", "bendingDetails": [ { "no": 1, "input": 100, "elongation": -1, "calculated": 99, "sum": 99, "shaded": false, "aAngle": 90 } ], // === 부품(PT) 전용 필드 - 구매품 === "electricOpenerPower": "220V", "electricOpenerCapacity": "300", "motorVoltage": "380V", "motorCapacity": "500", "chainSpec": "체인규격", // === BOM (자재명세서) === "bom": [ { "childItemCode": "KD-PT-001", "childItemName": "가이드레일", "quantity": 2, "unit": "EA", "unitPrice": 35000, "quantityFormula": "H / 1000", // 수량 계산식 (선택) "note": "비고", // 절곡품 BOM인 경우 "isBending": true, "width": 100, "bendingDetails": [...] } ], // === 인정 정보 (제품/부품) === "certificationNumber": "인정번호-001", "certificationStartDate": "2025-01-01", "certificationEndDate": "2027-12-31", // === 메타데이터 === "safetyStock": 10, "leadTime": 7, "isVariableSize": false, "currentRevision": 0, "isFinal": false } ``` #### 파일 업로드 (FormData) ```javascript const formData = new FormData(); // JSON 데이터 formData.append('data', JSON.stringify(itemData)); // 파일들 formData.append('specificationFile', specificationFile); // 시방서 formData.append('certificationFile', certificationFile); // 인정서 formData.append('bendingDiagram', bendingDiagramFile); // 절곡품 전개도 ``` #### Response Body (성공) ```json { "success": true, "message": "품목이 등록되었습니다.", "data": { "id": "1", "itemCode": "KD-FG-001", // ... 전체 품목 데이터 } } ``` #### Response Body (실패) ```json { "success": false, "message": "품목 등록에 실패했습니다.", "errors": { "itemName": ["품목명은 필수입니다."], "unit": ["단위는 필수입니다."] } } ``` ### 3.2 품목 수정 (PUT) **엔드포인트**: `PUT /api/items/{itemCode}` **Request Body**: 품목 등록과 동일 (변경된 필드만 전송 가능) **참고**: - `itemType`은 수정 불가 (품목 유형 변경 시 신규 등록 필요) - 파일은 새로운 파일 업로드 시만 전송 ### 3.3 품목 삭제 (DELETE) **엔드포인트**: `DELETE /api/items/{itemCode}` #### Response Body ```json { "success": true, "message": "품목이 삭제되었습니다." } ``` --- ## 4. 데이터 검증 규칙 ### 4.1 공통 필수 필드 모든 품목 유형에서 필수: - ✅ `itemType` - 품목 유형 - ✅ `itemName` - 품목명 - ✅ `unit` - 단위 - ✅ `isActive` - 활성 상태 (기본값: true) ### 4.2 제품(FG) 필수 필드 - ✅ `productName` - 상품명 - ✅ `itemName` - 품목명 - ✅ `itemCode` - 자동생성: `{productName}-{itemName}` ### 4.3 부품(PT) 필수 필드 **조립품 (ASSEMBLY)**: - ✅ `itemName` - 품목명 - ✅ `length` - 길이 - ✅ `itemCode` - 자동생성 규칙 있음 **절곡품 (BENDING)**: - ✅ `itemName` - 품목명 - ✅ `length` - 길이/목함 - ✅ `specification` - 규격 (재질) - ✅ `itemCode` - 자동생성 규칙 있음 **구매품 (PURCHASED)**: - ✅ `itemName` - 품목명 - ✅ `specification` - 규격 - ✅ `itemCode` - 자동생성 규칙 있음 ### 4.4 부자재/원자재/소모품 (SM/RM/CS) 필수 필드 - ✅ `itemName` - 품목명 - ✅ `unit` - 단위 - ✅ `specification` - 규격 - ✅ `itemCode` - 자동생성 규칙 있음 --- ## 5. 품목 코드 자동생성 규칙 ### 5.1 제품 (FG) **형식**: `{상품명}-{품목명}` **예시**: - 상품명: `프리미엄 스크린` - 품목명: `2000x2000` - 결과: `프리미엄 스크린-2000x2000` ### 5.2 부품 (PT) - 조립품 **형식**: `KD-{설치유형코드}{조립종류}{길이}` **예시**: - 설치유형: 벽면형 → `M` - 조립종류: `T` - 길이: `2438` - 결과: `KD-MT2438` ### 5.3 부품 (PT) - 절곡품 **형식**: `{재질}-{길이/목함}` **예시**: - 재질: `EGI 1.55T` - 길이: `2000` - 결과: `EGI 1.55T-2000` ### 5.4 부품 (PT) - 구매품 **형식**: `{품목명}` **예시**: `전동개폐기 220V 300KG` ### 5.5 부자재/원자재/소모품 (SM/RM/CS) **형식**: 수동 입력 또는 `{품목명}-{규격}` **예시**: - 품목명: `볼트` - 규격: `M6x20` - 결과: `볼트-M6x20` --- ## 6. 파일 업로드 요구사항 > **참조**: `/downloads/file_storage_implementation_guide.md` - 파일 저장소 시스템 전체 구현 가이드 ### 6.1 허용 파일 형식 **기본 정책**: - **최대 파일 크기**: 20MB - **파일명 처리**: - 사용자가 보는 이름 (display_name): 원본 파일명 유지 - 실제 저장 이름 (stored_name): 64bit 난수 (16자 hex) + 확장자 | 파일 종류 | 허용 확장자 | MIME 타입 | 비고 | |----------|-----------|----------|------| | **시방서** | `.pdf`, `.docx`, `.hwp`, `.jpg`, `.png` | `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/x-hwp`, `image/jpeg`, `image/png` | 문서 및 이미지 형식 모두 지원 | | **인정서** | `.pdf`, `.docx`, `.hwp`, `.jpg`, `.png` | `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/x-hwp`, `image/jpeg`, `image/png` | 문서 및 이미지 형식 모두 지원 | | **절곡품 전개도** | `.jpg`, `.png`, `.pdf` | `image/jpeg`, `image/png`, `application/pdf` | 이미지 및 PDF 형식 | | **기타 첨부** | `.xlsx`, `.xls`, `.csv`, `.zip`, `.rar` | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `application/vnd.ms-excel`, `text/csv`, `application/zip`, `application/x-rar-compressed` | Excel, 압축 파일 등 | **차단 확장자** (보안): ``` exe, sh, bat, cmd, dwg, dxf, step, iges ``` ### 6.2 파일 저장 경로 **경로 구조** (테넌트별 분리): ``` storage/app/tenants/{tenant_id}/{folder_key}/{year}/{month}/{stored_name} ``` **품목 관련 파일 경로 예시**: ``` storage/app/tenants/1/product/2025/01/a1b2c3d4e5f6g7h8.pdf storage/app/tenants/1/product/2025/01/i9j0k1l2m3n4o5p6.jpg ``` **임시 업로드 경로** (temp 폴더): ``` storage/app/tenants/{tenant_id}/temp/{year}/{month}/{stored_name} ``` ### 6.3 파일 업로드 프로세스 ``` [Frontend] 파일 선택 → multipart/form-data 전송 ↓ [Backend] 파일 검증 - 확장자 체크 (허용 목록) - MIME 타입 검증 - 파일 크기 체크 (20MB 이하) - 용량 체크 (테넌트 용량 확인) ↓ [Backend] temp 폴더에 임시 저장 - 난수 파일명 생성 (16자 hex + 확장자) - 경로: /tenants/{id}/temp/{year}/{month}/{random}.{ext} - DB 저장 (is_temp=true, folder_id=NULL) ↓ [Response] { file_id, display_name, file_size, mime_type } ↓ [Frontend] 품목 등록 시 file_id 전송 ↓ [Backend] 문서 저장 후 파일 이동 - temp → product 폴더로 이동 - DB 업데이트 (is_temp=false, folder_id, document_id) ``` ### 6.4 파일 응답 형식 **업로드 성공 응답**: ```json { "success": true, "data": { "file_id": 123, "display_name": "시방서.pdf", "stored_name": "a1b2c3d4e5f6g7h8.pdf", "file_size": 1024000, "mime_type": "application/pdf", "file_type": "document", "is_temp": true, "created_at": "2025-01-17T10:00:00Z" } } ``` **파일 URL 형식**: ``` GET /api/files/{file_id}/download → 파일 스트리밍 응답 (Content-Disposition: attachment) ``` ### 6.5 에러 응답 | HTTP 코드 | 에러 상황 | 메시지 예시 | |----------|----------|-----------| | 400 | 파일 없음 | `No file uploaded` | | 400 | 차단된 확장자 | `File extension '.exe' is not allowed` | | 400 | MIME 타입 불일치 | `Invalid MIME type` | | 413 | 파일 크기 초과 | `File size exceeds 20MB limit` | | 413 | 용량 초과 | `Storage quota exceeded. Please delete files or contact support.` | | 422 | 처리 불가 | `Failed to store file` | --- ## 7. 에러 코드 | HTTP 코드 | 설명 | 예시 | |----------|------|------| | 200 | 성공 | 조회, 수정, 삭제 성공 | | 201 | 생성 성공 | 품목 등록 성공 | | 400 | 잘못된 요청 | 필수 필드 누락, 유효성 검증 실패 | | 404 | 리소스 없음 | 품목을 찾을 수 없음 | | 409 | 충돌 | 품목코드 중복 | | 422 | 처리 불가 | 비즈니스 로직 오류 | | 500 | 서버 오류 | 예상치 못한 서버 오류 | --- ## 8. 다음 단계 ### 8.1 우선순위 1: 리스트 화면 API - [ ] `GET /api/items` 구현 - [ ] 페이지네이션 구현 - [ ] 검색 및 필터링 구현 ### 8.2 우선순위 2: 마스터 데이터 API - [ ] `GET /api/items/master-data` 구현 - [ ] 드롭다운 옵션 데이터 제공 ### 8.3 우선순위 3: 품목 등록 API - [ ] `POST /api/items` 구현 - [ ] 파일 업로드 처리 - [ ] 품목코드 자동생성 로직 ### 8.4 우선순위 4: 품목 수정/삭제 API - [ ] `GET /api/items/{itemCode}` 구현 - [ ] `PUT /api/items/{itemCode}` 구현 - [ ] `DELETE /api/items/{itemCode}` 구현 ### 8.5 우선순위 5: BOM 검색 API - [ ] `GET /api/items/search` 구현 --- ## 9. 프론트엔드 구현 현황 (2025-11-17) ### ✅ 완료된 화면 #### 품목 목록 화면 - **경로**: `/[locale]/(protected)/items` - **컴포넌트**: `ItemListClient.tsx` - **기능**: - 품목 유형별 탭 필터 (전체/제품/부품/부자재/원자재/소모품) - 통합 검색 (품목코드, 품목명, 규격) - 데스크톱 테이블 + 모바일 카드 반응형 레이아웃 - 페이지네이션 (클라이언트 사이드) - 일괄 삭제 - 품목유형 + 부품유형 Badge 표시 #### 품목 상세 화면 - **경로**: `/[locale]/(protected)/items/[itemCode]` - **컴포넌트**: `ItemDetailClient.tsx` - **기능**: - 품목 유형별 조건부 섹션 표시 - 제품(FG): 기본 정보, 제품 정보, BOM - 부품(PT) - 조립: 기본 정보, 조립 부품 세부 정보, BOM - 부품(PT) - 절곡: 기본 정보, 가이드레일 세부 정보 - 부품(PT) - 구매: 기본 정보 - 부자재/원자재/소모품: 기본 정보 - BOM 테이블 표시 #### 품목 등록/수정 화면 - **경로**: - 등록: `/[locale]/(protected)/items/new` - 수정: `/[locale]/(protected)/items/[itemCode]/edit` - **상태**: 🚧 개발 예정 ### ✅ 구현된 타입 정의 - **파일**: `src/types/item.ts` - **타입**: `ItemMaster`, `BOMLine`, `BendingDetail`, `ItemType`, `PartType` 등 완료 ### ✅ 구현된 API 클라이언트 - **파일**: `src/lib/api/items.ts` - **함수**: - `fetchItems()` - 목록 조회 - `fetchItemsPaginated()` - 페이지네이션 목록 - `fetchItemByCode()` - 상세 조회 - `createItem()` - 등록 - `updateItem()` - 수정 - `deleteItem()` - 삭제 - `uploadFile()` - 파일 업로드 - `searchItemCodes()` - 코드 검색 - `searchItemNames()` - 품목명 검색 ### 🚧 개발 대기 중 - 품목 등록/수정 폼 화면 - BOM 관리 인터페이스 - 절곡품 전개도 편집기 --- ## 10. 백엔드 API 구현 우선순위 ### Phase 1: 필수 API (회의 직후 착수) 1. ✅ `GET /api/items` - 품목 목록 조회 2. ✅ `GET /api/items/{itemCode}` - 품목 상세 조회 3. ✅ `GET /api/items/master-data` - 마스터 데이터 조회 ### Phase 2: CRUD API 4. ✅ `POST /api/items` - 품목 등록 5. ✅ `PUT /api/items/{itemCode}` - 품목 수정 6. ✅ `DELETE /api/items/{itemCode}` - 품목 삭제 ### Phase 3: 검색 및 유틸리티 7. ✅ `GET /api/items/search` - 통합 검색 8. ✅ `GET /api/items/search/codes` - 코드 검색 9. ✅ `GET /api/items/search/names` - 품목명 검색 10. ✅ `POST /api/items/{itemCode}/files` - 파일 업로드 ### Phase 4: BOM 관리 11. ✅ `GET /api/items/{itemCode}/bom` - BOM 조회 12. ✅ `POST /api/items/{itemCode}/bom` - BOM 라인 추가 13. ✅ `PUT /api/items/{itemCode}/bom/{lineId}` - BOM 라인 수정 14. ✅ `DELETE /api/items/{itemCode}/bom/{lineId}` - BOM 라인 삭제 --- ## 11. 회의 안건 (PHP 백엔드 팀) ### 1. API 엔드포인트 확정 - `/api/items` vs `/api/items/paginated` 중 선택 - 검색 API 분리 방식 (codes, names) 승인 ### 2. 데이터베이스 스키마 검토 - `items` 테이블 구조 - `bom_lines` 테이블 구조 - `item_revisions` 테이블 (버전 관리) - 파일 저장 경로 및 구조 ### 3. 인증 방식 확인 - Bearer Token vs Cookie 방식 - CORS 설정 ### 4. 파일 업로드 구현 - **참조 문서**: `/downloads/file_storage_implementation_guide.md` - 저장 경로: `storage/app/tenants/{tenant_id}/{folder_key}/{year}/{month}/{stored_name}` - 최대 파일 크기: 20MB - 허용 확장자: - 문서: pdf, docx, hwp - 이미지: jpg, png - 기타: xlsx, xls, csv, zip, rar - 차단 확장자: exe, sh, bat, cmd, dwg, dxf, step, iges - 파일명 처리: 난수 저장명 (16자 hex) + 원본명 보존 (display_name) ### 5. 에러 응답 형식 통일 ```json { "success": false, "message": "에러 메시지", "errors": { "fieldName": ["검증 실패 메시지"] } } ``` ### 6. 개발 일정 협의 - Phase 1 (필수 API): 목표 일정 - Phase 2-4: 순차 개발 일정 --- ## 12. 버전 히스토리 - **v1.0** (2025-11-17 09:00): 초안 작성, API 요구사항 정의 - **v1.1** (2025-11-17 17:30): 프론트엔드 구현 현황 반영, 검색 API 세분화, 모바일 레이아웃 추가, 회의 안건 작성 - **v1.2** (2025-11-17 회의 후): 파일 업로드 요구사항 개정 (회의 결과 반영) - 시방서/인정서: PDF뿐만 아니라 이미지(JPG, PNG), 문서(DOCX, HWP) 형식 지원 - 최대 파일 크기: 10MB → 20MB로 증가 - 파일 저장소 구현 가이드 참조 추가 (`/downloads/file_storage_implementation_guide.md`) - 테넌트별 파일 저장 경로 구조 명시 - 파일명 처리 방식 명시 (난수 저장명 + 원본명 보존) - 차단 확장자 목록 추가 (보안)