diff --git a/app/Http/Requests/ProjectManagement/ImportProjectRequest.php b/app/Http/Requests/ProjectManagement/ImportProjectRequest.php index 75f7297a..cbd4e4fa 100644 --- a/app/Http/Requests/ProjectManagement/ImportProjectRequest.php +++ b/app/Http/Requests/ProjectManagement/ImportProjectRequest.php @@ -36,6 +36,17 @@ public function rules(): array 'tasks.*.issues.*.description' => 'nullable|string', 'tasks.*.issues.*.type' => 'nullable|in:bug,feature,improvement', 'tasks.*.issues.*.status' => 'nullable|in:open,in_progress,resolved,closed', + // 일정 관련 + 'tasks.*.issues.*.start_date' => 'nullable|date', + 'tasks.*.issues.*.due_date' => 'nullable|date|after_or_equal:tasks.*.issues.*.start_date', + 'tasks.*.issues.*.estimated_hours' => 'nullable|integer|min:0', + 'tasks.*.issues.*.is_urgent' => 'nullable|boolean', + // 팀/담당자/고객사 (하이브리드) + 'tasks.*.issues.*.department_id' => 'nullable|integer|exists:departments,id', + 'tasks.*.issues.*.team' => 'nullable|string|max:100', + 'tasks.*.issues.*.assignee_id' => 'nullable|integer|exists:users,id', + 'tasks.*.issues.*.assignee_name' => 'nullable|string|max:100', + 'tasks.*.issues.*.client' => 'nullable|string|max:100', ]; } @@ -48,4 +59,4 @@ public function messages(): array 'tasks.*.issues.*.title.required' => '각 이슈의 title은 필수입니다.', ]; } -} \ No newline at end of file +} diff --git a/app/Http/Requests/ProjectManagement/StoreIssueRequest.php b/app/Http/Requests/ProjectManagement/StoreIssueRequest.php index 55fe325a..7d0f4808 100644 --- a/app/Http/Requests/ProjectManagement/StoreIssueRequest.php +++ b/app/Http/Requests/ProjectManagement/StoreIssueRequest.php @@ -30,6 +30,12 @@ public function rules(): array 'start_date' => 'nullable|date', 'due_date' => 'nullable|date|after_or_equal:start_date', 'estimated_hours' => 'nullable|integer|min:0|max:9999', + // 팀/담당자/고객사 (하이브리드) + 'department_id' => 'nullable|integer|exists:departments,id', + 'team' => 'nullable|string|max:100', + 'assignee_id' => 'nullable|integer|exists:users,id', + 'assignee_name' => 'nullable|string|max:100', + 'client' => 'nullable|string|max:100', ]; } @@ -48,6 +54,11 @@ public function attributes(): array 'start_date' => '시작일', 'due_date' => '마감일', 'estimated_hours' => '예상 시간', + 'department_id' => '부서', + 'team' => '팀/부서명', + 'assignee_id' => '담당자', + 'assignee_name' => '담당자명', + 'client' => '고객사', ]; } diff --git a/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php b/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php index b5f77801..ec416868 100644 --- a/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php +++ b/app/Http/Requests/ProjectManagement/UpdateIssueRequest.php @@ -30,6 +30,12 @@ public function rules(): array 'start_date' => 'nullable|date', 'due_date' => 'nullable|date|after_or_equal:start_date', 'estimated_hours' => 'nullable|integer|min:0|max:9999', + // 팀/담당자/고객사 (하이브리드) + 'department_id' => 'nullable|integer|exists:departments,id', + 'team' => 'nullable|string|max:100', + 'assignee_id' => 'nullable|integer|exists:users,id', + 'assignee_name' => 'nullable|string|max:100', + 'client' => 'nullable|string|max:100', ]; } @@ -48,6 +54,11 @@ public function attributes(): array 'start_date' => '시작일', 'due_date' => '마감일', 'estimated_hours' => '예상 시간', + 'department_id' => '부서', + 'team' => '팀/부서명', + 'assignee_id' => '담당자', + 'assignee_name' => '담당자명', + 'client' => '고객사', ]; } diff --git a/app/Models/Admin/AdminPmIssue.php b/app/Models/Admin/AdminPmIssue.php index 366ef800..696c0c1b 100644 --- a/app/Models/Admin/AdminPmIssue.php +++ b/app/Models/Admin/AdminPmIssue.php @@ -2,6 +2,7 @@ namespace App\Models\Admin; +use App\Models\Department; use App\Models\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -41,6 +42,12 @@ class AdminPmIssue extends Model 'due_date', 'estimated_hours', 'is_urgent', + // 팀/담당자/고객사 (하이브리드) + 'department_id', + 'team', + 'assignee_id', + 'assignee_name', + 'client', 'created_by', 'updated_by', 'deleted_by', @@ -53,6 +60,8 @@ class AdminPmIssue extends Model 'due_date' => 'date', 'estimated_hours' => 'integer', 'is_urgent' => 'boolean', + 'department_id' => 'integer', + 'assignee_id' => 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', 'deleted_by' => 'integer', @@ -159,6 +168,46 @@ public function updater(): BelongsTo return $this->belongsTo(User::class, 'updated_by'); } + /** + * 관계: 부서 (연동 시) + */ + public function department(): BelongsTo + { + return $this->belongsTo(Department::class, 'department_id'); + } + + /** + * 관계: 담당자 (연동 시) + */ + public function assignee(): BelongsTo + { + return $this->belongsTo(User::class, 'assignee_id'); + } + + /** + * 팀/부서 표시명 (FK 우선, 없으면 문자열) + */ + public function getTeamDisplayAttribute(): ?string + { + if ($this->department_id && $this->department) { + return $this->department->name; + } + + return $this->team; + } + + /** + * 담당자 표시명 (FK 우선, 없으면 문자열) + */ + public function getAssigneeDisplayAttribute(): ?string + { + if ($this->assignee_id && $this->assignee) { + return $this->assignee->name; + } + + return $this->assignee_name; + } + /** * 타입 아이콘 */ diff --git a/app/Services/ProjectManagement/ImportService.php b/app/Services/ProjectManagement/ImportService.php index cf7034ea..1dc5497f 100644 --- a/app/Services/ProjectManagement/ImportService.php +++ b/app/Services/ProjectManagement/ImportService.php @@ -60,6 +60,16 @@ public function importFromJson(array $data): array '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']++; @@ -109,6 +119,16 @@ public function importTasksToProject(int $projectId, array $tasks): array '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']++; @@ -143,8 +163,19 @@ public function getSampleTemplate(): array [ 'title' => '이슈 1', 'description' => '이슈 설명', - 'type' => 'bug', + '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' => '경동기업', ], ], ], @@ -188,4 +219,4 @@ public function validateJsonStructure(array $data): array return $errors; } -} \ No newline at end of file +} diff --git a/resources/views/project-management/projects/show.blade.php b/resources/views/project-management/projects/show.blade.php index 2b4346fb..47df58c9 100644 --- a/resources/views/project-management/projects/show.blade.php +++ b/resources/views/project-management/projects/show.blade.php @@ -302,6 +302,25 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin + +
+
+ + +
+
+ + +
+
+ + +
+
+
@@ -575,7 +594,9 @@ function renderTasks(container, tasks) { ${issue.due_date ? formatDate(issue.due_date) : '-'} - - + + ${[issue.client, issue.team, issue.assignee_name].filter(Boolean).join(' · ') || '-'} + ${issueStatusLabels[issue.status]} @@ -896,6 +917,10 @@ function closeIssueModal() { document.getElementById('issueStartDate').value = issue.start_date ? formatDate(issue.start_date) : ''; document.getElementById('issueDueDate').value = issue.due_date ? formatDate(issue.due_date) : ''; document.getElementById('issueEstimatedHours').value = issue.estimated_hours || ''; + // 팀/담당자/고객사 + document.getElementById('issueTeam').value = issue.team || ''; + document.getElementById('issueAssigneeName').value = issue.assignee_name || ''; + document.getElementById('issueClient').value = issue.client || ''; document.getElementById('issueModal').classList.remove('hidden'); } } @@ -920,6 +945,7 @@ function closeIssueModal() { if (result.success) { closeIssueModal(); loadIssues(); + loadTasks(); // 작업 탭 아코디언도 업데이트 } else { alert(result.message || '저장에 실패했습니다.'); }