Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multistep validation/approval #19069

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions inc/relation.constant.php
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,10 @@
'glpi_users' => 'usertitles_id',
],

'glpi_validationsteps' => [
'glpi_ticketvalidations' => 'validationsteps_id', // should it be _glpi_tickets ?
],

'glpi_virtualmachinestates' => [
'glpi_itemvirtualmachines' => 'virtualmachinestates_id',
],
Expand Down
5 changes: 5 additions & 0 deletions install/empty_data.php
Original file line number Diff line number Diff line change
Expand Up @@ -9520,6 +9520,11 @@ public function getEmptyData(): array
];
}

// initial validation steps
foreach (ValidationStep::getDefaults() as $validation_step) {
$tables[ValidationStep::getTable()][] = $validation_step;
}

return $tables;
}
};
Expand Down
159 changes: 159 additions & 0 deletions install/migrations/update_10.0.x_to_11.0.0/validationsteps.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2025 Teclib' and contributors.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
* @var \DBmysql $DB
* @var \Migration $migration
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

$migration->log('Preparing ValidationSteps migration', false);

create_validation_steps_table($migration);
insert_validation_steps_defaults($migration, $DB);
alter_validations_tables($migration, ['glpi_ticketvalidations']);
add_approval_status_to_ticket_templates($migration);

$migration->executeMigration();

$migration->log('ValidationSteps migration done', false);

function create_validation_steps_table(Migration $migration): void
{
$charset = DBConnection::getDefaultCharset();
$collation = DBConnection::getDefaultCollation();
$pk_sign = DBConnection::getDefaultPrimaryKeySignOption();

$query = "CREATE TABLE IF NOT EXISTS `glpi_validationsteps` (
`id` int {$pk_sign} NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`minimal_required_validation_percent` smallint NOT NULL DEFAULT '100',
`is_default` tinyint NOT NULL DEFAULT '0',
`date_mod` timestamp NULL DEFAULT NULL,
`date_creation` timestamp NULL DEFAULT NULL,
`comment` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=$charset COLLATE=$collation ROW_FORMAT=DYNAMIC;";

$migration->addPreQuery($query);
// $migration needs to be executed before adding keys
// because addKey() ckecks if key exists, so the table must exists
$migration->executeMigration();

$migration->addKey('glpi_validationsteps', 'name');
$migration->addKey('glpi_validationsteps', 'date_mod');
$migration->addKey('glpi_validationsteps', 'date_creation');
}

/**
* Add `validationstep_id` column in $validation_tables, after `id` column
*/
function alter_validations_tables(Migration $migration, array $validation_tables): void
{
foreach ($validation_tables as $table) {
$foreignKeyField = ValidationStep::getForeignKeyField();
$migration->addField(
$table,
$foreignKeyField,
'fkey',
[
'value' => '0',
'null' => false,
'after' => 'id',
]
);
$migration->addKey($table, $foreignKeyField);
}
}

function insert_validation_steps_defaults(Migration $migration, \DBmysql $DB): void
{
if (!$DB->tableExists(ValidationStep::getTable())) {
$migration->log('ValidationSteps table does not exist, skipping defaults insertion', true);

return;
}

$table_empty = (new DbUtils())->countElementsInTable(ValidationStep::getTable()) === 0;
if (!$table_empty) {
$migration->log('ValidationSteps table already filled, skipping defaults insertion', true);

return;
}

$defaults = ValidationStep::getDefaults();
foreach ($defaults as $values) {
$values = array_map([$DB, 'escape'], $values);
$migration->addPostQuery(
sprintf(
'INSERT INTO `glpi_validationsteps` (`name`, `minimal_required_validation_percent`, `is_default`, `date_mod`, `date_creation`) VALUES ("%s", "%s", "%s", "%s", "%s")',
$values['name'],
$values['minimal_required_validation_percent'],
$values['is_default'],
$values['date_mod'],
$values['date_creation']
)
);
}
}
function add_approval_status_to_ticket_templates(Migration $migration): void
{

$migration->log('Adding approval_status to ticket templates', false);

$migration->changeField(
'glpi_tickettemplates',
'allowed_statuses',
'allowed_statuses',
'string',
[
'value' => '[1,10,2,3,4,5,6]',
'null' => false,
'after' => 'comment',
]
);
}



/**
* CREATE TABLE `glpi_tickettemplates` (
* `id` int unsigned NOT NULL AUTO_INCREMENT,
* `name` varchar(255) DEFAULT NULL,
* `entities_id` int unsigned NOT NULL DEFAULT '0',
* `is_recursive` tinyint NOT NULL DEFAULT '0',
* `comment` text,
* `allowed_statuses` varchar(255) NOT NULL DEFAULT '[1,10,2,3,4,5,6]',
* PRIMARY KEY (`id`),
* KEY `name` (`name`),
* KEY `entities_id` (`entities_id`),
* KEY `is_recursive` (`is_recursive`)
* ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
*/
33 changes: 31 additions & 2 deletions install/mysql/glpi-empty.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7588,7 +7588,7 @@ CREATE TABLE `glpi_tickettemplates` (
`entities_id` int unsigned NOT NULL DEFAULT '0',
`is_recursive` tinyint NOT NULL DEFAULT '0',
`comment` text,
`allowed_statuses` varchar(255) NOT NULL DEFAULT '[1,2,3,4,5,6]',
`allowed_statuses` varchar(255) NOT NULL DEFAULT '[1,10,2,3,4,5,6]',
PRIMARY KEY (`id`),
KEY `name` (`name`),
KEY `entities_id` (`entities_id`),
Expand Down Expand Up @@ -7636,6 +7636,7 @@ CREATE TABLE `glpi_ticketvalidations` (
`entities_id` int unsigned NOT NULL DEFAULT '0',
`users_id` int unsigned NOT NULL DEFAULT '0',
`tickets_id` int unsigned NOT NULL DEFAULT '0',
`validationsteps_id` int unsigned NOT NULL DEFAULT '0',
`users_id_validate` int unsigned NOT NULL DEFAULT '0',
`itilvalidationtemplates_id` int unsigned NOT NULL DEFAULT '0',
`itemtype_target` varchar(255) NOT NULL,
Expand All @@ -7656,10 +7657,28 @@ CREATE TABLE `glpi_ticketvalidations` (
KEY `tickets_id` (`tickets_id`),
KEY `submission_date` (`submission_date`),
KEY `validation_date` (`validation_date`),
KEY `status` (`status`)
KEY `status` (`status`),
KEY `validationsteps_id` (`validationsteps_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;


### Dump table glpi_validationsteps - used by ValidationStep Dropdown

DROP TABLE IF EXISTS `glpi_validationsteps`;
CREATE TABLE `glpi_validationsteps` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`minimal_required_validation_percent` smallint NOT NULL DEFAULT '100',
`is_default` tinyint NOT NULL DEFAULT '0',
`date_mod` timestamp NULL DEFAULT NULL,
`date_creation` timestamp NULL DEFAULT NULL,
`comment` text,
PRIMARY KEY (`id`),
KEY `name` (`name`),
KEY `date_mod` (`date_mod`),
KEY `date_creation` (`date_creation`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;

### Dump table glpi_transfers

DROP TABLE IF EXISTS `glpi_transfers`;
Expand Down Expand Up @@ -10139,3 +10158,13 @@ CREATE TABLE `glpi_softwarelicenses_users` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;

SET FOREIGN_KEY_CHECKS=1;
#
#
#
#
#
#
#
#
#
#
7 changes: 5 additions & 2 deletions phpunit/functional/RuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,16 @@ public function testGetRuleWithCriteriasAndActions()
$this->assertFalse($rule->getRuleWithCriteriasAndActions(10000));
}

public function testMaxActionsCount()
/**
* Test the number of predefined actions in database.
*/
public function testMaxActionsCount(): void
{
$rule = new \Rule();
$this->assertSame(1, $rule->maxActionsCount());

$rule = new \RuleTicket();
$this->assertSame(45, $rule->maxActionsCount());
$this->assertSame(46, $rule->maxActionsCount());

$rule = new \RuleDictionnarySoftware();
$this->assertSame(7, $rule->maxActionsCount());
Expand Down
74 changes: 74 additions & 0 deletions phpunit/functional/RuleTicketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1347,4 +1347,78 @@ public function testDoNotComputeStatusFollowupWithRule()

$this->assertEquals(\CommonITILObject::ASSIGNED, $ticket->fields['status']);
}

/**
* - create rule on ticket creation
* - criterions : date after now
* - actions :
* - add a validation for user
*/
public function testAssignValidationStepByDefault(): void
{
$this->login();
$user_id = (int) getItemByTypeName('User', '_test_user', true);
$entity = getItemByTypeName('Entity', '_test_root_entity', true); // 4
assert(0 === countElementsInTable(\TicketValidation::getTable()), 'test expects no validation at start');

// arrange
$rule_builder = new \RuleBuilder('Add an approval', \RuleTicket::class);
$rule_builder
->setEntity($entity)
->setCondtion(\RuleTicket::ONADD)
->setIsRecursive(false)
->addCriteria('date', \Rule::PATTERN_DATE_IS_AFTER, date('2024-m-d'))
->addAction('add_validation', 'users_id_validate', $user_id);
// ->addAction('validationsteps_id', 'validationsteps_id', 1)
$this->createRule($rule_builder);

// act
$this->createItem(\Ticket::class, [
'name' => __METHOD__,
'content' => __METHOD__,
'entities_id' => $entity,
]);

// assert : a validation is created and the validation step is set to the default value.
$validations = (new \TicketValidation())->find();
$this->assertCount(1, $validations);
$validation = array_pop($validations);
$this->assertEquals(\ValidationStep::getDefault()->getID(), $validation['validationsteps_id']);
}

public function testAssignCreatedValidationStep(): void
{
$this->login();
$user_id = (int) getItemByTypeName('User', '_test_user', true);
$entity = getItemByTypeName('Entity', '_test_root_entity', true); // 4
assert(0 === countElementsInTable(\TicketValidation::getTable()), 'test expects no validation at start');

// arrange
$new_validationsteps_id = ($this->createItem(\ValidationStep::class, [
'name' => 'Tech team',
'minimal_required_validation_percent' => 100,
]))->getID();
$builder = new \RuleBuilder('Add an approval', \RuleTicket::class);
$builder
->setEntity($entity)
->setCondtion(\RuleTicket::ONADD)
->setIsRecursive(false)
->addCriteria('date', \Rule::PATTERN_DATE_IS_AFTER, date('2024-m-d'))
->addAction('add_validation', 'users_id_validate', $user_id)
->addAction('assign', 'validationsteps_id', $new_validationsteps_id);
$this->createRule($builder);

// act
$this->createItem(\Ticket::class, [
'name' => __METHOD__,
'content' => __METHOD__,
'entities_id' => $entity,
]);

// assert : a validation is created and the validation step is set to the default value.
$validations = (new \TicketValidation())->find();
$this->assertCount(1, $validations, 'There must be only a single Validation created.');
$validation = array_pop($validations);
$this->assertEquals($new_validationsteps_id, $validation['validationsteps_id'], 'The validationstep_id must be the just created one.');
}
}
1 change: 1 addition & 0 deletions phpunit/functional/TicketValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ public function testgetNumberToValidate(
'tickets_id' => $ticket->getID(),
'itemtype_target' => 'User',
'items_id_target' => $user_id,
'validationsteps_id' => $this->getInitialDefaultValidationStep()->getID()
]);

$this->assertEquals($expected ? ($initial_count + 1) : $initial_count, \TicketValidation::getNumberToValidate($user_id));
Expand Down
Loading
Loading