Skip to content

Commit 9b2eb2b

Browse files
authored
Fix - Handle mandatory template fields in history button escalation (#392)
* Fix - Handle mandatory template fields in history button escalation * Update CHANGELOG
1 parent 5a8a12d commit 9b2eb2b

3 files changed

Lines changed: 206 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [unreleased]
99

10+
### Fixed
11+
12+
- Handle mandatory template fields in history button escalation
13+
1014
## [2.9.18] - 2025-30-09
1115

1216
### Fixed

inc/ticket.class.php

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -605,24 +605,72 @@ public static function climb_group($tickets_id, $groups_id, $no_redirect = false
605605
'type' => CommonITILActor::ASSIGN,
606606
];
607607
if (!$group_ticket->find($condition)) {
608-
$ticket_group = new Group_Ticket();
609-
PluginEscaladeTask_Manager::setTicketTask([
610-
'tickets_id' => $tickets_id,
611-
'is_private' => true,
612-
'state' => Planning::INFO,
613-
// Sanitize before merging with $_POST['comment'] which is already sanitized
614-
'content' => Sanitizer::sanitize(
615-
'<p><i>' . sprintf(__('Escalation to the group %s.', 'escalade'), Sanitizer::unsanitize($group->getName())) . '</i></p><hr />',
616-
),
617-
]);
608+
// Get ticket to retrieve existing data for template validation
618609
$ticket = new Ticket();
619-
$ticket->update([
610+
$ticket->getFromDB($tickets_id);
611+
612+
// Prepare minimal update with only the fields needed to pass template validation
613+
// This ensures mandatory fields from templates are satisfied while only changing the assigned group
614+
$update_data = [
620615
'id' => $tickets_id,
621616
'_itil_assign' => [
622617
'groups_id' => $groups_id,
623618
'_type' => 'group',
624619
],
620+
// Also use _groups_id_assign to satisfy mandatory field validation
621+
'_groups_id_assign' => [$groups_id],
622+
];
623+
624+
// Add mandatory fields that are commonly required by templates
625+
// to prevent "Mandatory fields are not filled" errors
626+
$required_fields = ['name', 'content', 'itilcategories_id', 'urgency', 'entities_id', 'type'];
627+
foreach ($required_fields as $field) {
628+
if (isset($ticket->fields[$field]) && !empty($ticket->fields[$field])) {
629+
$update_data[$field] = $ticket->fields[$field];
630+
}
631+
}
632+
633+
// Add existing actors to satisfy mandatory actor fields from template
634+
$ticket_user = new \Ticket_User();
635+
$ticket_group_model = new \Group_Ticket();
636+
637+
// Get existing requesters
638+
$requesters = $ticket_user->find([
639+
'tickets_id' => $tickets_id,
640+
'type' => CommonITILActor::REQUESTER,
625641
]);
642+
if (!empty($requesters)) {
643+
$update_data['_users_id_requester'] = array_column($requesters, 'users_id');
644+
}
645+
646+
// Get existing observers
647+
$observers = $ticket_user->find([
648+
'tickets_id' => $tickets_id,
649+
'type' => CommonITILActor::OBSERVER,
650+
]);
651+
if (!empty($observers)) {
652+
$update_data['_users_id_observer'] = array_column($observers, 'users_id');
653+
}
654+
655+
// Get existing requester groups
656+
$requester_groups = $ticket_group_model->find([
657+
'tickets_id' => $tickets_id,
658+
'type' => CommonITILActor::REQUESTER,
659+
]);
660+
if (!empty($requester_groups)) {
661+
$update_data['_groups_id_requester'] = array_column($requester_groups, 'groups_id');
662+
}
663+
664+
// Get existing observer groups
665+
$observer_groups = $ticket_group_model->find([
666+
'tickets_id' => $tickets_id,
667+
'type' => CommonITILActor::OBSERVER,
668+
]);
669+
if (!empty($observer_groups)) {
670+
$update_data['_groups_id_observer'] = array_column($observer_groups, 'groups_id');
671+
}
672+
673+
$ticket->update($update_data);
626674
}
627675

628676
if (!$no_redirect) {

tests/Units/TicketTest.php

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,4 +1382,147 @@ public function testHistoryButtonEscalationWithMandatoryTemplateFields()
13821382
$group1->delete(['id' => $group1_id], true);
13831383
$group2->delete(['id' => $group2_id], true);
13841384
}
1385+
1386+
/**
1387+
* Test that using the History button escalation works correctly with mandatory "Assigned Group" field
1388+
*/
1389+
public function testHistoryButtonEscalationWithMandatoryAssignedGroupField()
1390+
{
1391+
$this->login();
1392+
1393+
// Load Escalade plugin configuration
1394+
$config = new PluginEscaladeConfig();
1395+
$conf = $config->find();
1396+
$conf = reset($conf);
1397+
$config->getFromDB($conf['id']);
1398+
$this->assertGreaterThan(0, $conf['id']);
1399+
PluginEscaladeConfig::loadInSession();
1400+
1401+
// Create a ticket template with mandatory "Assigned Group" field (field num 8)
1402+
$template = $this->createItem(\TicketTemplate::class, [
1403+
'name' => 'Test template with mandatory assigned group',
1404+
'entities_id' => 0,
1405+
'is_recursive' => 1,
1406+
]);
1407+
1408+
// Add mandatory field for "Groupe de techniciens" (Assigned Group)
1409+
$mandatory_field = $this->createItem(\TicketTemplateMandatoryField::class, [
1410+
'tickettemplates_id' => $template->getID(),
1411+
'num' => 8, // _groups_id_assign field number
1412+
]);
1413+
1414+
// Also add mandatory requester field to match real-world scenarios
1415+
$mandatory_field2 = $this->createItem(\TicketTemplateMandatoryField::class, [
1416+
'tickettemplates_id' => $template->getID(),
1417+
'num' => 4, // _users_id_requester field number
1418+
]);
1419+
1420+
// Create a category linked to this template
1421+
$category = $this->createItem(\ITILCategory::class, [
1422+
'name' => 'Test category with mandatory assigned group',
1423+
'tickettemplates_id_incident' => $template->getID(),
1424+
'is_incident' => 1,
1425+
'entities_id' => 0,
1426+
'is_recursive' => 1,
1427+
]);
1428+
1429+
// Create a requester user
1430+
$requester = $this->createItem(\User::class, [
1431+
'name' => 'requester_assigned_group_test',
1432+
'realname' => 'AssignedGroupTest',
1433+
'firstname' => 'Requester',
1434+
]);
1435+
1436+
// Create first escalation group
1437+
$group1 = $this->createItem(\Group::class, [
1438+
'name' => 'First assigned group for escalation',
1439+
'entities_id' => 0,
1440+
'is_recursive' => 1,
1441+
'is_assign' => 1,
1442+
]);
1443+
1444+
// Create second escalation group
1445+
$group2 = $this->createItem(\Group::class, [
1446+
'name' => 'Second assigned group for escalation',
1447+
'entities_id' => 0,
1448+
'is_recursive' => 1,
1449+
'is_assign' => 1,
1450+
]);
1451+
1452+
// Create third group for history button test
1453+
$group3 = $this->createItem(\Group::class, [
1454+
'name' => 'Third assigned group for history',
1455+
'entities_id' => 0,
1456+
'is_recursive' => 1,
1457+
'is_assign' => 1,
1458+
]);
1459+
1460+
// Create a ticket with the template, mandatory requester and mandatory assigned group filled
1461+
$ticket = $this->createItem(\Ticket::class, [
1462+
'name' => 'Test ticket with mandatory assigned group',
1463+
'content' => 'Content for testing mandatory assigned group in history button',
1464+
'itilcategories_id' => $category->getID(),
1465+
'_users_id_requester' => [$requester->getID()],
1466+
'_groups_id_assign' => [$group1->getID()],
1467+
]);
1468+
1469+
// Verify initial group assignment
1470+
$group_ticket = new \Group_Ticket();
1471+
$initial_groups = $group_ticket->find([
1472+
'tickets_id' => $ticket->getID(),
1473+
'type' => CommonITILActor::ASSIGN,
1474+
]);
1475+
$this->assertEquals(1, count($initial_groups));
1476+
1477+
// Escalate to second group
1478+
$this->createItem(\Group_Ticket::class, [
1479+
'tickets_id' => $ticket->getID(),
1480+
'groups_id' => $group2->getID(),
1481+
'type' => CommonITILActor::ASSIGN,
1482+
]);
1483+
1484+
// Escalate to third group to create more history
1485+
$this->createItem(\Group_Ticket::class, [
1486+
'tickets_id' => $ticket->getID(),
1487+
'groups_id' => $group3->getID(),
1488+
'type' => CommonITILActor::ASSIGN,
1489+
]);
1490+
1491+
// Now test the history button escalation using climb_group
1492+
// This reproduces issue #381 where mandatory "Groupe de techniciens" field causes an error
1493+
PluginEscaladeTicket::climb_group($ticket->getID(), $group1->getID(), true);
1494+
1495+
// Verify that no error occurred during the climb_group operation
1496+
// by checking that group1 is now assigned
1497+
$group1_assigned = $group_ticket->find([
1498+
'tickets_id' => $ticket->getID(),
1499+
'groups_id' => $group1->getID(),
1500+
'type' => CommonITILActor::ASSIGN,
1501+
]);
1502+
$this->assertGreaterThan(0, count($group1_assigned), 'Group 1 should be assigned after climb_group');
1503+
1504+
// Test climbing to group2 (another history group)
1505+
PluginEscaladeTicket::climb_group($ticket->getID(), $group2->getID(), true);
1506+
1507+
$group2_assigned = $group_ticket->find([
1508+
'tickets_id' => $ticket->getID(),
1509+
'groups_id' => $group2->getID(),
1510+
'type' => CommonITILActor::ASSIGN,
1511+
]);
1512+
$this->assertGreaterThan(0, count($group2_assigned), 'Group 2 should be assigned after second climb_group');
1513+
1514+
// Verify that the requester is still properly assigned
1515+
$ticket_user = new \Ticket_User();
1516+
$requesters_after = $ticket_user->find([
1517+
'tickets_id' => $ticket->getID(),
1518+
'type' => CommonITILActor::REQUESTER,
1519+
]);
1520+
$this->assertEquals(1, count($requesters_after));
1521+
$requester_after = reset($requesters_after);
1522+
$this->assertEquals($requester->getID(), $requester_after['users_id']);
1523+
1524+
// Verify that the ticket still has the correct category with template
1525+
$ticket->getFromDB($ticket->getID());
1526+
$this->assertEquals($category->getID(), $ticket->fields['itilcategories_id']);
1527+
}
13851528
}

0 commit comments

Comments
 (0)