diff --git a/app/Http/Controllers/Api/Admin/DocumentApiController.php b/app/Http/Controllers/Api/Admin/DocumentApiController.php index 5f61a910..e5ee7d5a 100644 --- a/app/Http/Controllers/Api/Admin/DocumentApiController.php +++ b/app/Http/Controllers/Api/Admin/DocumentApiController.php @@ -215,6 +215,151 @@ public function destroy(int $id): JsonResponse ]); } + /** + * 결재 제출 (DRAFT → PENDING) + */ + public function submit(int $id): JsonResponse + { + $tenantId = session('selected_tenant_id'); + $userId = auth()->id(); + + $document = Document::where('tenant_id', $tenantId)->findOrFail($id); + + if ($document->status !== Document::STATUS_DRAFT && $document->status !== Document::STATUS_REJECTED) { + return response()->json([ + 'success' => false, + 'message' => '작성중 또는 반려 상태의 문서만 제출할 수 있습니다.', + ], 422); + } + + $document->update([ + 'status' => Document::STATUS_PENDING, + 'submitted_at' => now(), + 'updated_by' => $userId, + ]); + + // 결재라인 상태 초기화 (반려 후 재제출 시) + $document->approvals()->update([ + 'status' => DocumentApproval::STATUS_PENDING, + 'comment' => null, + 'acted_at' => null, + ]); + + return response()->json([ + 'success' => true, + 'message' => '결재가 제출되었습니다.', + 'data' => $document->fresh(['approvals']), + ]); + } + + /** + * 결재 승인 (단계별) + */ + public function approve(Request $request, int $id): JsonResponse + { + $tenantId = session('selected_tenant_id'); + $userId = auth()->id(); + + $document = Document::where('tenant_id', $tenantId)->findOrFail($id); + + if ($document->status !== Document::STATUS_PENDING) { + return response()->json([ + 'success' => false, + 'message' => '결재중 상태의 문서만 승인할 수 있습니다.', + ], 422); + } + + // 현재 단계의 미처리 결재 찾기 + $pendingApproval = $document->approvals() + ->where('status', DocumentApproval::STATUS_PENDING) + ->orderBy('step') + ->first(); + + if (! $pendingApproval) { + return response()->json([ + 'success' => false, + 'message' => '승인 대기 중인 결재 단계가 없습니다.', + ], 422); + } + + $pendingApproval->update([ + 'user_id' => $userId, + 'status' => DocumentApproval::STATUS_APPROVED, + 'comment' => $request->input('comment'), + 'acted_at' => now(), + 'updated_by' => $userId, + ]); + + // 모든 결재가 완료되었는지 확인 + $remainingPending = $document->approvals() + ->where('status', DocumentApproval::STATUS_PENDING) + ->count(); + + if ($remainingPending === 0) { + $document->update([ + 'status' => Document::STATUS_APPROVED, + 'completed_at' => now(), + 'updated_by' => $userId, + ]); + } + + return response()->json([ + 'success' => true, + 'message' => $remainingPending === 0 ? '최종 승인되었습니다.' : '승인되었습니다. (다음 단계 대기)', + 'data' => $document->fresh(['approvals']), + ]); + } + + /** + * 결재 반려 + */ + public function reject(Request $request, int $id): JsonResponse + { + $tenantId = session('selected_tenant_id'); + $userId = auth()->id(); + + $request->validate([ + 'comment' => 'required|string|max:500', + ]); + + $document = Document::where('tenant_id', $tenantId)->findOrFail($id); + + if ($document->status !== Document::STATUS_PENDING) { + return response()->json([ + 'success' => false, + 'message' => '결재중 상태의 문서만 반려할 수 있습니다.', + ], 422); + } + + // 현재 단계 결재에 반려 기록 + $pendingApproval = $document->approvals() + ->where('status', DocumentApproval::STATUS_PENDING) + ->orderBy('step') + ->first(); + + if ($pendingApproval) { + $pendingApproval->update([ + 'user_id' => $userId, + 'status' => DocumentApproval::STATUS_REJECTED, + 'comment' => $request->input('comment'), + 'acted_at' => now(), + 'updated_by' => $userId, + ]); + } + + $document->update([ + 'status' => Document::STATUS_REJECTED, + 'completed_at' => now(), + 'updated_by' => $userId, + ]); + + return response()->json([ + 'success' => true, + 'message' => '문서가 반려되었습니다.', + 'data' => $document->fresh(['approvals']), + ]); + } + /** * 문서 데이터 저장 (기본필드 + 섹션 테이블 데이터) */ diff --git a/resources/views/documents/edit.blade.php b/resources/views/documents/edit.blade.php index 33e07518..96c9af86 100644 --- a/resources/views/documents/edit.blade.php +++ b/resources/views/documents/edit.blade.php @@ -320,8 +320,14 @@ class="px-6 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover: + @if(!$isCreate && $document && $document->canEdit()) + + @endif @endif @@ -418,5 +424,32 @@ class="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg- }); }); }); + +@if(!$isCreate && $document) +window.submitForApproval = function() { + if (!confirm('결재를 제출하시겠습니까? 제출 후에는 수정이 불가합니다.')) return; + + fetch('/api/admin/documents/{{ $document->id }}/submit', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, + } + }) + .then(response => response.json()) + .then(result => { + if (result.success) { + showToast(result.message, 'success'); + window.location.href = '/documents/{{ $document->id }}'; + } else { + showToast(result.message || '오류가 발생했습니다.', 'error'); + } + }) + .catch(error => { + console.error('Submit error:', error); + showToast('결재 제출 중 오류가 발생했습니다.', 'error'); + }); +}; +@endif @endpush \ No newline at end of file diff --git a/resources/views/documents/show.blade.php b/resources/views/documents/show.blade.php index 5291b174..f5335e13 100644 --- a/resources/views/documents/show.blade.php +++ b/resources/views/documents/show.blade.php @@ -10,7 +10,7 @@
- @if($document->status === 'DRAFT' || $document->status === 'REJECTED') + @if($document->canEdit()) @@ -19,6 +19,22 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition 수정 @endif + @if($document->isPending()) + + + @endif 목록 @@ -50,7 +66,15 @@ class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transiti
상태
- + {{ $document->status_label }}
@@ -147,7 +171,7 @@ class="text-sm text-blue-600 hover:text-blue-800"> @if($document->approvals && $document->approvals->count() > 0)
    - @foreach($document->approvals->sortBy('step_order') as $approval) + @foreach($document->approvals as $approval)
  1. @else - {{ $approval->step_order }} + {{ $approval->step }} @endif
    -

    {{ $approval->user->name ?? '미지정' }}

    -

    {{ $approval->step_name }}

    - @if($approval->approved_at) -

    {{ $approval->approved_at->format('Y-m-d H:i') }}

    +

    + {{ $approval->role }} + + ({{ $approval->status_label }}) + +

    +

    {{ $approval->user->name ?? '미지정' }}

    + @if($approval->acted_at) +

    {{ $approval->acted_at->format('Y-m-d H:i') }}

    @endif @if($approval->comment)

    {{ $approval->comment }}

    @@ -192,4 +221,103 @@ class="text-sm text-blue-600 hover:text-blue-800">
-@endsection \ No newline at end of file +{{-- 반려 사유 모달 --}} +@if($document->isPending()) + +@endif + +@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 2fafeb3a..b91e5c5e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -803,6 +803,10 @@ Route::get('/{id}', [DocumentApiController::class, 'show'])->name('show'); Route::patch('/{id}', [DocumentApiController::class, 'update'])->name('update'); Route::delete('/{id}', [DocumentApiController::class, 'destroy'])->name('destroy'); + // 결재 워크플로우 + Route::post('/{id}/submit', [DocumentApiController::class, 'submit'])->name('submit'); + Route::post('/{id}/approve', [DocumentApiController::class, 'approve'])->name('approve'); + Route::post('/{id}/reject', [DocumentApiController::class, 'reject'])->name('reject'); }); /*