where('tenant_id', $tenantId) ->search($search) ->active() ->when($itemType, function ($query, $types) { $typeList = explode(',', $types); $query->whereIn('item_type', $typeList); }) ->orderBy('code') ->paginate($perPage); } /** * BOM 재귀 트리 조회 */ public function getBomTree(int $itemId, int $maxDepth = 10): array { $item = Item::withoutGlobalScopes() ->where('tenant_id', session('selected_tenant_id')) ->with('details') ->findOrFail($itemId); return $this->buildBomNode($item, 0, $maxDepth, []); } /** * 품목 상세 조회 (1depth BOM + 파일 + 절곡정보) */ public function getItemDetail(int $itemId): array { $tenantId = session('selected_tenant_id'); $item = Item::withoutGlobalScopes() ->where('tenant_id', $tenantId) ->with(['details', 'category', 'files']) ->findOrFail($itemId); // BOM 1depth: 직접 연결된 자식 품목만 $bomChildren = []; $bomData = $item->bom ?? []; if (! empty($bomData)) { $childIds = array_column($bomData, 'child_item_id'); $children = Item::withoutGlobalScopes() ->where('tenant_id', $tenantId) ->whereIn('id', $childIds) ->get(['id', 'code', 'name', 'item_type', 'unit']) ->keyBy('id'); foreach ($bomData as $bom) { $child = $children->get($bom['child_item_id']); if ($child) { $bomChildren[] = [ 'id' => $child->id, 'code' => $child->code, 'name' => $child->name, 'item_type' => $child->item_type, 'unit' => $child->unit, 'quantity' => $bom['quantity'] ?? 1, ]; } } } return [ 'item' => $item, 'bom_children' => $bomChildren, ]; } // ── Private ── private function buildBomNode(Item $item, int $depth, int $maxDepth, array $visited): array { // 순환 참조 방지: visited 배열 + maxDepth 이중 안전장치 if (in_array($item->id, $visited) || $depth >= $maxDepth) { return $this->formatNode($item, $depth, []); } $visited[] = $item->id; $children = []; $bomData = $item->bom ?? []; if (! empty($bomData)) { $childIds = array_column($bomData, 'child_item_id'); $childItems = Item::withoutGlobalScopes() ->where('tenant_id', session('selected_tenant_id')) ->whereIn('id', $childIds) ->get() ->keyBy('id'); foreach ($bomData as $bom) { $childItem = $childItems->get($bom['child_item_id']); if ($childItem) { $childNode = $this->buildBomNode($childItem, $depth + 1, $maxDepth, $visited); $childNode['quantity'] = $bom['quantity'] ?? 1; $children[] = $childNode; } } } return $this->formatNode($item, $depth, $children); } private function formatNode(Item $item, int $depth, array $children): array { return [ 'id' => $item->id, 'code' => $item->code, 'name' => $item->name, 'item_type' => $item->item_type, 'unit' => $item->unit, 'depth' => $depth, 'has_children' => count($children) > 0, 'children' => $children, ]; } }