diff --git a/CHANGELOG.md b/CHANGELOG.md index bb036dc..ff68c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +### Fixed + +- Handle mandatory template fields in history button escalation + ## [2.9.18] - 2025-30-09 ### Fixed diff --git a/inc/ticket.class.php b/inc/ticket.class.php index 0aac77e..2c8e96c 100644 --- a/inc/ticket.class.php +++ b/inc/ticket.class.php @@ -605,24 +605,72 @@ public static function climb_group($tickets_id, $groups_id, $no_redirect = false 'type' => CommonITILActor::ASSIGN, ]; if (!$group_ticket->find($condition)) { - $ticket_group = new Group_Ticket(); - PluginEscaladeTask_Manager::setTicketTask([ - 'tickets_id' => $tickets_id, - 'is_private' => true, - 'state' => Planning::INFO, - // Sanitize before merging with $_POST['comment'] which is already sanitized - 'content' => Sanitizer::sanitize( - '

' . sprintf(__('Escalation to the group %s.', 'escalade'), Sanitizer::unsanitize($group->getName())) . '


', - ), - ]); + // Get ticket to retrieve existing data for template validation $ticket = new Ticket(); - $ticket->update([ + $ticket->getFromDB($tickets_id); + + // Prepare minimal update with only the fields needed to pass template validation + // This ensures mandatory fields from templates are satisfied while only changing the assigned group + $update_data = [ 'id' => $tickets_id, '_itil_assign' => [ 'groups_id' => $groups_id, '_type' => 'group', ], + // Also use _groups_id_assign to satisfy mandatory field validation + '_groups_id_assign' => [$groups_id], + ]; + + // Add mandatory fields that are commonly required by templates + // to prevent "Mandatory fields are not filled" errors + $required_fields = ['name', 'content', 'itilcategories_id', 'urgency', 'entities_id', 'type']; + foreach ($required_fields as $field) { + if (isset($ticket->fields[$field]) && !empty($ticket->fields[$field])) { + $update_data[$field] = $ticket->fields[$field]; + } + } + + // Add existing actors to satisfy mandatory actor fields from template + $ticket_user = new \Ticket_User(); + $ticket_group_model = new \Group_Ticket(); + + // Get existing requesters + $requesters = $ticket_user->find([ + 'tickets_id' => $tickets_id, + 'type' => CommonITILActor::REQUESTER, ]); + if (!empty($requesters)) { + $update_data['_users_id_requester'] = array_column($requesters, 'users_id'); + } + + // Get existing observers + $observers = $ticket_user->find([ + 'tickets_id' => $tickets_id, + 'type' => CommonITILActor::OBSERVER, + ]); + if (!empty($observers)) { + $update_data['_users_id_observer'] = array_column($observers, 'users_id'); + } + + // Get existing requester groups + $requester_groups = $ticket_group_model->find([ + 'tickets_id' => $tickets_id, + 'type' => CommonITILActor::REQUESTER, + ]); + if (!empty($requester_groups)) { + $update_data['_groups_id_requester'] = array_column($requester_groups, 'groups_id'); + } + + // Get existing observer groups + $observer_groups = $ticket_group_model->find([ + 'tickets_id' => $tickets_id, + 'type' => CommonITILActor::OBSERVER, + ]); + if (!empty($observer_groups)) { + $update_data['_groups_id_observer'] = array_column($observer_groups, 'groups_id'); + } + + $ticket->update($update_data); } if (!$no_redirect) { diff --git a/tests/Units/TicketTest.php b/tests/Units/TicketTest.php index 7b1e702..6884918 100644 --- a/tests/Units/TicketTest.php +++ b/tests/Units/TicketTest.php @@ -1382,4 +1382,147 @@ public function testHistoryButtonEscalationWithMandatoryTemplateFields() $group1->delete(['id' => $group1_id], true); $group2->delete(['id' => $group2_id], true); } + + /** + * Test that using the History button escalation works correctly with mandatory "Assigned Group" field + */ + public function testHistoryButtonEscalationWithMandatoryAssignedGroupField() + { + $this->login(); + + // Load Escalade plugin configuration + $config = new PluginEscaladeConfig(); + $conf = $config->find(); + $conf = reset($conf); + $config->getFromDB($conf['id']); + $this->assertGreaterThan(0, $conf['id']); + PluginEscaladeConfig::loadInSession(); + + // Create a ticket template with mandatory "Assigned Group" field (field num 8) + $template = $this->createItem(\TicketTemplate::class, [ + 'name' => 'Test template with mandatory assigned group', + 'entities_id' => 0, + 'is_recursive' => 1, + ]); + + // Add mandatory field for "Groupe de techniciens" (Assigned Group) + $mandatory_field = $this->createItem(\TicketTemplateMandatoryField::class, [ + 'tickettemplates_id' => $template->getID(), + 'num' => 8, // _groups_id_assign field number + ]); + + // Also add mandatory requester field to match real-world scenarios + $mandatory_field2 = $this->createItem(\TicketTemplateMandatoryField::class, [ + 'tickettemplates_id' => $template->getID(), + 'num' => 4, // _users_id_requester field number + ]); + + // Create a category linked to this template + $category = $this->createItem(\ITILCategory::class, [ + 'name' => 'Test category with mandatory assigned group', + 'tickettemplates_id_incident' => $template->getID(), + 'is_incident' => 1, + 'entities_id' => 0, + 'is_recursive' => 1, + ]); + + // Create a requester user + $requester = $this->createItem(\User::class, [ + 'name' => 'requester_assigned_group_test', + 'realname' => 'AssignedGroupTest', + 'firstname' => 'Requester', + ]); + + // Create first escalation group + $group1 = $this->createItem(\Group::class, [ + 'name' => 'First assigned group for escalation', + 'entities_id' => 0, + 'is_recursive' => 1, + 'is_assign' => 1, + ]); + + // Create second escalation group + $group2 = $this->createItem(\Group::class, [ + 'name' => 'Second assigned group for escalation', + 'entities_id' => 0, + 'is_recursive' => 1, + 'is_assign' => 1, + ]); + + // Create third group for history button test + $group3 = $this->createItem(\Group::class, [ + 'name' => 'Third assigned group for history', + 'entities_id' => 0, + 'is_recursive' => 1, + 'is_assign' => 1, + ]); + + // Create a ticket with the template, mandatory requester and mandatory assigned group filled + $ticket = $this->createItem(\Ticket::class, [ + 'name' => 'Test ticket with mandatory assigned group', + 'content' => 'Content for testing mandatory assigned group in history button', + 'itilcategories_id' => $category->getID(), + '_users_id_requester' => [$requester->getID()], + '_groups_id_assign' => [$group1->getID()], + ]); + + // Verify initial group assignment + $group_ticket = new \Group_Ticket(); + $initial_groups = $group_ticket->find([ + 'tickets_id' => $ticket->getID(), + 'type' => CommonITILActor::ASSIGN, + ]); + $this->assertEquals(1, count($initial_groups)); + + // Escalate to second group + $this->createItem(\Group_Ticket::class, [ + 'tickets_id' => $ticket->getID(), + 'groups_id' => $group2->getID(), + 'type' => CommonITILActor::ASSIGN, + ]); + + // Escalate to third group to create more history + $this->createItem(\Group_Ticket::class, [ + 'tickets_id' => $ticket->getID(), + 'groups_id' => $group3->getID(), + 'type' => CommonITILActor::ASSIGN, + ]); + + // Now test the history button escalation using climb_group + // This reproduces issue #381 where mandatory "Groupe de techniciens" field causes an error + PluginEscaladeTicket::climb_group($ticket->getID(), $group1->getID(), true); + + // Verify that no error occurred during the climb_group operation + // by checking that group1 is now assigned + $group1_assigned = $group_ticket->find([ + 'tickets_id' => $ticket->getID(), + 'groups_id' => $group1->getID(), + 'type' => CommonITILActor::ASSIGN, + ]); + $this->assertGreaterThan(0, count($group1_assigned), 'Group 1 should be assigned after climb_group'); + + // Test climbing to group2 (another history group) + PluginEscaladeTicket::climb_group($ticket->getID(), $group2->getID(), true); + + $group2_assigned = $group_ticket->find([ + 'tickets_id' => $ticket->getID(), + 'groups_id' => $group2->getID(), + 'type' => CommonITILActor::ASSIGN, + ]); + $this->assertGreaterThan(0, count($group2_assigned), 'Group 2 should be assigned after second climb_group'); + + // Verify that the requester is still properly assigned + $ticket_user = new \Ticket_User(); + $requesters_after = $ticket_user->find([ + 'tickets_id' => $ticket->getID(), + 'type' => CommonITILActor::REQUESTER, + ]); + $this->assertEquals(1, count($requesters_after)); + $requester_after = reset($requesters_after); + $this->assertEquals($requester->getID(), $requester_after['users_id']); + + // Verify that the ticket still has the correct category with template + $ticket->getFromDB($ticket->getID()); + $this->assertEquals($category->getID(), $ticket->fields['itilcategories_id']); + } }