Files
sam-api/app/Services/ItemMaster/EntityRelationshipService.php
hskwon bccfa19791 feat: Item Master 하이브리드 구조 전환 및 독립 API 추가
- CASCADE FK → 독립 엔티티 + entity_relationships 링크 테이블
- 독립 API 10개 추가 (섹션/필드/BOM CRUD, clone, usage)
- SectionTemplate 모델 제거 → ItemSection.is_template 통합
- 페이지-섹션, 섹션-필드, 섹션-BOM 링크/언링크 API 14개 추가
- Swagger 문서 업데이트
2025-11-26 14:09:31 +09:00

380 lines
11 KiB
PHP

<?php
namespace App\Services\ItemMaster;
use App\Models\ItemMaster\EntityRelationship;
use App\Models\ItemMaster\ItemBomItem;
use App\Models\ItemMaster\ItemField;
use App\Models\ItemMaster\ItemPage;
use App\Models\ItemMaster\ItemSection;
use App\Services\Service;
use Illuminate\Support\Collection;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* EntityRelationshipService - 엔티티 간 관계(링크) 관리 서비스
*/
class EntityRelationshipService extends Service
{
/**
* 페이지에 섹션 연결
*/
public function linkSectionToPage(int $pageId, int $sectionId, int $orderNo = 0): EntityRelationship
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
// 페이지 존재 확인
$page = ItemPage::where('tenant_id', $tenantId)->find($pageId);
if (! $page) {
throw new NotFoundHttpException(__('error.page_not_found'));
}
// 섹션 존재 확인
$section = ItemSection::where('tenant_id', $tenantId)->find($sectionId);
if (! $section) {
throw new NotFoundHttpException(__('error.section_not_found'));
}
return EntityRelationship::link(
$tenantId,
EntityRelationship::TYPE_PAGE,
$pageId,
EntityRelationship::TYPE_SECTION,
$sectionId,
$orderNo
);
}
/**
* 페이지에서 섹션 연결 해제
*/
public function unlinkSectionFromPage(int $pageId, int $sectionId): bool
{
$tenantId = $this->tenantId();
return EntityRelationship::unlink(
$tenantId,
EntityRelationship::TYPE_PAGE,
$pageId,
EntityRelationship::TYPE_SECTION,
$sectionId
);
}
/**
* 페이지에 필드 직접 연결
*/
public function linkFieldToPage(int $pageId, int $fieldId, int $orderNo = 0): EntityRelationship
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
// 페이지 존재 확인
$page = ItemPage::where('tenant_id', $tenantId)->find($pageId);
if (! $page) {
throw new NotFoundHttpException(__('error.page_not_found'));
}
// 필드 존재 확인
$field = ItemField::where('tenant_id', $tenantId)->find($fieldId);
if (! $field) {
throw new NotFoundHttpException(__('error.field_not_found'));
}
return EntityRelationship::link(
$tenantId,
EntityRelationship::TYPE_PAGE,
$pageId,
EntityRelationship::TYPE_FIELD,
$fieldId,
$orderNo
);
}
/**
* 페이지에서 필드 연결 해제
*/
public function unlinkFieldFromPage(int $pageId, int $fieldId): bool
{
$tenantId = $this->tenantId();
return EntityRelationship::unlink(
$tenantId,
EntityRelationship::TYPE_PAGE,
$pageId,
EntityRelationship::TYPE_FIELD,
$fieldId
);
}
/**
* 섹션에 필드 연결
*/
public function linkFieldToSection(int $sectionId, int $fieldId, int $orderNo = 0): EntityRelationship
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
// 섹션 존재 확인
$section = ItemSection::where('tenant_id', $tenantId)->find($sectionId);
if (! $section) {
throw new NotFoundHttpException(__('error.section_not_found'));
}
// 필드 존재 확인
$field = ItemField::where('tenant_id', $tenantId)->find($fieldId);
if (! $field) {
throw new NotFoundHttpException(__('error.field_not_found'));
}
return EntityRelationship::link(
$tenantId,
EntityRelationship::TYPE_SECTION,
$sectionId,
EntityRelationship::TYPE_FIELD,
$fieldId,
$orderNo
);
}
/**
* 섹션에서 필드 연결 해제
*/
public function unlinkFieldFromSection(int $sectionId, int $fieldId): bool
{
$tenantId = $this->tenantId();
return EntityRelationship::unlink(
$tenantId,
EntityRelationship::TYPE_SECTION,
$sectionId,
EntityRelationship::TYPE_FIELD,
$fieldId
);
}
/**
* 섹션에 BOM 항목 연결
*/
public function linkBomToSection(int $sectionId, int $bomId, int $orderNo = 0): EntityRelationship
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
// 섹션 존재 확인
$section = ItemSection::where('tenant_id', $tenantId)->find($sectionId);
if (! $section) {
throw new NotFoundHttpException(__('error.section_not_found'));
}
// BOM 항목 존재 확인
$bom = ItemBomItem::where('tenant_id', $tenantId)->find($bomId);
if (! $bom) {
throw new NotFoundHttpException(__('error.bom_not_found'));
}
return EntityRelationship::link(
$tenantId,
EntityRelationship::TYPE_SECTION,
$sectionId,
EntityRelationship::TYPE_BOM,
$bomId,
$orderNo
);
}
/**
* 섹션에서 BOM 항목 연결 해제
*/
public function unlinkBomFromSection(int $sectionId, int $bomId): bool
{
$tenantId = $this->tenantId();
return EntityRelationship::unlink(
$tenantId,
EntityRelationship::TYPE_SECTION,
$sectionId,
EntityRelationship::TYPE_BOM,
$bomId
);
}
/**
* 페이지의 모든 관계 조회
*/
public function getPageRelationships(int $pageId): Collection
{
$tenantId = $this->tenantId();
$page = ItemPage::where('tenant_id', $tenantId)->find($pageId);
if (! $page) {
throw new NotFoundHttpException(__('error.page_not_found'));
}
return EntityRelationship::where('tenant_id', $tenantId)
->where('parent_type', EntityRelationship::TYPE_PAGE)
->where('parent_id', $pageId)
->orderBy('order_no')
->get();
}
/**
* 섹션의 모든 자식 관계 조회
*/
public function getSectionChildRelationships(int $sectionId): Collection
{
$tenantId = $this->tenantId();
$section = ItemSection::where('tenant_id', $tenantId)->find($sectionId);
if (! $section) {
throw new NotFoundHttpException(__('error.section_not_found'));
}
return EntityRelationship::where('tenant_id', $tenantId)
->where('parent_type', EntityRelationship::TYPE_SECTION)
->where('parent_id', $sectionId)
->orderBy('order_no')
->get();
}
/**
* 특정 엔티티가 연결된 부모 목록 조회
*/
public function getParentRelationships(string $childType, int $childId): Collection
{
$tenantId = $this->tenantId();
return EntityRelationship::where('tenant_id', $tenantId)
->where('child_type', $childType)
->where('child_id', $childId)
->get();
}
/**
* 관계 순서 변경
*/
public function reorderRelationships(string $parentType, int $parentId, array $orderedChildIds): void
{
$tenantId = $this->tenantId();
foreach ($orderedChildIds as $index => $item) {
EntityRelationship::where([
'tenant_id' => $tenantId,
'parent_type' => $parentType,
'parent_id' => $parentId,
'child_type' => $item['child_type'],
'child_id' => $item['child_id'],
])->update(['order_no' => $index]);
}
}
/**
* 부모의 모든 자식 관계 일괄 삭제
*/
public function unlinkAllChildren(string $parentType, int $parentId, ?string $childType = null): int
{
$tenantId = $this->tenantId();
return EntityRelationship::unlinkAllChildren(
$tenantId,
$parentType,
$parentId,
$childType
);
}
/**
* 페이지 구조 조회 (섹션 + 직접 연결된 필드 포함)
*/
public function getPageStructure(int $pageId): array
{
$tenantId = $this->tenantId();
$page = ItemPage::where('tenant_id', $tenantId)->find($pageId);
if (! $page) {
throw new NotFoundHttpException(__('error.page_not_found'));
}
// 페이지의 모든 관계 조회
$relationships = EntityRelationship::where('tenant_id', $tenantId)
->where('parent_type', EntityRelationship::TYPE_PAGE)
->where('parent_id', $pageId)
->orderBy('order_no')
->get();
$structure = [
'page' => $page,
'sections' => [],
'direct_fields' => [],
];
foreach ($relationships as $rel) {
if ($rel->child_type === EntityRelationship::TYPE_SECTION) {
$section = ItemSection::find($rel->child_id);
if ($section) {
// 섹션의 자식(필드/BOM) 조회
$sectionChildren = $this->getSectionChildren($section->id);
$structure['sections'][] = [
'section' => $section,
'order_no' => $rel->order_no,
'fields' => $sectionChildren['fields'],
'bom_items' => $sectionChildren['bom_items'],
];
}
} elseif ($rel->child_type === EntityRelationship::TYPE_FIELD) {
$field = ItemField::find($rel->child_id);
if ($field) {
$structure['direct_fields'][] = [
'field' => $field,
'order_no' => $rel->order_no,
];
}
}
}
return $structure;
}
/**
* 섹션의 자식 엔티티 조회
*/
private function getSectionChildren(int $sectionId): array
{
$tenantId = $this->tenantId();
$relationships = EntityRelationship::where('tenant_id', $tenantId)
->where('parent_type', EntityRelationship::TYPE_SECTION)
->where('parent_id', $sectionId)
->orderBy('order_no')
->get();
$result = [
'fields' => [],
'bom_items' => [],
];
foreach ($relationships as $rel) {
if ($rel->child_type === EntityRelationship::TYPE_FIELD) {
$field = ItemField::find($rel->child_id);
if ($field) {
$result['fields'][] = [
'field' => $field,
'order_no' => $rel->order_no,
];
}
} elseif ($rel->child_type === EntityRelationship::TYPE_BOM) {
$bom = ItemBomItem::find($rel->child_id);
if ($bom) {
$result['bom_items'][] = [
'bom_item' => $bom,
'order_no' => $rel->order_no,
];
}
}
}
return $result;
}
}