Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ProcessMaker/Http/Controllers/Api/TaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public function index(Request $request, $getTotal = false, User $user = null)

// Apply process manager filter BEFORE PMQL to avoid conflicts with is_self_service filtering
if ($request->input('processesIManage') === 'true') {
$this->applyProcessManager($query, $user);
$this->applyProcessManager($query, $user, $request);
} else {
$this->applyForCurrentUser($query, $user);
}
Expand Down
97 changes: 72 additions & 25 deletions ProcessMaker/Traits/TaskControllerIndexMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

trait TaskControllerIndexMethods
{
private const SELF_SERVICE_STATUS = 'self service';

private function indexBaseQuery($request)
{
// Parse the includes parameter
Expand Down Expand Up @@ -291,6 +293,19 @@ private function applyStatusFilter($query, $request)
}
}

private function requestHasStatusFilter($request): bool
{
if ($request->filled('status') || $request->filled('statusfilter')) {
return true;
}

if ($this->advancedFilterHasStatus($request)) {
return true;
}

return preg_match('/(?:^|[\s(])status\s*(?:=|!=|<>)/i', $request->input('pmql', '')) === 1;
}

private function applyPmql($query, $request, $user)
{
$pmql = $request->input('pmql', '');
Expand Down Expand Up @@ -327,7 +342,7 @@ private function advancedFilterHasSelfServiceStatus($request): bool
foreach ($this->getAdvancedFilterArray($request) as $filter) {
$values = (array) ($filter['value'] ?? []);
foreach ($values as $v) {
if (mb_strtolower($v) === 'self service') {
if (mb_strtolower($v) === self::SELF_SERVICE_STATUS) {
return true;
}
}
Expand Down Expand Up @@ -384,34 +399,62 @@ private function applyAdvancedFilter($query, $request)
// If processesIManage is active, handle "Self Service" status filter specially
if ($isProcessManager && is_array($filterArray)) {
$hasSelfServiceFilter = false;
$filteredArray = [];
$nonStatusFilters = [];
$statusFilters = [];

foreach ($filterArray as $filter) {
// Check if this is a "Self Service" status filter
if (isset($filter['subject']['type']) &&
$filter['subject']['type'] === 'Status' &&
isset($filter['value']) &&
mb_strtolower($filter['value']) === 'self service') {
$hasSelfServiceFilter = true;
// Don't add this filter to the array - we'll handle it manually
$isStatusFilter = isset($filter['subject']['type']) && $filter['subject']['type'] === 'Status';
if (!$isStatusFilter) {
$nonStatusFilters[] = $filter;
continue;
}
$filteredArray[] = $filter;

$values = is_array($filter['value']) ? $filter['value'] : [$filter['value']];
$hasSelfServiceValue = in_array(
self::SELF_SERVICE_STATUS,
array_map(fn ($value) => is_string($value) ? mb_strtolower($value) : $value, $values),
true
);

if ($hasSelfServiceValue) {
$hasSelfServiceFilter = true;
$values = array_values(array_filter($values, function ($value) {
return !is_string($value) || mb_strtolower($value) !== self::SELF_SERVICE_STATUS;
}));

if (empty($values)) {
continue;
}

$filter['value'] = is_array($filter['value']) ? $values : $values[0];
}

$statusFilters[] = $filter;
}

// Apply the filtered advanced_filter (without Self Service)
if (!empty($filteredArray)) {
Filter::filter($query, $filteredArray);
if (!$hasSelfServiceFilter) {
Filter::filter($query, $filterArray);

return;
}

// Manually apply the Self Service filter for process managers
if ($hasSelfServiceFilter) {
$selfServiceTaskIds = ProcessRequestToken::select(['id'])
->whereIn('process_id', $processManagerIds)
->where('is_self_service', 1)
->whereNull('user_id')
->where('status', 'ACTIVE');
if (!empty($nonStatusFilters)) {
Filter::filter($query, $nonStatusFilters);
}

// Manually apply the Self Service filter for process managers
$selfServiceTaskIds = ProcessRequestToken::select(['id'])
->whereIn('process_id', $processManagerIds)
->where('is_self_service', 1)
->whereNull('user_id')
->where('status', 'ACTIVE');

if (!empty($statusFilters)) {
$query->where(function ($query) use ($statusFilters, $selfServiceTaskIds) {
Filter::filter($query, $statusFilters);
$query->orWhereIn('process_request_tokens.id', $selfServiceTaskIds);
});
} else {
$query->whereIn('process_request_tokens.id', $selfServiceTaskIds);
}
} else {
Expand Down Expand Up @@ -453,7 +496,7 @@ private function applyForCurrentUser($query, $user)
});
}

public function applyProcessManager($query, $user)
public function applyProcessManager($query, $user, $request)
{
$ids = Process::select(['id'])
->where(function ($subQuery) use ($user) {
Expand All @@ -472,17 +515,21 @@ public function applyProcessManager($query, $user)
return;
}

// Show tasks from processes the user manages that are ACTIVE
// Show tasks from processes the user manages. Default to ACTIVE unless the request
// already has an explicit status filter that will be applied later.
// OR show self-service tasks from those processes
// Store the process IDs in the query so we can use them later to add self-service tasks
// We'll add self-service tasks after PMQL is applied to avoid the is_self_service = 0 filter
$query->getQuery()->processManagerIds = $ids;

// Apply condition for regular tasks from managed processes
// Self-service tasks will be added after PMQL to avoid conflicts
$query->where(function ($query) use ($ids) {
$query->whereIn('process_request_tokens.process_id', $ids)
->where('process_request_tokens.status', 'ACTIVE');
$query->where(function ($query) use ($ids, $request) {
$query->whereIn('process_request_tokens.process_id', $ids);

if (!$this->requestHasStatusFilter($request)) {
$query->where('process_request_tokens.status', 'ACTIVE');
}
});
}

Expand Down
103 changes: 103 additions & 0 deletions tests/Feature/Api/TasksTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,109 @@ public function testAdvancedStatusFilterOverridesPmqlStatus()
$this->assertNotContains($activeTask->id, $returnedIds);
}

public function testProcessManagerAdvancedStatusFilterCanReturnCompletedTasks()
{
$manager = User::factory()->create();
$process = Process::factory()->create([
'properties' => ['manager_id' => $manager->id],
]);
$unmanagedProcess = Process::factory()->create();
$request = ProcessRequest::factory()->create(['process_id' => $process->id]);

$activeTask = ProcessRequestToken::factory()->create([
'status' => 'ACTIVE',
'element_type' => 'task',
'process_id' => $process->id,
'process_request_id' => $request->id,
'is_self_service' => 0,
]);

$completedTask = ProcessRequestToken::factory()->create([
'status' => 'CLOSED',
'element_type' => 'task',
'process_id' => $process->id,
'process_request_id' => $request->id,
'is_self_service' => 0,
]);

$unmanagedCompletedTask = ProcessRequestToken::factory()->create([
'status' => 'CLOSED',
'element_type' => 'task',
'process_id' => $unmanagedProcess->id,
'is_self_service' => 0,
]);

$statusFilter = json_encode([
[
'subject' => ['type' => 'Status'],
'operator' => '=',
'value' => 'Completed',
],
]);

$response = $this->actingAs($manager, 'api')->get(route('api.tasks.index', [
'processesIManage' => 'true',
'advanced_filter' => $statusFilter,
]));

$response->assertStatus(200);
$returnedIds = collect($response->json('data'))->pluck('id')->toArray();

$this->assertContains($completedTask->id, $returnedIds);
$this->assertNotContains($activeTask->id, $returnedIds);
$this->assertNotContains($unmanagedCompletedTask->id, $returnedIds);
}

public function testProcessManagerAdvancedStatusFilterCanReturnInProgressCompletedAndSelfServiceTasks()
{
$manager = User::factory()->create();
$process = Process::factory()->create([
'properties' => ['manager_id' => $manager->id],
]);

$activeTask = ProcessRequestToken::factory()->create([
'status' => 'ACTIVE',
'element_type' => 'task',
'process_id' => $process->id,
'is_self_service' => 0,
]);

$completedTask = ProcessRequestToken::factory()->create([
'status' => 'CLOSED',
'element_type' => 'task',
'process_id' => $process->id,
'is_self_service' => 0,
]);

$selfServiceTask = ProcessRequestToken::factory()->create([
'status' => 'ACTIVE',
'element_type' => 'task',
'process_id' => $process->id,
'user_id' => null,
'is_self_service' => 1,
]);

$statusFilter = json_encode([
[
'subject' => ['type' => 'Status'],
'operator' => '=',
'value' => ['In Progress', 'Completed', 'Self Service'],
],
]);

$response = $this->actingAs($manager, 'api')->get(route('api.tasks.index', [
'processesIManage' => 'true',
'advanced_filter' => $statusFilter,
]));

$response->assertStatus(200);
$returnedIds = collect($response->json('data'))->pluck('id')->toArray();

$this->assertContains($activeTask->id, $returnedIds);
$this->assertContains($completedTask->id, $returnedIds);
$this->assertContains($selfServiceTask->id, $returnedIds);
}

public function testPmqlStatusPreservedWhenNoAdvancedStatusFilter()
{
$user = User::factory()->create(['is_administrator' => true]);
Expand Down
Loading