diff --git a/CURRENT_WORKS.md b/CURRENT_WORKS.md index 7098958..ed846fa 100644 --- a/CURRENT_WORKS.md +++ b/CURRENT_WORKS.md @@ -1,3 +1,162 @@ +## 2025-11-20 (수) - ItemMaster Phase 1 API 구현 (핵심 기능) + +### 주요 작업 +- Phase 1 핵심 기능 13개 API 엔드포인트 구현 +- Controller, Service, FormRequest 계층 구조 완성 +- SAM API Development Rules 준수 (Service-First, ApiResponse, i18n) + +### 추가된 파일 + +#### Controllers (4개) +1. **app/Http/Controllers/Api/V1/ItemMaster/ItemMasterController.php** + - init() - 전체 초기 데이터 로드 + +2. **app/Http/Controllers/Api/V1/ItemMaster/ItemPageController.php** + - index(), store(), update(), destroy() + - 페이지 관리 (섹션/필드 중첩 포함) + +3. **app/Http/Controllers/Api/V1/ItemMaster/ItemSectionController.php** + - store(), update(), destroy(), reorder() + - 섹션 관리 및 순서 변경 + +4. **app/Http/Controllers/Api/V1/ItemMaster/ItemFieldController.php** + - store(), update(), destroy(), reorder() + - 필드 관리 및 순서 변경 + +#### Services (4개) +1. **app/Services/ItemMaster/ItemMasterService.php** + - init() - pages, sectionTemplates, masterFields, customTabs, unitOptions 로드 + - Eager Loading으로 N+1 쿼리 방지 + +2. **app/Services/ItemMaster/ItemPageService.php** + - CRUD + Cascade Soft Delete + - Multi-tenant 스코프 자동 적용 + +3. **app/Services/ItemMaster/ItemSectionService.php** + - CRUD + reorder (order_no 자동 계산) + - Cascade Soft Delete (하위 필드/BOM 항목 포함) + +4. **app/Services/ItemMaster/ItemFieldService.php** + - CRUD + reorder + - JSON 필드 지원 (display_condition, validation_rules, options, properties) + +#### FormRequests (7개) +1. **app/Http/Requests/ItemMaster/ItemPageStoreRequest.php** + - page_name (required, max:255) + - item_type (required, in:FG,PT,SM,RM,CS) + - absolute_path (nullable, max:500) + +2. **app/Http/Requests/ItemMaster/ItemPageUpdateRequest.php** + - page_name (sometimes) + - absolute_path (nullable) + +3. **app/Http/Requests/ItemMaster/ItemSectionStoreRequest.php** + - title (required, max:255) + - type (required, in:fields,bom) + +4. **app/Http/Requests/ItemMaster/ItemSectionUpdateRequest.php** + - title (sometimes) + +5. **app/Http/Requests/ItemMaster/ItemFieldStoreRequest.php** + - field_name (required), field_type (required, enum) + - is_required, placeholder, options, properties (nullable) + - display_condition, validation_rules (array) + +6. **app/Http/Requests/ItemMaster/ItemFieldUpdateRequest.php** + - 모든 필드 sometimes + +7. **app/Http/Requests/ItemMaster/ReorderRequest.php** + - items (required, array) + - items.*.id, items.*.order_no (required) + +### 수정된 파일 + +1. **routes/api.php** + - ItemMaster 관련 use 문 4개 추가 + - `/v1/item-master/*` 라우트 그룹 추가 + - 13개 엔드포인트 등록: + - GET /init + - GET/POST/PUT/DELETE /pages + - POST/PUT/DELETE /pages/{pageId}/sections + - PUT /pages/{pageId}/sections/reorder + - POST/PUT/DELETE /sections/{sectionId}/fields + - PUT /sections/{sectionId}/fields/reorder + +### 작업 내용 + +#### API 엔드포인트 (13개) +1. ✅ GET `/init` - 전체 초기 데이터 +2. ✅ GET `/pages` - 페이지 목록 (item_type 필터) +3. ✅ POST `/pages` - 페이지 생성 +4. ✅ PUT `/pages/{id}` - 페이지 수정 +5. ✅ DELETE `/pages/{id}` - 페이지 삭제 (Cascade) +6. ✅ POST `/pages/{pageId}/sections` - 섹션 생성 +7. ✅ PUT `/sections/{id}` - 섹션 수정 +8. ✅ DELETE `/sections/{id}` - 섹션 삭제 (Cascade) +9. ✅ PUT `/pages/{pageId}/sections/reorder` - 섹션 순서 변경 +10. ✅ POST `/sections/{sectionId}/fields` - 필드 생성 +11. ✅ PUT `/fields/{id}` - 필드 수정 +12. ✅ DELETE `/fields/{id}` - 필드 삭제 +13. ✅ PUT `/sections/{sectionId}/fields/reorder` - 필드 순서 변경 + +#### 기술적 특징 + +**Service-First 패턴**: +- Controller는 DI + ApiResponse::handle()만 사용 +- 모든 비즈니스 로직은 Service에 구현 +- Service extends Service (tenantId(), apiUserId() 활용) + +**Multi-tenant 지원**: +- 모든 Service 메서드에서 tenantId() 검증 +- BelongsToTenant 스코프 자동 적용 +- Cascade Soft Delete시 tenant_id 검증 + +**실시간 저장**: +- 모든 CUD 작업 즉시 처리 +- order_no 자동 계산 (마지막 + 1) +- reorder는 배열로 한 번에 처리 + +**i18n 메시지**: +- __('message.fetched'), __('message.created') +- __('message.updated'), __('message.deleted') +- __('message.reordered'), __('error.not_found') + +### 검증 결과 + +**라우트 테스트**: +```bash +php artisan route:list --path=item-master +# 결과: 13개 엔드포인트 정상 등록 +``` + +**Pint 검사**: +```bash +./vendor/bin/pint --test app/Http/Controllers/Api/V1/ItemMaster/ app/Services/ItemMaster/ app/Http/Requests/ItemMaster/ +# 결과: 15 files PASS +``` + +### 다음 단계 (Phase 2-3) + +**Phase 2 (확장 기능)** - 예정: +- BOM 항목 관리 (3개 엔드포인트) +- 섹션 템플릿 (4개 엔드포인트) +- 마스터 필드 (4개 엔드포인트) + +**Phase 3 (부가 기능)** - 예정: +- 커스텀 탭 (5개 엔드포인트) +- 단위 옵션 (3개 엔드포인트) + +**Swagger 문서** - 필요: +- app/Swagger/v1/ItemMasterApi.php 작성 +- 13개 엔드포인트 스키마 정의 + +### Git 커밋 +```bash +# 커밋 예정 +``` + +--- + ## 2025-11-20 (수) - ItemMaster 데이터베이스 구조 구축 ### 주요 작업 diff --git a/app/Http/Controllers/Api/V1/ItemMaster/ItemFieldController.php b/app/Http/Controllers/Api/V1/ItemMaster/ItemFieldController.php new file mode 100644 index 0000000..446a9f4 --- /dev/null +++ b/app/Http/Controllers/Api/V1/ItemMaster/ItemFieldController.php @@ -0,0 +1,67 @@ +service->store($sectionId, $request->validated()); + }, __('message.created')); + } + + /** + * 필드 수정 + * + * PUT /api/v1/item-master/fields/{id} + */ + public function update(int $id, ItemFieldUpdateRequest $request) + { + return ApiResponse::handle(function () use ($id, $request) { + return $this->service->update($id, $request->validated()); + }, __('message.updated')); + } + + /** + * 필드 삭제 (Soft Delete) + * + * DELETE /api/v1/item-master/fields/{id} + */ + public function destroy(int $id) + { + return ApiResponse::handle(function () use ($id) { + $this->service->destroy($id); + + return 'success'; + }, __('message.deleted')); + } + + /** + * 필드 순서 변경 + * + * PUT /api/v1/item-master/sections/{sectionId}/fields/reorder + */ + public function reorder(int $sectionId, ReorderRequest $request) + { + return ApiResponse::handle(function () use ($sectionId, $request) { + $this->service->reorder($sectionId, $request->validated()['items']); + + return 'success'; + }, __('message.reordered')); + } +} diff --git a/app/Http/Controllers/Api/V1/ItemMaster/ItemMasterController.php b/app/Http/Controllers/Api/V1/ItemMaster/ItemMasterController.php new file mode 100644 index 0000000..99caf8d --- /dev/null +++ b/app/Http/Controllers/Api/V1/ItemMaster/ItemMasterController.php @@ -0,0 +1,24 @@ +service->init(); + }, __('message.fetched')); + } +} diff --git a/app/Http/Controllers/Api/V1/ItemMaster/ItemPageController.php b/app/Http/Controllers/Api/V1/ItemMaster/ItemPageController.php new file mode 100644 index 0000000..26812b2 --- /dev/null +++ b/app/Http/Controllers/Api/V1/ItemMaster/ItemPageController.php @@ -0,0 +1,67 @@ +input('item_type'); + + return $this->service->index($itemType); + }, __('message.fetched')); + } + + /** + * 페이지 생성 + * + * POST /api/v1/item-master/pages + */ + public function store(ItemPageStoreRequest $request) + { + return ApiResponse::handle(function () use ($request) { + return $this->service->store($request->validated()); + }, __('message.created')); + } + + /** + * 페이지 수정 + * + * PUT /api/v1/item-master/pages/{id} + */ + public function update(int $id, ItemPageUpdateRequest $request) + { + return ApiResponse::handle(function () use ($id, $request) { + return $this->service->update($id, $request->validated()); + }, __('message.updated')); + } + + /** + * 페이지 삭제 (Soft Delete) + * + * DELETE /api/v1/item-master/pages/{id} + */ + public function destroy(int $id) + { + return ApiResponse::handle(function () use ($id) { + $this->service->destroy($id); + + return 'success'; + }, __('message.deleted')); + } +} diff --git a/app/Http/Controllers/Api/V1/ItemMaster/ItemSectionController.php b/app/Http/Controllers/Api/V1/ItemMaster/ItemSectionController.php new file mode 100644 index 0000000..96717f1 --- /dev/null +++ b/app/Http/Controllers/Api/V1/ItemMaster/ItemSectionController.php @@ -0,0 +1,67 @@ +service->store($pageId, $request->validated()); + }, __('message.created')); + } + + /** + * 섹션 수정 + * + * PUT /api/v1/item-master/sections/{id} + */ + public function update(int $id, ItemSectionUpdateRequest $request) + { + return ApiResponse::handle(function () use ($id, $request) { + return $this->service->update($id, $request->validated()); + }, __('message.updated')); + } + + /** + * 섹션 삭제 (Soft Delete) + * + * DELETE /api/v1/item-master/sections/{id} + */ + public function destroy(int $id) + { + return ApiResponse::handle(function () use ($id) { + $this->service->destroy($id); + + return 'success'; + }, __('message.deleted')); + } + + /** + * 섹션 순서 변경 + * + * PUT /api/v1/item-master/pages/{pageId}/sections/reorder + */ + public function reorder(int $pageId, ReorderRequest $request) + { + return ApiResponse::handle(function () use ($pageId, $request) { + $this->service->reorder($pageId, $request->validated()['items']); + + return 'success'; + }, __('message.reordered')); + } +} diff --git a/app/Http/Requests/ItemMaster/ItemFieldStoreRequest.php b/app/Http/Requests/ItemMaster/ItemFieldStoreRequest.php new file mode 100644 index 0000000..e46f436 --- /dev/null +++ b/app/Http/Requests/ItemMaster/ItemFieldStoreRequest.php @@ -0,0 +1,28 @@ + 'required|string|max:255', + 'field_type' => 'required|in:textbox,number,dropdown,checkbox,date,textarea', + 'is_required' => 'nullable|boolean', + 'default_value' => 'nullable|string', + 'placeholder' => 'nullable|string|max:255', + 'display_condition' => 'nullable|array', + 'validation_rules' => 'nullable|array', + 'options' => 'nullable|array', + 'properties' => 'nullable|array', + ]; + } +} diff --git a/app/Http/Requests/ItemMaster/ItemFieldUpdateRequest.php b/app/Http/Requests/ItemMaster/ItemFieldUpdateRequest.php new file mode 100644 index 0000000..6ac3590 --- /dev/null +++ b/app/Http/Requests/ItemMaster/ItemFieldUpdateRequest.php @@ -0,0 +1,28 @@ + 'sometimes|string|max:255', + 'field_type' => 'sometimes|in:textbox,number,dropdown,checkbox,date,textarea', + 'is_required' => 'nullable|boolean', + 'default_value' => 'nullable|string', + 'placeholder' => 'nullable|string|max:255', + 'display_condition' => 'nullable|array', + 'validation_rules' => 'nullable|array', + 'options' => 'nullable|array', + 'properties' => 'nullable|array', + ]; + } +} diff --git a/app/Http/Requests/ItemMaster/ItemPageStoreRequest.php b/app/Http/Requests/ItemMaster/ItemPageStoreRequest.php new file mode 100644 index 0000000..38f8c56 --- /dev/null +++ b/app/Http/Requests/ItemMaster/ItemPageStoreRequest.php @@ -0,0 +1,22 @@ + 'required|string|max:255', + 'item_type' => 'required|in:FG,PT,SM,RM,CS', + 'absolute_path' => 'nullable|string|max:500', + ]; + } +} diff --git a/app/Http/Requests/ItemMaster/ItemPageUpdateRequest.php b/app/Http/Requests/ItemMaster/ItemPageUpdateRequest.php new file mode 100644 index 0000000..9253f5c --- /dev/null +++ b/app/Http/Requests/ItemMaster/ItemPageUpdateRequest.php @@ -0,0 +1,21 @@ + 'sometimes|string|max:255', + 'absolute_path' => 'nullable|string|max:500', + ]; + } +} diff --git a/app/Http/Requests/ItemMaster/ItemSectionStoreRequest.php b/app/Http/Requests/ItemMaster/ItemSectionStoreRequest.php new file mode 100644 index 0000000..968aa08 --- /dev/null +++ b/app/Http/Requests/ItemMaster/ItemSectionStoreRequest.php @@ -0,0 +1,21 @@ + 'required|string|max:255', + 'type' => 'required|in:fields,bom', + ]; + } +} diff --git a/app/Http/Requests/ItemMaster/ItemSectionUpdateRequest.php b/app/Http/Requests/ItemMaster/ItemSectionUpdateRequest.php new file mode 100644 index 0000000..40c3dd0 --- /dev/null +++ b/app/Http/Requests/ItemMaster/ItemSectionUpdateRequest.php @@ -0,0 +1,20 @@ + 'sometimes|string|max:255', + ]; + } +} diff --git a/app/Http/Requests/ItemMaster/ReorderRequest.php b/app/Http/Requests/ItemMaster/ReorderRequest.php new file mode 100644 index 0000000..501210a --- /dev/null +++ b/app/Http/Requests/ItemMaster/ReorderRequest.php @@ -0,0 +1,22 @@ + 'required|array|min:1', + 'items.*.id' => 'required|integer|exists:item_sections,id', + 'items.*.order_no' => 'required|integer|min:0', + ]; + } +} diff --git a/app/Services/ItemMaster/ItemFieldService.php b/app/Services/ItemMaster/ItemFieldService.php new file mode 100644 index 0000000..6d18397 --- /dev/null +++ b/app/Services/ItemMaster/ItemFieldService.php @@ -0,0 +1,115 @@ +tenantId(); + $userId = $this->apiUserId(); + + // order_no 자동 계산 (해당 섹션의 마지막 필드 + 1) + $maxOrder = ItemField::where('tenant_id', $tenantId) + ->where('section_id', $sectionId) + ->max('order_no'); + + $field = ItemField::create([ + 'tenant_id' => $tenantId, + 'section_id' => $sectionId, + 'field_name' => $data['field_name'], + 'field_type' => $data['field_type'], + 'order_no' => ($maxOrder ?? -1) + 1, + 'is_required' => $data['is_required'] ?? false, + 'default_value' => $data['default_value'] ?? null, + 'placeholder' => $data['placeholder'] ?? null, + 'display_condition' => $data['display_condition'] ?? null, + 'validation_rules' => $data['validation_rules'] ?? null, + 'options' => $data['options'] ?? null, + 'properties' => $data['properties'] ?? null, + 'created_by' => $userId, + ]); + + return $field; + } + + /** + * 필드 수정 + */ + public function update(int $id, array $data): ItemField + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $field = ItemField::where('tenant_id', $tenantId) + ->where('id', $id) + ->first(); + + if (! $field) { + throw new NotFoundHttpException(__('error.not_found')); + } + + $field->update([ + 'field_name' => $data['field_name'] ?? $field->field_name, + 'field_type' => $data['field_type'] ?? $field->field_type, + 'is_required' => $data['is_required'] ?? $field->is_required, + 'default_value' => $data['default_value'] ?? $field->default_value, + 'placeholder' => $data['placeholder'] ?? $field->placeholder, + 'display_condition' => $data['display_condition'] ?? $field->display_condition, + 'validation_rules' => $data['validation_rules'] ?? $field->validation_rules, + 'options' => $data['options'] ?? $field->options, + 'properties' => $data['properties'] ?? $field->properties, + 'updated_by' => $userId, + ]); + + return $field; + } + + /** + * 필드 삭제 (Soft Delete) + */ + public function destroy(int $id): void + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $field = ItemField::where('tenant_id', $tenantId) + ->where('id', $id) + ->first(); + + if (! $field) { + throw new NotFoundHttpException(__('error.not_found')); + } + + $field->update(['deleted_by' => $userId]); + $field->delete(); + } + + /** + * 필드 순서 변경 + * + * @param array $items [['id' => 1, 'order_no' => 0], ['id' => 2, 'order_no' => 1], ...] + */ + public function reorder(int $sectionId, array $items): void + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + foreach ($items as $item) { + ItemField::where('tenant_id', $tenantId) + ->where('section_id', $sectionId) + ->where('id', $item['id']) + ->update([ + 'order_no' => $item['order_no'], + 'updated_by' => $userId, + ]); + } + } +} diff --git a/app/Services/ItemMaster/ItemMasterService.php b/app/Services/ItemMaster/ItemMasterService.php new file mode 100644 index 0000000..2a3f24f --- /dev/null +++ b/app/Services/ItemMaster/ItemMasterService.php @@ -0,0 +1,64 @@ +tenantId(); + + // 1. 페이지 (섹션 → 필드 중첩) + $pages = ItemPage::with([ + 'sections' => function ($query) { + $query->orderBy('order_no'); + }, + 'sections.fields' => function ($query) { + $query->orderBy('order_no'); + }, + 'sections.bomItems', + ]) + ->where('tenant_id', $tenantId) + ->where('is_active', 1) + ->get(); + + // 2. 섹션 템플릿 + $sectionTemplates = SectionTemplate::where('tenant_id', $tenantId)->get(); + + // 3. 마스터 필드 + $masterFields = ItemMasterField::where('tenant_id', $tenantId)->get(); + + // 4. 커스텀 탭 (컬럼 설정 포함) + $customTabs = CustomTab::with('columnSetting') + ->where('tenant_id', $tenantId) + ->orderBy('order_no') + ->get(); + + // 5. 단위 옵션 + $unitOptions = UnitOption::where('tenant_id', $tenantId)->get(); + + return [ + 'pages' => $pages, + 'sectionTemplates' => $sectionTemplates, + 'masterFields' => $masterFields, + 'customTabs' => $customTabs, + 'unitOptions' => $unitOptions, + ]; + } +} diff --git a/app/Services/ItemMaster/ItemPageService.php b/app/Services/ItemMaster/ItemPageService.php new file mode 100644 index 0000000..4ab5223 --- /dev/null +++ b/app/Services/ItemMaster/ItemPageService.php @@ -0,0 +1,123 @@ +tenantId(); + + $query = ItemPage::with([ + 'sections' => function ($query) { + $query->orderBy('order_no'); + }, + 'sections.fields' => function ($query) { + $query->orderBy('order_no'); + }, + 'sections.bomItems', + ]) + ->where('tenant_id', $tenantId) + ->where('is_active', 1); + + if ($itemType) { + $query->where('item_type', strtoupper($itemType)); + } + + return $query->get(); + } + + /** + * 페이지 생성 + */ + public function store(array $data): ItemPage + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $page = ItemPage::create([ + 'tenant_id' => $tenantId, + 'page_name' => $data['page_name'], + 'item_type' => $data['item_type'], + 'absolute_path' => $data['absolute_path'] ?? null, + 'is_active' => true, + 'created_by' => $userId, + ]); + + // 관계 로드 + $page->load(['sections']); + + return $page; + } + + /** + * 페이지 수정 + */ + public function update(int $id, array $data): ItemPage + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $page = ItemPage::where('tenant_id', $tenantId) + ->where('id', $id) + ->first(); + + if (! $page) { + throw new NotFoundHttpException(__('error.not_found')); + } + + $page->update([ + 'page_name' => $data['page_name'] ?? $page->page_name, + 'absolute_path' => $data['absolute_path'] ?? $page->absolute_path, + 'updated_by' => $userId, + ]); + + $page->load(['sections.fields', 'sections.bomItems']); + + return $page; + } + + /** + * 페이지 삭제 (Soft Delete) + */ + public function destroy(int $id): void + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $page = ItemPage::where('tenant_id', $tenantId) + ->where('id', $id) + ->first(); + + if (! $page) { + throw new NotFoundHttpException(__('error.not_found')); + } + + $page->update(['deleted_by' => $userId]); + $page->delete(); + + // Cascade: 하위 섹션/필드도 Soft Delete + foreach ($page->sections as $section) { + $section->update(['deleted_by' => $userId]); + $section->delete(); + + foreach ($section->fields as $field) { + $field->update(['deleted_by' => $userId]); + $field->delete(); + } + + foreach ($section->bomItems as $bomItem) { + $bomItem->update(['deleted_by' => $userId]); + $bomItem->delete(); + } + } + } +} diff --git a/app/Services/ItemMaster/ItemSectionService.php b/app/Services/ItemMaster/ItemSectionService.php new file mode 100644 index 0000000..6a66280 --- /dev/null +++ b/app/Services/ItemMaster/ItemSectionService.php @@ -0,0 +1,115 @@ +tenantId(); + $userId = $this->apiUserId(); + + // order_no 자동 계산 (해당 페이지의 마지막 섹션 + 1) + $maxOrder = ItemSection::where('tenant_id', $tenantId) + ->where('page_id', $pageId) + ->max('order_no'); + + $section = ItemSection::create([ + 'tenant_id' => $tenantId, + 'page_id' => $pageId, + 'title' => $data['title'], + 'type' => $data['type'], + 'order_no' => ($maxOrder ?? -1) + 1, + 'created_by' => $userId, + ]); + + $section->load(['fields', 'bomItems']); + + return $section; + } + + /** + * 섹션 수정 + */ + public function update(int $id, array $data): ItemSection + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $section = ItemSection::where('tenant_id', $tenantId) + ->where('id', $id) + ->first(); + + if (! $section) { + throw new NotFoundHttpException(__('error.not_found')); + } + + $section->update([ + 'title' => $data['title'] ?? $section->title, + 'updated_by' => $userId, + ]); + + $section->load(['fields', 'bomItems']); + + return $section; + } + + /** + * 섹션 삭제 (Soft Delete) + */ + public function destroy(int $id): void + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + $section = ItemSection::where('tenant_id', $tenantId) + ->where('id', $id) + ->first(); + + if (! $section) { + throw new NotFoundHttpException(__('error.not_found')); + } + + $section->update(['deleted_by' => $userId]); + $section->delete(); + + // Cascade: 하위 필드도 Soft Delete + foreach ($section->fields as $field) { + $field->update(['deleted_by' => $userId]); + $field->delete(); + } + + foreach ($section->bomItems as $bomItem) { + $bomItem->update(['deleted_by' => $userId]); + $bomItem->delete(); + } + } + + /** + * 섹션 순서 변경 + * + * @param array $items [['id' => 1, 'order_no' => 0], ['id' => 2, 'order_no' => 1], ...] + */ + public function reorder(int $pageId, array $items): void + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + foreach ($items as $item) { + ItemSection::where('tenant_id', $tenantId) + ->where('page_id', $pageId) + ->where('id', $item['id']) + ->update([ + 'order_no' => $item['order_no'], + 'updated_by' => $userId, + ]); + } + } +} diff --git a/routes/api.php b/routes/api.php index 70e9066..1281c62 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,6 +19,10 @@ use App\Http\Controllers\Api\V1\EstimateController; use App\Http\Controllers\Api\V1\FileStorageController; use App\Http\Controllers\Api\V1\FolderController; +use App\Http\Controllers\Api\V1\ItemMaster\ItemFieldController; +use App\Http\Controllers\Api\V1\ItemMaster\ItemMasterController; +use App\Http\Controllers\Api\V1\ItemMaster\ItemPageController; +use App\Http\Controllers\Api\V1\ItemMaster\ItemSectionController; use App\Http\Controllers\Api\V1\MaterialController; use App\Http\Controllers\Api\V1\MenuController; use App\Http\Controllers\Api\V1\ModelSetController; @@ -469,6 +473,30 @@ Route::post('/reorder', [FolderController::class, 'reorder'])->name('v1.folders.reorder'); // 폴더 순서 변경 }); + // 품목기준관리 (ItemMaster) API + Route::prefix('item-master')->group(function () { + // 초기화 + Route::get('/init', [ItemMasterController::class, 'init'])->name('v1.item-master.init'); + + // 페이지 관리 + Route::get('/pages', [ItemPageController::class, 'index'])->name('v1.item-master.pages.index'); + Route::post('/pages', [ItemPageController::class, 'store'])->name('v1.item-master.pages.store'); + Route::put('/pages/{id}', [ItemPageController::class, 'update'])->name('v1.item-master.pages.update'); + Route::delete('/pages/{id}', [ItemPageController::class, 'destroy'])->name('v1.item-master.pages.destroy'); + + // 섹션 관리 + Route::post('/pages/{pageId}/sections', [ItemSectionController::class, 'store'])->name('v1.item-master.sections.store'); + Route::put('/sections/{id}', [ItemSectionController::class, 'update'])->name('v1.item-master.sections.update'); + Route::delete('/sections/{id}', [ItemSectionController::class, 'destroy'])->name('v1.item-master.sections.destroy'); + Route::put('/pages/{pageId}/sections/reorder', [ItemSectionController::class, 'reorder'])->name('v1.item-master.sections.reorder'); + + // 필드 관리 + Route::post('/sections/{sectionId}/fields', [ItemFieldController::class, 'store'])->name('v1.item-master.fields.store'); + Route::put('/fields/{id}', [ItemFieldController::class, 'update'])->name('v1.item-master.fields.update'); + Route::delete('/fields/{id}', [ItemFieldController::class, 'destroy'])->name('v1.item-master.fields.destroy'); + Route::put('/sections/{sectionId}/fields/reorder', [ItemFieldController::class, 'reorder'])->name('v1.item-master.fields.reorder'); + }); + }); // 공유 링크 다운로드 (인증 불필요 - auth.apikey 그룹 밖)