From 601a30c0fb5db6f35aca9f0ee805ce35762985d5 Mon Sep 17 00:00:00 2001 From: atisne Date: Tue, 12 May 2026 11:25:23 +0200 Subject: [PATCH] fix: #0009394 Cannot unassign requirement of a test case after deleting its execution We cannot unassign a requirement on a test case whose its execution has been removed. If a test case is not (or rather no more) executed, we should be able to update the associated requirements. We update the execution deletion to revert the link status between the test case and the requirements. The execution deletion was implemented twice. We merged the both to keep only one. --- lib/execute/execSetResults.php | 2 +- lib/functions/exec.inc.php | 71 -------------------- lib/functions/testcase.class.php | 111 +++++++++++++++++++++++++++---- 3 files changed, 99 insertions(+), 85 deletions(-) diff --git a/lib/execute/execSetResults.php b/lib/execute/execSetResults.php index f78c827165..e393949b31 100644 --- a/lib/execute/execSetResults.php +++ b/lib/execute/execSetResults.php @@ -353,7 +353,7 @@ if(!$args->reload_caller) { if ($args->doDelete) { - $dummy = delete_execution($db,$args->exec_to_delete); + $dummy = $tcase_mgr->deleteExecution($args->exec_to_delete); if ($dummy){ $tc_info = $tcase_mgr->getExternalID($tcase_id); $tp_info = $tplan_mgr->get_by_id($args->tplan_id); diff --git a/lib/functions/exec.inc.php b/lib/functions/exec.inc.php index 1e27f523fa..b0780c9641 100644 --- a/lib/functions/exec.inc.php +++ b/lib/functions/exec.inc.php @@ -517,77 +517,6 @@ function get_execution(&$dbHandler,$execution_id,$opt=null) return $rs; } -/** - * delete one test execution from database (include child data and relations) - * - * @param resource &$db reference to database handler - * @param datatype $execution_id - * - * @return boolean result of delete - * - * @TODO delete attachments FROM DISK when are saved on Filesystem - * @TODO run SQL as transaction if database engine allows - **/ -function delete_execution(&$db,$exec_id) -{ - $tables = tlObjectWithDB::getDBTables( - array('executions','execution_bugs','cfield_execution_values', - 'execution_tcsteps','attachments')); - - $sid = intval($exec_id); - - - // Attachments NEED special processing. - - // get test step exec attachments if any exists - $dummy = " SELECT id FROM {$tables['execution_tcsteps']} " . - " WHERE execution_id = {$sid}"; - - $rs = $db->fetchRowsIntoMap($dummy,'id'); - if(!is_null($rs)) - { - foreach($rs as $fik => $v) - { - deleteAttachment($db,$fik,false); - } - } - - - // execution attachments - $dummy = " SELECT id FROM {$tables['attachments']} " . - " WHERE fk_table = 'executions' AND fk_id = {$sid}"; - - $rs = $db->fetchRowsIntoMap($dummy,'id'); - if(!is_null($rs)) - { - foreach($rs as $fik => $v) - { - deleteAttachment($db,$fik,false); - } - } - - // order is CRITIC, because is DELETING ORDER => ATTENTION to Foreing Keys - $sql = array(); - $sql[] = "DELETE FROM {$tables['execution_bugs']} WHERE execution_id = {$sid}"; - $sql[] = "DELETE FROM {$tables['cfield_execution_values']} WHERE execution_id = {$sid}"; - $sql[] = "DELETE FROM {$tables['execution_tcsteps']} WHERE execution_id = {$sid}"; - - // This delete HAS TO BE THE LATEST, because is the PARENT - $ldx = count($sql); - $sql[$ldx] = "DELETE FROM {$tables['executions']} WHERE id = {$sid}"; - - foreach ($sql as $the_stm) - { - $result = $db->exec_query($the_stm); - if (!$result) - { - break; - } - } - - return $result; -} - /** * @param $db resource the database connecton * @param $execID integer the execution id whose notes should be set diff --git a/lib/functions/testcase.class.php b/lib/functions/testcase.class.php index 06818dcf0e..7f10180150 100644 --- a/lib/functions/testcase.class.php +++ b/lib/functions/testcase.class.php @@ -4845,26 +4845,75 @@ private function getShowViewerActions($mode) { } /** - * given an executio id delete execution and related data. - * - */ - function deleteExecution($executionID) - { - $whereClause = " WHERE execution_id = {$executionID} "; - $sql = array("DELETE FROM {$this->tables['execution_bugs']} {$whereClause} ", - "DELETE FROM {$this->tables['cfield_execution_values']} {$whereClause} ", - "DELETE FROM {$this->tables['executions']} WHERE id = {$executionID}" ); + * given an execution id delete execution and related data. + * + */ + function deleteExecution($executionID) { + $execID = intval($executionID); - foreach ($sql as $the_stm) - { + // Delete attachements + // of steps + $sql = "SELECT id FROM {$this->tables['execution_tcsteps']} " . + "WHERE execution_id = {$execID}"; + + $rs = $this->db->fetchRowsIntoMap($sql, 'id'); + if(!is_null($rs)) { + foreach($rs as $fik => $v) { + deleteAttachment($this->db, $fik, false); + } + } + // of the execution + $sql = "SELECT id FROM {$this->tables['attachments']} " . + "WHERE fk_table = 'executions' AND fk_id = {$execID}"; + + $rs = $this->db->fetchRowsIntoMap($sql, 'id'); + if(!is_null($rs)) { + foreach($rs as $fik => $v) { + deleteAttachment($this->db, $fik, false); + } + } + + // Retrieve TC version ID + $sql = "SELECT tcversion_id from {$this->tables['executions']} WHERE id={$execID}"; + $tcversion_id = $this->db->fetchOneValue($sql); + + // Delete the execution and linked data + $whereClause = "WHERE execution_id = {$execID}"; + $sql = array("DELETE FROM {$this->tables['execution_bugs']} {$whereClause}", + "DELETE FROM {$this->tables['cfield_execution_values']} {$whereClause}", + "DELETE FROM {$this->tables['execution_tcsteps']} {$whereClause}", + "DELETE FROM {$this->tables['executions']} WHERE id = {$execID}"); + + foreach ($sql as $the_stm) { $result = $this->db->exec_query($the_stm); - if (!$result) - { + if (!$result) { break; } } + + // Reopen closed relations by exec + $tcvRelations = (array) $this->getTCVRelationsRaw($tcversion_id); + if( count($tcvRelations) > 0 ) { + $itemSet = array_keys($tcvRelations); + $this->openClosedTCVRelation($itemSet, LINK_TC_RELATION_CLOSED_BY_EXEC); + } + + // Reopen closed requirements by exec + // Check if Test Project has the requirement management feature enabled + $tproject_id = $this->tproject_id; + if (!$tproject_id) { + // retrieve TC ID + $tcase_id = $this->tree_manager->get_node_hierarchy_info($tcversion_id)['parent_id']; + + $tproject_id = $this->getTestProjectFromTestCase($tcase_id); + } + $topt = $this->tproject_mgr->getOptions($tproject_id); + if( $topt->requirementsEnabled ) { + $this->openClosedReqLinks($tcversion_id, + LINK_TC_REQ_CLOSED_BY_EXEC); } + } @@ -8755,6 +8804,22 @@ function closeOpenTCVRelation( $relationID, $reason ) { } + /** + * Reopen closed TCV relation after execution deletion + */ + function openClosedTCVRelation($relationID, $reason) { + + $debugMsg = "/* {$this->debugMsg}" . __FUNCTION__ . ' */ '; + $sql = " $debugMsg UPDATE {$this->tables['testcase_relations']} " . + " SET link_status = " . LINK_TC_RELATION_OPEN . + " WHERE id IN(" . implode(',', $relationID) . ")" . + " AND link_status = " . intval($reason); + + $this->db->exec_query($sql); + + } + + /** * **/ @@ -8900,6 +8965,26 @@ function closeOpenReqLinks( $tcversion_id, $reason, $opt=null ) { // No audit yet } + /** + * Reopen closed requirement links after execution deletion + */ + function openClosedReqLinks($tcversion_id, $reason) { + + $debugMsg = "/* {$this->debugMsg}" . __FUNCTION__ . ' */ '; + + $commonWhere = " WHERE tcversion_id = " . intval($tcversion_id) . + " AND link_status = " . intval($reason); + + /* Note: We can unfreeze requirements only if there is no others + executed TC linked to them */ + + // Work on Coverage + $sql = "$debugMsg UPDATE {$this->tables['req_coverage']} " . + " SET link_status = " . LINK_TC_REQ_OPEN . + $commonWhere; + $this->db->exec_query($sql); + } + /** * */