Files
sam-manage/app/Services/ItemManagementService.php
권혁성 f271f8bdc3 feat:품목관리 3-Panel 페이지 신규 구현 + FormulaEvaluatorService 연동
- 품목관리 3-Panel 레이아웃 (좌:목록, 중:BOM/수식산출, 우:상세)
- FormulaApiService로 API 견적수식 엔진 연동
- FG 품목 선택 시 기본값(W:1000, H:1000, QTY:1) 자동 산출
- 수식 산출 결과 트리 렌더링 (그룹별/소계/합계)
- 중앙 패널 클릭 시 우측 상세만 변경 (skipCenterUpdate)
- API 인증 버튼 전역 헤더로 이동 (모든 페이지에서 사용 가능)
- FormulaApiService에 Bearer 토큰 지원 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:50:24 +09:00

137 lines
4.3 KiB
PHP

<?php
namespace App\Services;
use App\Models\Items\Item;
use Illuminate\Pagination\LengthAwarePaginator;
class ItemManagementService
{
/**
* 품목 목록 조회 (검색, 유형 필터, 페이지네이션)
*/
public function getItemList(array $filters): LengthAwarePaginator
{
$tenantId = session('selected_tenant_id');
$search = $filters['search'] ?? null;
$itemType = $filters['item_type'] ?? null;
$perPage = $filters['per_page'] ?? 50;
return Item::withoutGlobalScopes()
->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,
];
}
}