diff --git a/app/Http/Controllers/Api/V1/ItemsBomController.php b/app/Http/Controllers/Api/V1/ItemsBomController.php index 2c64c7b..2c7ba51 100644 --- a/app/Http/Controllers/Api/V1/ItemsBomController.php +++ b/app/Http/Controllers/Api/V1/ItemsBomController.php @@ -16,6 +16,49 @@ */ class ItemsBomController extends Controller { + /** + * GET /api/v1/items/bom + * BOM이 있는 전체 품목 목록 조회 (item_id 없이) + */ + public function listAll(Request $request) + { + return ApiResponse::handle(function () use ($request) { + $tenantId = app('tenant_id'); + $perPage = (int) ($request->input('per_page', $request->input('size', 20))); + $itemType = $request->input('item_type'); + + $query = Item::query() + ->where('tenant_id', $tenantId) + ->whereNotNull('bom') + ->where('bom', '!=', '[]') + ->where('bom', '!=', 'null'); + + // item_type 필터 (선택) + if ($itemType) { + $query->where('item_type', strtoupper($itemType)); + } + + $items = $query->orderBy('id') + ->paginate($perPage, ['id', 'code', 'name', 'item_type', 'unit', 'bom']); + + // BOM 개수 추가 + $items->getCollection()->transform(function ($item) { + $bom = $item->bom ?? []; + return [ + 'id' => $item->id, + 'code' => $item->code, + 'name' => $item->name, + 'item_type' => $item->item_type, + 'unit' => $item->unit, + 'bom_count' => count($bom), + 'bom' => $this->expandBomItems($bom), + ]; + }); + + return $items; + }, __('message.bom.fetch')); + } + /** * GET /api/v1/items/{id}/bom * BOM 라인 목록 조회 (flat list) diff --git a/app/Services/ItemService.php b/app/Services/ItemService.php index dea89cb..fd56fdb 100644 --- a/app/Services/ItemService.php +++ b/app/Services/ItemService.php @@ -344,7 +344,7 @@ protected function fetchCategoryTree(?int $parentId = null) /** * 목록/검색 (동적 테이블 라우팅) * - * @param array $params 검색 파라미터 (item_type 또는 group_id 필수) + * @param array $params 검색 파라미터 (item_type/group_id 없으면 group_id=1 기본값) */ public function index(array $params): LengthAwarePaginator { @@ -355,9 +355,9 @@ public function index(array $params): LengthAwarePaginator $groupId = $params['group_id'] ?? null; $active = $params['active'] ?? null; - // item_type 또는 group_id 필수 검증 + // item_type 또는 group_id 없으면 group_id = 1 기본값 적용 if (! $itemType && ! $groupId) { - throw new BadRequestHttpException(__('error.item_type_or_group_required')); + $groupId = 1; } // group_id로 조회 시 해당 그룹의 모든 item_type 조회 @@ -778,11 +778,29 @@ public function toggle(int $id, string $itemType): array * code 기반 품목 조회 (동적 테이블 라우팅, BOM 포함 옵션) * * @param string $code 품목 코드 - * @param string $itemType 품목 유형 (필수) + * @param string $itemType 품목 유형 (없으면 items 테이블에서 직접 검색) * @param bool $includeBom BOM 포함 여부 */ public function showByCode(string $code, string $itemType, bool $includeBom = false): Model { + // item_type 없으면 items 테이블에서 직접 검색 + if (empty($itemType)) { + $item = Item::where('tenant_id', $this->tenantId()) + ->where('code', $code) + ->with(['category:id,name', 'details']) + ->first(); + + if (! $item) { + throw new BadRequestHttpException(__('error.not_found')); + } + + if ($includeBom && ! empty($item->bom) && method_exists($item, 'loadBomChildren')) { + $item->loadBomChildren(); + } + + return $item; + } + // 동적 테이블 라우팅 $modelInfo = $this->getModelInfoByItemType($itemType); $query = $this->newQuery($itemType)->where('code', $code); diff --git a/routes/api.php b/routes/api.php index 71ab878..30d1618 100644 --- a/routes/api.php +++ b/routes/api.php @@ -778,6 +778,10 @@ Route::delete('/{id}', [ItemsController::class, 'destroy'])->name('v1.items.destroy'); // 품목 삭제 }); + // Items BOM - 전체 BOM 목록 (item_id 없이) + // 주의: /items/{id}/bom 보다 먼저 정의해야 함 ('bom'이 {id}로 인식되지 않도록) + Route::get('items/bom', [ItemsBomController::class, 'listAll'])->name('v1.items.bom.list-all'); + // Items BOM (ID-based BOM API) Route::prefix('items/{id}/bom')->group(function () { Route::get('', [ItemsBomController::class, 'index'])->name('v1.items.bom.index'); // BOM 목록 (flat)