diff --git a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml index 3be94a74b2..663ba74ff9 100755 --- a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml +++ b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml @@ -460,6 +460,18 @@ parent_incident_id ref + + id AND status NOT IN ('rejected','resolved','closed')]]> + + parent_request_id + UserRequest + true + DEL_MANUAL + + + parent_request_id + ref + parent_problem_id Problem @@ -987,6 +999,9 @@ + + + @@ -1301,90 +1316,20 @@ LifecycleAction Get('public_log'); - $sLogPublic = $oLog->GetModifiedEntry('html'); - if ($sLogPublic != '') - { - $sOQL = "SELECT UserRequest WHERE parent_incident_id=:ticket"; - $oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oRequest = $oChildRequestSet->Fetch()) - { - $oRequest->set('public_log',$sLogPublic); - $oRequest->DBUpdate(); - } - - } - $oLog = $this->Get('private_log'); - $sLogPrivate = $oLog->GetModifiedEntry('html'); - if ($sLogPrivate != '') - { - $sOQL = "SELECT UserRequest WHERE parent_incident_id=:ticket"; - $oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oRequest = $oChildRequestSet->Fetch()) - { - $oRequest->set('private_log',$sLogPrivate); - $oRequest->DBUpdate(); - } - } - return true; - + if (MetaModel::IsValidClass('UserRequest')) { + return $this->UpdateChildTicketLog('UserRequest', 'parent_incident_id', ['public_log' => 'public_log', 'private_log' => 'private_log'] ); + } + return true; }]]> + false public LifecycleAction Get('public_log'); - $sLogPublic = $oLog->GetModifiedEntry('html'); - if ($sLogPublic != '') - { - $sOQL = "SELECT Incident WHERE parent_incident_id=:ticket"; - $oChildIncidentSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oIncident = $oChildIncidentSet->Fetch()) - { - $oIncident->set('public_log',$sLogPublic); - $oIncident->DBUpdate(); - } - - } - $oLog = $this->Get('private_log'); - $sLogPrivate = $oLog->GetModifiedEntry('html'); - if ($sLogPrivate != '') - { - $sOQL = "SELECT Incident WHERE parent_incident_id=:ticket"; - $oChildIncidentSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oIncident = $oChildIncidentSet->Fetch()) - { - $oIncident->set('private_log',$sLogPrivate); - $oIncident->DBUpdate(); - } - } - return true; - + return $this->UpdateChildTicketLog('Incident', 'parent_incident_id', ['public_log' => 'public_log', 'private_log' => 'private_log']); }]]> @@ -1558,6 +1503,9 @@ 10 + + 15 + 20 diff --git a/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/en.dict.itop-incident-mgmt-itil.php b/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/en.dict.itop-incident-mgmt-itil.php index 2966031a09..a5383abb92 100644 --- a/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/en.dict.itop-incident-mgmt-itil.php +++ b/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/en.dict.itop-incident-mgmt-itil.php @@ -44,6 +44,8 @@ 'UI-IncidentManagementOverview-OpenIncidentByStatus' => 'Open incidents by status', 'UI-IncidentManagementOverview-OpenIncidentByAgent' => 'Open incidents by agent', 'UI-IncidentManagementOverview-OpenIncidentByCustomer' => 'Open incidents by customer', + 'Class:Incident/Method:UpdateChildTicketWith:public_log' => 'Public log entry from parent Incident %2$s:

', + 'Class:Incident/Method:UpdateChildTicketWith:private_log' => 'Private log entry from parent Incident [[Incident:%1$s]]:

', ]); // Dictionnay conventions @@ -193,6 +195,10 @@ 'Class:Incident/Attribute:parent_incident_id+' => '', 'Class:Incident/Attribute:parent_incident_ref' => 'Parent incident ref', 'Class:Incident/Attribute:parent_incident_ref+' => '', + 'Class:Incident/Attribute:parent_request_id' => 'Parent request', + 'Class:Incident/Attribute:parent_request_id+' => '', + 'Class:Incident/Attribute:parent_request_ref' => 'Parent request ref', + 'Class:Incident/Attribute:parent_request_ref+' => '', 'Class:Incident/Attribute:parent_change_id' => 'Parent change', 'Class:Incident/Attribute:parent_change_id+' => '', 'Class:Incident/Attribute:parent_change_ref' => 'Parent change ref', diff --git a/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/fr.dict.itop-incident-mgmt-itil.php b/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/fr.dict.itop-incident-mgmt-itil.php index c7672c7027..7b329d4a2b 100644 --- a/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/fr.dict.itop-incident-mgmt-itil.php +++ b/datamodels/2.x/itop-incident-mgmt-itil/dictionaries/fr.dict.itop-incident-mgmt-itil.php @@ -179,15 +179,19 @@ 'Class:Incident/Attribute:pending_reason+' => '', 'Class:Incident/Attribute:parent_incident_id' => 'Incident parent', 'Class:Incident/Attribute:parent_incident_id+' => '', - 'Class:Incident/Attribute:parent_incident_ref' => 'Référence incident parent', + 'Class:Incident/Attribute:parent_incident_ref' => 'Réf. incident parent', 'Class:Incident/Attribute:parent_incident_ref+' => '', + 'Class:Incident/Attribute:parent_request_id' => 'Demande parente', + 'Class:Incident/Attribute:parent_request_id+' => '', + 'Class:Incident/Attribute:parent_request_ref' => 'Réf. demande parente', + 'Class:Incident/Attribute:parent_request_ref+' => '', 'Class:Incident/Attribute:parent_change_id' => 'Changement parent', 'Class:Incident/Attribute:parent_change_id+' => '', - 'Class:Incident/Attribute:parent_change_ref' => 'Ref Changement parent', + 'Class:Incident/Attribute:parent_change_ref' => 'Réf. changement parent', 'Class:Incident/Attribute:parent_change_ref+' => '', 'Class:Incident/Attribute:parent_problem_id' => 'Problème lié', 'Class:Incident/Attribute:parent_problem_id+' => '', - 'Class:Incident/Attribute:parent_problem_ref' => 'Référence problème lié', + 'Class:Incident/Attribute:parent_problem_ref' => 'Réf. problème lié', 'Class:Incident/Attribute:parent_problem_ref+' => '', 'Class:Incident/Attribute:related_request_list' => 'Requêtes filles', 'Class:Incident/Attribute:related_request_list+' => '', diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index 766272cbec..9169fe93eb 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -1451,44 +1451,17 @@ public LifecycleAction UpdateChildTicketLog('UserRequest', 'parent_request_id', ['public_log' => 'public_log', 'private_log' => 'private_log' ]); +}]]> +
+ + false + public + LifecycleAction + Get('public_log'); - $sLogPublic = $oLog->GetModifiedEntry('html'); - if ($sLogPublic != '') - { - $sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket"; - $oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oRequest = $oChildRequestSet->Fetch()) - { - $oRequest->set('public_log',$sLogPublic); - $oRequest->DBUpdate(); - } - - } - $oLog = $this->Get('private_log'); - $sLogPrivate = $oLog->GetModifiedEntry('html'); - if ($sLogPrivate != '') - { - $sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket"; - $oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oRequest = $oChildRequestSet->Fetch()) - { - $oRequest->set('private_log',$sLogPrivate); - $oRequest->DBUpdate(); - } - } - return true; - + return $this->UpdateChildTicketLog('Incident', 'parent_request_id', ['public_log' => 'public_log', 'private_log' => 'private_log']); }]]> @@ -1526,6 +1499,7 @@ parent::OnUpdate(); $this->Set('last_update', time()); $this->UpdateChildRequestLog(); + $this->UpdateChildIncidentLog(); }]]> diff --git a/datamodels/2.x/itop-request-mgmt-itil/dictionaries/en.dict.itop-request-mgmt-itil.php b/datamodels/2.x/itop-request-mgmt-itil/dictionaries/en.dict.itop-request-mgmt-itil.php index a2d2fe1861..4c7c504fd0 100644 --- a/datamodels/2.x/itop-request-mgmt-itil/dictionaries/en.dict.itop-request-mgmt-itil.php +++ b/datamodels/2.x/itop-request-mgmt-itil/dictionaries/en.dict.itop-request-mgmt-itil.php @@ -37,6 +37,8 @@ 'UI-RequestManagementOverview-OpenRequestByCustomer' => 'Open requests by customer', 'Class:UserRequest:KnownErrorList' => 'Known Errors', 'Class:UserRequest:KnownErrorList+' => 'Known Errors related to Functional CI linked to the current ticket', + 'Class:UserRequest/Method:UpdateChildTicketWith:public_log' => 'Public log automatic copy from parent User Request %2$s:

', + 'Class:UserRequest/Method:UpdateChildTicketWith:private_log' => 'Private log automatic copy from parent User Request [[UserRequest:%1$s]]:

', ]); // Dictionnay conventions diff --git a/datamodels/2.x/itop-request-mgmt-itil/dictionaries/fr.dict.itop-request-mgmt-itil.php b/datamodels/2.x/itop-request-mgmt-itil/dictionaries/fr.dict.itop-request-mgmt-itil.php index b61397a8fd..ec07842c85 100644 --- a/datamodels/2.x/itop-request-mgmt-itil/dictionaries/fr.dict.itop-request-mgmt-itil.php +++ b/datamodels/2.x/itop-request-mgmt-itil/dictionaries/fr.dict.itop-request-mgmt-itil.php @@ -42,6 +42,8 @@ 'UI-RequestManagementOverview-OpenRequestByCustomer' => 'Requêtes ouvertes par client', 'Class:UserRequest:KnownErrorList' => 'Erreurs connues', 'Class:UserRequest:KnownErrorList+' => 'Erreurs connues liées à des éléments de configuration impactés par ce ticket', + 'Class:UserRequest/Method:UpdateChildTicketWith:public_log' => 'Copie automatique du log public de la demande parente %2$s:

', + 'Class:UserRequest/Method:UpdateChildTicketWith:private_log' => 'Copie automatique du log privé de la demande parente [[UserRequest:%1$s]]:

', ]); // Dictionnay conventions diff --git a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml index db1edcb30d..97025b5810 100755 --- a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml +++ b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml @@ -1486,44 +1486,8 @@ public LifecycleAction Get('public_log'); - $sLogPublic = $oLog->GetModifiedEntry('html'); - if ($sLogPublic != '') - { - $sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket"; - $oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oRequest = $oChildRequestSet->Fetch()) - { - $oRequest->set('public_log',$sLogPublic); - $oRequest->DBUpdate(); - } - - } - $oLog = $this->Get('private_log'); - $sLogPrivate = $oLog->GetModifiedEntry('html'); - if ($sLogPrivate != '') - { - $sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket"; - $oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), - array(), - array( - 'ticket' => $this->GetKey(), - ) - ); - while($oRequest = $oChildRequestSet->Fetch()) - { - $oRequest->set('private_log',$sLogPrivate); - $oRequest->DBUpdate(); - } - } - return true; - +{ + return $this->UpdateChildTicketLog('UserRequest', 'parent_request_id', ['public_log' => 'public_log', 'private_log' => 'private_log'] ); }]]> diff --git a/datamodels/2.x/itop-request-mgmt/dictionaries/en.dict.itop-request-mgmt.php b/datamodels/2.x/itop-request-mgmt/dictionaries/en.dict.itop-request-mgmt.php index 1aeaac0830..5e8f40bacb 100644 --- a/datamodels/2.x/itop-request-mgmt/dictionaries/en.dict.itop-request-mgmt.php +++ b/datamodels/2.x/itop-request-mgmt/dictionaries/en.dict.itop-request-mgmt.php @@ -41,6 +41,8 @@ 'Menu:UserRequest:MyWorkOrders+' => 'All work orders assigned to me', 'Class:Problem:KnownProblemList' => 'Known problems', 'Tickets:Related:OpenIncidents' => 'Open incidents', + 'Class:UserRequest/Method:UpdateChildTicketWith:public_log' => 'Public log automatic copy from parent User Request %2$s:

', + 'Class:UserRequest/Method:UpdateChildTicketWith:private_log' => 'Private log automatic copy from parent User Request [[UserRequest:%1$s]]:

', ]); // Dictionnay conventions diff --git a/datamodels/2.x/itop-request-mgmt/dictionaries/fr.dict.itop-request-mgmt.php b/datamodels/2.x/itop-request-mgmt/dictionaries/fr.dict.itop-request-mgmt.php index 802d264f02..5e9bc7a2ef 100644 --- a/datamodels/2.x/itop-request-mgmt/dictionaries/fr.dict.itop-request-mgmt.php +++ b/datamodels/2.x/itop-request-mgmt/dictionaries/fr.dict.itop-request-mgmt.php @@ -42,6 +42,8 @@ 'UI-RequestManagementOverview-OpenRequestByCustomer' => 'Requêtes ouvertes par organisation', 'Class:UserRequest:KnownErrorList' => 'Erreurs connues', 'Class:UserRequest:KnownErrorList+' => 'Erreurs connues liées à des éléments de configuration impactés par ce ticket', + 'Class:UserRequest/Method:UpdateChildTicketWith:public_log' => 'Copie automatique du log public de la demande parente %2$s:

', + 'Class:UserRequest/Method:UpdateChildTicketWith:private_log' => 'Copie automatique du log privé de la demande parente [[UserRequest:%1$s]]:

', 'Menu:UserRequest:MyWorkOrders' => 'Tâches qui me sont assignées', 'Menu:UserRequest:MyWorkOrders+' => '', 'Class:Problem:KnownProblemList' => 'Problèmes connus', diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index f9a5ec37f6..99ee4c78e8 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -351,6 +351,63 @@ }]]>
+ + 'private_log']) + * So in the example parent.public_log will be copied into each child.private_log + * with a prefix using a dictionary entry like 'Class:/Method:UpdateChildTicketWith:' with one placeholder %1$s for the parent ticket ref + * resulting in an entry like "Copy of public log entry from parent Incident I-000123: " in the child private_log + * + */]]> + + false + public + LifecycleAction + GetTargetClass() !== get_class($this))) { + ErrorLog::Debug("Attribute $sChildParentAttCode should be an external key of class $sChildClass, pointing to class ".get_class($this),"DataModel"); + return true; // Do nothing + } + + $sParentClass = get_class($this); + $aChildEntries = []; + foreach ($aLogAttCodes as $sParentAttCode => $sChildAttCode) { + if (MetaModel::IsValidAttCode($sParentClass, $sParentAttCode) && MetaModel::GetAttributeDef($sParentClass, $sParentAttCode) instanceof AttributeCaseLog + && MetaModel::IsValidAttCode($sChildClass, $sChildAttCode) && MetaModel::GetAttributeDef($sChildClass, $sChildAttCode) instanceof AttributeCaseLog + && (!utils::IsNullOrEmptyString($this->Get($sParentAttCode)->GetModifiedEntry('html')))) { + $aChildEntries[$sChildAttCode] = Dict::Format('Class:'.$sParentClass.'/Method:UpdateChildTicketWith:'.$sParentAttCode, $this->GetKey(), $this->Get('ref')).$this->Get($sParentAttCode)->GetModifiedEntry('html'); + } + } + if ($aChildEntries == []) { + return true; // nothing to update + } + + $sOQL = "SELECT $sChildClass WHERE $sChildParentAttCode = :ticket_id"; + $oChildSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sOQL), [], ['ticket_id' => $this->GetKey()]); + while ($oChild = $oChildSet->Fetch()) { + if (is_object($oChild)) { // Seems that empty set, maybe in case of OQL syntax error, can return a single empty object instead of no object at all + foreach ($aChildEntries as $sAttCode => $sEntry) { + $oChild->Set($sAttCode, $sEntry); + } + $oChild->DBUpdate(); + } + } + return true; + + }]]> +
diff --git a/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-tickets/UpdateChildTicketLogTest.php b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-tickets/UpdateChildTicketLogTest.php new file mode 100644 index 0000000000..5781d0711b --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-tickets/UpdateChildTicketLogTest.php @@ -0,0 +1,166 @@ + +// + +namespace Combodo\iTop\Test\UnitTest\Module\iTopTickets; + +use Combodo\iTop\Test\UnitTest\ItopDataTestCase; +use ormCaseLog; +use MetaModel; + +class UpdateChildTicketLogTest extends ItopDataTestCase +{ + public function testUpdateChildTicketLog_PublicLogOnTwoChild(): void + { + //Given a parent ticket with two child ticket + list($iParentTicket, $aChildrenTree) = $this->GivenUserRequests(2); + $this->assertCount(2, $aChildrenTree[$iParentTicket], 'The test setup should create exactly two child tickets.'); + $sParentPublicLogEntry = 'This is a public log entry for the parent ticket.'; + $oParentTicket = MetaModel::GetObject('UserRequest', $iParentTicket); + + // When I enter a public_log entry for the parent ticket + $oParentTicket->Set('public_log', $sParentPublicLogEntry); + $oParentTicket->DBUpdate(); + + // Then the log should be copied to all descendants and contain parent references recursively + $this->AssertLogContainsParentsRefOrKeyRecursively($iParentTicket, $aChildrenTree[$iParentTicket], 'public_log', $sParentPublicLogEntry); + } + + public function testUpdateChildTicketLog_PrivateAndPublicLog(): void + { + //Given a parent ticket with two child ticket + list($iParentTicket, $aChildrenTree) = $this->GivenUserRequests(3); + $sParentPublicLogEntry = 'This is a public log entry for the parent ticket.'; + $sParentPrivateLogEntry = 'This is a private log entry for the parent ticket.'; + + // When I enter both a public_log and a private_log entry for the parent ticket + $oParentTicket = MetaModel::GetObject('UserRequest', $iParentTicket); + $oParentTicket->Set('public_log', $sParentPublicLogEntry); + $oParentTicket->Set('private_log', $sParentPrivateLogEntry); + $oParentTicket->DBUpdate(); + + // Then both logs should be copied to all descendants and keep ancestor references recursively + $this->AssertLogContainsParentsRefOrKeyRecursively($iParentTicket, $aChildrenTree[$iParentTicket], 'public_log', $sParentPublicLogEntry); + $this->AssertLogContainsParentsRefOrKeyRecursively($iParentTicket, $aChildrenTree[$iParentTicket], 'private_log', $sParentPrivateLogEntry); + } + + public function testUpdateChildTicketLog_PrivateLogOnMultipleLevels(): void + { + //Given a parent ticket with two child ticket + list($iParentTicket, $aChildrenTree) = $this->GivenUserRequests(1, 4); + $sParentPrivateLogEntry = 'This is a private log entry for the parent ticket.'; + + // When I enter both a public_log and a private_log entry for the parent ticket + $oParentTicket = MetaModel::GetObject('UserRequest', $iParentTicket); + $oParentTicket->Set('private_log', $sParentPrivateLogEntry); + $oParentTicket->DBUpdate(); + + // Then the private log should be copied on each level with parent + grand-parent +... references + $this->AssertLogContainsParentsRefOrKeyRecursively($iParentTicket, $aChildrenTree[$iParentTicket], 'private_log', $sParentPrivateLogEntry); + } + + private function AssertLogContainsParentsRefOrKeyRecursively(int $iParentTicket, array $aChildrenTree, string $sLogAttCode, string $sExpectedEntry, array $aAncestors = []): void + { + $oParentTicket = MetaModel::GetObject('UserRequest', $iParentTicket); + $aAncestorsToFind = array_merge($aAncestors, [[ + 'id' => (string) $iParentTicket, + 'ref' => (string) $oParentTicket->Get('ref'), + ]]); + + foreach ($aChildrenTree as $iChildOrIndex => $aChildNodeOrId) { + if (is_array($aChildNodeOrId)) { + $iChildTicket = (int) $iChildOrIndex; + $aGrandChildrenTree = $aChildNodeOrId; + } else { + $iChildTicket = (int) $aChildNodeOrId; + $aGrandChildrenTree = []; + } + + $oChildTicket = MetaModel::GetObject('UserRequest', $iChildTicket); + $sLastLogEntry = $oChildTicket->Get($sLogAttCode)->GetLatestEntry(); + $this->assertNotEmpty($sLastLogEntry, "The $sLogAttCode entry was not copied to child ticket #$iChildTicket."); + $this->assertStringContainsString($sExpectedEntry, $sLastLogEntry, "The $sLogAttCode entry on child ticket #$iChildTicket does not contain the original parent entry."); + foreach ($aAncestorsToFind as $aAncestor) { + $bContainsAncestorRef = strpos($sLastLogEntry, $aAncestor['ref']) !== false; + $bContainsAncestorId = strpos($sLastLogEntry, $aAncestor['id']) !== false; + $this->assertTrue( + $bContainsAncestorRef || $bContainsAncestorId, + "The $sLogAttCode entry on child ticket #$iChildTicket does not contain ancestor ref '{$aAncestor['ref']}' nor ancestor id '{$aAncestor['id']}'." + ); + } + + if ($aGrandChildrenTree !== []) { + $this->AssertLogContainsParentsRefOrKeyRecursively($iChildTicket, $aGrandChildrenTree, $sLogAttCode, $sExpectedEntry, $aAncestorsToFind); + } + } + } + /** + * @return array + * @throws \Exception + */ + private function GivenUserRequests(int $iCount, int $iLevel = 2): array + { + $iOrg = $this->GivenObjectInDB('Organization', [ + 'name' => 'Test Organization for Log Update', + ]); + // Given a parent ticket + $iParentTicket = $this->GivenObjectInDB('UserRequest', [ + 'title' => 'Parent Ticket for Log Update Test', + 'description' => 'This is the parent ticket for testing log updates.', + 'org_id' => $iOrg, + 'ref' => 'R-00001', + ]); + + $iRemainingLevels = max(0, $iLevel - 1); + $iRefCounter = 1; + $aChildrenTree = [ + $iParentTicket => $this->GivenChildTicketsRecursive($iParentTicket, $iCount, $iRemainingLevels, $iOrg, $iRefCounter), + ]; + + return [$iParentTicket, $aChildrenTree]; + } + + private function GivenChildTicketsRecursive(int $iParentTicket, int $iCount, int $iRemainingLevels, int $iOrg, int &$iRefCounter): array + { + if ($iRemainingLevels <= 0) { + return []; + } + + $aChildren = []; + for ($i = 1; $i <= $iCount; $i++) { + $iRefCounter++; + $sRef = sprintf('R-%05d', $iRefCounter); + $iChildTicket = $this->GivenObjectInDB('UserRequest', [ + 'title' => "Child Ticket $sRef for Log Update Test", + 'description' => "This is child ticket $sRef for testing log updates.", + 'org_id' => $iOrg, + 'parent_request_id' => $iParentTicket, + 'ref' => $sRef, + ]); + + if ($iRemainingLevels > 1) { + $aChildren[$iChildTicket] = $this->GivenChildTicketsRecursive($iChildTicket, $iCount, $iRemainingLevels - 1, $iOrg, $iRefCounter); + } else { + $aChildren[] = $iChildTicket; + } + } + + return $aChildren; + } +}