From 4568f9ae9ac8ab4bd1dd969f97a3950e87ff1c21 Mon Sep 17 00:00:00 2001 From: Roly Gutierrez Date: Wed, 24 Dec 2025 15:19:28 -0400 Subject: [PATCH 1/5] FOUR-28520 Reassign > The imported process does not list all reassign user list ## Description When Assignment type=process variable and variable Name (groups) is set with a correct value then when reassign button is pressed, only the process manager is listed. ## Related tickets https://processmaker.atlassian.net/browse/FOUR-28520 --- resources/js/common/reassignMixin.js | 44 +++++++++---------- resources/js/tasks/api/index.js | 41 ++++++++++++++++- .../js/tasks/components/TasksPreview.vue | 3 +- .../taskPreview/TaskPreviewAssignment.vue | 41 ++++++++++++----- routes/api.php | 6 +-- 5 files changed, 94 insertions(+), 41 deletions(-) diff --git a/resources/js/common/reassignMixin.js b/resources/js/common/reassignMixin.js index fa1a5c81b5..1b6af9b5f9 100644 --- a/resources/js/common/reassignMixin.js +++ b/resources/js/common/reassignMixin.js @@ -1,3 +1,5 @@ +import { getReassignUsers as getReassignUsersApi } from "../tasks/api"; + export default { data() { return { @@ -21,32 +23,28 @@ export default { this.allowReassignment = response.data[this.task.id]; }); }, - getReassignUsers(filter = null) { - const params = { }; - if (filter) { - params.filter = filter; - } - if (this.task?.id) { - params.assignable_for_task_id = this.task.id; - // The variables are needed to calculate the rule expression. - if (this?.formData) { - params.form_data = this.formData; - delete params.form_data._user; - delete params.form_data._request; - delete params.form_data._process; - } - } + async getReassignUsers(filter = null) { + try { + const response = await getReassignUsersApi( + filter, + this.task?.id, + this?.formData, + this.currentTaskUserId + ); - ProcessMaker.apiClient.post('users_task_count', params ).then(response => { this.reassignUsers = []; - response.data.data.forEach((user) => { - this.reassignUsers.push({ - text: user.fullname, - value: user.id, - active_tasks_count: user.active_tasks_count + if (response?.data) { + response.data.forEach((user) => { + this.reassignUsers.push({ + text: user.fullname, + value: user.id, + active_tasks_count: user.active_tasks_count + }); }); - }); - }); + } + } catch (error) { + console.error('Error loading reassign users:', error); + } }, onReassignInput: _.debounce(function (filter) { this.getReassignUsers(filter); diff --git a/resources/js/tasks/api/index.js b/resources/js/tasks/api/index.js index 0b55e7e35f..c89c360bca 100644 --- a/resources/js/tasks/api/index.js +++ b/resources/js/tasks/api/index.js @@ -1,12 +1,49 @@ import { getApi } from "../variables/index"; -export const getReassignUsers = async (filter = null, taskId = null, currentTaskUserId = null) => { +/** + * Get reassign users using POST with form_data (for rule expression evaluation) + * This replaces the obsolete GET method with the advanced POST logic from reassignMixin + * + * @param {string|null} filter - Filter string to search users + * @param {number|null} taskId - Task ID to get assignable users for + * @param {Object|null} formData - Form data needed to calculate rule expressions + * @param {number|null} currentTaskUserId - User ID to exclude from results (matches: task?.user_id ?? task?.user?.id) + * @returns {Promise} Response data with users array + */ +export const getReassignUsers = async ( + filter = null, + taskId = null, + formData = null, + currentTaskUserId = null +) => { const api = getApi(); - const response = await api.get("users_task_count", { params: { filter, assignable_for_task_id: taskId, include_current_user: true } }); + const params = {}; + + if (filter) { + params.filter = filter; + } + + if (taskId) { + params.assignable_for_task_id = taskId; + + // The variables are needed to calculate the rule expression. + if (formData) { + params.form_data = { ...formData }; + // Remove internal variables + delete params.form_data._user; + delete params.form_data._request; + delete params.form_data._process; + } + } + + const response = await api.post("users_task_count", params); const data = response.data; + + // Filter out current user to prevent self-reassignment (matches mixin logic) if (currentTaskUserId && Array.isArray(data?.data)) { data.data = data.data.filter((user) => user.id !== currentTaskUserId); } + return data; }; diff --git a/resources/js/tasks/components/TasksPreview.vue b/resources/js/tasks/components/TasksPreview.vue index e9b27459df..8f3ec7c85f 100644 --- a/resources/js/tasks/components/TasksPreview.vue +++ b/resources/js/tasks/components/TasksPreview.vue @@ -201,6 +201,8 @@ @@ -413,7 +415,6 @@ export default { }, openReassignment() { this.showReassignment = !this.showReassignment; - this.getReassignUsers(); }, getTaskDefinitionForReassignmentPermission() { ProcessMaker.apiClient diff --git a/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue b/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue index 52f066c087..dab085918e 100644 --- a/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue +++ b/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue @@ -67,6 +67,14 @@ const props = defineProps({ type: Object, required: true, }, + formData: { + type: Object, + default: null, + }, + currentTaskUserId: { + type: Number, + default: null, + }, }); const emit = defineEmits(["on-reassign-user"]); @@ -80,18 +88,29 @@ const disabledAssign = ref(false); // Computed properties const disabled = computed(() => !selectedUser.value || !comments.value?.trim()); -// Load the reassign users +// Load the reassign users using the centralized function with form_data const loadReassignUsers = async (filter) => { - const response = await getReassignUsers(filter, props.task.id, props.task.user_id); - - reassignUsers.value = []; - response.data.forEach((user) => { - reassignUsers.value.push({ - text: user.fullname, - value: user.id, - active_tasks_count: user.active_tasks_count, - }); - }); + try { + const response = await getReassignUsers( + filter, + props.task?.id, + props.formData, + props.currentTaskUserId + ); + + reassignUsers.value = []; + if (response?.data) { + response.data.forEach((user) => { + reassignUsers.value.push({ + text: user.fullname, + value: user.id, + active_tasks_count: user.active_tasks_count, + }); + }); + } + } catch (error) { + console.error('Error loading reassign users:', error); + } }; /** diff --git a/routes/api.php b/routes/api.php index 2b3ffc38bb..8955a4deeb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -60,10 +60,8 @@ Route::delete('users/{user}', [UserController::class, 'destroy'])->name('users.destroy')->middleware('can:delete-users'); Route::put('password/change', [ChangePasswordController::class, 'update'])->name('password.update'); Route::put('users/update_language', [UserController::class, 'updateLanguage'])->name('users.updateLanguage'); - Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count') - ->middleware('can:view-users'); - Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post') - ->middleware('can:view-users'); + Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count'); + Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post'); // User Groups Route::put('users/{user}/groups', [UserController::class, 'updateGroups'])->name('users.groups.update')->middleware('can:edit-users'); From 854e16cf61b82cb7be951990eb5d50ba8305f4df Mon Sep 17 00:00:00 2001 From: Roly Gutierrez Date: Tue, 6 Jan 2026 17:41:22 -0400 Subject: [PATCH 2/5] FOUR-28520 Added getUsersFromProcessVariable() to ProcessRequestToken to extract user IDs from form data (assignedUsers/assignedGroups variables). Uses Process::getConsolidatedUsers() to get users from groups recursively. Validates array inputs and filters invalid IDs. Includes PHPUnit tests. --- .../Http/Controllers/Api/UserController.php | 3 + ProcessMaker/Models/ProcessRequestToken.php | 55 +++ resources/js/common/reassignMixin.js | 2 +- .../taskPreview/TaskPreviewAssignment.vue | 1 + tests/Model/ProcessRequestTokenTest.php | 380 ++++++++++++++++++ 5 files changed, 440 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Http/Controllers/Api/UserController.php b/ProcessMaker/Http/Controllers/Api/UserController.php index c1454f5589..3ccc621032 100644 --- a/ProcessMaker/Http/Controllers/Api/UserController.php +++ b/ProcessMaker/Http/Controllers/Api/UserController.php @@ -260,6 +260,9 @@ public function getUsersTaskCount(Request $request) if ($assignmentRule === 'rule_expression' && $request->has('form_data')) { $include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data')); } + if ($assignmentRule === 'process_variable' && $request->has('form_data')) { + $include_ids = $processRequestToken->getUsersFromProcessVariable($request->input('form_data')); + } } if (!empty($include_ids)) { diff --git a/ProcessMaker/Models/ProcessRequestToken.php b/ProcessMaker/Models/ProcessRequestToken.php index 06b9251607..f930869e3e 100644 --- a/ProcessMaker/Models/ProcessRequestToken.php +++ b/ProcessMaker/Models/ProcessRequestToken.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Models; +use AWS\CRT\Log as CRTLog; use Carbon\Carbon; use DB; use DOMXPath; @@ -991,6 +992,60 @@ public function getAssignmentRule() return $assignment; } + /** + * Get user IDs from process variables for task assignment. + * + * Extracts user IDs and group IDs from form data based on the activity's + * assignedUsers and assignedGroups properties. Retrieves all users from + * specified groups (including subgroups recursively) and combines them + * with directly assigned users. + * + * Used when assignment rule is 'process_variable'. + * + * @param array $form_data Form data containing process variable values. + * Keys must match activity's assignedUsers and + * assignedGroups properties. Values must be arrays. + * + * @return array Unique numeric user IDs (direct users + users from groups). + */ + public function getUsersFromProcessVariable(array $form_data) + { + $activity = $this->getBpmnDefinition()->getBpmnElementInstance(); + $assignedUsers = $activity->getProperty('assignedUsers', null); + $assignedGroups = $activity->getProperty('assignedGroups', null); + + $usersIds = []; + $groupsIds = []; + + // Validate and get user IDs from form_data + if ($assignedUsers && isset($form_data[$assignedUsers]) && is_array($form_data[$assignedUsers])) { + $usersIds = $form_data[$assignedUsers]; + } + + // Validate and get group IDs from form_data + if ($assignedGroups && isset($form_data[$assignedGroups]) && is_array($form_data[$assignedGroups])) { + $groupsIds = $form_data[$assignedGroups]; + } + + // Get users from groups using the Process model method + $usersFromGroups = []; + if (!empty($groupsIds) && $this->process) { + // Use the getConsolidatedUsers method from the Process model + // This method gets users from groups including subgroups recursively + $this->process->getConsolidatedUsers($groupsIds, $usersFromGroups); + } + + // Combine direct users with users from groups + $allUserIds = array_unique(array_merge($usersIds, $usersFromGroups)); + + // Convert to numeric array and filter valid values + $allUserIds = array_values(array_filter($allUserIds, function($id) { + return !empty($id) && is_numeric($id) && $id > 0; + })); + + return $allUserIds; + } + /** * Get the assignees for the token. * diff --git a/resources/js/common/reassignMixin.js b/resources/js/common/reassignMixin.js index 1b6af9b5f9..0e6379dcb9 100644 --- a/resources/js/common/reassignMixin.js +++ b/resources/js/common/reassignMixin.js @@ -28,7 +28,7 @@ export default { const response = await getReassignUsersApi( filter, this.task?.id, - this?.formData, + this.task?.request_data, this.currentTaskUserId ); diff --git a/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue b/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue index dab085918e..dd911e30b6 100644 --- a/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue +++ b/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue @@ -91,6 +91,7 @@ const disabled = computed(() => !selectedUser.value || !comments.value?.trim()); // Load the reassign users using the centralized function with form_data const loadReassignUsers = async (filter) => { try { + console.log('reassignUsers11', JSON.stringify(props.formData)); const response = await getReassignUsers( filter, props.task?.id, diff --git a/tests/Model/ProcessRequestTokenTest.php b/tests/Model/ProcessRequestTokenTest.php index ff8388a5fc..0d35ff1afb 100644 --- a/tests/Model/ProcessRequestTokenTest.php +++ b/tests/Model/ProcessRequestTokenTest.php @@ -3,7 +3,13 @@ namespace Tests\Model; use DOMXPath; +use Mockery; +use ProcessMaker\Models\Group; +use ProcessMaker\Models\GroupMember; +use ProcessMaker\Models\Process; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken; +use ProcessMaker\Models\User; use ProcessMaker\Nayra\Storage\BpmnDocument; use stdClass; use Tests\TestCase; @@ -39,4 +45,378 @@ public function testSetStagePropertiesInRecord() $this->assertEquals(7, $token->stage_id); $this->assertEquals('Review', $token->stage_name); } + + /** + * Test getUsersFromProcessVariable with direct users only + */ + public function testGetUsersFromProcessVariableWithDirectUsers() + { + // Create process and token + $process = Process::factory()->create(); + $request = ProcessRequest::factory()->create(['process_id' => $process->id]); + + // Create users + $user1 = User::factory()->create(['status' => 'ACTIVE']); + $user2 = User::factory()->create(['status' => 'ACTIVE']); + + // Mock the BPMN definition and activity + $activity = $this->createMock(\ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface::class); + $activity->method('getProperty') + ->willReturnCallback(function ($key, $default) { + if ($key === 'assignedUsers') { + return 'assigned_users_var'; + } + if ($key === 'assignedGroups') { + return null; + } + return $default; + }); + + $bpmnDefinition = $this->createMock(\ProcessMaker\Nayra\Storage\BpmnElement::class); + $bpmnDefinition->method('getBpmnElementInstance') + ->willReturn($activity); + + $token = $this->getMockBuilder(ProcessRequestToken::class) + ->onlyMethods(['getBpmnDefinition']) + ->getMock(); + + $token->process_id = $process->id; + $token->process_request_id = $request->id; + $token->element_id = 'task_1'; + $token->process = $process; + + $token->expects($this->atLeastOnce()) + ->method('getBpmnDefinition') + ->willReturn($bpmnDefinition); + + // Form data with direct users + $formData = [ + 'assigned_users_var' => [$user1->id, $user2->id], + ]; + + // Act + $result = $token->getUsersFromProcessVariable($formData); + + // Assert + $this->assertIsArray($result); + $this->assertCount(2, $result); + $this->assertContains($user1->id, $result); + $this->assertContains($user2->id, $result); + } + + /** + * Test getUsersFromProcessVariable with groups only + */ + public function testGetUsersFromProcessVariableWithGroups() + { + // Create process and token + $process = Process::factory()->create(); + $request = ProcessRequest::factory()->create(['process_id' => $process->id]); + + // Create groups and users + $group1 = Group::factory()->create(['status' => 'ACTIVE']); + $group2 = Group::factory()->create(['status' => 'ACTIVE']); + $user1 = User::factory()->create(['status' => 'ACTIVE']); + $user2 = User::factory()->create(['status' => 'ACTIVE']); + $user3 = User::factory()->create(['status' => 'ACTIVE']); + + // Add users to groups + GroupMember::factory()->create([ + 'group_id' => $group1->id, + 'member_id' => $user1->id, + 'member_type' => User::class, + ]); + GroupMember::factory()->create([ + 'group_id' => $group1->id, + 'member_id' => $user2->id, + 'member_type' => User::class, + ]); + GroupMember::factory()->create([ + 'group_id' => $group2->id, + 'member_id' => $user3->id, + 'member_type' => User::class, + ]); + + // Mock the BPMN definition and activity + $activity = $this->createMock(\ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface::class); + $activity->method('getProperty') + ->willReturnCallback(function ($key, $default) { + if ($key === 'assignedUsers') { + return null; + } + if ($key === 'assignedGroups') { + return 'assigned_groups_var'; + } + return $default; + }); + + $bpmnDefinition = $this->createMock(\ProcessMaker\Nayra\Storage\BpmnElement::class); + $bpmnDefinition->method('getBpmnElementInstance') + ->willReturn($activity); + + $token = $this->getMockBuilder(ProcessRequestToken::class) + ->onlyMethods(['getBpmnDefinition']) + ->getMock(); + + $token->process_id = $process->id; + $token->process_request_id = $request->id; + $token->element_id = 'task_1'; + $token->process = $process; + + $token->expects($this->atLeastOnce()) + ->method('getBpmnDefinition') + ->willReturn($bpmnDefinition); + + // Form data with groups + $formData = [ + 'assigned_groups_var' => [$group1->id, $group2->id], + ]; + + // Act + $result = $token->getUsersFromProcessVariable($formData); + + // Assert + $this->assertIsArray($result); + $this->assertCount(3, $result); + $this->assertContains($user1->id, $result); + $this->assertContains($user2->id, $result); + $this->assertContains($user3->id, $result); + } + + /** + * Test getUsersFromProcessVariable with users and groups combined + */ + public function testGetUsersFromProcessVariableWithUsersAndGroups() + { + // Create process and token + $process = Process::factory()->create(); + $request = ProcessRequest::factory()->create(['process_id' => $process->id]); + + // Create users + $directUser = User::factory()->create(['status' => 'ACTIVE']); + + // Create group with users + $group = Group::factory()->create(['status' => 'ACTIVE']); + $groupUser = User::factory()->create(['status' => 'ACTIVE']); + + GroupMember::factory()->create([ + 'group_id' => $group->id, + 'member_id' => $groupUser->id, + 'member_type' => User::class, + ]); + + // Mock the BPMN definition and activity + $activity = $this->createMock(\ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface::class); + $activity->method('getProperty') + ->willReturnCallback(function ($key, $default) { + if ($key === 'assignedUsers') { + return 'assigned_users_var'; + } + if ($key === 'assignedGroups') { + return 'assigned_groups_var'; + } + return $default; + }); + + $bpmnDefinition = $this->createMock(\ProcessMaker\Nayra\Storage\BpmnElement::class); + $bpmnDefinition->method('getBpmnElementInstance') + ->willReturn($activity); + + $token = $this->getMockBuilder(ProcessRequestToken::class) + ->onlyMethods(['getBpmnDefinition']) + ->getMock(); + + $token->process_id = $process->id; + $token->process_request_id = $request->id; + $token->element_id = 'task_1'; + $token->process = $process; + + $token->expects($this->atLeastOnce()) + ->method('getBpmnDefinition') + ->willReturn($bpmnDefinition); + + // Form data with both users and groups + $formData = [ + 'assigned_users_var' => [$directUser->id], + 'assigned_groups_var' => [$group->id], + ]; + + // Act + $result = $token->getUsersFromProcessVariable($formData); + + // Assert + $this->assertIsArray($result); + $this->assertCount(2, $result); + $this->assertContains($directUser->id, $result); + $this->assertContains($groupUser->id, $result); + } + + /** + * Test getUsersFromProcessVariable with empty form data + */ + public function testGetUsersFromProcessVariableWithEmptyFormData() + { + // Create process and token + $process = Process::factory()->create(); + $request = ProcessRequest::factory()->create(['process_id' => $process->id]); + + // Mock the BPMN definition and activity + $activity = $this->createMock(\ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface::class); + $activity->method('getProperty') + ->willReturnCallback(function ($key, $default) { + if ($key === 'assignedUsers') { + return 'assigned_users_var'; + } + if ($key === 'assignedGroups') { + return 'assigned_groups_var'; + } + return $default; + }); + + $bpmnDefinition = $this->createMock(\ProcessMaker\Nayra\Storage\BpmnElement::class); + $bpmnDefinition->method('getBpmnElementInstance') + ->willReturn($activity); + + $token = $this->getMockBuilder(ProcessRequestToken::class) + ->onlyMethods(['getBpmnDefinition']) + ->getMock(); + + $token->process_id = $process->id; + $token->process_request_id = $request->id; + $token->element_id = 'task_1'; + $token->process = $process; + + $token->expects($this->atLeastOnce()) + ->method('getBpmnDefinition') + ->willReturn($bpmnDefinition); + + // Empty form data + $formData = []; + + // Act + $result = $token->getUsersFromProcessVariable($formData); + + // Assert + $this->assertIsArray($result); + $this->assertEmpty($result); + } + + /** + * Test getUsersFromProcessVariable with non-array values (should be ignored) + */ + public function testGetUsersFromProcessVariableWithNonArrayValues() + { + // Create process and token + $process = Process::factory()->create(); + $request = ProcessRequest::factory()->create(['process_id' => $process->id]); + + // Mock the BPMN definition and activity + $activity = $this->createMock(\ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface::class); + $activity->method('getProperty') + ->willReturnCallback(function ($key, $default) { + if ($key === 'assignedUsers') { + return 'assigned_users_var'; + } + if ($key === 'assignedGroups') { + return 'assigned_groups_var'; + } + return $default; + }); + + $bpmnDefinition = $this->createMock(\ProcessMaker\Nayra\Storage\BpmnElement::class); + $bpmnDefinition->method('getBpmnElementInstance') + ->willReturn($activity); + + $token = $this->getMockBuilder(ProcessRequestToken::class) + ->onlyMethods(['getBpmnDefinition']) + ->getMock(); + + $token->process_id = $process->id; + $token->process_request_id = $request->id; + $token->element_id = 'task_1'; + $token->process = $process; + + $token->expects($this->atLeastOnce()) + ->method('getBpmnDefinition') + ->willReturn($bpmnDefinition); + + // Form data with non-array values + $formData = [ + 'assigned_users_var' => 'not_an_array', + 'assigned_groups_var' => 123, + ]; + + // Act + $result = $token->getUsersFromProcessVariable($formData); + + // Assert - should return empty array since values are not arrays + $this->assertIsArray($result); + $this->assertEmpty($result); + } + + /** + * Test getUsersFromProcessVariable filters invalid user IDs + */ + public function testGetUsersFromProcessVariableFiltersInvalidIds() + { + // Create process and token + $process = Process::factory()->create(); + $request = ProcessRequest::factory()->create(['process_id' => $process->id]); + + // Create valid user + $validUser = User::factory()->create(['status' => 'ACTIVE']); + + // Mock the BPMN definition and activity + $activity = $this->createMock(\ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface::class); + $activity->method('getProperty') + ->willReturnCallback(function ($key, $default) { + if ($key === 'assignedUsers') { + return 'assigned_users_var'; + } + if ($key === 'assignedGroups') { + return null; + } + return $default; + }); + + $bpmnDefinition = $this->createMock(\ProcessMaker\Nayra\Storage\BpmnElement::class); + $bpmnDefinition->method('getBpmnElementInstance') + ->willReturn($activity); + + $token = $this->getMockBuilder(ProcessRequestToken::class) + ->onlyMethods(['getBpmnDefinition']) + ->getMock(); + + $token->process_id = $process->id; + $token->process_request_id = $request->id; + $token->element_id = 'task_1'; + $token->process = $process; + + $token->expects($this->atLeastOnce()) + ->method('getBpmnDefinition') + ->willReturn($bpmnDefinition); + + // Form data with valid and invalid IDs + $formData = [ + 'assigned_users_var' => [ + $validUser->id, + null, + '', + 0, + -1, + 'invalid_string', + ], + ]; + + // Act + $result = $token->getUsersFromProcessVariable($formData); + + // Assert - should only contain valid user ID + $this->assertIsArray($result); + $this->assertCount(1, $result); + $this->assertContains($validUser->id, $result); + $this->assertNotContains(null, $result); + $this->assertNotContains(0, $result); + $this->assertNotContains(-1, $result); + } } From 509b85514a808dea4daec33d7bd1e9aefcf0f9e9 Mon Sep 17 00:00:00 2001 From: Roly Gutierrez Date: Tue, 6 Jan 2026 17:46:52 -0400 Subject: [PATCH 3/5] FOUR-28520 Fix observations. --- .../js/tasks/components/taskPreview/TaskPreviewAssignment.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue b/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue index dd911e30b6..dab085918e 100644 --- a/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue +++ b/resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue @@ -91,7 +91,6 @@ const disabled = computed(() => !selectedUser.value || !comments.value?.trim()); // Load the reassign users using the centralized function with form_data const loadReassignUsers = async (filter) => { try { - console.log('reassignUsers11', JSON.stringify(props.formData)); const response = await getReassignUsers( filter, props.task?.id, From 06cf51e8cf99c261be434692b13f9a1a931faeed Mon Sep 17 00:00:00 2001 From: Roly Gutierrez Date: Tue, 6 Jan 2026 17:53:26 -0400 Subject: [PATCH 4/5] FOUR-28520 Fix observations. --- ProcessMaker/Models/ProcessRequestToken.php | 5 ++-- tests/Model/ProcessRequestTokenTest.php | 33 +++++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/ProcessMaker/Models/ProcessRequestToken.php b/ProcessMaker/Models/ProcessRequestToken.php index f930869e3e..a32cf13dab 100644 --- a/ProcessMaker/Models/ProcessRequestToken.php +++ b/ProcessMaker/Models/ProcessRequestToken.php @@ -2,7 +2,6 @@ namespace ProcessMaker\Models; -use AWS\CRT\Log as CRTLog; use Carbon\Carbon; use DB; use DOMXPath; @@ -1037,9 +1036,9 @@ public function getUsersFromProcessVariable(array $form_data) // Combine direct users with users from groups $allUserIds = array_unique(array_merge($usersIds, $usersFromGroups)); - + // Convert to numeric array and filter valid values - $allUserIds = array_values(array_filter($allUserIds, function($id) { + $allUserIds = array_values(array_filter($allUserIds, function ($id) { return !empty($id) && is_numeric($id) && $id > 0; })); diff --git a/tests/Model/ProcessRequestTokenTest.php b/tests/Model/ProcessRequestTokenTest.php index 0d35ff1afb..210dd4aaa5 100644 --- a/tests/Model/ProcessRequestTokenTest.php +++ b/tests/Model/ProcessRequestTokenTest.php @@ -2,15 +2,12 @@ namespace Tests\Model; -use DOMXPath; -use Mockery; use ProcessMaker\Models\Group; use ProcessMaker\Models\GroupMember; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken; use ProcessMaker\Models\User; -use ProcessMaker\Nayra\Storage\BpmnDocument; use stdClass; use Tests\TestCase; @@ -69,6 +66,7 @@ public function testGetUsersFromProcessVariableWithDirectUsers() if ($key === 'assignedGroups') { return null; } + return $default; }); @@ -79,12 +77,12 @@ public function testGetUsersFromProcessVariableWithDirectUsers() $token = $this->getMockBuilder(ProcessRequestToken::class) ->onlyMethods(['getBpmnDefinition']) ->getMock(); - + $token->process_id = $process->id; $token->process_request_id = $request->id; $token->element_id = 'task_1'; $token->process = $process; - + $token->expects($this->atLeastOnce()) ->method('getBpmnDefinition') ->willReturn($bpmnDefinition); @@ -147,6 +145,7 @@ public function testGetUsersFromProcessVariableWithGroups() if ($key === 'assignedGroups') { return 'assigned_groups_var'; } + return $default; }); @@ -157,12 +156,12 @@ public function testGetUsersFromProcessVariableWithGroups() $token = $this->getMockBuilder(ProcessRequestToken::class) ->onlyMethods(['getBpmnDefinition']) ->getMock(); - + $token->process_id = $process->id; $token->process_request_id = $request->id; $token->element_id = 'task_1'; $token->process = $process; - + $token->expects($this->atLeastOnce()) ->method('getBpmnDefinition') ->willReturn($bpmnDefinition); @@ -215,6 +214,7 @@ public function testGetUsersFromProcessVariableWithUsersAndGroups() if ($key === 'assignedGroups') { return 'assigned_groups_var'; } + return $default; }); @@ -225,12 +225,12 @@ public function testGetUsersFromProcessVariableWithUsersAndGroups() $token = $this->getMockBuilder(ProcessRequestToken::class) ->onlyMethods(['getBpmnDefinition']) ->getMock(); - + $token->process_id = $process->id; $token->process_request_id = $request->id; $token->element_id = 'task_1'; $token->process = $process; - + $token->expects($this->atLeastOnce()) ->method('getBpmnDefinition') ->willReturn($bpmnDefinition); @@ -270,6 +270,7 @@ public function testGetUsersFromProcessVariableWithEmptyFormData() if ($key === 'assignedGroups') { return 'assigned_groups_var'; } + return $default; }); @@ -280,12 +281,12 @@ public function testGetUsersFromProcessVariableWithEmptyFormData() $token = $this->getMockBuilder(ProcessRequestToken::class) ->onlyMethods(['getBpmnDefinition']) ->getMock(); - + $token->process_id = $process->id; $token->process_request_id = $request->id; $token->element_id = 'task_1'; $token->process = $process; - + $token->expects($this->atLeastOnce()) ->method('getBpmnDefinition') ->willReturn($bpmnDefinition); @@ -320,6 +321,7 @@ public function testGetUsersFromProcessVariableWithNonArrayValues() if ($key === 'assignedGroups') { return 'assigned_groups_var'; } + return $default; }); @@ -330,12 +332,12 @@ public function testGetUsersFromProcessVariableWithNonArrayValues() $token = $this->getMockBuilder(ProcessRequestToken::class) ->onlyMethods(['getBpmnDefinition']) ->getMock(); - + $token->process_id = $process->id; $token->process_request_id = $request->id; $token->element_id = 'task_1'; $token->process = $process; - + $token->expects($this->atLeastOnce()) ->method('getBpmnDefinition') ->willReturn($bpmnDefinition); @@ -376,6 +378,7 @@ public function testGetUsersFromProcessVariableFiltersInvalidIds() if ($key === 'assignedGroups') { return null; } + return $default; }); @@ -386,12 +389,12 @@ public function testGetUsersFromProcessVariableFiltersInvalidIds() $token = $this->getMockBuilder(ProcessRequestToken::class) ->onlyMethods(['getBpmnDefinition']) ->getMock(); - + $token->process_id = $process->id; $token->process_request_id = $request->id; $token->element_id = 'task_1'; $token->process = $process; - + $token->expects($this->atLeastOnce()) ->method('getBpmnDefinition') ->willReturn($bpmnDefinition); From 7ec804683a34274c4a42568d4dd62ef526e7f593 Mon Sep 17 00:00:00 2001 From: Roly Gutierrez Date: Wed, 7 Jan 2026 09:21:23 -0400 Subject: [PATCH 5/5] FOUR-28520 Fix code review observations. --- routes/api.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/routes/api.php b/routes/api.php index 8955a4deeb..2b3ffc38bb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -60,8 +60,10 @@ Route::delete('users/{user}', [UserController::class, 'destroy'])->name('users.destroy')->middleware('can:delete-users'); Route::put('password/change', [ChangePasswordController::class, 'update'])->name('password.update'); Route::put('users/update_language', [UserController::class, 'updateLanguage'])->name('users.updateLanguage'); - Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count'); - Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post'); + Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count') + ->middleware('can:view-users'); + Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post') + ->middleware('can:view-users'); // User Groups Route::put('users/{user}/groups', [UserController::class, 'updateGroups'])->name('users.groups.update')->middleware('can:edit-users');