tenantId(); $size = (int)($params['size'] ?? 20); $sort = $params['sort'] ?? 'sort_order'; $order = strtolower($params['order'] ?? 'asc') === 'desc' ? 'desc' : 'asc'; return CategoryField::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->orderBy($sort, $order) ->paginate($size); } public function store(int $categoryId, array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $this->assertCategoryExists($tenantId, $categoryId); $v = Validator::make($data, [ 'field_key' => 'required|string|max:30|alpha_dash', 'field_name' => 'required|string|max:100', 'field_type' => 'required|string|max:20', 'is_required' => 'nullable|in:Y,N', 'sort_order' => 'nullable|integer|min:0', 'default_value' => 'nullable|string|max:100', 'options' => 'nullable|json', 'description' => 'nullable|string|max:255', ]); $payload = $v->validate(); // 카테고리 내 field_key 유니크 검증 $exists = CategoryField::query() ->where(compact('tenant_id')) ->where('category_id', $categoryId) ->where('field_key', $payload['field_key']) ->exists(); if ($exists) { throw new BadRequestHttpException(__('error.duplicate_key')); // ko/error.php에 매핑 } $payload['tenant_id'] = $tenantId; $payload['category_id'] = $categoryId; $payload['is_required'] = $payload['is_required'] ?? 'N'; $payload['sort_order'] = $payload['sort_order'] ?? 0; $payload['created_by'] = $userId; return CategoryField::create($payload); } public function show(int $fieldId) { $tenantId = $this->tenantId(); $field = CategoryField::query() ->where('tenant_id', $tenantId) ->find($fieldId); if (!$field) { throw new BadRequestHttpException(__('error.not_found')); } return $field; } public function update(int $fieldId, array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $field = CategoryField::query() ->where('tenant_id', $tenantId) ->find($fieldId); if (!$field) { throw new BadRequestHttpException(__('error.not_found')); } $v = Validator::make($data, [ 'field_key' => 'sometimes|string|max:30|alpha_dash', 'field_name' => 'sometimes|string|max:100', 'field_type' => 'sometimes|string|max:20', 'is_required' => 'sometimes|in:Y,N', 'sort_order' => 'sometimes|integer|min:0', 'default_value' => 'nullable|string|max:100', 'options' => 'nullable|json', 'description' => 'nullable|string|max:255', ]); $payload = $v->validate(); if (isset($payload['field_key']) && $payload['field_key'] !== $field->field_key) { $dup = CategoryField::query() ->where('tenant_id', $tenantId) ->where('category_id', $field->category_id) ->where('field_key', $payload['field_key']) ->exists(); if ($dup) { throw new BadRequestHttpException(__('error.duplicate_key')); } } $payload['updated_by'] = $userId; $field->update($payload); return $field->refresh(); } public function destroy(int $fieldId): void { $tenantId = $this->tenantId(); $field = CategoryField::query() ->where('tenant_id', $tenantId) ->find($fieldId); if (!$field) { throw new BadRequestHttpException(__('error.not_found')); } $field->delete(); } public function reorder(int $categoryId, array $items): void { $tenantId = $this->tenantId(); $this->assertCategoryExists($tenantId, $categoryId); $rows = $items['items'] ?? $items; // 둘 다 허용 if (!is_array($rows)) { throw new BadRequestHttpException(__('error.invalid_payload')); } DB::transaction(function () use ($tenantId, $categoryId, $rows) { foreach ($rows as $row) { if (!isset($row['id'], $row['sort_order'])) continue; CategoryField::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->where('id', $row['id']) ->update(['sort_order' => (int)$row['sort_order']]); } }); } public function bulkUpsert(int $categoryId, array $items): array { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $this->assertCategoryExists($tenantId, $categoryId); if (!is_array($items) || empty($items)) { throw new BadRequestHttpException(__('error.empty_items')); } $result = ['created' => 0, 'updated' => 0]; DB::transaction(function () use ($tenantId, $userId, $categoryId, $items, &$result) { foreach ($items as $it) { $v = Validator::make($it, [ 'id' => 'nullable|integer', 'field_key' => 'sometimes|required_without:id|string|max:30|alpha_dash', 'field_name' => 'required|string|max:100', 'field_type' => 'required|string|max:20', 'is_required' => 'nullable|in:Y,N', 'sort_order' => 'nullable|integer|min:0', 'default_value' => 'nullable|string|max:100', 'options' => 'nullable|json', 'description' => 'nullable|string|max:255', ]); $payload = $v->validate(); if (!empty($payload['id'])) { $model = CategoryField::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->find($payload['id']); if (!$model) { throw new BadRequestHttpException(__('error.not_found')); } // field_key 변경 유니크 검사 if (isset($payload['field_key']) && $payload['field_key'] !== $model->field_key) { $dup = CategoryField::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->where('field_key', $payload['field_key']) ->exists(); if ($dup) { throw new BadRequestHttpException(__('error.duplicate_key')); } } $payload['updated_by'] = $userId; $model->update($payload); $result['updated']++; } else { // 신규 생성 if (empty($payload['field_key'])) { throw new BadRequestHttpException(__('error.required', ['attr' => 'field_key'])); } $dup = CategoryField::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->where('field_key', $payload['field_key']) ->exists(); if ($dup) { throw new BadRequestHttpException(__('error.duplicate_key')); } $payload['tenant_id'] = $tenantId; $payload['category_id'] = $categoryId; $payload['is_required'] = $payload['is_required'] ?? 'N'; $payload['sort_order'] = $payload['sort_order'] ?? 0; $payload['created_by'] = $userId; CategoryField::create($payload); $result['created']++; } } }); return $result; } private function assertCategoryExists(int $tenantId, int $categoryId): void { $exists = Category::query() ->where('tenant_id', $tenantId) ->where('id', $categoryId) ->exists(); if (!$exists) { throw new BadRequestHttpException(__('error.category_not_found')); } } }