@extends('layouts.master') @section('title') {{ $requestItem->document_name ?? __('Document') }} - {{ __('Document Viewer') }} @endsection @section('content') @php $authUser = Auth::user(); $adminContactNote = __('Note: please contact admin.'); $withAdminContactNote = static function ($message) use ($adminContactNote): string { $message = trim((string) $message); if ($message === '' || str_contains($message, $adminContactNote)) { return $message; } return trim($message . ' ' . $adminContactNote); }; $viewerDashboardContext = strtolower(trim((string) request()->query('view', ''))); $documentViewerQuery = in_array($viewerDashboardContext, ['work', 'requests'], true) ? ['view' => $viewerDashboardContext] : []; $commentsUrl = route('document-viewer.comments', array_merge(['id' => $requestItem->id], $documentViewerQuery)); $rawUserRole = strtolower(trim((string) ($authUser->role ?? 'user'))); $rawCurrentHolderRole = strtolower(trim((string) ($requestItem->current_holder_role ?? 'user'))); $roleAlias = fn ($role) => \App\Support\WorkflowPermission::normalizeRole((string) $role); $userRole = $roleAlias((string) ($authUser->role ?? 'user')); $currentState = $requestItem->workflow_state ?? 'submitted'; $currentHolderRole = $roleAlias((string) ($requestItem->current_holder_role ?? 'user')); $backDashboardRoute = $userRole === \App\Support\WorkflowPermission::ROLE_REQUESTER ? 'requester.dashboard' : 'role.dashboard'; $backDashboardUrl = route($backDashboardRoute, $documentViewerQuery); $isCurrentHolder = (int) ($requestItem->current_holder_user_id ?? 0) === (int) ($authUser->id ?? 0); $requestOrganizationName = strtolower(trim((string) ($requestItem->organization_name ?? ''))); $userOrganizationName = strtolower(trim((string) ($authUser->organization_name ?? ''))); $roleCanRequestDocument = \App\Support\WorkflowPermission::canUseAdminPermission((string) ($authUser->role ?? ''), \App\Support\WorkflowPermission::ADMIN_PERMISSION_REQUEST_DOCUMENT); $roleCanReviewDocument = \App\Support\WorkflowPermission::canUseAdminPermission((string) ($authUser->role ?? ''), \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT); $roleCanRegisterDocumentNumber = \App\Support\WorkflowPermission::canUseAdminPermission((string) ($authUser->role ?? ''), \App\Support\WorkflowPermission::ADMIN_PERMISSION_REGISTER_DOCUMENT_NUMBER); $canUsePriorityHandling = \App\Support\WorkflowPermission::canUseAdminPermission((string) ($authUser->role ?? ''), 'use_priority_handling'); $earlyWorkflowSteps = collect(is_array($requestItem->workflow_steps_override) && count($requestItem->workflow_steps_override) > 0 ? $requestItem->workflow_steps_override : (optional($requestItem->workflowTemplate)->steps ?? [])); $earlyCurrentStepConfig = $earlyWorkflowSteps ->first(fn ($step) => is_array($step) && (int) ($step['order'] ?? 0) === (int) ($requestItem->current_workflow_step_order ?? 0)); if (!is_array($earlyCurrentStepConfig)) { $earlyCurrentStepConfig = $earlyWorkflowSteps ->first(fn ($step) => is_array($step) && (string) ($step['to_state'] ?? '') === (string) ($requestItem->workflow_state ?? '')); } $earlyStepOrganizationName = static function ($step): string { if (!is_array($step)) { return ''; } $organizationName = trim((string) data_get($step, 'organization_name', '')); return $organizationName !== '' ? $organizationName : trim((string) data_get($step, 'office_name', '')); }; $earlyCurrentStepOrganizationRaw = $earlyStepOrganizationName($earlyCurrentStepConfig); $earlyCurrentStepOrganizationName = strtolower($earlyCurrentStepOrganizationRaw); $earlyCurrentStepType = strtolower(trim((string) data_get($earlyCurrentStepConfig, 'step_type', ''))); $earlyCurrentStepOrganizationDisabled = $earlyCurrentStepType === 'organization' && $earlyCurrentStepOrganizationRaw !== '' && !\App\Models\Organization::activeNameExists($earlyCurrentStepOrganizationRaw); $earlyCurrentStepOrder = (int) ($requestItem->current_workflow_step_order ?? 0); $isAssignedBackToRequesterOwner = $earlyCurrentStepOrder <= 0 && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && (int) ($requestItem->current_holder_user_id ?? 0) > 0 && (int) ($requestItem->current_holder_user_id ?? 0) === (int) ($requestItem->user_id ?? 0); $isSharedRoleQueue = $userRole !== '' && $currentHolderRole !== '' && $userRole === $currentHolderRole && $requestItem->current_holder_user_id === null && ($requestOrganizationName === '' || $userOrganizationName === '' || $requestOrganizationName === $userOrganizationName); $isOrganizationQueue = $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && $requestItem->current_holder_user_id === null && $requestOrganizationName !== '' && $userOrganizationName !== '' && $requestOrganizationName === $userOrganizationName; $legacyOrganizationQueueAccess = $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && !$isAssignedBackToRequesterOwner && $roleCanReviewDocument && !$earlyCurrentStepOrganizationDisabled && $userOrganizationName !== '' && ( ($earlyCurrentStepOrganizationName !== '' && $earlyCurrentStepOrganizationName === $userOrganizationName) || ($requestOrganizationName !== '' && $requestOrganizationName === $userOrganizationName) ); $holderCanAct = !$earlyCurrentStepOrganizationDisabled && ($isCurrentHolder || $isSharedRoleQueue || $isOrganizationQueue || $legacyOrganizationQueueAccess); $focusComment = false; $isAdmin = $userRole === 'admin'; $isRequesterOwner = (int) ($requestItem->user_id ?? 0) === (int) ($authUser->id ?? 0); $forceRequesterPerspective = $viewerDashboardContext === 'requests' && $isRequesterOwner; $isSameOrganization = strtolower(trim((string) ($requestItem->organization_name ?? ''))) === strtolower(trim((string) ($authUser->organization_name ?? ''))); $hasHistoryAccess = $requestItem->history->contains(function ($entry) use ($authUser) { return (int) ($entry->actor_user_id ?? 0) === (int) ($authUser->id ?? 0); }); $canComment = $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseUiPermission((string) ($authUser->role ?? \App\Support\WorkflowPermission::ROLE_REQUESTER), 'review/comment'); $canAddReviewDocument = $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseUiPermission((string) ($authUser->role ?? \App\Support\WorkflowPermission::ROLE_REQUESTER), 'upload_document'); $reviewUnreadCount = $requestItem->history ->filter(function ($entry) use ($authUser) { if (($entry->action ?? null) !== 'review_comment' || (bool) ($entry->is_read ?? false)) { return false; } if ((int) ($entry->actor_user_id ?? 0) === (int) ($authUser->id ?? 0)) { return false; } return trim((string) ($entry->note ?? '')) !== '' || !empty(data_get($entry->meta, 'attachment.path')); }) ->count(); $commentEntries = $requestItem->history ->filter(fn ($entry) => ($entry->action ?? null) === 'review_comment' && !empty($entry->note)) ->values(); $statusHistoryEntries = $requestItem->history ->filter(fn ($entry) => ($entry->action ?? null) !== 'review_comment') ->values(); $storedWorkflowOverride = is_array($requestItem->workflow_steps_override) ? collect($requestItem->workflow_steps_override) ->filter(fn ($step) => is_array($step)) ->sortBy(fn ($step) => (int) ($step['order'] ?? 0)) ->values() ->all() : []; $templateWorkflowSteps = collect(optional($requestItem->workflowTemplate)->steps ?? []) ->filter(fn ($step) => is_array($step)) ->sortBy(fn ($step) => (int) ($step['order'] ?? 0)) ->values() ->all(); $hasWorkflowOverride = count($storedWorkflowOverride) > 0 && $storedWorkflowOverride != $templateWorkflowSteps; $workflowSteps = collect($hasWorkflowOverride ? $storedWorkflowOverride : $templateWorkflowSteps); $workflowStepsOrdered = $workflowSteps ->filter(fn ($step) => is_array($step)) ->sortBy(fn ($step) => (int) ($step['order'] ?? 0)) ->values(); $formatDuration = function ($startedAt, $endedAt = null) { if (!$startedAt) { return null; } $endPoint = $endedAt ?: now(); $totalSeconds = (int) abs($startedAt->diffInSeconds($endPoint)); $totalMinutes = intdiv($totalSeconds, 60); if ($totalMinutes < 60) { return __(':count min', ['count' => max(1, $totalMinutes)]); } $totalHours = intdiv($totalMinutes, 60); $minutesRemainder = $totalMinutes % 60; if ($totalHours < 24) { return $minutesRemainder > 0 ? __(':hours hr :minutes min', ['hours' => $totalHours, 'minutes' => $minutesRemainder]) : __(':count hr', ['count' => $totalHours]); } $days = intdiv($totalHours, 24); $hoursRemainder = $totalHours % 24; return $hoursRemainder > 0 ? __(':days day :hours hr', ['days' => $days, 'hours' => $hoursRemainder]) : __(':count day', ['count' => $days]); }; $formatDurationSeconds = function (int $totalSeconds) { $totalSeconds = max(0, $totalSeconds); $totalMinutes = intdiv($totalSeconds, 60); if ($totalMinutes < 60) { return __(':count min', ['count' => max(1, $totalMinutes)]); } $totalHours = intdiv($totalMinutes, 60); $minutesRemainder = $totalMinutes % 60; if ($totalHours < 24) { return $minutesRemainder > 0 ? __(':hours hr :minutes min', ['hours' => $totalHours, 'minutes' => $minutesRemainder]) : __(':count hr', ['count' => $totalHours]); } $days = intdiv($totalHours, 24); $hoursRemainder = $totalHours % 24; return $hoursRemainder > 0 ? __(':days day :hours hr', ['days' => $days, 'hours' => $hoursRemainder]) : __(':count day', ['count' => $days]); }; $formatRemaining = function ($startedAt, int $slaDays) use ($formatDuration): ?array { if (!$startedAt || $slaDays <= 0) { return null; } $deadline = $startedAt->copy()->addDays($slaDays); $nowPoint = now(); $isOverdue = $nowPoint->greaterThan($deadline); return [ 'label' => $formatDuration($isOverdue ? $deadline : $nowPoint, $isOverdue ? $nowPoint : $deadline), 'overdue' => $isOverdue, ]; }; $historyAscending = $statusHistoryEntries->sortBy('created_at')->values(); $workflowTransitionEntries = $historyAscending ->filter(function ($entry) { $meta = is_array($entry->meta) ? $entry->meta : []; $action = strtolower(trim((string) ($entry->action ?? ''))); $fromStepOrder = (int) data_get($meta, 'from_step_order', -1); $toStepOrder = (int) data_get($meta, 'to_step_order', -1); if ($action === 'submit_document') { return true; } if ($fromStepOrder >= 0 && $toStepOrder >= 0 && $fromStepOrder !== $toStepOrder) { return true; } return (string) ($entry->from_state ?? '') !== (string) ($entry->to_state ?? ''); }) ->values(); $requestFormData = is_array($requestItem->form_data) ? $requestItem->form_data : []; $dynamicFacultyLabel = trim((string) ( $requestFormData['__selected_faculty_label'] ?? $requestFormData['__selected_faculty_value'] ?? $requestFormData['__selected_faculty_organization'] ?? '' )); $isFacultyStep = function (array $step): bool { $stepLabel = strtolower(trim((string) ($step['label'] ?? ''))); $organizationName = strtolower(trim((string) ($step['organization_name'] ?? ''))); return strtolower(trim((string) ($step['actor_role'] ?? ''))) === 'faculty' || str_contains($stepLabel, 'faculty') || str_contains($organizationName, 'faculty') || str_contains($organizationName, 'medicine'); }; $currentStepOrder = (int) ($requestItem->current_workflow_step_order ?? 0); $latestSendBackForProgress = $statusHistoryEntries ->filter(fn ($entry) => (string) ($entry->action ?? '') === \App\Support\WorkflowPermission::ACTION_REQUEST_REVISION) ->filter(function ($entry) { $meta = is_array($entry->meta) ? $entry->meta : []; return trim((string) data_get($meta, 'sent_back_by_role', '')) !== ''; }) ->sortByDesc('created_at') ->first(); $latestSendBackForProgressMeta = is_array($latestSendBackForProgress?->meta) ? $latestSendBackForProgress->meta : []; $latestSendBackAlreadyReturnedForProgress = $latestSendBackForProgress ? $statusHistoryEntries ->filter(fn ($entry) => (int) ($entry->id ?? 0) > (int) ($latestSendBackForProgress->id ?? 0)) ->contains(function ($entry) { if ((string) ($entry->action ?? '') !== \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR) { return false; } $meta = is_array($entry->meta) ? $entry->meta : []; return (bool) data_get($meta, 'returned_back', false); }) : false; $isPendingRequesterReturnForProgress = $latestSendBackForProgress !== null && !$latestSendBackAlreadyReturnedForProgress && strtolower(trim((string) data_get($latestSendBackForProgressMeta, 'sent_back_target_type', ''))) !== 'stage' && (int) data_get($latestSendBackForProgressMeta, 'sent_back_selected_target_step_order', 0) <= 0 && \App\Support\WorkflowPermission::normalizeRole((string) data_get($latestSendBackForProgressMeta, 'sent_back_to_role', '')) === \App\Support\WorkflowPermission::ROLE_REQUESTER && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && !in_array($currentState, [ \App\Models\RequesterRequest::STATE_COMPLETED, \App\Models\RequesterRequest::STATE_REJECTED, ], true); $isReturnedToRequesterStage = $currentStepOrder <= 0 && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && $currentState === \App\Models\RequesterRequest::STATE_SUBMITTED; $isReturnedToRequesterStage = $isReturnedToRequesterStage || $isPendingRequesterReturnForProgress; $currentStepIndex = $currentStepOrder > 0 ? $workflowStepsOrdered->search(fn ($step) => (int) ($step['order'] ?? 0) === $currentStepOrder) : ($isReturnedToRequesterStage ? null : $workflowStepsOrdered->search(fn ($step) => (string) ($step['to_state'] ?? '') === $currentState)); if ($currentStepIndex === false) { $currentStepIndex = null; } $workflowIsCompleted = strtolower((string) ($requestItem->status ?? '')) === 'completed' || $currentState === 'completed'; $isActionableStatus = in_array((string) ($requestItem->status ?? ''), ['pending', 'approved'], true); $isPrematureCompletedStepForUi = function ($steps, int $index, array $step): bool { $toState = strtolower(trim((string) ($step['to_state'] ?? ''))); if ($toState !== \App\Models\RequesterRequest::STATE_COMPLETED) { return false; } for ($i = $index + 1; $i < $steps->count(); $i++) { $nextCandidate = $steps->get($i); if (!is_array($nextCandidate)) { continue; } $nextState = strtolower(trim((string) ($nextCandidate['to_state'] ?? ''))); if ($nextState !== \App\Models\RequesterRequest::STATE_COMPLETED) { return true; } } return false; }; $nextStep = $workflowStepsOrdered ->values() ->first(function ($step, $index) use ($currentStepOrder, $workflowStepsOrdered, $isPrematureCompletedStepForUi) { if (!is_array($step)) { return false; } $order = (int) ($step['order'] ?? 0); if ($order <= $currentStepOrder) { return false; } return !$isPrematureCompletedStepForUi($workflowStepsOrdered, (int) $index, $step); }); $hasTemplateFlow = $workflowStepsOrdered->isNotEmpty(); $isFinalForwardStep = $hasTemplateFlow && $currentStepIndex !== null && ( $nextStep === null || (is_array($nextStep) && strtolower((string) ($nextStep['to_state'] ?? '')) === 'completed') ); // Pre-group history entries by to_state to support repeated stages. $historyByState = []; foreach ($historyAscending as $hEntry) { $hState = (string) ($hEntry->to_state ?? ''); if ($hState !== '') { $historyByState[$hState][] = $hEntry; } } // Pre-compute per-step occurrence index (0-based) so the Nth occurrence of a state // in the template matches the Nth history entry with that state. $stepStateSeenCount = []; $stepOccurrenceIndexes = []; foreach ($workflowStepsOrdered->keys() as $idx) { $st = (string) ($workflowStepsOrdered->get($idx)['to_state'] ?? ''); $stepOccurrenceIndexes[$idx] = $stepStateSeenCount[$st] ?? 0; $stepStateSeenCount[$st] = ($stepStateSeenCount[$st] ?? 0) + 1; } $timelineRelevantUserIds = $historyAscending ->pluck('actor_user_id') ->filter(fn ($id) => (int) $id > 0) ->map(fn ($id) => (int) $id) ->push((int) ($requestItem->current_holder_user_id ?? 0)) ->filter(fn ($id) => $id > 0) ->unique() ->values(); $timelineUsersById = \App\Models\User::query() ->whereIn('id', $timelineRelevantUserIds) ->get() ->keyBy('id'); $localizedOrganizationName = function (string $organizationName): string { static $organizationLabelCache = []; $organizationName = trim($organizationName); if ($organizationName === '') { return ''; } $cacheKey = strtolower($organizationName) . '|' . app()->getLocale(); if (array_key_exists($cacheKey, $organizationLabelCache)) { return $organizationLabelCache[$cacheKey]; } $organization = \App\Models\Organization::query() ->whereRaw('LOWER(TRIM(name)) = ?', [strtolower($organizationName)]) ->orWhereRaw('LOWER(TRIM(name_en)) = ?', [strtolower($organizationName)]) ->orWhereRaw('LOWER(TRIM(name_kh)) = ?', [strtolower($organizationName)]) ->first(['name', 'name_en', 'name_kh']); if ($organization === null) { return $organizationLabelCache[$cacheKey] = $organizationName; } $english = trim((string) ($organization->name_en ?? '')); $khmer = trim((string) ($organization->name_kh ?? '')); $fallback = trim((string) ($organization->name ?? $organizationName)); return $organizationLabelCache[$cacheKey] = app()->getLocale() === 'kh' ? ($khmer !== '' ? $khmer : ($english !== '' ? $english : $fallback)) : ($english !== '' ? $english : ($khmer !== '' ? $khmer : $fallback)); }; $firstWorkflowStepOrderForTiming = (int) data_get($workflowStepsOrdered->first(), 'order', 0); $stageElapsedSecondsByOrder = []; $stageOpenStartedAtByOrder = []; $addStageElapsedSeconds = function (int $stepOrder, $startedAt, $endedAt) use (&$stageElapsedSecondsByOrder): void { if ($stepOrder <= 0 || !$startedAt || !$endedAt) { return; } $seconds = (int) $startedAt->diffInSeconds($endedAt, false); if ($seconds <= 0) { return; } $stageElapsedSecondsByOrder[$stepOrder] = ($stageElapsedSecondsByOrder[$stepOrder] ?? 0) + $seconds; }; foreach ($workflowTransitionEntries as $transitionEntry) { $transitionedAt = $transitionEntry->created_at ?? null; if (!$transitionedAt) { continue; } $meta = is_array($transitionEntry->meta) ? $transitionEntry->meta : []; $action = strtolower(trim((string) ($transitionEntry->action ?? ''))); $fromStepOrder = (int) data_get($meta, 'from_step_order', 0); $toStepOrder = (int) data_get($meta, 'to_step_order', 0); if ($action === 'submit_document' && $toStepOrder <= 0 && $firstWorkflowStepOrderForTiming > 0) { $toStepOrder = $firstWorkflowStepOrderForTiming; } if ($fromStepOrder > 0 && $fromStepOrder !== $toStepOrder) { if (isset($stageOpenStartedAtByOrder[$fromStepOrder])) { $addStageElapsedSeconds($fromStepOrder, $stageOpenStartedAtByOrder[$fromStepOrder], $transitionedAt); unset($stageOpenStartedAtByOrder[$fromStepOrder]); } } if ($toStepOrder > 0 && $fromStepOrder !== $toStepOrder) { $stageOpenStartedAtByOrder[$toStepOrder] = $transitionedAt; } } $stageElapsedSecondsForOrder = function (int $stepOrder, string $statusBadge) use ($stageElapsedSecondsByOrder, $stageOpenStartedAtByOrder, $workflowIsCompleted, $requestItem): int { $seconds = $stageElapsedSecondsByOrder[$stepOrder] ?? 0; $openStartedAt = $stageOpenStartedAtByOrder[$stepOrder] ?? null; if ($openStartedAt) { $endPoint = $statusBadge === 'current' ? now() : ($workflowIsCompleted ? ($requestItem->updated_at ?: now()) : null); if ($endPoint) { $seconds += max(0, (int) $openStartedAt->diffInSeconds($endPoint, false)); } } return $seconds; }; $workflowTimeline = $workflowStepsOrdered->map(function ($step, $index) use ($historyAscending, $workflowTransitionEntries, $historyByState, $stepOccurrenceIndexes, $currentState, $currentStepIndex, $formatDuration, $formatDurationSeconds, $formatRemaining, $workflowIsCompleted, $dynamicFacultyLabel, $isFacultyStep, $timelineUsersById, $currentHolderRole, $requestItem, $firstWorkflowStepOrderForTiming, $isPendingRequesterReturnForProgress, $localizedOrganizationName, $stageElapsedSecondsForOrder) { $stepState = (string) ($step['to_state'] ?? ''); $stepOrder = (int) ($step['order'] ?? 0); $occurrenceIdx = $stepOccurrenceIndexes[$index] ?? 0; $stateHistory = $historyByState[$stepState] ?? []; $historyEntry = $workflowTransitionEntries->first(function ($entry) use ($stepOrder) { $meta = is_array($entry->meta) ? $entry->meta : []; return $stepOrder > 0 && (int) data_get($meta, 'to_step_order', 0) === $stepOrder; }); if (!$historyEntry && $stepOrder === $firstWorkflowStepOrderForTiming) { $historyEntry = $workflowTransitionEntries->first(function ($entry) use ($stepState) { return strtolower(trim((string) ($entry->action ?? ''))) === 'submit_document' && (string) ($entry->to_state ?? '') === $stepState; }); } if (!$historyEntry) { $historyEntry = $stateHistory[$occurrenceIdx] ?? null; } $nextHistoryIndex = $workflowTransitionEntries->search(function ($entry) use ($historyEntry) { return $historyEntry && (int) ($entry->id ?? 0) === (int) ($historyEntry->id ?? 0); }); $nextHistoryEntry = null; if ($historyEntry) { $nextHistoryEntry = $workflowTransitionEntries->first(function ($entry) use ($historyEntry, $stepOrder) { if ((int) ($entry->id ?? 0) <= (int) ($historyEntry->id ?? 0)) { return false; } $meta = is_array($entry->meta) ? $entry->meta : []; return $stepOrder > 0 && (int) data_get($meta, 'from_step_order', 0) === $stepOrder; }); } if (!$nextHistoryEntry && $nextHistoryIndex !== false && isset($workflowTransitionEntries[$nextHistoryIndex + 1])) { $nextHistoryEntry = $workflowTransitionEntries[$nextHistoryIndex + 1]; } $startedAt = optional($historyEntry)->created_at; if (!$startedAt && $currentStepIndex !== null && $index === $currentStepIndex) { $startedAt = optional($workflowTransitionEntries->last())->created_at ?: $requestItem->updated_at ?: $requestItem->created_at; } $slaDays = (int) ($step['sla_days'] ?? 0); $slaLabel = $slaDays > 0 ? ($slaDays === 1 ? __('1 day') : __(':count days', ['count' => $slaDays])) : null; $remainingMeta = null; if ($workflowIsCompleted) { $statusBadge = 'completed'; } elseif ($isPendingRequesterReturnForProgress) { $statusBadge = 'upcoming'; } elseif ($currentStepIndex === null) { $statusBadge = $index === 0 ? 'first' : 'upcoming'; } elseif ($index < $currentStepIndex) { $statusBadge = 'completed'; } elseif ($index === $currentStepIndex) { $statusBadge = 'current'; } elseif ($index === $currentStepIndex + 1) { $statusBadge = 'next'; } else { $statusBadge = 'upcoming'; } if ($statusBadge === 'current') { $remainingMeta = $formatRemaining($startedAt, $slaDays); } $endedAt = $statusBadge === 'current' ? now() : ($nextHistoryEntry ? $nextHistoryEntry->created_at : $startedAt); $elapsedLabel = ($startedAt && $endedAt) ? $formatDuration($startedAt, $endedAt) : null; $stageElapsedSeconds = $stageElapsedSecondsForOrder($stepOrder, $statusBadge); if ($stageElapsedSeconds > 0) { $elapsedLabel = $formatDurationSeconds($stageElapsedSeconds); } $stepType = strtolower(trim((string) ($step['step_type'] ?? ''))); $actorRole = strtolower(trim((string) ($step['actor_role'] ?? ''))); $normalizedActorRole = \App\Support\WorkflowPermission::normalizeRole($actorRole); $baseLabel = trim((string) ($step['label'] ?? '')) !== '' ? __((string) $step['label']) : __(\App\Models\RequesterRequest::workflowLabel($stepState)); if ($normalizedActorRole === \App\Support\WorkflowPermission::ROLE_VICE_RECTOR) { $baseLabel = __('Vice Rector'); } elseif ($normalizedActorRole === \App\Support\WorkflowPermission::ROLE_RECTOR) { $baseLabel = __('Rector'); } if (($stepType === 'faculty' || $actorRole === 'faculty' || $isFacultyStep($step)) && $dynamicFacultyLabel !== '') { $baseLabel = __('Faculty') . ': ' . $dynamicFacultyLabel; } // If this step targets a named organization, prefer name_kh/name_en from organizations table. $stepOrganizationName = trim((string) ($step['organization_name'] ?? '')); if ($stepOrganizationName !== '') { $baseLabel = $localizedOrganizationName($stepOrganizationName); } if ($actorRole === \App\Support\WorkflowPermission::ROLE_VICE_RECTOR) { $historyMeta = is_array($historyEntry?->meta) ? $historyEntry->meta : []; $notificationRole = \App\Support\WorkflowPermission::normalizeRole((string) data_get($historyMeta, 'notification_role', '')); $viceRectorUserId = $notificationRole === \App\Support\WorkflowPermission::ROLE_VICE_RECTOR ? (int) data_get($historyMeta, 'notification_user_id', 0) : 0; if ($viceRectorUserId <= 0 && \App\Support\WorkflowPermission::normalizeRole((string) ($historyEntry->actor_role ?? '')) === \App\Support\WorkflowPermission::ROLE_VICE_RECTOR) { $viceRectorUserId = (int) ($historyEntry->actor_user_id ?? 0); } if ($viceRectorUserId <= 0 && !empty($step['rector_user_id'])) { $viceRectorUserId = (int) $step['rector_user_id']; } if ( $viceRectorUserId <= 0 && $statusBadge === 'current' && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_VICE_RECTOR ) { $viceRectorUserId = (int) ($requestItem->current_holder_user_id ?? 0); } $viceRectorName = trim((string) data_get($timelineUsersById->get($viceRectorUserId), 'name', '')); if ($viceRectorName !== '') { $baseLabel = $baseLabel . ' : ' . $viceRectorName; } } $permissionType = strtolower(trim((string) ($step['permission_type'] ?? ''))); if (!in_array($permissionType, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true)) { $permissionType = in_array($stepType, ['approval', 'vice_rector'], true) || in_array($normalizedActorRole, [ \App\Support\WorkflowPermission::ROLE_RECTOR, \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, ], true) ? \App\Support\WorkflowPermission::PERMISSION_APPROVER : \App\Support\WorkflowPermission::PERMISSION_REVIEWER; } $baseLabel = ($permissionType === \App\Support\WorkflowPermission::PERMISSION_APPROVER ? __('Review and decide by') : __('Review by')) . ': ' . $baseLabel; return [ 'label' => $baseLabel, 'permission_type' => $permissionType, 'state' => $stepState, 'actor_role' => (string) ($step['actor_role'] ?? ''), 'organization_name' => trim((string) ($step['organization_name'] ?? '')), 'rector_user_id' => !empty($step['rector_user_id']) ? (int) $step['rector_user_id'] : null, 'sla_days' => $slaDays, 'sla_label' => $slaLabel, 'started_at' => $startedAt, 'elapsed_label' => $elapsedLabel, 'remaining_label' => $remainingMeta['label'] ?? null, 'is_overdue' => (bool) ($remainingMeta['overdue'] ?? false), 'status_badge' => $statusBadge, 'history_entry' => $historyEntry, ]; })->values(); $timelineFirstStep = $workflowTimeline->first(); $timelineCurrentStep = $workflowTimeline->firstWhere('status_badge', 'current'); $timelineNextStep = $workflowTimeline->firstWhere('status_badge', 'next'); $workflowStartedAt = optional($workflowTransitionEntries->first())->created_at ?: $requestItem->created_at; $workflowFinishedAt = null; if (in_array(strtolower((string) ($requestItem->status ?? '')), ['completed', 'rejected'], true)) { $workflowFinishedAt = optional($workflowTransitionEntries ->filter(function ($entry) { return in_array(strtolower((string) ($entry->to_state ?? '')), [ \App\Models\RequesterRequest::STATE_COMPLETED, \App\Models\RequesterRequest::STATE_REJECTED, ], true); }) ->last())->created_at; $workflowFinishedAt = $workflowFinishedAt ?: $requestItem->updated_at; } $totalWorkflowTimeLabel = $workflowStartedAt ? $formatDuration($workflowStartedAt, $workflowFinishedAt ?: now()) : null; $currentStepConfig = $currentStepOrder > 0 ? $workflowSteps->first(fn ($step) => (int) ($step['order'] ?? 0) === $currentStepOrder) : null; if (!is_array($currentStepConfig) && $currentStepIndex !== null && $workflowStepsOrdered->has($currentStepIndex)) { $currentStepConfig = $workflowStepsOrdered->get($currentStepIndex); } $currentStepPermissionRole = strtolower(trim((string) data_get($currentStepConfig, 'permission_type', ''))); if (!in_array($currentStepPermissionRole, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true) && is_array($currentStepConfig)) { $currentStepType = strtolower(trim((string) ($currentStepConfig['step_type'] ?? ''))); $currentStepActorRole = \App\Support\WorkflowPermission::normalizeRole((string) ($currentStepConfig['actor_role'] ?? '')); $currentStepPermissionRole = in_array($currentStepType, ['approval', 'vice_rector'], true) || in_array($currentStepActorRole, [ \App\Support\WorkflowPermission::ROLE_RECTOR, \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, ], true) ? \App\Support\WorkflowPermission::PERMISSION_APPROVER : \App\Support\WorkflowPermission::PERMISSION_REVIEWER; } if (!in_array($currentStepPermissionRole, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true)) { $currentStepPermissionRole = (string) ($authUser->role ?? \App\Support\WorkflowPermission::ROLE_REQUESTER); } $workflowStepOrganizationName = static function ($step): string { if (!is_array($step)) { return ''; } $organizationName = trim((string) data_get($step, 'organization_name', '')); return $organizationName !== '' ? $organizationName : trim((string) data_get($step, 'office_name', '')); }; $currentStepOrganizationRaw = $workflowStepOrganizationName($currentStepConfig); $currentStepOrganizationName = strtolower($currentStepOrganizationRaw); $currentStepTypeForOrganizationGuard = strtolower(trim((string) data_get($currentStepConfig, 'step_type', ''))); $currentStepOrganizationDisabled = $currentStepTypeForOrganizationGuard === 'organization' && $currentStepOrganizationRaw !== '' && !\App\Models\Organization::activeNameExists($currentStepOrganizationRaw); $isCurrentOrganizationReviewerTurn = $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && $roleCanReviewDocument && $holderCanAct && !$currentStepOrganizationDisabled && ( $currentStepOrganizationName === '' || $userOrganizationName === '' || $currentStepOrganizationName === $userOrganizationName ) && !in_array($currentState, [ \App\Models\RequesterRequest::STATE_COMPLETED, \App\Models\RequesterRequest::STATE_REJECTED, ], true); if ($isCurrentOrganizationReviewerTurn && !in_array($currentStepPermissionRole, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true)) { $currentStepPermissionRole = \App\Support\WorkflowPermission::PERMISSION_REVIEWER; } $canComment = $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseUiPermission($currentStepPermissionRole, 'review/comment'); $canAddReviewDocument = $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseUiPermission($currentStepPermissionRole, 'upload_document'); $currentStepSlaDays = (int) ($currentStepConfig['sla_days'] ?? 0); $currentStepStartedAt = optional($statusHistoryEntries->first())->created_at; $overdueByDays = null; $isOverdue = false; if ($currentStepSlaDays > 0 && $currentStepStartedAt) { $elapsedDays = $currentStepStartedAt->diffInDays(now()); $overdueByDays = max(0, $elapsedDays - $currentStepSlaDays); $isOverdue = $overdueByDays > 0; } $canAdminHead = $userRole === \App\Support\WorkflowPermission::ROLE_ADMIN && in_array($currentState, ['submitted', 'organization_review'], true) && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_ADMIN && $holderCanAct && in_array($requestItem->status, ['pending', 'approved'], true); $nextStepRoleForPermission = (string) ($nextStep['actor_role'] ?? ''); $permissionRole = $currentStepPermissionRole; $canAssignStaffByPermission = \App\Support\WorkflowPermission::can( $permissionRole, \App\Support\WorkflowPermission::ACTION_REQUEST_REVISION, $currentState, $nextStepRoleForPermission ); $canForwardByPermission = \App\Support\WorkflowPermission::can( $permissionRole, \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR, $currentState, $nextStepRoleForPermission ); $canSendBackByPermission = \App\Support\WorkflowPermission::can( $permissionRole, \App\Support\WorkflowPermission::ACTION_REQUEST_REVISION, $currentState, $nextStepRoleForPermission ); $canApproveByPermission = \App\Support\WorkflowPermission::can( $permissionRole, \App\Support\WorkflowPermission::ACTION_APPROVE_DOCUMENT, $currentState, $nextStepRoleForPermission ); $canRejectByPermission = \App\Support\WorkflowPermission::can( $permissionRole, \App\Support\WorkflowPermission::ACTION_REJECT_DOCUMENT, $currentState, $nextStepRoleForPermission ); $canRequestMoreInfoByPermission = \App\Support\WorkflowPermission::can( $permissionRole, \App\Support\WorkflowPermission::ACTION_REQUEST_MORE_INFO, $currentState, $nextStepRoleForPermission ); $lastSendBackEntry = $statusHistoryEntries ->filter(fn ($entry) => (string) ($entry->action ?? '') === \App\Support\WorkflowPermission::ACTION_REQUEST_REVISION) ->filter(function ($entry) { $meta = is_array($entry->meta) ? $entry->meta : []; return trim((string) data_get($meta, 'sent_back_by_role', '')) !== ''; }) ->sortByDesc('created_at') ->first(); $lastSendBackMeta = is_array($lastSendBackEntry?->meta) ? $lastSendBackEntry->meta : []; $sendBackAlreadyReturned = $lastSendBackEntry ? $statusHistoryEntries ->filter(fn ($entry) => (int) ($entry->id ?? 0) > (int) ($lastSendBackEntry->id ?? 0)) ->contains(function ($entry) { if ((string) ($entry->action ?? '') !== \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR) { return false; } $meta = is_array($entry->meta) ? $entry->meta : []; return (bool) data_get($meta, 'returned_back', false); }) : false; $sentBackToRole = \App\Support\WorkflowPermission::normalizeRole((string) data_get($lastSendBackMeta, 'sent_back_to_role', '')); $sentBackToUserId = (int) data_get($lastSendBackMeta, 'sent_back_to_user_id', 0); $sentBackToState = (string) data_get($lastSendBackMeta, 'sent_back_to_state', (string) ($lastSendBackEntry->to_state ?? '')); $sentBackToStepOrder = (int) data_get($lastSendBackMeta, 'sent_back_selected_target_step_order', 0); $sentBackTargetType = strtolower(trim((string) data_get($lastSendBackMeta, 'sent_back_target_type', ''))); $sentBackOrganization = strtolower(trim((string) data_get($lastSendBackMeta, 'sent_back_selected_target_organization', data_get($lastSendBackMeta, 'organization_name', '')))); $currentOrganization = strtolower(trim((string) ($requestItem->organization_name ?? ''))); $sentBackByUserId = (int) data_get($lastSendBackMeta, 'sent_back_by_user_id', (int) ($lastSendBackEntry->actor_user_id ?? 0)); $sentBackTargetsRequesterOwner = $sentBackTargetType !== 'stage' && $sentBackToStepOrder <= 0 && (int) ($requestItem->user_id ?? 0) > 0 && (int) ($requestItem->user_id ?? 0) === (int) ($authUser->id ?? 0) && ( $sentBackToUserId === 0 || $sentBackToUserId === (int) ($authUser->id ?? 0) || in_array($sentBackTargetType, ['', 'requester', 'user'], true) || $sentBackToRole === \App\Support\WorkflowPermission::ROLE_REQUESTER ); $stageSendBackTargetsDocumentOwner = $sentBackTargetsRequesterOwner; $sentBackTargetsCurrentOrganizationQueue = $sentBackToRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && ($sentBackTargetType === 'stage' || $sentBackToStepOrder > 0) && !$stageSendBackTargetsDocumentOwner && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseAdminPermission((string) ($authUser->role ?? ''), \App\Support\WorkflowPermission::ADMIN_PERMISSION_ORGANIZATION_OFFICER) && ( $sentBackOrganization === '' || $currentOrganization === '' || $sentBackOrganization === $currentOrganization || $sentBackOrganization === $userOrganizationName ); if ($sentBackTargetsCurrentOrganizationQueue) { $sentBackTargetMatchesCurrentHolder = true; } elseif ($sentBackToUserId > 0) { $sentBackTargetMatchesCurrentHolder = $sentBackToUserId === (int) ($authUser->id ?? 0) && ( $sentBackToUserId === (int) ($requestItem->current_holder_user_id ?? 0) || ( (int) ($requestItem->current_holder_user_id ?? 0) <= 0 && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER ) ); } else { $sentBackTargetMatchesCurrentHolder = $sentBackToRole !== '' && $sentBackToRole === \App\Support\WorkflowPermission::normalizeRole((string) ($requestItem->current_holder_role ?? '')) && $sentBackToRole === $userRole; } $sentBackTargetMatchesCurrentStep = ( ($sentBackToState === '' || $sentBackToState === (string) $currentState) && ($sentBackToStepOrder <= 0 || $sentBackToStepOrder === (int) ($requestItem->current_workflow_step_order ?? 0)) ) || $sentBackTargetsCurrentOrganizationQueue; $sentBackOrganizationMatches = $sentBackOrganization === '' || $currentOrganization === '' || $sentBackOrganization === $currentOrganization || $sentBackOrganization === $userOrganizationName; $isPendingReturnBackTargetBase = $lastSendBackEntry !== null && !$sendBackAlreadyReturned && ( $sentBackByUserId !== (int) ($authUser->id ?? 0) || ($forceRequesterPerspective && $sentBackTargetsRequesterOwner) ) && $sentBackTargetMatchesCurrentHolder && $sentBackTargetMatchesCurrentStep && $sentBackOrganizationMatches && $holderCanAct; $returnBackBlockedReason = ''; $returnBackIsDocumentOwnerTarget = (int) ($requestItem->user_id ?? 0) > 0 && (int) ($requestItem->user_id ?? 0) === (int) ($authUser->id ?? 0) && ( $sentBackToUserId === (int) ($authUser->id ?? 0) || ( $sentBackToRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && (int) ($requestItem->current_holder_user_id ?? 0) === (int) ($authUser->id ?? 0) ) ); $returnBackNeedsReviewPermission = !$returnBackIsDocumentOwnerTarget; if ($isPendingReturnBackTargetBase && $returnBackNeedsReviewPermission && !$roleCanReviewDocument) { $returnBackBlockedReason = $withAdminContactNote(__('Cannot return back because your role no longer has Review Document permission.')); } if ($stageSendBackTargetsDocumentOwner && !$sendBackAlreadyReturned) { $canForwardByPermission = false; $canSendBackByPermission = false; $canApproveByPermission = false; $canRejectByPermission = false; $canComment = false; $canAddReviewDocument = false; } $isReturnBackContext = $isPendingReturnBackTargetBase; $isReturnBackForward = $isReturnBackContext && $returnBackBlockedReason === ''; $isWaitingForSentBackReturn = $lastSendBackEntry !== null && !$sendBackAlreadyReturned && $sentBackByUserId === (int) ($authUser->id ?? 0) && !($forceRequesterPerspective && $sentBackTargetsRequesterOwner); if ($isWaitingForSentBackReturn) { $canForwardByPermission = false; $canSendBackByPermission = false; $canApproveByPermission = false; $canRejectByPermission = false; } $forwardActionLabel = $isReturnBackContext ? __('Return Back') : ($isFinalForwardStep ? __('Complete') : __('Forward to Next Step')); $forwardActionShortLabel = $isReturnBackContext ? __('Return') : ($isFinalForwardStep ? __('Complete') : __('Forward')); $forwardActionModalTitle = $isReturnBackContext ? __('Return Back to Next Step') : ($isFinalForwardStep ? __('Complete Workflow Step') : __('Forward to Next Step')); $forwardActionModalDescription = $isReturnBackContext ? __('This will return the document back to the stage that requested revision.') : ($isFinalForwardStep ? __('This is the final stage transition. Submitting will complete this workflow.') : __('This will forward the document to the next configured step in the workflow.')); $forwardActionNotePlaceholder = $isReturnBackContext ? __('Add a return note...') : ($isFinalForwardStep ? __('Add a completion note...') : __('Add a forwarding note...')); $forwardActionButtonClass = $isReturnBackContext ? 'btn-secondary' : ($isFinalForwardStep ? 'btn-success' : 'btn-primary'); $forwardActionIcon = $isReturnBackContext ? 'ri-arrow-left-right-line' : ($isFinalForwardStep ? 'ri-check-line' : 'ri-arrow-right-line'); $forwardBlockReason = ''; $approveBlockReason = ''; $forwardTargetStep = $nextStep; if ($isReturnBackContext) { $sentBackFromStepOrder = (int) data_get($lastSendBackMeta, 'sent_back_from_step_order', 0); $returnTargetStep = null; if ($sentBackFromStepOrder > 0) { $returnTargetStep = $workflowStepsOrdered->first(function ($step) use ($sentBackFromStepOrder) { return is_array($step) && (int) ($step['order'] ?? 0) === $sentBackFromStepOrder; }); if (!is_array($returnTargetStep)) { $returnTargetStep = $workflowStepsOrdered->first(function ($step) use ($sentBackFromStepOrder, $workflowStepsOrdered, $isPrematureCompletedStepForUi) { if (!is_array($step)) { return false; } $order = (int) ($step['order'] ?? 0); if ($order <= $sentBackFromStepOrder) { return false; } $stepIndex = $workflowStepsOrdered->search(function ($candidate) use ($order) { return is_array($candidate) && (int) ($candidate['order'] ?? 0) === $order; }); return $stepIndex === false || !$isPrematureCompletedStepForUi($workflowStepsOrdered, (int) $stepIndex, $step); }); } } $forwardTargetStep = is_array($returnTargetStep) ? $returnTargetStep : null; } if (($canForwardByPermission || $isReturnBackForward) && is_array($forwardTargetStep)) { $nextStepType = strtolower(trim((string) ($forwardTargetStep['step_type'] ?? ''))); $nextStepActorRole = \App\Support\WorkflowPermission::normalizeRole((string) ($forwardTargetStep['actor_role'] ?? '')); $nextStepOrganization = $workflowStepOrganizationName($forwardTargetStep); if ($nextStepActorRole === '' || in_array($nextStepActorRole, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true) ) { $nextStepActorRole = match ($nextStepType) { 'vice_rector' => \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, 'rector' => \App\Support\WorkflowPermission::ROLE_RECTOR, 'approval' => \App\Support\WorkflowPermission::ROLE_RECTOR, 'organization' => \App\Support\WorkflowPermission::ROLE_REQUESTER, default => $nextStepActorRole, }; } if (!empty($forwardTargetStep['rector_user_id'])) { $assignedStepUser = \App\Models\User::query() ->whereKey((int) $forwardTargetStep['rector_user_id']) ->first(['role']); if ($assignedStepUser) { $nextStepActorRole = \App\Support\WorkflowPermission::normalizeRole((string) $assignedStepUser->role); } } if ($nextStepType === 'organization') { if ($nextStepOrganization === '') { $forwardBlockReason = $withAdminContactNote(__('Cannot forward because the next organization stage has no organization selected.')); } elseif (!\App\Models\Organization::activeNameExists($nextStepOrganization)) { $forwardBlockReason = $withAdminContactNote(__(':organization is disabled. Please re-enable it or update the workflow before sending documents there.', [ 'organization' => $localizedOrganizationName($nextStepOrganization), ])); } else { $nextOrganizationReviewers = \App\Models\User::query() ->where('is_active', true) ->whereRaw('LOWER(TRIM(organization_name)) = ?', [strtolower($nextStepOrganization)]) ->get() ->filter(function ($candidate) { $role = \App\Support\WorkflowPermission::normalizeRole((string) ($candidate->role ?? '')); return \App\Support\WorkflowPermission::canUseAdminPermission( $role, \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT ) && \App\Support\WorkflowPermission::canUseAdminPermission( $role, \App\Support\WorkflowPermission::ADMIN_PERMISSION_ORGANIZATION_OFFICER ); }) ->values(); if ($nextOrganizationReviewers->isEmpty()) { $forwardBlockReason = $withAdminContactNote(__('Cannot forward because the next step is :organization, but there is no active reviewer in this organization. Please assign an active user with Review Document and Organization Officer permission.', [ 'organization' => $localizedOrganizationName($nextStepOrganization), ])); } } } elseif (in_array($nextStepActorRole, [ \App\Support\WorkflowPermission::ROLE_RECTOR, \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, ], true)) { if (!\App\Support\WorkflowPermission::canUseAdminPermission( $nextStepActorRole, \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT )) { $forwardBlockReason = $withAdminContactNote(__('Cannot forward because :role no longer has Review Document permission.', [ 'role' => ucwords(str_replace('_', ' ', $nextStepActorRole)), ])); } else { $hasActiveAssignee = \App\Models\User::query() ->where('is_active', true) ->whereRaw('LOWER(TRIM(role)) = ?', [strtolower($nextStepActorRole)]) ->exists(); if (!$hasActiveAssignee) { $forwardBlockReason = $withAdminContactNote(__('Cannot forward because there is no active :role user assigned for the next step.', [ 'role' => ucwords(str_replace('_', ' ', $nextStepActorRole)), ])); } } } } if ($isReturnBackContext && !is_array($forwardTargetStep)) { $forwardBlockReason = $withAdminContactNote(__('Cannot return back because there is no remaining workflow stage to receive this document.')); } if ($isReturnBackContext && $forwardBlockReason !== '') { $forwardBlockReason = str_replace( ['Cannot forward because', 'Cannot forward'], ['Cannot return back because', 'Cannot return back'], $forwardBlockReason ); } $forwardBlockedButtonClass = $isReturnBackContext ? 'js-returnback-blocked' : 'js-forward-blocked'; $shouldWarnMissingRegistryBeforeForward = $roleCanRegisterDocumentNumber && trim((string) ($requestItem->registry_document_number ?? '')) === '' && !$isReturnBackContext; $forwardModalButtonAttributes = $shouldWarnMissingRegistryBeforeForward ? 'data-registry-forward-target="#modalForwardRector"' : 'data-bs-toggle="modal" data-bs-target="#modalForwardRector"'; if ($canApproveByPermission && is_array($nextStep)) { $nextStepType = strtolower(trim((string) ($nextStep['step_type'] ?? ''))); $nextStepActorRole = \App\Support\WorkflowPermission::normalizeRole((string) ($nextStep['actor_role'] ?? '')); $nextStepOrganization = $workflowStepOrganizationName($nextStep); if ($nextStepActorRole === '' || in_array($nextStepActorRole, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true) ) { $nextStepActorRole = match ($nextStepType) { 'vice_rector' => \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, 'rector' => \App\Support\WorkflowPermission::ROLE_RECTOR, 'approval' => \App\Support\WorkflowPermission::ROLE_RECTOR, 'organization' => \App\Support\WorkflowPermission::ROLE_REQUESTER, default => $nextStepActorRole, }; } if (!empty($nextStep['rector_user_id'])) { $assignedStepUser = \App\Models\User::query() ->whereKey((int) $nextStep['rector_user_id']) ->first(['role']); if ($assignedStepUser) { $nextStepActorRole = \App\Support\WorkflowPermission::normalizeRole((string) $assignedStepUser->role); } } if ($nextStepType === 'organization') { if ($nextStepOrganization === '') { $approveBlockReason = $withAdminContactNote(__('Cannot approve because the next organization stage has no organization selected.')); } elseif (!\App\Models\Organization::activeNameExists($nextStepOrganization)) { $approveBlockReason = $withAdminContactNote(__(':organization is disabled. Please re-enable it or update the workflow before sending documents there.', [ 'organization' => $localizedOrganizationName($nextStepOrganization), ])); } else { $nextOrganizationReviewers = \App\Models\User::query() ->where('is_active', true) ->whereRaw('LOWER(TRIM(organization_name)) = ?', [strtolower($nextStepOrganization)]) ->get() ->filter(function ($candidate) { $role = \App\Support\WorkflowPermission::normalizeRole((string) ($candidate->role ?? '')); return \App\Support\WorkflowPermission::canUseAdminPermission( $role, \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT ) && \App\Support\WorkflowPermission::canUseAdminPermission( $role, \App\Support\WorkflowPermission::ADMIN_PERMISSION_ORGANIZATION_OFFICER ); }) ->values(); if ($nextOrganizationReviewers->isEmpty()) { $approveBlockReason = $withAdminContactNote(__('Cannot approve because the next step is :organization, but there is no active reviewer in this organization. Please assign an active user with Review Document and Organization Officer permission.', [ 'organization' => $localizedOrganizationName($nextStepOrganization), ])); } } } elseif (in_array($nextStepActorRole, [ \App\Support\WorkflowPermission::ROLE_RECTOR, \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, ], true)) { if (!\App\Support\WorkflowPermission::canUseAdminPermission( $nextStepActorRole, \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT )) { $approveBlockReason = $withAdminContactNote(__('Cannot approve because :role no longer has Review Document permission.', [ 'role' => ucwords(str_replace('_', ' ', $nextStepActorRole)), ])); } else { $hasActiveAssignee = \App\Models\User::query() ->where('is_active', true) ->whereRaw('LOWER(TRIM(role)) = ?', [strtolower($nextStepActorRole)]) ->exists(); if (!$hasActiveAssignee) { $approveBlockReason = $withAdminContactNote(__('Cannot approve because there is no active :role user assigned for the next step.', [ 'role' => ucwords(str_replace('_', ' ', $nextStepActorRole)), ])); } } } } $effectiveCurrentStepOrder = $currentStepOrder > 0 ? $currentStepOrder : (int) (($currentStepIndex !== null && $workflowStepsOrdered->has($currentStepIndex)) ? (int) ($workflowStepsOrdered->get($currentStepIndex)['order'] ?? 0) : 0); $firstWorkflowStepOrder = (int) data_get($workflowStepsOrdered->first(), 'order', 0); $isFirstWorkflowStage = $firstWorkflowStepOrder > 0 && $effectiveCurrentStepOrder > 0 && $effectiveCurrentStepOrder === $firstWorkflowStepOrder; $isAtRequesterReturnStage = $effectiveCurrentStepOrder <= 0 && $currentState === \App\Models\RequesterRequest::STATE_SUBMITTED; if ($isReturnBackForward && $roleCanReviewDocument && $holderCanAct && !$isAtRequesterReturnStage) { $canSendBackByPermission = true; } $canSendBackInUi = $canSendBackByPermission && !$isAtRequesterReturnStage; $getSendBackTargetBlockReason = function (array $step) use ($localizedOrganizationName, $withAdminContactNote): string { $stepType = strtolower(trim((string) ($step['step_type'] ?? ''))); $organizationName = trim((string) ($step['organization_name'] ?? '')); $actorRole = \App\Support\WorkflowPermission::normalizeRole((string) ($step['actor_role'] ?? '')); if ($stepType === 'organization') { if ($organizationName === '') { return $withAdminContactNote(__('Cannot send back because the target organization stage has no organization selected.')); } $hasReviewer = \App\Models\User::query() ->where('is_active', true) ->whereRaw('LOWER(TRIM(organization_name)) = ?', [strtolower($organizationName)]) ->get() ->contains(function ($candidate) { $role = \App\Support\WorkflowPermission::normalizeRole((string) ($candidate->role ?? '')); return \App\Support\WorkflowPermission::canUseAdminPermission( $role, \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT ) && \App\Support\WorkflowPermission::canUseAdminPermission( $role, \App\Support\WorkflowPermission::ADMIN_PERMISSION_ORGANIZATION_OFFICER ); }); if (!$hasReviewer) { return $withAdminContactNote(__('Cannot send back because :organization has no active reviewer with Review Document permission.', [ 'organization' => $localizedOrganizationName($organizationName), ])); } } if (in_array($actorRole, [ \App\Support\WorkflowPermission::ROLE_RECTOR, \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, ], true)) { if (!\App\Support\WorkflowPermission::canUseAdminPermission( $actorRole, \App\Support\WorkflowPermission::ADMIN_PERMISSION_REVIEW_DOCUMENT )) { return $withAdminContactNote(__('Cannot send back because :role no longer has Review Document permission.', [ 'role' => ucwords(str_replace('_', ' ', $actorRole)), ])); } $hasAssignee = \App\Models\User::query() ->where('is_active', true) ->whereRaw('LOWER(TRIM(role)) = ?', [strtolower($actorRole)]) ->exists(); if (!$hasAssignee) { return $withAdminContactNote(__('Cannot send back because there is no active :role user.', [ 'role' => ucwords(str_replace('_', ' ', $actorRole)), ])); } } return ''; }; $sendBackStageOptions = $workflowStepsOrdered ->filter(function ($step) use ($effectiveCurrentStepOrder) { if (!is_array($step)) { return false; } $order = (int) ($step['order'] ?? 0); if ($order <= 0) { return false; } if ($effectiveCurrentStepOrder > 0 && $order >= $effectiveCurrentStepOrder) { return false; } $actorRole = \App\Support\WorkflowPermission::normalizeRole((string) ($step['actor_role'] ?? '')); $toState = strtolower(trim((string) ($step['to_state'] ?? ''))); if ($actorRole === \App\Support\WorkflowPermission::ROLE_RECTOR || $toState === \App\Models\RequesterRequest::STATE_RECTOR_REVIEW) { return false; } return $toState !== \App\Models\RequesterRequest::STATE_COMPLETED; }) ->map(function ($step) use ($dynamicFacultyLabel, $isFacultyStep, $getSendBackTargetBlockReason, $localizedOrganizationName) { $order = (int) ($step['order'] ?? 0); $stepState = (string) ($step['to_state'] ?? ''); $stepType = strtolower(trim((string) ($step['step_type'] ?? ''))); $actorRole = strtolower(trim((string) ($step['actor_role'] ?? ''))); $organizationName = trim((string) ($step['organization_name'] ?? '')); $label = trim((string) ($step['label'] ?? '')) !== '' ? __((string) $step['label']) : __((string) \App\Models\RequesterRequest::workflowLabel($stepState)); if ($stepType === 'organization' && $organizationName !== '') { $label = $localizedOrganizationName($organizationName); } if (($stepType === 'faculty' || $actorRole === 'faculty' || $isFacultyStep((array) $step)) && $dynamicFacultyLabel !== '') { $label = __('Faculty') . ': ' . $dynamicFacultyLabel; } return [ 'order' => $order, 'label' => $label, 'block_reason' => $getSendBackTargetBlockReason((array) $step), ]; }) ->values(); $sendBackUserOptions = collect($sendBackTargetOptions ?? []); $sendBackCombinedTargetOptions = collect(); $sendBackRequesterOption = $sendBackUserOptions->first(fn ($option) => (bool) ($option['is_requester'] ?? false)); if (is_array($sendBackRequesterOption) && (int) ($sendBackRequesterOption['id'] ?? 0) > 0) { $requesterName = trim((string) ($sendBackRequesterOption['name'] ?? '')); $sendBackCombinedTargetOptions->push([ 'value' => 'user:' . (int) ($sendBackRequesterOption['id'] ?? 0), 'label' => __('Requester') . ($requesterName !== '' ? ' - ' . $requesterName : ''), 'block_reason' => '', ]); } if ($sendBackStageOptions->isNotEmpty()) { $sendBackCombinedTargetOptions = $sendBackCombinedTargetOptions->merge( $sendBackStageOptions->map(function ($option) { $order = (int) ($option['order'] ?? 0); $label = (string) ($option['label'] ?? ''); return [ 'value' => 'stage:' . $order, 'label' => __('Step :order', ['order' => $order]) . ' - ' . $label, 'block_reason' => trim((string) ($option['block_reason'] ?? '')), ]; })->filter(fn ($option) => (int) str_replace('stage:', '', (string) ($option['value'] ?? '')) > 0) ); } $sendBackBlockedReason = ''; $selectableSendBackTargets = $sendBackCombinedTargetOptions->filter(function ($option) { return trim((string) ($option['block_reason'] ?? '')) === ''; }); $blockedSendBackTargets = $sendBackCombinedTargetOptions->filter(function ($option) { return trim((string) ($option['block_reason'] ?? '')) !== ''; }); if ($canSendBackInUi && $selectableSendBackTargets->isEmpty()) { if ($blockedSendBackTargets->isNotEmpty()) { $sendBackBlockedReason = trim((string) ($blockedSendBackTargets->first()['block_reason'] ?? '')); } if ($sendBackBlockedReason === '') { $sendBackBlockedReason = $withAdminContactNote(__('Cannot send back because there is no available target stage or user.')); } } $canAdminHead = $canAssignStaffByPermission && $holderCanAct && in_array($requestItem->status, ['pending', 'approved'], true); $canHeadForward = $canForwardByPermission && $holderCanAct && in_array($requestItem->status, ['pending', 'approved'], true) && in_array($currentState, ['submitted', 'organization_review', 'v_rector_processing'], true); $headAssignedToOfficer = false; $headRejectedPreviewOnly = false; $canReviewer = false; $canRector = ($canApproveByPermission || $canRejectByPermission || $canSendBackInUi) && in_array($currentState, ['submitted', 'organization_review', 'rector_review', 'v_rector_processing'], true) && $holderCanAct && $isActionableStatus; $canAssignRector = $userRole === 'rector' && $currentState === 'rector_review' && $currentHolderRole === 'rector' && $isCurrentHolder && $isActionableStatus; $canCentralOrganizationForward = false; $canStageReviewer = $currentStepPermissionRole === \App\Support\WorkflowPermission::PERMISSION_REVIEWER && $roleCanReviewDocument && $canForwardByPermission && $holderCanAct && $isActionableStatus; if (!$canStageReviewer && $roleCanReviewDocument && $isCurrentOrganizationReviewerTurn && $isActionableStatus) { $canStageReviewer = true; $canForwardByPermission = true; $canSendBackByPermission = true; $canSendBackInUi = !$isAtRequesterReturnStage; $canComment = true; $canAddReviewDocument = true; } $canStageApprover = $currentStepPermissionRole === \App\Support\WorkflowPermission::PERMISSION_APPROVER && $roleCanReviewDocument && ($canApproveByPermission || $canRejectByPermission || $canSendBackInUi) && $holderCanAct && $isActionableStatus; $workQueueCurrentStepOrganizationName = strtolower(trim((string) data_get($currentStepConfig, 'organization_name', ''))); $workQueueCurrentStepActorRole = \App\Support\WorkflowPermission::normalizeRole( (string) data_get($currentStepConfig, 'actor_role', '') ); $workQueueUserOrganizationIsInWorkflow = $workflowStepsOrdered ->filter(fn ($step) => is_array($step)) ->contains(function ($step) use ($userOrganizationName) { $stepOrganizationName = strtolower(trim((string) data_get($step, 'organization_name', ''))); return $userOrganizationName !== '' && $stepOrganizationName !== '' && $stepOrganizationName === $userOrganizationName; }); $workQueueCurrentStepMatchesUserOrganization = $userOrganizationName !== '' && $workQueueCurrentStepOrganizationName !== '' && $userOrganizationName === $workQueueCurrentStepOrganizationName; $workQueueUserRoleCanActForOrganization = $roleCanReviewDocument && ( $currentHolderRole === $userRole || $workQueueCurrentStepActorRole === $userRole || \App\Support\WorkflowPermission::canUseAdminPermission( (string) ($authUser->role ?? ''), \App\Support\WorkflowPermission::ADMIN_PERMISSION_ORGANIZATION_OFFICER ) ); $userCanActAsRequesterAndOrganizationReviewer = $isRequesterOwner && $roleCanReviewDocument && $isActionableStatus && $workQueueUserOrganizationIsInWorkflow && $workQueueCurrentStepMatchesUserOrganization && $workQueueUserRoleCanActForOrganization && !in_array($currentState, [ \App\Models\RequesterRequest::STATE_COMPLETED, \App\Models\RequesterRequest::STATE_REJECTED, ], true); if ($userCanActAsRequesterAndOrganizationReviewer) { $holderCanAct = true; $canForwardByPermission = true; $canSendBackByPermission = true; $canSendBackInUi = !$isAtRequesterReturnStage; $canComment = $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseUiPermission($currentStepPermissionRole, 'review/comment'); $canAddReviewDocument = $roleCanReviewDocument && \App\Support\WorkflowPermission::canUseUiPermission($currentStepPermissionRole, 'upload_document'); if ($currentStepPermissionRole === \App\Support\WorkflowPermission::PERMISSION_REVIEWER) { $canStageReviewer = true; } if ($currentStepPermissionRole === \App\Support\WorkflowPermission::PERMISSION_APPROVER) { $canApproveByPermission = true; $canRejectByPermission = true; $canStageApprover = true; } } $shouldForceRequesterPerspective = $forceRequesterPerspective && !$userCanActAsRequesterAndOrganizationReviewer; if ($shouldForceRequesterPerspective) { $canAssignStaffByPermission = false; $canForwardByPermission = false; $canSendBackByPermission = false; $canSendBackInUi = false; $canApproveByPermission = false; $canRejectByPermission = false; $canAdminHead = false; $canHeadForward = false; $canReviewer = false; $canRector = false; $canCentralOrganizationForward = false; $canStageReviewer = false; $canStageApprover = false; if (!$sentBackTargetsRequesterOwner) { $isReturnBackContext = false; $isReturnBackForward = false; $isWaitingForSentBackReturn = false; } } $hasPrimaryActionAvailable = $canAdminHead || $canHeadForward || $canReviewer || $canRector || $canCentralOrganizationForward || $canStageReviewer || $canStageApprover || ($userRole === 'vice_rector' && $holderCanAct && $isActionableStatus && $canForwardByPermission); $hideAddDocumentInNoAction = !$hasPrimaryActionAvailable && $userRole !== \App\Support\WorkflowPermission::ROLE_REQUESTER; $hasTemplateFilledData = !empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0; $paymentUnitPrice = null; $paymentQuantity = null; $paymentTotal = null; $selectedFacultyLabel = null; if ($hasTemplateFilledData) { $rawUnitPrice = data_get($builderSubmittedData, '__pricing_unit_price'); $rawQuantity = data_get($builderSubmittedData, '__pricing_quantity'); $rawTotal = data_get($builderSubmittedData, '__payment_total', data_get($builderSubmittedData, '__pricing_total')); $rawFacultyLabel = trim((string) data_get($builderSubmittedData, '__selected_faculty_label', data_get($builderSubmittedData, '__selected_faculty_value', ''))); if (is_numeric($rawUnitPrice)) { $paymentUnitPrice = (float) $rawUnitPrice; } if (is_numeric($rawQuantity)) { $paymentQuantity = (float) $rawQuantity; } if (is_numeric($rawTotal)) { $paymentTotal = (float) $rawTotal; } if ($rawFacultyLabel !== '') { $selectedFacultyLabel = $rawFacultyLabel; } } // Payment summary: only true if pricing data was saved into form_data. // The controller only saves __pricing_* keys when the admin has enabled pricing in the form builder. $hasPaymentSummary = $paymentUnitPrice !== null || $paymentQuantity !== null || $paymentTotal !== null; $hasRequirementsForOrganization = false; // Organization requirements: required files configured on document type (or payment if applicable). $hasRequirementsForRegistration = !empty($documentType->required_files) || $hasPaymentSummary; $reviewerStageEnteredAt = optional( $statusHistoryEntries ->filter(fn ($entry) => (string) ($entry->to_state ?? '') === \App\Models\RequesterRequest::STATE_ORGANIZATION_REVIEW && (string) ($entry->from_state ?? '') !== \App\Models\RequesterRequest::STATE_ORGANIZATION_REVIEW) ->sortByDesc('created_at') ->first() )->created_at; $reviewerRequirementsDone = $statusHistoryEntries ->filter(fn ($entry) => (string) ($entry->action ?? '') === 'mark_requirements_done') ->filter(fn ($entry) => (string) ($entry->to_state ?? '') === \App\Models\RequesterRequest::STATE_ORGANIZATION_REVIEW) ->filter(fn ($entry) => \App\Support\WorkflowPermission::isReviewerOfficerGroup((string) ($entry->actor_role ?? ''))) ->contains(function ($entry) use ($reviewerStageEnteredAt) { return $reviewerStageEnteredAt === null || optional($entry->created_at)->greaterThanOrEqualTo($reviewerStageEnteredAt); }); $normalizedOrganizationName = strtolower(trim((string) ($authUser->organization_name ?? ''))); $isRegistrationOrganization = $userRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && (str_contains($normalizedOrganizationName, 'registration') || str_contains($normalizedOrganizationName, 'registrat')); $registrationStageState = \App\Models\RequesterRequest::STATE_ORGANIZATION_REVIEW; $registrationStageEnteredAt = optional( $statusHistoryEntries ->filter(fn ($entry) => (string) ($entry->to_state ?? '') === $registrationStageState && (string) ($entry->from_state ?? '') !== (string) ($entry->to_state ?? '')) ->sortByDesc('created_at') ->first() )->created_at; $registrationWorkflowStates = $workflowStepsOrdered ->filter(function ($step) use ($roleAlias) { $role = $roleAlias((string) ($step['actor_role'] ?? '')); return in_array($role, [\App\Support\WorkflowPermission::ROLE_REQUESTER, 'registration'], true); }) ->map(fn ($step) => (string) ($step['to_state'] ?? '')) ->filter(fn ($state) => $state !== '') ->values() ->all(); if (empty($registrationWorkflowStates)) { $registrationWorkflowStates = [ \App\Models\RequesterRequest::STATE_SUBMITTED, \App\Models\RequesterRequest::STATE_ORGANIZATION_REVIEW, ]; } $registrationRequirementsDone = $statusHistoryEntries ->filter(fn ($entry) => (string) ($entry->action ?? '') === 'mark_requirements_done') ->filter(fn ($entry) => in_array((string) ($entry->to_state ?? ''), $registrationWorkflowStates, true)) ->filter(fn ($entry) => $roleAlias((string) ($entry->actor_role ?? '')) === \App\Support\WorkflowPermission::ROLE_REQUESTER) ->isNotEmpty(); $workflowActorAliases = $workflowStepsOrdered ->map(fn ($step) => $roleAlias((string) ($step['actor_role'] ?? ''))) ->values(); $removableWorkflowStages = $workflowStepsOrdered ->filter(fn ($step) => (int) ($step['order'] ?? 0) > $currentStepOrder) ->values(); $canCustomizeWorkflowStages = $canUsePriorityHandling && in_array((string) ($requestItem->status ?? ''), ['pending', 'approved'], true) && $removableWorkflowStages->isNotEmpty(); $containsText = function (string $value, string $needle): bool { return str_contains(strtolower(trim($value)), strtolower($needle)); }; $hasRegistrationStep = $workflowStepsOrdered->contains(function ($step) use ($containsText) { if (!is_array($step)) { return false; } $actorRole = strtolower(trim((string) ($step['actor_role'] ?? ''))); $stepType = strtolower(trim((string) ($step['step_type'] ?? ''))); $organizationName = (string) ($step['organization_name'] ?? ''); $label = (string) ($step['label'] ?? ''); return $actorRole === 'registration' || $stepType === 'registration' || $containsText($organizationName, 'registrat') || $containsText($label, 'registrat'); }); $showSubmissionStatus = $hasRegistrationStep; $canRegistrationRequirements = $isRegistrationOrganization && (string) $currentState === $registrationStageState && $holderCanAct && in_array((string) ($requestItem->status ?? ''), ['pending', 'approved'], true); $registrationNeedsRequirementsProcess = $isRegistrationOrganization && !$isFinalForwardStep && (string) $currentState === $registrationStageState; $hasRequirementsToCheck = $hasRequirementsForRegistration; $canUseRequirementsCard = ($currentStepPermissionRole === \App\Support\WorkflowPermission::PERMISSION_REVIEWER || ($isRegistrationOrganization && $registrationNeedsRequirementsProcess)) && $hasRequirementsToCheck; $requirementsDoneForRole = $isRegistrationOrganization ? $registrationRequirementsDone : $reviewerRequirementsDone; $canMarkRequirementsDone = (($currentStepPermissionRole === \App\Support\WorkflowPermission::PERMISSION_REVIEWER && $canStageReviewer) || ($canRegistrationRequirements && $registrationNeedsRequirementsProcess)) && $hasRequirementsToCheck; $mustCompleteRequirementsBeforeForward = $isRegistrationOrganization && $registrationNeedsRequirementsProcess && !$requirementsDoneForRole && $hasRequirementsForRegistration; $viceRectorPrintUrl = $hasTemplateFilledData ? route('document-viewer.filled-pdf.export', ['id' => $requestItem->id, 'action' => 'print']) : ($mainFile['print_url'] ?? ($mainFile['preview_url'] ?? $mainFile['url'] ?? null)); $viceRectorDownloadUrl = $hasTemplateFilledData ? route('document-viewer.filled-pdf.export', ['id' => $requestItem->id, 'action' => 'download']) : ($mainFile['download_url'] ?? $mainFile['url'] ?? null); $printReferenceUrl = $roleCanReviewDocument ? route('document-viewer.reference-id.print', $requestItem->id) : null; $quickPrintDocumentUrl = $hasTemplateFilledData ? route('document-viewer.filled-pdf.export', ['id' => $requestItem->id, 'action' => 'print']) : ($mainFile['print_url'] ?? ($mainFile['preview_url'] ?? $mainFile['url'] ?? null)); $displayState = $currentState; $isSentBackToRequester = $currentState === 'submitted' && $currentHolderRole === \App\Support\WorkflowPermission::ROLE_REQUESTER && !empty(trim((string) ($requestItem->last_action_note ?? ''))); $stateBadgeColor = match($displayState) { 'completed' => 'success', 'rejected' => 'danger', 'v_rector_processing' => 'primary', default => 'info', }; $hasFinishedDocumentNumberColumn = \Illuminate\Support\Facades\Schema::hasColumn('document_requests', 'finished_document_number'); $registryDocumentNumberValue = trim((string) ($requestItem->registry_document_number ?? '')); $hasRegistryDocumentNumber = $registryDocumentNumberValue !== ''; $finishedDocumentNumberValue = $hasFinishedDocumentNumberColumn ? trim((string) ($requestItem->finished_document_number ?? '')) : ''; $hasFinishedDocumentNumber = $finishedDocumentNumberValue !== ''; $isLastWorkflowStageForFinishedNumber = (bool) ($workflowIsCompleted ?? false) || (bool) ($isFinalForwardStep ?? false) || $nextStep === null; @endphp {{-- Header bar --}}
{{ __('Back') }}
{{ $isSentBackToRequester ? __('Sent Back to You') : __(\App\Models\RequesterRequest::workflowLabel($displayState)) }}
{{-- Workflow Progress timeline --}}
{{ __('Workflow Progress') }}
@if($hasWorkflowOverride) {{ __('Customized for this document') }} @endif
@if(!empty($publicTrackingUrl)) @endif @if($canCustomizeWorkflowStages) @endif
@if($totalWorkflowTimeLabel)
{{ __('Total Workflow Time') }}: {{ $totalWorkflowTimeLabel }}
@endif @if($workflowTimeline->isNotEmpty()) {{-- Horizontal stepper --}}
@php $requesterStageOrganization = trim((string) data_get($requestItem, 'user.organization_name', '')); $requesterStageLabel = $requesterStageOrganization !== '' ? $localizedOrganizationName($requesterStageOrganization) : ''; $requestSubmittedAt = $requestItem->created_at; $isRequesterCurrent = (bool) ($isReturnedToRequesterStage ?? false); $requesterStageSubmitter = trim((string) ($requestItem->submitter_label ?? '')); $submitterStageLabel = __('Submit by') . ': ' . ($requesterStageLabel !== '' ? $requesterStageLabel : ($requesterStageSubmitter !== '' ? __($requesterStageSubmitter) : __('User'))); @endphp
@if($isRequesterCurrent) @else @endif
{{ $submitterStageLabel }}
@if($requestSubmittedAt)
{{ __('Submitted') }}: {{ \App\Support\DateFormatter::localized($requestSubmittedAt, 'd F Y, h:i A') }}
@endif
@foreach($workflowTimeline as $stepItem) @php $isCompleted = $stepItem['status_badge'] === 'completed'; $isCurrent = $stepItem['status_badge'] === 'current'; $isRejected = $requestItem->status === 'rejected' && $loop->last; @endphp {{-- connector line (not before first item) --}} @if(!$loop->first)
@endif
@if($isRejected) @elseif($isCompleted) @elseif($isCurrent) @else {{ $loop->iteration }} @endif
{{ $stepItem['label'] }}
@if($stepItem['sla_days'] > 0)
{{ __('SLA') }}: {{ $stepItem['sla_label'] }}
@endif @if($isCompleted && !empty($stepItem['elapsed_label']))
{{ __('Time Spent') }}: {{ $stepItem['elapsed_label'] }}
@elseif($isCurrent && !empty($stepItem['elapsed_label']))
{{ __('Time Spent') }}: {{ $stepItem['elapsed_label'] }}
@endif
@endforeach
{{-- Activity log --}} @if($historyAscending->isNotEmpty())
@php $historyUserIds = $historyAscending ->flatMap(function ($item) { $meta = is_array($item->meta) ? $item->meta : []; return [ (int) ($item->actor_user_id ?? 0), (int) data_get($meta, 'sent_back_by_user_id', 0), (int) data_get($meta, 'sent_back_to_user_id', 0), (int) data_get($meta, 'returned_back_from_user_id', 0), (int) data_get($meta, 'returned_back_to_user_id', 0), ]; }) ->filter(fn ($id) => (int) $id > 0) ->unique() ->values(); $historyUsersById = \App\Models\User::query() ->whereIn('id', $historyUserIds) ->get() ->keyBy('id'); @endphp @foreach($historyAscending->sortByDesc('created_at') as $entry) @php $entryAction = (string) ($entry->action ?? ''); $entryMeta = is_array($entry->meta) ? $entry->meta : []; $isReturnBackAction = $entryAction === \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR && (bool) data_get($entryMeta, 'returned_back', false); $isSendBackAction = $entryAction === \App\Support\WorkflowPermission::ACTION_REQUEST_REVISION; $isRejectAction = $entryAction === \App\Support\WorkflowPermission::ACTION_REJECT_DOCUMENT; $isApproveAction = $entryAction === \App\Support\WorkflowPermission::ACTION_APPROVE_DOCUMENT; $isCompleteAction = $entryAction === \App\Support\WorkflowPermission::ACTION_COMPLETE_POST_APPROVAL; $isFinalForwardCompletion = $entryAction === \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR && strtolower((string) ($entry->to_state ?? '')) === \App\Models\RequesterRequest::STATE_COMPLETED; $isReviewCommentAction = $entryAction === 'review_comment'; $isOverdueAction = $entryAction === 'overdue_sla'; $isAdminCustomizeAction = $entryAction === 'admin_customize_workflow'; $fromStateLabel = \App\Models\RequesterRequest::workflowLabel((string) ($entry->from_state ?? '')); $toStateLabel = \App\Models\RequesterRequest::workflowLabel((string) ($entry->to_state ?? '')); $fromRoleRaw = (string) ($entry->actor_role ?? data_get($entryMeta, 'sent_back_by_role', data_get($entryMeta, 'returned_back_from_role', ''))); $toRoleRaw = (string) data_get($entryMeta, 'notification_role', data_get($entryMeta, 'sent_back_to_role', data_get($entryMeta, 'returned_back_to_role', ''))); $fromRole = \App\Support\WorkflowPermission::normalizeRole($fromRoleRaw); $toRole = \App\Support\WorkflowPermission::normalizeRole($toRoleRaw); $roleLabel = function (string $roleKey): string { return \App\Support\WorkflowPermission::roleLabelForAdmin($roleKey); }; $fromFlowLabel = $fromRole !== '' ? $roleLabel($fromRole) : $fromStateLabel; $toFlowLabel = $toRole !== '' ? $roleLabel($toRole) : $toStateLabel; $activityLabel = match (true) { $isFinalForwardCompletion => __('Complete'), $isSendBackAction => __('Send Back'), $isReturnBackAction => __('Return Back'), $entryAction === \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR => __('Forward'), $isApproveAction => __('Approve'), $isRejectAction => __('Reject'), $entryAction === 'submit_document' => __('Submit Document'), $entryAction === 'overdue_sla' => __('Overdue SLA'), $isCompleteAction => __('Complete'), $isReviewCommentAction => __('Comment'), $isAdminCustomizeAction => __('Workflow Customized'), $entryAction === \App\Support\WorkflowPermission::ACTION_REQUEST_MORE_INFO => __('Request More Information'), $entryAction === \App\Support\WorkflowPermission::ACTION_MARK_REQUIREMENTS_DONE => __('Mark Requirements Done'), $entryAction === 'register_document_number' => __('Registry Number Registered'), $entryAction === 'update_registry_document_number' => __('Registry Number Updated'), $entryAction === 'register_finished_number' => __('Finished Number Registered'), $entryAction === 'update_finished_number' => __('Finished Number Updated'), $entryAction === 'reassign_rector' => __('Rector Reassigned'), default => __('Workflow Update'), }; $activityBadgeKey = match (true) { $isFinalForwardCompletion, $isCompleteAction => 'completed', $isSendBackAction => 'send_back', $isReturnBackAction => 'returned_back', $isReviewCommentAction => 'commented', $entryAction === \App\Support\WorkflowPermission::ACTION_FORWARD_TO_RECTOR => 'forwarded', $entryAction === \App\Support\WorkflowPermission::ACTION_APPROVE_DOCUMENT => 'approved', $entryAction === \App\Support\WorkflowPermission::ACTION_REJECT_DOCUMENT => 'rejected', $entryAction === \App\Support\WorkflowPermission::ACTION_REQUEST_MORE_INFO => 'request_more_information', $entryAction === \App\Support\WorkflowPermission::ACTION_MARK_REQUIREMENTS_DONE => 'completed', $entryAction === 'submit_document' => 'submit_document', $entryAction === 'admin_customize_workflow' => 'admin_customize_workflow', $entryAction === 'overdue_sla' => 'overdue_sla', default => $entryAction, }; $noteMarkClass = ($isSendBackAction || $isRejectAction || $isOverdueAction) ? 'workflow-note-mark--danger' : ($isReturnBackAction ? 'workflow-note-mark--warning' : ($isApproveAction ? 'workflow-note-mark--approve' : ($isCompleteAction ? 'workflow-note-mark--complete' : 'workflow-note-mark--info'))); $noteLabel = ($isSendBackAction || $isRejectAction) ? __('Reason') : ($isReturnBackAction ? __('Return Note') : ($isCompleteAction ? __('Completion Note') : ($isOverdueAction ? __('Overdue Note') : __('Note')))); $noteText = trim((string) ($entry->note ?? '')); $formatNumberHistoryValue = static function ($value): string { $value = trim((string) $value); return $value !== '' ? $value : __('Not set'); }; $numberHistoryNoteText = null; if (in_array($entryAction, ['register_document_number', 'update_registry_document_number'], true)) { $oldNumber = trim((string) data_get($entryMeta, 'old_registry_document_number', '')); $newNumber = trim((string) data_get($entryMeta, 'registry_document_number', '')); if ($entryAction === 'update_registry_document_number' && $newNumber !== '') { $numberHistoryNoteText = __('Updated from :old to :new', [ 'old' => $formatNumberHistoryValue($oldNumber), 'new' => $formatNumberHistoryValue($newNumber), ]); } elseif ($entryAction === 'register_document_number' && $newNumber !== '') { $numberHistoryNoteText = __('Registered number: :number', [ 'number' => $formatNumberHistoryValue($newNumber), ]); } } if (in_array($entryAction, ['register_finished_number', 'update_finished_number'], true)) { $oldNumber = trim((string) data_get($entryMeta, 'old_finished_document_number', '')); $newNumber = trim((string) data_get($entryMeta, 'finished_document_number', '')); if ($entryAction === 'update_finished_number' && $newNumber !== '') { $numberHistoryNoteText = __('Updated from :old to :new', [ 'old' => $formatNumberHistoryValue($oldNumber), 'new' => $formatNumberHistoryValue($newNumber), ]); } elseif ($entryAction === 'register_finished_number' && $newNumber !== '') { $numberHistoryNoteText = __('Registered number: :number', [ 'number' => $formatNumberHistoryValue($newNumber), ]); } } if ($numberHistoryNoteText !== null) { $noteText = $numberHistoryNoteText; } if ($isAdminCustomizeAction) { $removedStageCount = count((array) data_get($entryMeta, 'removed_stage_orders', [])); $noteText = $removedStageCount > 0 ? __('Removed :count upcoming workflow stage(s) for this document.', ['count' => $removedStageCount]) : __('Updated one upcoming workflow stage to approver for this document.'); } if ($entryAction === 'reassign_rector' && str_starts_with($noteText, 'Reassigned to rector:')) { $reassignedRectorName = trim(substr($noteText, strlen('Reassigned to rector:'))); $noteText = __('Reassigned to rector: :name', ['name' => $reassignedRectorName]); } $systemNoteTranslations = [ 'Past SLA time' => __('Past SLA time'), 'Registry document number registered.' => __('Registry document number registered.'), 'Registry document number updated.' => __('Registry document number updated.'), 'Finished number registered.' => __('Finished number registered.'), 'Finished number updated.' => __('Finished number updated.'), 'Added a document for review tracking.' => __('Added a document for review tracking.'), ]; $noteText = $systemNoteTranslations[$noteText] ?? $noteText; $fromUserId = (int) data_get($entryMeta, $isReturnBackAction ? 'returned_back_from_user_id' : 'sent_back_by_user_id', 0); $toUserId = (int) data_get($entryMeta, $isReturnBackAction ? 'returned_back_to_user_id' : 'sent_back_to_user_id', 0); $fromUserName = trim((string) optional($historyUsersById->get($fromUserId))->name); $toUserName = trim((string) optional($historyUsersById->get($toUserId))->name); $actorName = trim((string) optional($entry->actor)->name); $performedBy = $actorName !== '' ? $actorName : __($fromFlowLabel); $activityOrganizationName = trim((string) data_get($entryMeta, 'organization_name', '')); $sentBackTargetOrganization = trim((string) data_get($entryMeta, 'sent_back_target_organization', '')); $returnedBackTargetOrganization = trim((string) data_get($entryMeta, 'returned_back_target_organization', '')); $targetOrganizationDisplay = $sentBackTargetOrganization !== '' ? $sentBackTargetOrganization : ($returnedBackTargetOrganization !== '' ? $returnedBackTargetOrganization : $activityOrganizationName); $recordedActorOrganizationName = trim((string) data_get($entryMeta, 'actor_organization_name', '')); $actorOrganizationName = $recordedActorOrganizationName !== '' ? $recordedActorOrganizationName : trim((string) optional($entry->actor)->organization_name); $actorOrganizationDisplay = $actorOrganizationName !== '' ? $localizedOrganizationName($actorOrganizationName) : ''; $fromUserDisplay = $actorOrganizationName !== '' ? $localizedOrganizationName($actorOrganizationName) : ($fromUserName !== '' ? $fromUserName : __($fromFlowLabel)); $toUserDisplay = $targetOrganizationDisplay !== '' ? $localizedOrganizationName($targetOrganizationDisplay) : ($toUserName !== '' ? $toUserName : __($toFlowLabel)); @endphp
{{ \App\Support\DateFormatter::localized($entry->created_at, 'd F Y, H:i') }}
{{ __('By') }}: {{ $performedBy }} @if($actorOrganizationDisplay !== '' && !$isSendBackAction && !$isReturnBackAction) {{ __('From') }}: {{ $actorOrganizationDisplay }} @endif @if($isSendBackAction || $isReturnBackAction) {{ __('From') }}: {{ $fromUserDisplay }} → {{ __('To') }}: {{ $toUserDisplay }} @endif @if($noteText !== '') {{ $noteLabel }}: {{ $noteText }} @endif
@endforeach
@endif @else

{{ __('No workflow template is attached to this request yet.') }}

@endif
{{-- LEFT COLUMN --}}
{{-- Document Information --}}
{{ __('Document Information') }}

{{ __('Document Type') }}

{{ $requestItem->document_type_label ?: ($documentType->name ?? 'N/A') }}

{{ __('Document Title') }}

{{ trim((string) ($requestItem->document_name ?? '')) !== '' ? $requestItem->document_name : 'N/A' }}

{{ __('Document ID') }}

{{ trim((string) ($requestItem->document_code ?? '')) !== '' ? $requestItem->document_code : 'N/A' }}

{{ __('UHS Code') }}

{{ trim((string) ($requestItem->uhs_code ?? '')) !== '' ? $requestItem->uhs_code : 'N/A' }}

{{ __('Registry Document Number') }}

{{ trim((string) ($requestItem->registry_document_number ?? '')) !== '' ? $requestItem->registry_document_number : '' }}

{{ __('Finished Number') }}

{{ $hasFinishedDocumentNumberColumn && $hasFinishedDocumentNumber ? $finishedDocumentNumberValue : '' }}

{{ __('Created By') }}

{{ $requestItem->submitter_label ?: 'N/A' }}

{{ __('Description') }}

{{ trim((string) ($requestItem->description ?? '')) !== '' ? $requestItem->description : 'N/A' }}

{{ __('Created At') }}

{{ \App\Support\DateFormatter::localized($requestItem->created_at, 'F j, Y') }} {{ __('time') }} {{ \App\Support\DateFormatter::localized($requestItem->created_at, 'h:i A') }}

{{ __('Last Updated') }}

{{ \App\Support\DateFormatter::localized($requestItem->updated_at, 'F j, Y') }} {{ __('time') }} {{ \App\Support\DateFormatter::localized($requestItem->updated_at, 'h:i A') }}

@if($showSubmissionStatus)

{{ __('Submission Status') }}

@if($hasRegistrationStep) {{ __('Registration') }} @endif
@endif
@if($canUseRequirementsCard)
@if($isRegistrationOrganization)
{{ __('Required Supporting Files') }}: {{ count($supportingFiles) }}
{{ __('Please verify uploaded supporting files before forwarding.') }}
@if(!empty($documentType->required_files))
    @foreach($documentType->required_files as $reqFile) @php $fileLabel = is_array($reqFile) ? ($reqFile['label'] ?? '') : (string) $reqFile; $fileQty = is_array($reqFile) ? (int) ($reqFile['quantity'] ?? 1) : 1; @endphp @if(!empty($fileLabel))
  • {{ $fileLabel }} (Qty: {{ $fileQty }})
  • @endif @endforeach
@endif @elseif($hasPaymentSummary)
{{ __('Total Payable') }}: ${{ number_format((float) ($paymentTotal ?? 0), 2) }}
{{ __('Qty') }}: {{ number_format((float) ($paymentQuantity ?? 0), 2) }} - {{ __('Unit') }}: ${{ number_format((float) ($paymentUnitPrice ?? 0), 2) }}
@else
{{ __('Submission Checklist') }}
{{ __('Confirm this stage requirements are done before forwarding to the next step.') }}
@endif
@if($requirementsDoneForRole) {{ __('Marked Complete') }} @elseif($canMarkRequirementsDone) @endif
@endif {{-- Supporting Files --}}
{{ __('Supporting Files') }}
{{ count($supportingFiles) }}
@if(count($supportingFiles) > 0)
@foreach($supportingFiles as $file)

{{ $file['name'] }}

{{ strtoupper($file['extension'] ?: 'FILE') }} - {{ $file['size_label'] }}
@endforeach
@else

{{ __('No supporting files uploaded.') }}

@endif
{{-- Status History removed: replaced by Workflow Progress timeline --}}
{{-- RIGHT COLUMN --}}
{{-- Quick Actions --}}
{{ __('Quick Actions') }}
@php $quickActionDivider = static function (string $label, string $icon = ''): string { $iconHtml = trim($icon) !== '' ? '' : ''; return '
' . $iconHtml . e(__($label)) . '
'; }; @endphp @if($isRequesterOwner) {!! $quickActionDivider('Document Access', 'ri-eye-line') !!} @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @else @endif @endif {!! $quickActionDivider('Document Review', 'ri-chat-3-line') !!} @if($currentStepOrganizationDisabled) {!! $quickActionDivider('Document Access', 'ri-eye-line') !!} @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @endif @elseif($isWaitingForSentBackReturn) @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @if(!$canComment) {!! $quickActionDivider('Document Access', 'ri-eye-line') !!} @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @endif @endif @elseif($canStageReviewer) @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if($mustCompleteRequirementsBeforeForward && $hasRequirementsToCheck) @endif @if($returnBackBlockedReason !== '') @elseif($forwardBlockReason !== '') @else @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @elseif($canStageApprover) @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if($canApproveByPermission) @if($approveBlockReason !== '') @else @endif @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @if($canRejectByPermission) @endif @elseif($returnBackBlockedReason !== '') @if($canComment && !$sentBackTargetsRequesterOwner) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif @endif @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @endif @if(($canComment && !$sentBackTargetsRequesterOwner) || (!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) || ($mainFile && $mainFile['exists'])) {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @elseif($isReturnBackForward) @if($canComment && !$sentBackTargetsRequesterOwner) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif @endif @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @endif @if(($canComment && !$sentBackTargetsRequesterOwner) || (!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) || ($mainFile && $mainFile['exists'])) {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if($forwardBlockReason !== '') @else @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @elseif($roleCanRequestDocument && !$roleCanReviewDocument) @if(!$isRequesterOwner) {!! $quickActionDivider('Document Access', 'ri-eye-line') !!} @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @else @endif @endif @elseif($userRole === 'admin') @if($headAssignedToOfficer || $headRejectedPreviewOnly) @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Status', 'ri-information-line') !!} @endif @else @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if(($canAdminHead || $canHeadForward) && $canForwardByPermission) @if($mustCompleteRequirementsBeforeForward && $hasRequirementsToCheck) @endif @if($returnBackBlockedReason !== '') @elseif($forwardBlockReason !== '') @else @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @else @endif @endif @elseif(\App\Support\WorkflowPermission::isReviewerOfficerGroup($userRole)) @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if($canReviewer && $canForwardByPermission) @if(!$reviewerRequirementsDone && $hasRequirementsForOrganization) @endif @if($returnBackBlockedReason !== '') @elseif($forwardBlockReason !== '') @else @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @else @endif @elseif($userRole === 'vice_rector') @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if($holderCanAct && $isActionableStatus && $canForwardByPermission) @if($returnBackBlockedReason !== '') @elseif($forwardBlockReason !== '') @else @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @else @endif @elseif($userRole === 'rector' || $canApproveByPermission || $canRejectByPermission) @if($canComment) {{ __('Review Document') }} @if($reviewUnreadCount > 0) {{ $reviewUnreadCount > 99 ? '99+' : $reviewUnreadCount }} @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {!! $quickActionDivider('Workflow Actions', 'ri-route-line') !!} @endif @if($canRector) @if($canApproveByPermission) @if($approveBlockReason !== '') @else @endif @endif @if($canSendBackInUi) @if($sendBackBlockedReason !== '') @else @endif @endif @if($canRejectByPermission) @endif @else @endif @else {!! $quickActionDivider('Document Access', 'ri-eye-line') !!} @if(!empty($builderTemplateUrl) && isset($builderSubmittedData) && is_array($builderSubmittedData) && count($builderSubmittedData) > 0) {{ __('Preview Document') }} @elseif($mainFile && $mainFile['exists']) {{ __('Preview Document') }} @else @endif @endif @if($roleCanRegisterDocumentNumber) {!! $quickActionDivider('Numbering', 'ri-hashtag') !!} @if($hasRegistryDocumentNumber && $hasFinishedDocumentNumberColumn) @php $finishedNumberButtonTarget = $isLastWorkflowStageForFinishedNumber ? '#modalFinishedDocumentNumber' : '#modalFinishedDocumentNumberWarning'; @endphp @endif @endif @if($roleCanReviewDocument && (!empty($quickPrintDocumentUrl) || !empty($printReferenceUrl))) {!! $quickActionDivider('Printing', 'ri-printer-line') !!} @endif @if($roleCanReviewDocument && !empty($quickPrintDocumentUrl)) {{ __('Print Document') }} @endif @if($roleCanReviewDocument && !empty($printReferenceUrl)) {{ __('Print Document ID') }} @endif @if(!empty($publicTrackingUrl)) {!! $quickActionDivider('Tracking', 'ri-qr-code-line') !!} @endif
@endsection @push('modals') @php $transitionUrl = route('document-viewer.workflow.transition', $requestItem->id); @endphp @if($roleCanRegisterDocumentNumber) @if($hasRegistryDocumentNumber && $hasFinishedDocumentNumberColumn) @if(!$isLastWorkflowStageForFinishedNumber) @endif @endif @endif @if(!empty($publicTrackingUrl)) @endif @if($canCustomizeWorkflowStages) @php $customWorkflowPermissionType = static function (array $stage): string { $permissionType = strtolower(trim((string) ($stage['permission_type'] ?? ''))); if (in_array($permissionType, [ \App\Support\WorkflowPermission::PERMISSION_REVIEWER, \App\Support\WorkflowPermission::PERMISSION_APPROVER, ], true)) { return $permissionType; } $stepType = strtolower(trim((string) ($stage['step_type'] ?? ''))); $actorRole = \App\Support\WorkflowPermission::normalizeRole((string) ($stage['actor_role'] ?? '')); $isApproverStage = in_array($stepType, ['approval', 'vice_rector'], true) || in_array($actorRole, [ \App\Support\WorkflowPermission::ROLE_RECTOR, \App\Support\WorkflowPermission::ROLE_VICE_RECTOR, ], true); return $isApproverStage ? \App\Support\WorkflowPermission::PERMISSION_APPROVER : \App\Support\WorkflowPermission::PERMISSION_REVIEWER; }; $lockedApproverStageCount = $workflowStepsOrdered ->filter(fn ($stage) => (int) ($stage['order'] ?? 0) <= $currentStepOrder) ->filter(fn ($stage) => $customWorkflowPermissionType((array) $stage) === \App\Support\WorkflowPermission::PERMISSION_APPROVER) ->count(); @endphp @endif {{-- Modal: Forward to Next Step (Head / Admin) --}} @if($canForwardByPermission || $isReturnBackForward) @endif {{-- Modal: Send Back with Reason (Head / Admin) --}} @if($canSendBackInUi) @endif @if(\App\Support\WorkflowPermission::isReviewerOfficerGroup($userRole) || $isRegistrationOrganization) @endif @if($canAddReviewDocument && !$hideAddDocumentInNoAction) @endif {{-- Workflow action modals --}} @if(\App\Support\WorkflowPermission::isReviewerOfficerGroup($userRole) && ($canForwardByPermission || $canSendBackInUi)) @if($canForwardByPermission) @endif @if($canSendBackInUi) @endif @endif {{-- Rector modals --}} @if($canApproveByPermission || $canRejectByPermission || $canSendBackInUi) @if($canApproveByPermission) @endif @if($canSendBackInUi) @endif @if($canRejectByPermission) @endif @endif {{-- Central Organization rejection modal --}} @if($canRejectByPermission) @endif @endpush @section('script') @include('document-viewer.partials.workflow-action-undo') @include('document-viewer.partials.registry-forward-warning-script') @endsection @push('modals') {{-- workflow action modals are below --}}