tenantId(); $query = ItemSection::where('tenant_id', $tenantId); if ($isTemplate === true) { $query->templates(); } elseif ($isTemplate === false) { $query->nonTemplates(); } return $query->orderBy('created_at', 'desc')->get(); } /** * 독립 섹션 생성 (페이지 연결 없음) * * POST /api/v1/item-master/sections */ public function storeIndependent(array $data): ItemSection { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $section = ItemSection::create([ 'tenant_id' => $tenantId, 'group_id' => $data['group_id'] ?? 1, 'title' => $data['title'], 'type' => $data['type'], 'order_no' => 0, 'is_template' => $data['is_template'] ?? false, 'is_default' => $data['is_default'] ?? false, 'description' => $data['description'] ?? null, 'created_by' => $userId, ]); return $section->loadRelatedEntities(); } /** * 섹션 복제 * * POST /api/v1/item-master/sections/{id}/clone */ public function clone(int $id): ItemSection { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $original = ItemSection::where('tenant_id', $tenantId) ->where('id', $id) ->first(); if (! $original) { throw new NotFoundHttpException(__('error.section_not_found')); } // 원본 섹션의 관련 엔티티 로드 (entity_relationships 기반) $original->loadRelatedEntities(); // 섹션 복제 $cloned = ItemSection::create([ 'tenant_id' => $tenantId, 'group_id' => $original->group_id, 'title' => $original->title.' (복사본)', 'type' => $original->type, 'order_no' => 0, 'is_template' => $original->is_template, 'is_default' => false, 'description' => $original->description, 'created_by' => $userId, ]); // 필드 복제 (entity_relationships 기반) foreach ($original->getRelation('fields') as $orderNo => $field) { $clonedField = ItemField::create([ 'tenant_id' => $tenantId, 'group_id' => $field->group_id, 'field_name' => $field->field_name, 'field_type' => $field->field_type, 'order_no' => $field->order_no, 'is_required' => $field->is_required, 'default_value' => $field->default_value, 'placeholder' => $field->placeholder, 'display_condition' => $field->display_condition, 'validation_rules' => $field->validation_rules, 'options' => $field->options, 'properties' => $field->properties, 'created_by' => $userId, ]); // 섹션-필드 관계 생성 EntityRelationship::link( $tenantId, EntityRelationship::TYPE_SECTION, $cloned->id, EntityRelationship::TYPE_FIELD, $clonedField->id, $field->order_no, null, $field->group_id ); } // BOM 항목 복제 (entity_relationships 기반) foreach ($original->getRelation('bomItems') as $orderNo => $bom) { $clonedBom = ItemBomItem::create([ 'tenant_id' => $tenantId, 'group_id' => $bom->group_id, 'item_code' => $bom->item_code, 'item_name' => $bom->item_name, 'quantity' => $bom->quantity, 'unit' => $bom->unit, 'unit_price' => $bom->unit_price, 'total_price' => $bom->total_price, 'spec' => $bom->spec, 'note' => $bom->note, 'created_by' => $userId, ]); // 섹션-BOM 관계 생성 EntityRelationship::link( $tenantId, EntityRelationship::TYPE_SECTION, $cloned->id, EntityRelationship::TYPE_BOM, $clonedBom->id, $orderNo, null, $bom->group_id ); } return $cloned->loadRelatedEntities(); } /** * 섹션 사용처 조회 (어떤 페이지에 연결되어 있는지) * * GET /api/v1/item-master/sections/{id}/usage */ public function getUsage(int $id): array { $tenantId = $this->tenantId(); $section = ItemSection::where('tenant_id', $tenantId) ->where('id', $id) ->first(); if (! $section) { throw new NotFoundHttpException(__('error.section_not_found')); } // entity_relationships 기반 연결 $linkedPages = $section->linkedPages()->get(); return [ 'section_id' => $id, 'linked_pages' => $linkedPages, 'total_usage_count' => $linkedPages->count(), ]; } /** * 섹션 생성 및 페이지에 연결 */ public function store(int $pageId, array $data): ItemSection { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); // order_no 자동 계산 (해당 페이지에 연결된 마지막 섹션 + 1) $maxOrder = EntityRelationship::where('parent_type', EntityRelationship::TYPE_PAGE) ->where('parent_id', $pageId) ->where('child_type', EntityRelationship::TYPE_SECTION) ->max('order_no'); // 섹션 생성 $section = ItemSection::create([ 'tenant_id' => $tenantId, 'group_id' => $data['group_id'] ?? 1, 'title' => $data['title'], 'type' => $data['type'], 'order_no' => ($maxOrder ?? -1) + 1, 'created_by' => $userId, ]); // 페이지-섹션 관계 생성 EntityRelationship::link( $tenantId, EntityRelationship::TYPE_PAGE, $pageId, EntityRelationship::TYPE_SECTION, $section->id, ($maxOrder ?? -1) + 1, null, $data['group_id'] ?? 1 ); $section->loadRelatedEntities(); 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->loadRelatedEntities(); return $section; } /** * 섹션 삭제 (Soft Delete) * * 독립 엔티티 아키텍처: 섹션만 삭제하고 연결된 필드/BOM은 unlink만 수행 * * @throws \App\Exceptions\BusinessException 잠금된 연결이 있는 경우 */ 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')); } // 잠금 체크: 이 섹션이 잠금된 연결로 보호되고 있는지 확인 $this->checkCanDelete(EntityRelationship::TYPE_SECTION, $id); // 1. entity_relationships에서 이 섹션의 모든 부모 관계 해제 // (page→section 관계에서 이 섹션 제거) // 주의: 잠금된 연결이 있으면 예외 발생 EntityRelationship::where('child_type', EntityRelationship::TYPE_SECTION) ->where('child_id', $id) ->where('is_locked', false) ->delete(); // 2. entity_relationships에서 이 섹션의 모든 자식 관계 해제 // (section→field, section→bom 관계 삭제) // 주의: 잠금된 연결이 있으면 예외 발생 (unlinkAllChildren에서 처리) EntityRelationship::unlinkAllChildren( $tenantId, EntityRelationship::TYPE_SECTION, $id ); // 3. 섹션만 Soft Delete (필드/BOM은 독립 엔티티로 유지) $section->update(['deleted_by' => $userId]); $section->delete(); } /** * 섹션 순서 변경 (entity_relationships 기반) * * @param array $items [['id' => 1, 'order_no' => 0], ['id' => 2, 'order_no' => 1], ...] */ public function reorder(int $pageId, array $items): void { foreach ($items as $item) { // entity_relationships에서 순서 업데이트 EntityRelationship::where('parent_type', EntityRelationship::TYPE_PAGE) ->where('parent_id', $pageId) ->where('child_type', EntityRelationship::TYPE_SECTION) ->where('child_id', $item['id']) ->update(['order_no' => $item['order_no']]); // 섹션 자체의 order_no도 동기화 ItemSection::where('id', $item['id']) ->update(['order_no' => $item['order_no']]); } } }