diff --git a/src/ProjectTask.php b/src/ProjectTask.php index 04df4e4a4da..df854ba8544 100644 --- a/src/ProjectTask.php +++ b/src/ProjectTask.php @@ -608,38 +608,52 @@ public function post_clone($source, $history) */ public function autoSetDate(array $input): array { + if (isset($input['real_start_date']) && $input['real_start_date'] === '') { + $input['real_start_date'] = null; + } + if (isset($input['real_end_date']) && $input['real_end_date'] === '') { + $input['real_end_date'] = null; + } + $percent_done = (int) ($input['percent_done'] ?? $this->fields['percent_done'] ?? 0); $real_start_date = $input['real_start_date'] ?? $this->fields['real_start_date'] ?? null; $real_end_date = $input['real_end_date'] ?? $this->fields['real_end_date'] ?? null; - if ($percent_done < 100 && $real_end_date) { + if ($percent_done < 100 && $real_end_date && !array_key_exists('real_end_date', $input)) { $input['real_end_date'] = null; } elseif ( isset($this->fields['percent_done']) && (int) $this->fields['percent_done'] === 100 && $percent_done < 100 + && !array_key_exists('real_end_date', $input) ) { $input['real_end_date'] = null; - } elseif (($real_start_date && $real_end_date) || $percent_done === 0) { - // If both real start and end dates are set, or if the task is not started, - return $input; - } else { - // Set automatically the real start date if not set - if (empty($real_start_date) && $percent_done > 0) { - $input['real_start_date'] = Session::getCurrentTime(); - } - // Set automatically the real end date if not set - if (empty($real_end_date) && $percent_done === 100) { - $input['real_end_date'] = Session::getCurrentTime(); - } - // Set automatically the effective duration if not set - if (!empty($input['real_start_date']) && !empty($input['real_end_date'])) { - $input['effective_duration'] = $this->autoSetEffectiveDuration( - $input['real_start_date'], - $input['real_end_date'] - ); + } + + if ($percent_done === 0) { + if (empty($input['real_start_date']) && empty($input['real_end_date'])) { + return $input; } } + // Set automatically the real start date if not set + if (empty($real_start_date) && $percent_done > 0) { + $input['real_start_date'] = Session::getCurrentTime(); + } + // Set automatically the real end date if not set + if (empty($real_end_date) && $percent_done === 100) { + $input['real_end_date'] = Session::getCurrentTime(); + } + + // Calculate effective duration when both dates are present + $final_start_date = $input['real_start_date'] ?? $this->fields['real_start_date'] ?? null; + $final_end_date = $input['real_end_date'] ?? $this->fields['real_end_date'] ?? null; + if (!empty($final_start_date) && !empty($final_end_date)) { + $input['effective_duration'] = $this->autoSetEffectiveDuration( + $final_start_date, + $final_end_date + ); + } + return $input; } diff --git a/tests/functional/ProjectTaskTest.php b/tests/functional/ProjectTaskTest.php index b2a29862c7b..9bddfc3ccbe 100644 --- a/tests/functional/ProjectTaskTest.php +++ b/tests/functional/ProjectTaskTest.php @@ -37,6 +37,7 @@ use Glpi\Team\Team; use Glpi\Tests\DbTestCase; use Project; +use ProjectTask; /* Test for inc/projecttask.class.php */ @@ -49,7 +50,7 @@ public function testPlanningConflict() $user = getItemByTypeName('User', 'tech'); $users_id = (int) $user->fields['id']; - $ptask = new \ProjectTask(); + $ptask = new ProjectTask(); $this->assertSame( 0, (int) $ptask->add([ @@ -194,16 +195,16 @@ public function testPlanningConflict() public function testGetTeamRoles(): void { - $roles = \ProjectTask::getTeamRoles(); + $roles = ProjectTask::getTeamRoles(); $this->assertContains(Team::ROLE_OWNER, $roles); $this->assertContains(Team::ROLE_MEMBER, $roles); } public function testGetTeamRoleName(): void { - $roles = \ProjectTask::getTeamRoles(); + $roles = ProjectTask::getTeamRoles(); foreach ($roles as $role) { - $this->assertNotEmpty(\ProjectTask::getTeamRoleName($role)); + $this->assertNotEmpty(ProjectTask::getTeamRoleName($role)); } } @@ -213,7 +214,7 @@ public function testGetTeamRoleName(): void public function testTeamManagement(): void { $this->login(); - $project_task = new \ProjectTask(); + $project_task = new ProjectTask(); $project = new Project(); $projects_id = $project->add([ @@ -397,7 +398,7 @@ public function testCloneProjectTask() // Clone the task $clonedTaskId = $task->clone(); - $clonedTask = \ProjectTask::getById($clonedTaskId); + $clonedTask = ProjectTask::getById($clonedTaskId); // Check if the cloned task is in the same project with the same name $this->assertEquals($project->getID(), $clonedTask->fields['projects_id']); @@ -426,7 +427,7 @@ public function testCloneProjectTask() $this->assertEquals($team, $team_clone); // Check if the subtask has been cloned - $clonedSubtask = new \ProjectTask(); + $clonedSubtask = new ProjectTask(); $clonedSubtask->getFromDBByCrit([ 'projects_id' => $project->getID(), 'projecttasks_id' => $clonedTaskId, @@ -437,7 +438,7 @@ public function testCloneProjectTask() $this->assertEquals($subtask->fields['name'] . ' (copy)', $clonedSubtask->fields['name']); // Check if the subtask of the subtask has been cloned - $clonedSubtask2 = new \ProjectTask(); + $clonedSubtask2 = new ProjectTask(); $clonedSubtask2->getFromDBByCrit([ 'projects_id' => $project->getID(), 'projecttasks_id' => $clonedSubtask->getID(), @@ -810,14 +811,14 @@ public function testAutoSetDateForUpdate() 'name' => 'Project 1', ]); - $task = $this->createItem(\ProjectTask::class, ['projects_id' => $project->getId()]); + $task = $this->createItem(ProjectTask::class, ['projects_id' => $project->getId()]); // Force initial fields (bypass prepareInputForAdd) - $DB->update(\ProjectTask::getTable(), $fields, ['id' => $task->getID()]); + $DB->update(ProjectTask::getTable(), $fields, ['id' => $task->getID()]); - $this->updateItem(\ProjectTask::class, $task->getID(), $input); + $this->updateItem(ProjectTask::class, $task->getID(), $input); - $ptask = new \ProjectTask(); + $ptask = new ProjectTask(); $this->assertTrue($ptask->getFromDB($task->getID())); foreach ($result as $field => $value) { @@ -835,7 +836,7 @@ public function testGetActiveProjectTaskIDsForUser() $user = $this->createItem(\User::getType(), ['name' => __FUNCTION__ . 'user']); // Check if a user with no project returns an empty array - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); // Create project $project = $this->createItem(Project::getType(), [ @@ -844,16 +845,16 @@ public function testGetActiveProjectTaskIDsForUser() ]); // Check if a user with a project with no tasks returns an empty array - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); // Create project task - $project_task = $this->createItem(\ProjectTask::getType(), [ + $project_task = $this->createItem(ProjectTask::getType(), [ 'projects_id' => $project->getID(), 'name' => 'project task', ]); // Check if a user with a project with tasks, where the user is not a member of the team, returns an empty array - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); // Create user team $user_team = $this->createItem(\ProjectTaskTeam::getType(), [ @@ -865,7 +866,7 @@ public function testGetActiveProjectTaskIDsForUser() // Check if a user with a project with tasks, where the user is a member of the team, returns an array with the task ID $this->assertEquals( [['id' => $project_task->getID()]], - \ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()]) + ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()]) ); // Create group @@ -887,11 +888,11 @@ public function testGetActiveProjectTaskIDsForUser() // Check if a user with a project with tasks, where the user is a member of the group and the group is a member of the team, returns an array with the task ID if $search_in_groups is true $this->assertEquals( [['id' => $project_task->getID()]], - \ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()]) + ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()]) ); // Check if a user with a project with tasks, where the user is a member of the group and the group is a member of the team, returns an empty array if $search_in_groups is false - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()], false)); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()], false)); // Templates should be excluded $project = $this->updateItem( @@ -899,7 +900,7 @@ public function testGetActiveProjectTaskIDsForUser() $project->getID(), ['is_template' => true] ); - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForUser([$user->getID()])); } public function testGetActiveProjectTaskIDsForGroup(): void @@ -911,7 +912,7 @@ public function testGetActiveProjectTaskIDsForGroup(): void $group = $this->createItem(\Group::getType(), ['name' => __FUNCTION__ . 'group']); // Check if a group with no project returns an empty array - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); // Create project $project = $this->createItem(Project::getType(), [ @@ -921,16 +922,16 @@ public function testGetActiveProjectTaskIDsForGroup(): void ]); // Check if a group with a project with no tasks returns an empty array - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); // Create project task - $project_task = $this->createItem(\ProjectTask::getType(), [ + $project_task = $this->createItem(ProjectTask::getType(), [ 'projects_id' => $project->getID(), 'name' => 'project task', ]); // Check if a group with a project with tasks, where the group is not a member of the team, returns an empty array - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); // Create group team $this->createItem(\ProjectTaskTeam::getType(), [ @@ -942,7 +943,7 @@ public function testGetActiveProjectTaskIDsForGroup(): void // Check if a group with a project with tasks, where the group is a member of the team, returns an array with the task ID $this->assertEquals( [['id' => $project_task->getID()]], - \ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()]) + ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()]) ); // Templates should be excluded @@ -951,6 +952,55 @@ public function testGetActiveProjectTaskIDsForGroup(): void $project->getID(), ['is_template' => true] ); - $this->assertEmpty(\ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); + $this->assertEmpty(ProjectTask::getActiveProjectTaskIDsForGroup([$group->getID()])); + } + + /** + * Verify that real_end_date is saved and that effective_duration is calculated + */ + public function testAutoSetDate(): void + { + $_SESSION['glpi_currenttime'] = '2024-03-15 10:30:00'; + + $project = $this->createItem(Project::class, [ + 'name' => 'Project 1', + ]); + + $task = $this->createItem(ProjectTask::class, [ + 'projects_id' => $project->getID(), + 'name' => 'Test Task', + 'percent_done' => 0, + ]); + + // 1. When percent_done = 100, real_end_date should be set automatically + $input = ['percent_done' => 100]; + $result = $task->autoSetDate($input); + $this->assertEquals('2024-03-15 10:30:00', $result['real_start_date']); + $this->assertEquals('2024-03-15 10:30:00', $result['real_end_date']); + + // 2. When using manual dates, effective_duration must be calculated + $input = [ + 'percent_done' => 100, + 'real_start_date' => '2024-03-10 08:00:00', + 'real_end_date' => '2024-03-12 18:00:00', + ]; + $result = $task->autoSetDate($input); + $this->assertArrayHasKey('effective_duration', $result); + $expected_duration = (new \DateTime('2024-03-12 18:00:00'))->getTimestamp() + - (new \DateTime('2024-03-10 08:00:00'))->getTimestamp(); + $this->assertEquals($expected_duration, $result['effective_duration']); + + // 3. When percent_done drops below 100%, real_end_date must be reset + $task2 = $this->createItem(ProjectTask::class, [ + 'projects_id' => $project->getID(), + 'name' => 'task 2', + 'percent_done' => 100, + 'real_start_date' => '2024-03-10 08:00:00', + 'real_end_date' => '2024-03-12 18:00:00', + ]); + + $input = ['percent_done' => 50]; + $result = $task2->autoSetDate($input); + $this->assertNull($result['real_end_date']); } }