- ImportService에 importIssuesToTask 메서드 추가
- ImportController에 importIssues 액션 추가
- ImportIssuesRequest FormRequest 생성
- POST /api/admin/pm/import/task/{taskId}/issues 라우트 추가
- import.blade.php UI에 '기존 작업에 이슈 추가' 모드 추가
- ImportProjectRequest에 tasks 레벨 검증 규칙 보완
265 lines
11 KiB
PHP
265 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services\ProjectManagement;
|
|
|
|
use App\Models\Admin\AdminPmIssue;
|
|
use App\Models\Admin\AdminPmProject;
|
|
use App\Models\Admin\AdminPmTask;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ImportService
|
|
{
|
|
/**
|
|
* JSON 데이터로 프로젝트, 작업, 이슈 일괄 생성
|
|
*/
|
|
public function importFromJson(array $data): array
|
|
{
|
|
return DB::transaction(function () use ($data) {
|
|
$result = [
|
|
'project' => 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;
|
|
}
|
|
}
|