null, 'tasks_count' => 0, 'issues_count' => 0, ]; // 1. 프로젝트 생성 $projectData = $data['project']; $project = AdminPmProject::create([ 'name' => $projectData['name'], 'description' => $projectData['description'] ?? null, 'status' => $projectData['status'] ?? AdminPmProject::STATUS_ACTIVE, 'start_date' => $projectData['start_date'] ?? null, 'end_date' => $projectData['end_date'] ?? null, 'created_by' => auth()->id(), ]); $result['project'] = $project; // 2. 작업 생성 $tasks = $data['tasks'] ?? []; $sortOrder = 1; foreach ($tasks as $taskData) { $task = AdminPmTask::create([ 'project_id' => $project->id, 'title' => $taskData['title'], 'description' => $taskData['description'] ?? null, 'status' => $taskData['status'] ?? AdminPmTask::STATUS_TODO, 'priority' => $taskData['priority'] ?? AdminPmTask::PRIORITY_MEDIUM, 'due_date' => $taskData['due_date'] ?? null, 'sort_order' => $sortOrder++, 'created_by' => auth()->id(), ]); $result['tasks_count']++; // 3. 작업별 이슈 생성 $issues = $taskData['issues'] ?? []; foreach ($issues as $issueData) { AdminPmIssue::create([ 'project_id' => $project->id, 'task_id' => $task->id, 'title' => $issueData['title'], 'description' => $issueData['description'] ?? null, 'type' => $issueData['type'] ?? AdminPmIssue::TYPE_BUG, 'status' => $issueData['status'] ?? AdminPmIssue::STATUS_OPEN, 'start_date' => $issueData['start_date'] ?? null, 'due_date' => $issueData['due_date'] ?? null, 'estimated_hours' => $issueData['estimated_hours'] ?? null, 'is_urgent' => $issueData['is_urgent'] ?? false, // 팀/담당자/고객사 (하이브리드) 'department_id' => $issueData['department_id'] ?? null, 'team' => $issueData['team'] ?? null, 'assignee_id' => $issueData['assignee_id'] ?? null, 'assignee_name' => $issueData['assignee_name'] ?? null, 'client' => $issueData['client'] ?? null, 'created_by' => auth()->id(), ]); $result['issues_count']++; } } return $result; }); } /** * 기존 프로젝트에 작업/이슈 추가 */ public function importTasksToProject(int $projectId, array $tasks): array { return DB::transaction(function () use ($projectId, $tasks) { $project = AdminPmProject::findOrFail($projectId); $result = [ 'project_id' => $projectId, 'tasks_count' => 0, 'issues_count' => 0, ]; $maxSortOrder = AdminPmTask::where('project_id', $projectId)->max('sort_order') ?? 0; $sortOrder = $maxSortOrder + 1; foreach ($tasks as $taskData) { $task = AdminPmTask::create([ 'project_id' => $project->id, 'title' => $taskData['title'], 'description' => $taskData['description'] ?? null, 'status' => $taskData['status'] ?? AdminPmTask::STATUS_TODO, 'priority' => $taskData['priority'] ?? AdminPmTask::PRIORITY_MEDIUM, 'due_date' => $taskData['due_date'] ?? null, 'sort_order' => $sortOrder++, 'created_by' => auth()->id(), ]); $result['tasks_count']++; $issues = $taskData['issues'] ?? []; foreach ($issues as $issueData) { AdminPmIssue::create([ 'project_id' => $project->id, 'task_id' => $task->id, 'title' => $issueData['title'], 'description' => $issueData['description'] ?? null, 'type' => $issueData['type'] ?? AdminPmIssue::TYPE_BUG, 'status' => $issueData['status'] ?? AdminPmIssue::STATUS_OPEN, 'start_date' => $issueData['start_date'] ?? null, 'due_date' => $issueData['due_date'] ?? null, 'estimated_hours' => $issueData['estimated_hours'] ?? null, 'is_urgent' => $issueData['is_urgent'] ?? false, // 팀/담당자/고객사 (하이브리드) 'department_id' => $issueData['department_id'] ?? null, 'team' => $issueData['team'] ?? null, 'assignee_id' => $issueData['assignee_id'] ?? null, 'assignee_name' => $issueData['assignee_name'] ?? null, 'client' => $issueData['client'] ?? null, 'created_by' => auth()->id(), ]); $result['issues_count']++; } } return $result; }); } /** * 기존 작업에 이슈만 추가 */ public function importIssuesToTask(int $taskId, array $issues): array { return DB::transaction(function () use ($taskId, $issues) { $task = AdminPmTask::findOrFail($taskId); $result = [ 'task_id' => $taskId, 'task_title' => $task->title, 'project_id' => $task->project_id, 'issues_count' => 0, ]; foreach ($issues as $issueData) { AdminPmIssue::create([ 'project_id' => $task->project_id, 'task_id' => $task->id, 'title' => $issueData['title'], 'description' => $issueData['description'] ?? null, 'type' => $issueData['type'] ?? AdminPmIssue::TYPE_FEATURE, 'status' => $issueData['status'] ?? AdminPmIssue::STATUS_OPEN, 'start_date' => $issueData['start_date'] ?? null, 'due_date' => $issueData['due_date'] ?? null, 'estimated_hours' => $issueData['estimated_hours'] ?? null, 'is_urgent' => $issueData['is_urgent'] ?? false, // 팀/담당자/고객사 (하이브리드) 'department_id' => $issueData['department_id'] ?? null, 'team' => $issueData['team'] ?? null, 'assignee_id' => $issueData['assignee_id'] ?? null, 'assignee_name' => $issueData['assignee_name'] ?? null, 'client' => $issueData['client'] ?? null, 'created_by' => auth()->id(), ]); $result['issues_count']++; } return $result; }); } /** * JSON 샘플 템플릿 반환 */ public function getSampleTemplate(): array { return [ 'project' => [ 'name' => '프로젝트명 (필수)', 'description' => '프로젝트 설명 (선택)', 'status' => 'active', 'start_date' => date('Y-m-d'), 'end_date' => date('Y-m-d', strtotime('+3 months')), ], 'tasks' => [ [ 'title' => '작업 1 (필수)', 'description' => '작업 설명 (선택)', 'status' => 'todo', 'priority' => 'medium', 'due_date' => date('Y-m-d', strtotime('+1 week')), 'issues' => [ [ 'title' => '이슈 1', 'description' => '이슈 설명', 'type' => 'feature', 'status' => 'open', 'start_date' => date('Y-m-d'), 'due_date' => date('Y-m-d', strtotime('+1 week')), 'estimated_hours' => 8, 'is_urgent' => false, // 방법 1: FK 연동 (DB에 존재하는 ID) 'department_id' => null, 'assignee_id' => null, // 방법 2: 문자열 직접 입력 (연동 없이) 'team' => '개발팀', 'assignee_name' => '홍길동', 'client' => '경동기업', ], ], ], [ 'title' => '작업 2', 'status' => 'todo', 'priority' => 'high', ], ], ]; } /** * JSON 유효성 검사 (FormRequest 전에 미리 체크) */ public function validateJsonStructure(array $data): array { $errors = []; if (! isset($data['project'])) { $errors[] = 'project 객체가 필요합니다.'; } elseif (! isset($data['project']['name']) || empty($data['project']['name'])) { $errors[] = 'project.name은 필수입니다.'; } if (isset($data['tasks']) && is_array($data['tasks'])) { foreach ($data['tasks'] as $index => $task) { if (! isset($task['title']) || empty($task['title'])) { $errors[] = "tasks[{$index}].title은 필수입니다."; } if (isset($task['issues']) && is_array($task['issues'])) { foreach ($task['issues'] as $issueIndex => $issue) { if (! isset($issue['title']) || empty($issue['title'])) { $errors[] = "tasks[{$index}].issues[{$issueIndex}].title은 필수입니다."; } } } } } return $errors; } }