when($params['item_category'] ?? null, fn ($q, $v) => $q->where('model_type', $v)) ->when($params['item_sep'] ?? null, fn ($q, $v) => $q->where('item_sep', $v)) ->when($params['model_UA'] ?? null, fn ($q, $v) => $q->where('model_UA', $v)) ->when($params['check_type'] ?? null, fn ($q, $v) => $q->where('check_type', $v)) ->when($params['model_name'] ?? null, fn ($q, $v) => $q->where('model_name', $v)) ->when($params['exit_direction'] ?? null, fn ($q, $v) => $q->where('exit_direction', $v)) ->when($params['search'] ?? null, fn ($q, $v) => $q->where( fn ($q2) => $q2 ->where('name', 'like', "%{$v}%") ->orWhere('code', 'like', "%{$v}%") ->orWhere('model_name', 'like', "%{$v}%") ->orWhere('search_keyword', 'like', "%{$v}%") )) ->orderByDesc('id') ->paginate($params['size'] ?? 50); } public function filters(): array { return [ 'item_sep' => BendingModel::whereIn('model_type', self::CATEGORIES)->whereNotNull('item_sep')->distinct()->pluck('item_sep')->sort()->values(), 'model_UA' => BendingModel::whereIn('model_type', self::CATEGORIES)->whereNotNull('model_UA')->distinct()->pluck('model_UA')->sort()->values(), 'check_type' => BendingModel::whereIn('model_type', self::CATEGORIES)->whereNotNull('check_type')->distinct()->pluck('check_type')->sort()->values(), 'model_name' => BendingModel::whereIn('model_type', self::CATEGORIES)->whereNotNull('model_name')->distinct()->pluck('model_name')->sort()->values(), 'finishing_type' => BendingModel::whereIn('model_type', self::CATEGORIES)->whereNotNull('finishing_type')->distinct()->pluck('finishing_type')->sort()->values(), ]; } public function find(int $id): BendingModel { return BendingModel::whereIn('model_type', self::CATEGORIES)->findOrFail($id); } public function create(array $data): BendingModel { // component 이미지 복사 (기초관리 원본 → 독립 복사본) if (! empty($data['components'])) { $data['components'] = $this->copyComponentImages($data['components']); } return BendingModel::create([ 'tenant_id' => $this->tenantId(), 'model_type' => $data['item_category'] ?? 'GUIDERAIL_MODEL', 'code' => $data['code'], 'name' => $data['name'], 'model_name' => $data['model_name'] ?? null, 'model_UA' => $data['model_UA'] ?? null, 'item_sep' => $data['item_sep'] ?? null, 'finishing_type' => $data['finishing_type'] ?? null, 'check_type' => $data['check_type'] ?? null, 'rail_width' => $data['rail_width'] ?? null, 'rail_length' => $data['rail_length'] ?? null, 'exit_direction' => $data['exit_direction'] ?? null, 'front_bottom_width' => $data['front_bottom_width'] ?? null, 'box_width' => $data['box_width'] ?? null, 'box_height' => $data['box_height'] ?? null, 'bar_width' => $data['bar_width'] ?? null, 'bar_height' => $data['bar_height'] ?? null, 'components' => $data['components'] ?? null, 'material_summary' => $data['material_summary'] ?? null, 'search_keyword' => $data['search_keyword'] ?? null, 'author' => $data['author'] ?? null, 'memo' => $data['memo'] ?? null, 'registration_date' => $data['registration_date'] ?? null, 'options' => $this->buildOptions($data), 'is_active' => true, 'created_by' => $this->apiUserId(), ]); } public function update(int $id, array $data): BendingModel { $item = BendingModel::whereIn('model_type', self::CATEGORIES)->findOrFail($id); $columns = [ 'code', 'name', 'model_name', 'model_UA', 'item_sep', 'finishing_type', 'check_type', 'rail_width', 'rail_length', 'exit_direction', 'front_bottom_width', 'box_width', 'box_height', 'bar_width', 'bar_height', 'components', 'material_summary', 'search_keyword', 'author', 'registration_date', ]; foreach ($columns as $col) { if (array_key_exists($col, $data)) { // components 저장 시 이미지 복사 if ($col === 'components' && ! empty($data[$col])) { $item->{$col} = $this->copyComponentImages($data[$col]); } else { $item->{$col} = $data[$col]; } } } // memo → options if (array_key_exists('memo', $data)) { $item->setOption('memo', $data['memo']); } if (array_key_exists('modified_by', $data)) { $item->setOption('modified_by', $data['modified_by']); } $item->updated_by = $this->apiUserId(); $item->save(); return $item; } public function delete(int $id): bool { $item = BendingModel::whereIn('model_type', self::CATEGORIES)->findOrFail($id); $item->deleted_by = $this->apiUserId(); $item->save(); return $item->delete(); } /** * component의 image_file_id가 bending_item 원본이면 복사본 생성 */ private function copyComponentImages(array $components): array { $tenantId = $this->tenantId(); foreach ($components as &$comp) { $fileId = $comp['image_file_id'] ?? null; if (! $fileId) { continue; } $source = File::find($fileId); if (! $source || ! $source->file_path) { continue; } // 이미 component_image면 복사 불필요 (이미 독립 복사본) if ($source->field_key === 'component_image') { continue; } // bending_item 원본이면 복사 try { $extension = pathinfo($source->stored_name, PATHINFO_EXTENSION); $storedName = bin2hex(random_bytes(8)) . '.' . $extension; $directory = sprintf('%d/bending/model-parts/%s/%s', $tenantId, date('Y'), date('m')); $newPath = $directory . '/' . $storedName; Storage::disk('r2')->put($newPath, Storage::disk('r2')->get($source->file_path)); $newFile = File::create([ 'tenant_id' => $tenantId, 'display_name' => $source->display_name, 'stored_name' => $storedName, 'file_path' => $newPath, 'file_size' => $source->file_size, 'mime_type' => $source->mime_type, 'file_type' => 'image', 'field_key' => 'component_image', 'document_id' => 0, 'document_type' => 'bending_model', 'is_temp' => false, 'uploaded_by' => $this->apiUserId(), 'created_by' => $this->apiUserId(), ]); $comp['image_file_id'] = $newFile->id; } catch (\Throwable $e) { // 복사 실패 시 원본 ID 유지 } } unset($comp); return $components; } private function buildOptions(array $data): ?array { $opts = []; foreach (['memo', 'modified_by'] as $key) { if (! empty($data[$key])) { $opts[$key] = $data[$key]; } } return empty($opts) ? null : $opts; } }