tenantId(); $page = (int) ($params['page'] ?? 1); $size = (int) ($params['size'] ?? 20); $q = trim((string) ($params['q'] ?? '')); $status = $params['status'] ?? null; $processType = $params['process_type'] ?? null; $query = Process::query() ->where('tenant_id', $tenantId) ->with('classificationRules'); // 검색어 if ($q !== '') { $query->where(function ($qq) use ($q) { $qq->where('process_name', 'like', "%{$q}%") ->orWhere('process_code', 'like', "%{$q}%") ->orWhere('description', 'like', "%{$q}%") ->orWhere('department', 'like', "%{$q}%"); }); } // 상태 필터 if ($status === 'active') { $query->where('is_active', true); } elseif ($status === 'inactive') { $query->where('is_active', false); } // 공정구분 필터 if ($processType) { $query->where('process_type', $processType); } $query->orderBy('process_code'); return $query->paginate($size, ['*'], 'page', $page); } /** * 공정 상세 조회 */ public function show(int $id) { $tenantId = $this->tenantId(); $process = Process::where('tenant_id', $tenantId) ->with('classificationRules') ->find($id); if (! $process) { throw new NotFoundHttpException(__('error.not_found')); } return $process; } /** * 공정 생성 */ public function store(array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($data, $tenantId, $userId) { // 공정코드 자동 생성 $data['process_code'] = $this->generateProcessCode($tenantId); $data['tenant_id'] = $tenantId; $data['created_by'] = $userId; $data['is_active'] = $data['is_active'] ?? true; // work_steps가 문자열이면 배열로 변환 if (isset($data['work_steps']) && is_string($data['work_steps'])) { $data['work_steps'] = array_map('trim', explode(',', $data['work_steps'])); } $rules = $data['classification_rules'] ?? []; unset($data['classification_rules']); $process = Process::create($data); // 분류 규칙 저장 $this->syncClassificationRules($process, $rules); return $process->load('classificationRules'); }); } /** * 공정 수정 */ public function update(int $id, array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $process = Process::where('tenant_id', $tenantId)->find($id); if (! $process) { throw new NotFoundHttpException(__('error.not_found')); } return DB::transaction(function () use ($process, $data, $userId) { $data['updated_by'] = $userId; // work_steps가 문자열이면 배열로 변환 if (isset($data['work_steps']) && is_string($data['work_steps'])) { $data['work_steps'] = array_map('trim', explode(',', $data['work_steps'])); } $rules = $data['classification_rules'] ?? null; unset($data['classification_rules']); $process->update($data); // 분류 규칙 동기화 (전달된 경우만) if ($rules !== null) { $this->syncClassificationRules($process, $rules); } return $process->fresh('classificationRules'); }); } /** * 공정 삭제 */ public function destroy(int $id) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $process = Process::where('tenant_id', $tenantId)->find($id); if (! $process) { throw new NotFoundHttpException(__('error.not_found')); } $process->update(['deleted_by' => $userId]); $process->delete(); return true; } /** * 공정 일괄 삭제 */ public function destroyMany(array $ids) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $count = Process::where('tenant_id', $tenantId) ->whereIn('id', $ids) ->update(['deleted_by' => $userId, 'deleted_at' => now()]); return $count; } /** * 공정 상태 토글 */ public function toggleActive(int $id) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $process = Process::where('tenant_id', $tenantId)->find($id); if (! $process) { throw new NotFoundHttpException(__('error.not_found')); } $process->update([ 'is_active' => ! $process->is_active, 'updated_by' => $userId, ]); return $process->fresh('classificationRules'); } /** * 공정코드 자동 생성 (P-001, P-002, ...) */ private function generateProcessCode(int $tenantId): string { $lastProcess = Process::where('tenant_id', $tenantId) ->withTrashed() ->orderByRaw("CAST(SUBSTRING(process_code, 3) AS UNSIGNED) DESC") ->first(); if ($lastProcess && preg_match('/^P-(\d+)$/', $lastProcess->process_code, $matches)) { $nextNum = (int) $matches[1] + 1; } else { $nextNum = 1; } return sprintf('P-%03d', $nextNum); } /** * 분류 규칙 동기화 */ private function syncClassificationRules(Process $process, array $rules): void { // 기존 규칙 삭제 $process->classificationRules()->delete(); // 새 규칙 생성 foreach ($rules as $index => $rule) { ProcessClassificationRule::create([ 'process_id' => $process->id, 'registration_type' => $rule['registration_type'] ?? 'pattern', 'rule_type' => $rule['rule_type'], 'matching_type' => $rule['matching_type'], 'condition_value' => $rule['condition_value'], 'priority' => $rule['priority'] ?? $index, 'description' => $rule['description'] ?? null, 'is_active' => $rule['is_active'] ?? true, ]); } } /** * 드롭다운용 공정 옵션 목록 */ public function options() { $tenantId = $this->tenantId(); return Process::where('tenant_id', $tenantId) ->where('is_active', true) ->orderBy('process_code') ->select('id', 'process_code', 'process_name', 'process_type', 'department') ->get(); } /** * 통계 */ public function getStats() { $tenantId = $this->tenantId(); $total = Process::where('tenant_id', $tenantId)->count(); $active = Process::where('tenant_id', $tenantId)->where('is_active', true)->count(); $inactive = Process::where('tenant_id', $tenantId)->where('is_active', false)->count(); $byType = Process::where('tenant_id', $tenantId) ->where('is_active', true) ->groupBy('process_type') ->selectRaw('process_type, count(*) as count') ->pluck('count', 'process_type'); return [ 'total' => $total, 'active' => $active, 'inactive' => $inactive, 'by_type' => $byType, ]; } }