From d48a38eaf654bffd58e20656dd566a66fda34bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 5 Mar 2026 11:36:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[approval]=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=ED=95=A8=20=EB=AF=B8=EC=9D=BD=EC=9D=8C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=B1=83=EC=A7=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - approvals 테이블에 drafter_read_at 컬럼 추가 (API 마이그레이션) - 승인/반려/전결 완료 시 drafter_read_at = null 설정 - getBadgeCounts()에 completed_unread 카운트 추가 - 사이드메뉴 완료함에 미읽음 뱃지 표시 (주황색) - 완료함 페이지 진입 시 일괄 읽음 처리 - 상세 페이지 열람 시 개별 읽음 처리 --- .../Api/Admin/ApprovalApiController.php | 28 +++++++++++++++++++ app/Models/Approvals/Approval.php | 2 ++ app/Providers/ViewServiceProvider.php | 1 + app/Services/ApprovalService.php | 20 +++++++++++++ resources/views/approvals/completed.blade.php | 8 ++++++ resources/views/approvals/show.blade.php | 8 ++++++ routes/api.php | 2 ++ 7 files changed, 69 insertions(+) diff --git a/app/Http/Controllers/Api/Admin/ApprovalApiController.php b/app/Http/Controllers/Api/Admin/ApprovalApiController.php index 639c21b2..574b0fd2 100644 --- a/app/Http/Controllers/Api/Admin/ApprovalApiController.php +++ b/app/Http/Controllers/Api/Admin/ApprovalApiController.php @@ -494,6 +494,34 @@ public function badgeCounts(): JsonResponse return response()->json(['success' => true, 'data' => $counts]); } + /** + * 완료함 읽음 처리 (일괄) + */ + public function markCompletedAsRead(): JsonResponse + { + $count = $this->service->markCompletedAsRead(auth()->id()); + + return response()->json([ + 'success' => true, + 'message' => $count > 0 ? "{$count}건 읽음 처리되었습니다." : '새로운 완료 건이 없습니다.', + 'data' => ['marked_count' => $count], + ]); + } + + /** + * 개별 문서 기안자 읽음 처리 + */ + public function markReadSingle(int $id): JsonResponse + { + $approval = $this->service->getApproval($id); + + if ($approval->drafter_id === auth()->id() && ! $approval->drafter_read_at) { + $approval->update(['drafter_read_at' => now()]); + } + + return response()->json(['success' => true]); + } + // ========================================================================= // 첨부파일 // ========================================================================= diff --git a/app/Models/Approvals/Approval.php b/app/Models/Approvals/Approval.php index af5b5053..a563e743 100644 --- a/app/Models/Approvals/Approval.php +++ b/app/Models/Approvals/Approval.php @@ -20,6 +20,7 @@ class Approval extends Model 'attachments' => 'array', 'drafted_at' => 'datetime', 'completed_at' => 'datetime', + 'drafter_read_at' => 'datetime', 'current_step' => 'integer', 'is_urgent' => 'boolean', ]; @@ -38,6 +39,7 @@ class Approval extends Model 'department_id', 'drafted_at', 'completed_at', + 'drafter_read_at', 'current_step', 'attachments', 'recall_reason', diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index e8524567..578e6ae7 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -78,6 +78,7 @@ public function boot(): void $menuBadges['byUrl']['/approval-mgmt/pending'] = ['count' => $counts['pending'], 'color' => '#ef4444']; $menuBadges['byUrl']['/approval-mgmt/drafts'] = ['count' => $counts['draft'], 'color' => '#3b82f6']; $menuBadges['byUrl']['/approval-mgmt/references'] = ['count' => $counts['reference_unread'], 'color' => '#10b981']; + $menuBadges['byUrl']['/approval-mgmt/completed'] = ['count' => $counts['completed_unread'], 'color' => '#f59e0b']; } catch (\Throwable $e) { // 테이블 미존재 등 예외 무시 } diff --git a/app/Services/ApprovalService.php b/app/Services/ApprovalService.php index 625f041a..0da70582 100644 --- a/app/Services/ApprovalService.php +++ b/app/Services/ApprovalService.php @@ -303,6 +303,7 @@ public function approve(int $id, ?string $comment = null): Approval $approval->update([ 'status' => Approval::STATUS_APPROVED, 'completed_at' => now(), + 'drafter_read_at' => null, 'updated_by' => auth()->id(), ]); @@ -344,6 +345,7 @@ public function reject(int $id, string $comment): Approval $approval->update([ 'status' => Approval::STATUS_REJECTED, 'completed_at' => now(), + 'drafter_read_at' => null, 'updated_by' => auth()->id(), ]); @@ -507,6 +509,7 @@ public function preDecide(int $id, ?string $comment = null): Approval $approval->update([ 'status' => Approval::STATUS_APPROVED, 'completed_at' => now(), + 'drafter_read_at' => null, 'updated_by' => auth()->id(), ]); @@ -753,13 +756,30 @@ public function getBadgeCounts(int $userId): array ->where('is_read', false) ->count(); + $completedUnreadCount = Approval::where('drafter_id', $userId) + ->whereIn('status', [Approval::STATUS_APPROVED, Approval::STATUS_REJECTED]) + ->whereNull('drafter_read_at') + ->count(); + return [ 'pending' => $pendingCount, 'draft' => $draftCount, 'reference_unread' => $referenceUnreadCount, + 'completed_unread' => $completedUnreadCount, ]; } + /** + * 완료함 미읽음 일괄 읽음 처리 + */ + public function markCompletedAsRead(int $userId): int + { + return Approval::where('drafter_id', $userId) + ->whereIn('status', [Approval::STATUS_APPROVED, Approval::STATUS_REJECTED]) + ->whereNull('drafter_read_at') + ->update(['drafter_read_at' => now()]); + } + // ========================================================================= // Private 헬퍼 // ========================================================================= diff --git a/resources/views/approvals/completed.blade.php b/resources/views/approvals/completed.blade.php index 374bab2d..c9258c9d 100644 --- a/resources/views/approvals/completed.blade.php +++ b/resources/views/approvals/completed.blade.php @@ -39,12 +39,20 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc