diff --git a/ProcessMaker/Traits/TaskControllerIndexMethods.php b/ProcessMaker/Traits/TaskControllerIndexMethods.php index aba096c360..2ad7b59031 100644 --- a/ProcessMaker/Traits/TaskControllerIndexMethods.php +++ b/ProcessMaker/Traits/TaskControllerIndexMethods.php @@ -6,6 +6,7 @@ use Illuminate\Database\QueryException; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; use ProcessMaker\Filters\Filter; use ProcessMaker\Managers\DataManager; use ProcessMaker\Models\Process; @@ -69,7 +70,7 @@ private function applyFilters($query, $request) } $filterByFields = [ - 'process_id', + 'process_request_tokens.process_id' => 'process_id', 'process_request_tokens.user_id' => 'user_id', 'process_request_tokens.status' => 'status', 'element_id', @@ -247,60 +248,6 @@ private function applyColumnOrdering($query, $request) 'is_priority', ]; - $hasProcessRequestOrdering = false; - $hasUserOrdering = false; - foreach ($orderColumns as $column) { - $normalizedColumn = preg_replace( - '/^(process_request|processRequests)\./', - 'process_requests.', - $column - ); - - if (in_array($normalizedColumn, array_map( - fn ($name) => 'process_requests.' . $name, - $processRequestColumns - ), true) || preg_match('/^data\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/', $column)) { - $hasProcessRequestOrdering = true; - } - if ($column === 'user.name') { - $hasUserOrdering = true; - } - } - - if ($hasProcessRequestOrdering) { - $query->leftJoin( - 'process_requests', - 'process_requests.id', - '=', - 'process_request_tokens.process_request_id' - ); - } - if ($hasUserOrdering) { - $query->leftJoin( - 'users', - 'users.id', - '=', - 'process_request_tokens.user_id' - ); - } - if ($hasProcessRequestOrdering || $hasUserOrdering) { - if ($query->getQuery()->columns === null) { - $query->select('process_request_tokens.*'); - } else { - $query->select(array_map(function ($column) { - if (!is_string($column)) { - return $column; - } - - $column = ltrim($column, '.'); - - return str_contains($column, '.') - ? $column - : 'process_request_tokens.' . $column; - }, $query->getQuery()->columns)); - } - } - $hasValidOrdering = false; foreach ($orderColumns as $index => $column) { $direction = strtolower($orderDirections[$index] ?? $orderDirections[0] ?? 'asc'); @@ -312,17 +259,44 @@ private function applyColumnOrdering($query, $request) ); if ($column === 'user.name') { - $query->orderBy('users.firstname', $direction) - ->orderBy('users.lastname', $direction); + $query->orderBy( + $this->relatedOrderSubquery('users', 'firstname', 'users.id', 'process_request_tokens.user_id'), + $direction + )->orderBy( + $this->relatedOrderSubquery('users', 'lastname', 'users.id', 'process_request_tokens.user_id'), + $direction + ); $hasValidOrdering = true; } elseif (preg_match('/^data\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/', $column, $matches)) { - $query->orderBy('process_requests.data->' . str_replace('.', '->', $matches[1]), $direction); + $jsonColumn = 'data->' . str_replace('.', '->', $matches[1]); + $query->orderBy( + $this->relatedOrderSubquery( + 'process_requests', + $jsonColumn, + 'process_requests.id', + 'process_request_tokens.process_request_id' + ), + $direction + ); $hasValidOrdering = true; } elseif (in_array($normalizedColumn, array_map( fn ($name) => 'process_requests.' . $name, $processRequestColumns ), true)) { - $query->orderBy($normalizedColumn, $direction); + $columnName = substr($normalizedColumn, strlen('process_requests.')); + if ($columnName === 'id') { + $query->orderBy('process_request_tokens.process_request_id', $direction); + } else { + $query->orderBy( + $this->relatedOrderSubquery( + 'process_requests', + $columnName, + 'process_requests.id', + 'process_request_tokens.process_request_id' + ), + $direction + ); + } $hasValidOrdering = true; } elseif (in_array($column, $tokenColumns, true)) { $query->orderBy('process_request_tokens.' . $column, $direction); @@ -335,6 +309,14 @@ private function applyColumnOrdering($query, $request) } } + private function relatedOrderSubquery(string $table, string $column, string $localKey, string $foreignKey) + { + return DB::table($table) + ->select($column) + ->whereColumn($localKey, $foreignKey) + ->limit(1); + } + private function applyStatusFilter($query, $request) { $statusFilter = $request->input('statusfilter', ''); @@ -342,7 +324,7 @@ private function applyStatusFilter($query, $request) $statusFilter = array_map(function ($value) { return mb_strtoupper(trim($value)); }, explode(',', $statusFilter)); - $query->whereIn('status', $statusFilter); + $query->whereIn('process_request_tokens.status', $statusFilter); } } @@ -544,8 +526,8 @@ private function applyForCurrentUser($query, $user) } $query->where(function ($query) use ($user) { - $query->where('user_id', $user->id) - ->orWhereIn('id', $user->availableSelfServiceTasksQuery()); + $query->where('process_request_tokens.user_id', $user->id) + ->orWhereIn('process_request_tokens.id', $user->availableSelfServiceTasksQuery()); }); } diff --git a/tests/Feature/Api/TaskListNonAdminCaseTitleOrderTest.php b/tests/Feature/Api/TaskListNonAdminCaseTitleOrderTest.php new file mode 100644 index 0000000000..0093076420 --- /dev/null +++ b/tests/Feature/Api/TaskListNonAdminCaseTitleOrderTest.php @@ -0,0 +1,193 @@ +user = User::factory()->create([ + 'is_administrator' => false, + 'status' => 'ACTIVE', + ]); + $otherUser = User::factory()->create([ + 'is_administrator' => false, + 'status' => 'ACTIVE', + ]); + + $zetaRequest = ProcessRequest::factory()->create(); + $alphaRequest = ProcessRequest::factory()->create(); + $otherRequest = ProcessRequest::factory()->create(); + $zetaRequest->forceFill(['case_title' => 'Zeta mandate'])->saveQuietly(); + $alphaRequest->forceFill(['case_title' => 'Alpha mandate'])->saveQuietly(); + $otherRequest->forceFill(['case_title' => 'Omega mandate'])->saveQuietly(); + + $zetaTask = ProcessRequestToken::factory()->create([ + 'user_id' => $this->user->id, + 'process_id' => $zetaRequest->process_id, + 'process_request_id' => $zetaRequest->id, + 'element_type' => 'task', + 'status' => 'ACTIVE', + 'is_self_service' => false, + 'is_priority' => true, + 'completed_at' => null, + ]); + $alphaTask = ProcessRequestToken::factory()->create([ + 'user_id' => $this->user->id, + 'process_id' => $alphaRequest->process_id, + 'process_request_id' => $alphaRequest->id, + 'element_type' => 'task', + 'status' => 'ACTIVE', + 'is_self_service' => false, + 'is_priority' => true, + 'completed_at' => null, + ]); + ProcessRequestToken::factory()->create([ + 'user_id' => $otherUser->id, + 'process_id' => $otherRequest->process_id, + 'process_request_id' => $otherRequest->id, + 'element_type' => 'task', + 'status' => 'ACTIVE', + 'is_self_service' => false, + 'is_priority' => true, + 'completed_at' => null, + ]); + + $response = $this->apiCall('GET', '/tasks', [ + 'page' => 1, + 'include' => 'process,processRequest,processRequest.user,user', + 'pmql' => '(user_id = ' . $this->user->id . ')', + 'per_page' => 15, + 'order_by' => 'process_requests.case_title', + 'order_direction' => 'desc', + 'non_system' => true, + 'processesIManage' => 'false', + 'advanced_filter' => json_encode([ + [ + 'subject' => ['type' => 'Status'], + 'operator' => '=', + 'value' => 'In Progress', + ], + [ + 'subject' => ['type' => 'Field', 'value' => 'is_priority'], + 'operator' => '=', + 'value' => true, + ], + ]), + ]); + + $response->assertStatus(200); + + $rows = $response->json('data'); + $this->assertCount(2, $rows); + $this->assertSame( + [$zetaTask->id, $alphaTask->id], + array_column($rows, 'id') + ); + $this->assertSame( + ['Zeta mandate', 'Alpha mandate'], + array_map(fn ($row) => $row['process_request']['case_title'] ?? null, $rows) + ); + } + + public function testNonAdminCanListTasksOrderedByCaseTitleDescAndFulltextSearch() + { + $this->user = User::factory()->create([ + 'is_administrator' => false, + 'status' => 'ACTIVE', + ]); + $otherUser = User::factory()->create([ + 'is_administrator' => false, + 'status' => 'ACTIVE', + ]); + + $zetaRequest = ProcessRequest::factory()->create(); + $alphaRequest = ProcessRequest::factory()->create(); + $otherRequest = ProcessRequest::factory()->create(); + $zetaRequest->forceFill(['case_title' => 'Zeta mandate'])->saveQuietly(); + $alphaRequest->forceFill(['case_title' => 'Alpha mandate'])->saveQuietly(); + $otherRequest->forceFill(['case_title' => 'Omega mandate'])->saveQuietly(); + + $zetaTask = ProcessRequestToken::factory()->create([ + 'user_id' => $this->user->id, + 'process_id' => $zetaRequest->process_id, + 'process_request_id' => $zetaRequest->id, + 'element_name' => 'Review of the client name', + 'element_type' => 'task', + 'status' => 'ACTIVE', + 'is_self_service' => false, + 'is_priority' => true, + 'completed_at' => null, + ]); + $alphaTask = ProcessRequestToken::factory()->create([ + 'user_id' => $this->user->id, + 'process_id' => $alphaRequest->process_id, + 'process_request_id' => $alphaRequest->id, + 'element_type' => 'task', + 'status' => 'ACTIVE', + 'is_self_service' => false, + 'is_priority' => true, + 'completed_at' => null, + ]); + ProcessRequestToken::factory()->create([ + 'user_id' => $otherUser->id, + 'process_id' => $otherRequest->process_id, + 'process_request_id' => $otherRequest->id, + 'element_type' => 'task', + 'status' => 'ACTIVE', + 'is_self_service' => false, + 'is_priority' => true, + 'completed_at' => null, + ]); + + $response = $this->apiCall('GET', '/tasks', [ + 'page' => 1, + 'include' => 'process,processRequest,processRequest.user,user', + 'pmql' => '(user_id = ' . $this->user->id . ') AND (fulltext LIKE "%Review of the client name%") ', + 'per_page' => 15, + 'order_by' => 'process_requests.case_title', + 'order_direction' => 'desc', + 'non_system' => true, + 'processesIManage' => 'false', + 'advanced_filter' => json_encode([ + [ + 'subject' => ['type' => 'Status'], + 'operator' => '=', + 'value' => 'In Progress', + ], + [ + 'subject' => ['type' => 'Field', 'value' => 'is_priority'], + 'operator' => '=', + 'value' => true, + ], + ]), + ]); + + $response->assertStatus(200); + + $rows = $response->json('data'); + $this->assertCount(1, $rows); + $this->assertSame( + [$zetaTask->id], + array_column($rows, 'id') + ); + $this->assertSame( + ['Zeta mandate'], + array_map(fn ($row) => $row['process_request']['case_title'] ?? null, $rows) + ); + } +}