From 299abcf3764795967f42994c55cff4460e48a7ef Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Thu, 12 Mar 2026 11:25:21 +0100 Subject: [PATCH 1/8] refactor: Follow embodied impact refacctoring --- src/AbstractImpact.php | 90 +++++++++++++++++++ src/CronTask.php | 43 ++++++--- src/EmbodiedImpact.php | 68 +------------- .../Embodied/EmbodiedImpactInterface.php | 2 +- src/Impact/Usage/AbstractUsageImpact.php | 59 ++++++------ src/Impact/Usage/Boavizta/Computer.php | 23 +++-- src/Impact/Usage/Engine.php | 9 +- src/Impact/Usage/UsageImpactInterface.php | 23 +++-- src/Toolbox.php | 12 +++ tests/units/EmbodiedImpactTest.php | 4 +- tests/units/Impact/Usage/EngineTest.php | 12 +-- 11 files changed, 202 insertions(+), 143 deletions(-) diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php index 26a88457..b965b44a 100644 --- a/src/AbstractImpact.php +++ b/src/AbstractImpact.php @@ -34,6 +34,10 @@ use CommonDBChild; use GlpiPlugin\Carbon\Impact\Type; +use CommonDBTM; +use DBmysql; +use DBmysqlIterator; +use Toolbox as GlpiToolbox; abstract class AbstractImpact extends CommonDBChild { @@ -42,6 +46,11 @@ abstract class AbstractImpact extends CommonDBChild public static $rightname = 'carbon:report'; + public function canEdit($ID): bool + { + return false; + } + public function rawSearchOptions() { $tab = parent::rawSearchOptions(); @@ -132,6 +141,87 @@ public function rawSearchOptions() return $tab; } + /** + * Get iterator of items without known embodied impact for a specified itemtype + * + * @template T of CommonDBTM + * @param class-string $itemtype + * @param array $crit Criteria array of WHERE, ORDER, GROUP BY, LEFT JOIN, INNER JOIN, RIGHT JOIN, HAVING, LIMIT + * @return DBmysqlIterator + */ + public static function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlIterator + { + /** @var DBmysql $DB */ + global $DB; + + // Check $itemtype inherits from CommonDBTM + if (!GlpiToolbox::isCommonDBTM($itemtype)) { + throw new \LogicException('itemtype is not a CommonDBTM object'); + } + + // clean $crit array: remove mostly SELECT, FROM + $crit = array_intersect_key($crit, array_flip([ + 'WHERE', + 'ORDER', + 'GROUP BY', + 'LEFT JOIN', + 'INNER JOIN', + 'RIGHT JOIN', + 'HAVING', + 'LIMIT', + ])); + + $table = self::getTable(); + $glpi_item_type_table = getTableForItemType($itemtype . 'Type'); + $glpi_item_type_fk = getForeignKeyFieldForTable($glpi_item_type_table); + $item_type_table = getTableForItemType('GlpiPlugin\\Carbon\\' . $itemtype . 'Type'); + $item_table = $itemtype::getTable(); + + $iterator = $DB->request(array_merge_recursive([ + 'SELECT' => [ + $itemtype::getTableField('id'), + ], + 'FROM' => $item_table, + 'LEFT JOIN' => [ + $table => [ + 'FKEY' => [ + $table => 'items_id', + $item_table => 'id', + + ], + 'AND' => [ + 'itemtype' => $itemtype, + ], + ], + $item_type_table => [ + [ + 'FKEY' => [ + $item_type_table => $glpi_item_type_fk, + $item_table => $glpi_item_type_fk, + ], + ], + ], + ], + 'WHERE' => [ + [ + // No calculated data or data to recalculate + 'OR' => [ + self::getTableField('items_id') => null, + self::getTableField('recalculate') => 1, + ], + ], [ + // Item not marked to exclude from calculation + 'OR' => [ + $item_type_table . '.is_ignore' => 0, + $item_type_table . '.id' => null, + ], + ] + ], + ], $crit)); + + return $iterator; + } + /** * Get impact value in a human r eadable format, selecting the best unit * diff --git a/src/CronTask.php b/src/CronTask.php index e7d3bbbc..65956533 100644 --- a/src/CronTask.php +++ b/src/CronTask.php @@ -140,6 +140,13 @@ public static function cronUsageImpact(GlpiCronTask $task): int // Half of job for GWP, the other half for other impacts $limit_per_type = max(1, floor($limit_per_type / 2)); + /** + * Huge quantity of SQL queries will be executed + * We NEED to check memory usage to avoid running out of memory + * @see DbMysql::doQuery() + */ + $memory_limit = Toolbox::getMemoryLimit(); + // Calculate GWP $count = 0; foreach ($usage_impacts as $usage_impact_type) { @@ -151,14 +158,29 @@ public static function cronUsageImpact(GlpiCronTask $task): int } // Calculate other impacts + $limit = ['LIMIT' => $limit_per_type]; foreach (PLUGIN_CARBON_TYPES as $itemtype) { - $usage_impact = UsageEngine::getEngineFromItemtype($itemtype); - if ($usage_impact === null) { - continue; + foreach (UsageImpact::getItemsToEvaluate($itemtype, $limit) as $row) { + $item = new $itemtype(); + if (!$item->getFromDB($row['id'])) { + continue; + } + $usage_impact = UsageEngine::getEngineFromItemtype($item); + if ($usage_impact === null) { + // An error occured while configuring the engine + continue; + } + if ($usage_impact->evaluateItem()) { + $count++; + } + + // Check free memory + if ($memory_limit && $memory_limit < memory_get_usage()) { + // 8 MB memory left, emergency exit + // Terminate the task + break 2; + } } - $usage_impact->setLimit($limit_per_type); - $count = $usage_impact->evaluateItems($usage_impact->getItemsToEvaluate()); - $task->addVolume($count); } return ($count > 0 ? 1 : 0); @@ -183,18 +205,13 @@ public static function cronEmbodiedImpact(GlpiCronTask $task): int * We NEED to check memory usage to avoid running out of memory * @see DbMysql::doQuery() */ - $memory_limit = GlpiToolbox::getMemoryLimit() - 8 * 1024 * 1024; - if ($memory_limit < 0) { - // May happen in test seems that ini_get("memory_limits") returns - // enpty string in PHPUnit environment - $memory_limit = null; - } + $memory_limit = Toolbox::getMemoryLimit(); /** @var int $count count of successfully evaluated assets */ $count = 0; $limit = ['LIMIT' => $limit_per_type]; foreach (PLUGIN_CARBON_TYPES as $itemtype) { - foreach (AbstractEmbodiedImpact::getItemsToEvaluate($itemtype, $limit) as $row) { + foreach (EmbodiedImpact::getItemsToEvaluate($itemtype, $limit) as $row) { $item = new $itemtype(); if (!$item->getFromDB($row['id'])) { continue; diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index b609ef71..9d391722 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -49,79 +49,13 @@ public static function getTypeName($nb = 0) return _n("Embodied impact", "Embodied impacts", $nb, 'carbon'); } - public function canEdit($ID): bool - { - return false; - } - - /** - * Get iterator of items without known embodied impact for a specified itemtype - * - * @template T of CommonDBTM - * @param class-string $itemtype - * @param array $crit Criteria array of WHERE, ORDER, GROUP BY, LEFT JOIN, INNER JOIN, RIGHT JOIN, HAVING, LIMIT - * @return DBmysqlIterator - */ - public static function getAssetsToCalculate(string $itemtype, array $crit = []): DBmysqlIterator - { - /** @var DBmysql $DB */ - global $DB; - - // Check $itemtype inherits from CommonDBTM - if (!GlpiToolbox::isCommonDBTM($itemtype)) { - throw new \LogicException('itemtype is not a CommonDBTM object'); - } - - // clean $crit array: remove mostly SELECT, FROM - $crit = array_intersect_key($crit, array_flip([ - 'WHERE', - 'ORDER', - 'GROUP BY', - 'LEFT JOIN', - 'INNER JOIN', - 'RIGHT JOIN', - 'HAVING', - 'LIMIT', - ])); - - $table = self::getTable(); - $itemtype_table = $itemtype::getTable(); - - $iterator = $DB->request(array_merge_recursive([ - 'SELECT' => [ - $itemtype::getTableField('id'), - ], - 'FROM' => $itemtype_table, - 'LEFT JOIN' => [ - $table => [ - 'FKEY' => [ - $table => 'items_id', - $itemtype_table => 'id', - - ], - 'AND' => [ - 'itemtype' => $itemtype, - ], - ], - ], - 'WHERE' => [ - 'OR' => [ - self::getTableField('items_id') => null, - self::getTableField('recalculate') => 1, - ], - ], - ], $crit)); - - return $iterator; - } - public function calculateImpact(string $lca_type, int $limit = 0): int { $crit = []; if ($limit > 0) { $crit['LIMIT'] = $limit; } - $iterator = $this->getAssetsToCalculate($lca_type::getItemtype(), $crit); + $iterator = self::getItemsToEvaluate($lca_type::getItemtype(), $crit); $count = 0; foreach ($iterator as $item) { $lca = new $lca_type($item['id']); diff --git a/src/Impact/Embodied/EmbodiedImpactInterface.php b/src/Impact/Embodied/EmbodiedImpactInterface.php index fa9512e2..1b0655b9 100644 --- a/src/Impact/Embodied/EmbodiedImpactInterface.php +++ b/src/Impact/Embodied/EmbodiedImpactInterface.php @@ -62,7 +62,7 @@ public static function getEvaluableQuery(string $itemtype, array $crit = [], boo * * @template T of CommonDBTM * @param class-string $itemtype - * @param array $crit criterias + * @param array $crit criteria * @return DBmysqlIterator */ public static function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlIterator; diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index d3201116..0d663828 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -39,14 +39,15 @@ use DbUtils; use GlpiPlugin\Carbon\DataTracking\AbstractTracked; use GlpiPlugin\Carbon\Impact\Type; +use GlpiPlugin\Carbon\Toolbox; use GlpiPlugin\Carbon\UsageImpact; use Location as GlpiLocation; use Toolbox as GlpiToolbox; abstract class AbstractUsageImpact implements UsageImpactInterface { - /** @var string Handled itemtype */ - protected static string $itemtype = ''; + /** @var CommonDBTM Item to analyze */ + protected CommonDBTM $item; /** @var int maximum number of entries to build */ protected int $limit = 0; @@ -63,8 +64,12 @@ abstract class AbstractUsageImpact implements UsageImpactInterface /** @var array of TrackedFloat */ protected array $impacts = []; - public function __construct() + public function __construct(CommonDBTM $item) { + if ($item->isNewItem()) { + throw new \LogicException("Given item is empty"); + } + $this->item = $item; foreach (array_flip(Type::getImpactTypes()) as $type) { $this->impacts[$type] = null; } @@ -93,22 +98,16 @@ final public function getUnit(int $type, bool $short = true): ?string return null; } - public static function getItemtype(): string - { - return static::$itemtype; - } - public function setLimit(int $limit) { $this->limit = $limit; } - public function getItemsToEvaluate(array $crit = []): DBmysqlIterator + public function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlIterator { /** @var DBmysql $DB */ global $DB; - $itemtype = static::$itemtype; if ($itemtype === '') { throw new \LogicException('Itemtype not set'); } @@ -122,8 +121,7 @@ public function getItemsToEvaluate(array $crit = []): DBmysqlIterator UsageImpact::getTableField('recalculate') => 1, ], ]; - $crit[UsageImpact::getTableField('id')] = null; - $iterator = $DB->request($this->getEvaluableQuery($crit, false)); + $iterator = $DB->request($this->getEvaluableQuery($itemtype, $crit, false)); return $iterator; } @@ -135,12 +133,7 @@ public function evaluateItems(DBmysqlIterator $iterator): int * We NEED to check memory usage to avoid running out of memory * @see DBmysql::doQuery() */ - $memory_limit = GlpiToolbox::getMemoryLimit() - 8 * 1024 * 1024; - if ($memory_limit < 0) { - // May happen in test seems that ini_get("memory_limits") returns - // enpty string in PHPUnit environment - $memory_limit = null; - } + $memory_limit = Toolbox::getMemoryLimit(); /** @var int $attempts_count count of evaluation attempts */ $attempts_count = 0; @@ -168,17 +161,13 @@ public function evaluateItems(DBmysqlIterator $iterator): int return $count; } - public function evaluateItem(int $id): bool + public function evaluateItem(): bool { - $itemtype = static::$itemtype; - $item = $itemtype::getById($id); - if ($item === false) { - return false; - } + $itemtype = get_class($this->item); try { $this->getVersion(); - $impacts = $this->doEvaluation($item); + $impacts = $this->doEvaluation($this->item); } catch (\RuntimeException $e) { return false; } @@ -191,7 +180,7 @@ public function evaluateItem(int $id): bool // Find an existing row, if any $input = [ 'itemtype' => $itemtype, - 'items_id' => $id, + 'items_id' => $this->item->getID(), ]; $usage_impact = new UsageImpact(); $usage_impact->getFromDBByCrit($input); @@ -229,13 +218,21 @@ public function evaluateItem(int $id): bool return false; } - public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true): array + public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array { - $itemtype = static::$itemtype; - $item_table = $itemtype::getTable(); + $item_table = getTableForItemType($itemtype); $glpi_location_table = GlpiLocation::getTable(); + $item_type_table = getTableForItemType('GlpiPlugin\\Carbon\\' . $itemtype . 'Type'); $usage_impact_table = UsageImpact::getTable(); + $crit[] = [ + 'OR' => [ + $item_type_table . '.is_ignore' => 0, + $item_type_table . '.id' => null, + ], + ['NOT' => [GlpiLocation::getTableField('id') => null]] + ]; + $request = [ 'SELECT' => [ $itemtype::getTableField('id'), @@ -260,9 +257,7 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true ], ], ], - 'WHERE' => [ - ['NOT' => [GlpiLocation::getTableField('id') => null]], - ] + $crit, + 'WHERE' => $crit, ]; if ($entity_restrict) { diff --git a/src/Impact/Usage/Boavizta/Computer.php b/src/Impact/Usage/Boavizta/Computer.php index 1788225d..98e9a8ce 100644 --- a/src/Impact/Usage/Boavizta/Computer.php +++ b/src/Impact/Usage/Boavizta/Computer.php @@ -44,6 +44,7 @@ use GlpiPlugin\Carbon\ComputerType; use GlpiPlugin\Carbon\ComputerUsageProfile; use GlpiPlugin\Carbon\Location; +use GlpiPlugin\Carbon\UsageImpact; use GlpiPlugin\Carbon\UsageInfo; use Infocom; use Item_DeviceMemory; @@ -59,9 +60,9 @@ class Computer extends AbstractAsset protected string $endpoint = 'server'; - public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true): array + public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array { - $item_table = self::$itemtype::getTable(); + $item_table = getTableForItemType($itemtype); $item_model_table = self::$model_itemtype::getTable(); $glpi_computertypes_table = GlpiComputerType::getTable(); $computertypes_table = ComputerType::getTable(); @@ -69,6 +70,7 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true $usage_info_table = UsageInfo::getTable(); $computerUsageProfile_table = ComputerUsageProfile::getTable(); $infocom_table = Infocom::getTable(); + $usage_impact_table = UsageImpact::getTable(); $request = [ 'SELECT' => [ @@ -79,7 +81,7 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true $location_table => [ 'FKEY' => [ $item_table => 'locations_id', - $location_table => 'id', + $location_table => 'locations_id', ], ], $usage_info_table => [ @@ -129,6 +131,17 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true ['AND' => [Infocom::getTableField('itemtype') => self::$itemtype]], ], ], + $usage_impact_table => [ + 'FKEY' => [ + $usage_impact_table => 'items_id', + $item_table => 'id', + ['AND' + => [ + UsageImpact::getTableField('itemtype') => $itemtype, + ], + ], + ], + ], ], 'WHERE' => [ 'AND' => [ @@ -146,8 +159,8 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true ['NOT' => [Infocom::getTableField('use_date') => null]], ['NOT' => [Infocom::getTableField('delivery_date') => null]], ['NOT' => [Infocom::getTableField('buy_date') => null]], - ['NOT' => [Infocom::getTableField('date_creation') => null]], - ['NOT' => [Infocom::getTableField('date_mod') => null]], + // ['NOT' => [Infocom::getTableField('date_creation') => null]], + // ['NOT' => [Infocom::getTableField('date_mod') => null]], ], ], [ 'AND' => [ diff --git a/src/Impact/Usage/Engine.php b/src/Impact/Usage/Engine.php index 0c1862ae..7e3aa6d1 100644 --- a/src/Impact/Usage/Engine.php +++ b/src/Impact/Usage/Engine.php @@ -55,12 +55,13 @@ public static function getAvailableBackends(): array * * Returns null if no engine found * - * @template T of CommonDBTM - * @param class-string $itemtype itemtype of assets to analyze + * @param CommonDBTM $item item to analyze * @return AbstractUsageImpact|null an instance if an embodied impact calculation object or null on error */ - public static function getEngineFromItemtype(string $itemtype): ?AbstractUsageImpact + public static function getEngineFromItemtype(CommonDBTM $item): ?AbstractUsageImpact { + $itemtype = get_class($item); + $usage_impact_namespace = Config::getUsageImpactEngine(); $usage_impact_class = $usage_impact_namespace . '\\' . $itemtype; if (!class_exists($usage_impact_class) || !is_subclass_of($usage_impact_class, AbstractUsageImpact::class)) { @@ -68,7 +69,7 @@ public static function getEngineFromItemtype(string $itemtype): ?AbstractUsageIm } /** @var AbstractUsageImpact $usage_impact */ - $usage_impact = new $usage_impact_class(); + $usage_impact = new $usage_impact_class($item); try { return self::configureEngine($usage_impact); } catch (\RuntimeException $e) { diff --git a/src/Impact/Usage/UsageImpactInterface.php b/src/Impact/Usage/UsageImpactInterface.php index dd04f05b..f6d4da29 100644 --- a/src/Impact/Usage/UsageImpactInterface.php +++ b/src/Impact/Usage/UsageImpactInterface.php @@ -47,13 +47,6 @@ interface UsageImpactInterface */ // public static function getEngine(CommonDBTM $item): EngineInterface; - /** - * Get the itemtype of the asset handled by this class - * - * @return string - */ - public static function getItemtype(): string; - /** * Set the maximum count of items to calculate with evaluateItems() * @@ -64,19 +57,24 @@ public function setLimit(int $limit); /** * Get query to find items we can evaluate - * @param array $crit Criterias to aass to WHERE clause + * + * @template T of CommonDBTM + * @param class-string $itemtype + * @param array $crit Criteria * * @return array */ - public function getEvaluableQuery(array $crit = []): array; + public function getEvaluableQuery(string $itemtype, array $crit = []): array; /** * Get an iterator of items to evaluate * - * @param array $crit + * @template T of CommonDBTM + * @param class-string $itemtype + * @param array $crit criteria * @return DBmysqlIterator */ - public function getItemsToEvaluate(array $crit = []): DBmysqlIterator; + public function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlIterator; /** * Start the evaluation of all items @@ -89,8 +87,7 @@ public function evaluateItems(DBmysqlIterator $iterator): int; /** * Evaluate all impacts of the asset * - * @param int $id * @return bool true if success, false otherwise */ - public function evaluateItem(int $id): bool; + public function evaluateItem(): bool; } diff --git a/src/Toolbox.php b/src/Toolbox.php index a9784421..33600302 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -44,6 +44,7 @@ use Infocom; use Location; use Mexitek\PHPColors\Color; +use Toolbox as GlpiToolbox; class Toolbox { @@ -719,4 +720,15 @@ protected static function contrastRatio(Color $color_1, Color $color_2): float $l2 = self::relative_luminance($color_2); return ($l1 > $l2) ? ($l1 + 0.05) / ($l2 + 0.05) : ($l2 + 0.05) / ($l1 + 0.05); } + + public static function getMemoryLimit(): ?int + { + $memory_limit = GlpiToolbox::getMemoryLimit() - 8 * 1024 * 1024; + if ($memory_limit < 0) { + // May happen in test seems that ini_get("memory_limits") returns + // enpty string in PHPUnit environment + $memory_limit = null; + } + return $memory_limit; + } } diff --git a/tests/units/EmbodiedImpactTest.php b/tests/units/EmbodiedImpactTest.php index 41b2dddf..2bbf4e30 100644 --- a/tests/units/EmbodiedImpactTest.php +++ b/tests/units/EmbodiedImpactTest.php @@ -64,11 +64,11 @@ public function testNotCalculatedAssetMustBeCalculated() ]); // Check that we get all not calculated assets for the given itemtype - $iterator = EmbodiedImpact::getAssetsToCalculate($itemtype); + $iterator = EmbodiedImpact::getItemsToEvaluate($itemtype); $this->assertEquals(2, $iterator->count()); // Check that we can filter not calculated assets by ID - $iterator = EmbodiedImpact::getAssetsToCalculate($itemtype, [ + $iterator = EmbodiedImpact::getItemsToEvaluate($itemtype, [ 'WHERE' => [ 'NOT' => [$itemtype::getTableField('id') => $not_calculated_asset_2->getID()], ], diff --git a/tests/units/Impact/Usage/EngineTest.php b/tests/units/Impact/Usage/EngineTest.php index 1a5a31eb..69ba8e39 100644 --- a/tests/units/Impact/Usage/EngineTest.php +++ b/tests/units/Impact/Usage/EngineTest.php @@ -58,17 +58,17 @@ public function testGetEngineFromItemtypeForBoavizta() ->getMock(); $client_stub->method('request')->willReturn($version_response); - $itemtype = GlpiComputer::class; - $result = Engine::getEngineFromItemtype($itemtype, $client_stub); + $item = $this->createItem(GlpiComputer::class); + $result = Engine::getEngineFromItemtype($item, $client_stub); $this->assertTrue($result instanceof Computer); - $itemtype = GlpiMonitor::class; - $result = Engine::getEngineFromItemtype($itemtype, $client_stub); + $item = $this->createItem(GlpiMonitor::class); + $result = Engine::getEngineFromItemtype($item, $client_stub); $this->assertTrue($result instanceof Monitor); // This case returns null, as Boavizta does not provide data - $itemtype = GlpiNetworkEquipment::class; - $result = Engine::getEngineFromItemtype($itemtype, $client_stub); + $item = $this->createItem(GlpiNetworkEquipment::class); + $result = Engine::getEngineFromItemtype($item, $client_stub); $this->assertNull($result); } } From ecc288f9471ad3d81583ab80ee31a08dd379dc0a Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Thu, 12 Mar 2026 11:28:08 +0100 Subject: [PATCH 2/8] test(Impact\Usage\Boavizta): add tests --- .../Impact/Usage/Boavizta/AbstractAsset.php | 283 ++++++++++++++++++ .../Impact/Usage/Boavizta/ComputerTest.php | 68 +++++ .../Impact/Usage/Boavizta/MonitorTest.php | 49 +++ 3 files changed, 400 insertions(+) create mode 100644 tests/src/Impact/Usage/Boavizta/AbstractAsset.php create mode 100644 tests/units/Impact/Usage/Boavizta/ComputerTest.php create mode 100644 tests/units/Impact/Usage/Boavizta/MonitorTest.php diff --git a/tests/src/Impact/Usage/Boavizta/AbstractAsset.php b/tests/src/Impact/Usage/Boavizta/AbstractAsset.php new file mode 100644 index 00000000..44228913 --- /dev/null +++ b/tests/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -0,0 +1,283 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Tests\Impact\Usage\Boavizta; + +use CommonDBTM; +use DBmysql; +use GlpiPlugin\Carbon\ComputerType; +use GlpiPlugin\Carbon\ComputerUsageProfile; +use GlpiPlugin\Carbon\Impact\Usage\AbstractUsageImpact; +use GlpiPlugin\Carbon\Location; +use GlpiPlugin\Carbon\Tests\DbTestCase; +use GlpiPlugin\Carbon\UsageImpact; +use GlpiPlugin\Carbon\UsageInfo; +use Infocom; +use Location as GlpiLocation; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; + +#[CoversClass(AbstractUsageImpact::class)] +abstract class AbstractAsset extends DbTestCase +{ + protected static string $instance_type = ''; + + /** @var class-string itemtype of the an asset (i.e. Computer, Monotir, ...) */ + protected static string $itemtype = ''; + + /** @var class-string itemtype of the type of an asset (i.e. ComputerType, MonitorType) */ + protected static string $itemtype_type = ''; + + /** @var class-string itemtype of the model of an asset (i.e. ComputerModel, MonitorModel) */ + protected static string $itemtype_model = ''; + + /** + * Create an asset with all required data to make it evaluable + * + * @return array An asset and related objects + */ + protected function getEvaluableAsset(): array + { + $glpi_location = $this->createItem(GlpiLocation::class); + $location = $this->createItem(Location::class, [ + $glpi_location->getForeignKeyField() => $glpi_location->getID(), + 'boavizta_zone' => 'FRA' + ]); + $glpi_asset_type = $this->createItem(static::$itemtype_type); + $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ + $glpi_asset_type->getForeignKeyField() => $glpi_asset_type->getID(), + 'power_consumption' => 42, + 'category' => ComputerType::CATEGORY_DESKTOP + ]); + $asset = $this->createItem(static::$itemtype, [ + $glpi_asset_type->getForeignKeyField() => $glpi_asset_type->getID(), + $glpi_location->getForeignKeyField() => $glpi_location->getID() + ]); + $infocom = $this->createItem(Infocom::class, [ + 'itemtype' => get_class($asset), + 'items_id' => $asset->getID(), + 'use_date' => '2024-01-01', + ]); + $usage_info = $this->createItem(UsageInfo::class, [ + 'itemtype' => get_class($asset), + 'items_id' => $asset->getID(), + ComputerUsageProfile::getForeignKeyField() => 2 // Office hours ID + ]); + + return [ + $asset, + $glpi_location, + $location, + $glpi_asset_type, + $asset_type, + $infocom, + $usage_info, + ]; + } + + public function testGetItemsToEvaluate() + { + if (static::$itemtype === '' || static::$itemtype_type === '' || static::$itemtype_model === '') { + // Ensure that the inherited test class is properly implemented for this test + $this->fail('Itemtype propertiy not set in ' . static::class); + } + + // Test the asset is evaluable when no impact is in the DB + [$asset] = $this->getEvaluableAsset(); + $instance = new static::$instance_type($asset); + $iterator = $instance->getItemsToEvaluate(static::$itemtype, [ + $asset->getTableField('id') => $asset->getID(), + ]); + $this->assertEquals(1, $iterator->count()); + + // Test the asset is no longer evaluable when there is impact in the DB + [$asset] = $this->getEvaluableAsset(); + $usage_impact = $this->createItem(UsageImpact::class, [ + 'itemtype' => $asset->getType(), + 'items_id' => $asset->getID(), + 'recalculate' => 0, + ]); + $iterator = $instance->getItemsToEvaluate(static::$itemtype, [ + $asset::getTableField('id') => $asset->getID(), + ]); + $this->assertEquals(0, $iterator->count()); + + // Test the asset is evaluable when there is impact in the DB but recamculate is set + [$asset] = $this->getEvaluableAsset(); + $usage_impact = $this->createItem(UsageImpact::class, [ + 'itemtype' => $asset->getType(), + 'items_id' => $asset->getID(), + 'recalculate' => 1, + ]); + $iterator = $instance->getItemsToEvaluate(static::$itemtype, [ + $asset::getTableField('id') => $asset->getID(), + ]); + $this->assertEquals(1, $iterator->count()); + } + + public function testGetEvaluableQuery() + { + /** @var DBmysql $DB */ + global $DB; + + // Test an asset with all requirements + [$asset] = $this->getEvaluableAsset(); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(1, $iterator->count()); + + // Test an asset without a location + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $asset->update(['locations_id' => 0] + $asset->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + // Test an asset without a boavizta_zone + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $location->update(['boavizta_zone' => ''] + $location->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + + // Test an asset without usage info + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $usage_info->delete($usage_info->fields, true); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + // Test an asset without usage profile + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $usage_info->update(['plugin_carbon_computerusageprofiles_id' => 0] + $usage_info->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + // Test an asset in the bin + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $asset->delete($asset->fields, false); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + // Test an asset set as a template + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $asset->update(['is_template' => 1] + $asset->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + // Test an asset without a power consumption + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $asset_type->update(['power_consumption' => 0] + $asset_type->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + + // Test an asset without a infocom date + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $infocom->update(['use_date' => null] + $infocom->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + } + + // public function testResetForItem() + // { + // $asset = $this->createItem(static::$itemtype); + // $instance = $this->createItem(UsageImpact::class, [ + // 'itemtype' => get_class($asset), + // 'items_id' => $asset->getID(), + // ]); + + // $result = AbstractUsageImpact::resetForItem($asset); + // $this->assertTrue($result); + // $result = UsageImpact::getById($instance->getID()); + // $this->assertFalse($result); + // } +} diff --git a/tests/units/Impact/Usage/Boavizta/ComputerTest.php b/tests/units/Impact/Usage/Boavizta/ComputerTest.php new file mode 100644 index 00000000..f964981e --- /dev/null +++ b/tests/units/Impact/Usage/Boavizta/ComputerTest.php @@ -0,0 +1,68 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Impact\Usage\Boavizta\Tests; + +use Computer as GlpiComputer; +use ComputerModel as GlpiComputerModel; +use ComputerType as GlpiComputerType; +use GlpiPlugin\Carbon\Impact\Usage\Boavizta\Computer as BoaviztaComputer; +use GlpiPlugin\Carbon\Tests\Impact\Usage\Boavizta\AbstractAsset; +use PHPUnit\Framework\Attributes\CoversClass; + +#[CoversClass(BoaviztaComputer::class)] +class ComputerTest extends AbstractAsset +{ + protected static string $instance_type = BoaviztaComputer::class; + protected static string $itemtype = GlpiComputer::class; + protected static string $itemtype_type = GlpiComputerType::class; + protected static string $itemtype_model = GlpiComputerModel::class; + + public function testGetEvaluableQuery() + { + global $DB; + parent::testGetEvaluableQuery(); + + // Test an asset without a category + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $asset_type->update(['category' => 0] + $asset_type->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + } +} diff --git a/tests/units/Impact/Usage/Boavizta/MonitorTest.php b/tests/units/Impact/Usage/Boavizta/MonitorTest.php new file mode 100644 index 00000000..f39cf11f --- /dev/null +++ b/tests/units/Impact/Usage/Boavizta/MonitorTest.php @@ -0,0 +1,49 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Impact\Usage\Boavizta\Tests; + +use Monitor as GlpiMonitor; +use MonitorModel as GlpiMonitorModel; +use MonitorType as GlpiMonitorType; +use GlpiPlugin\Carbon\Impact\Usage\Boavizta\Monitor as BoaviztaMonitor; +use GlpiPlugin\Carbon\Tests\Impact\Usage\Boavizta\AbstractAsset; +use PHPUnit\Framework\Attributes\CoversClass; + +#[CoversClass(BoaviztaMonitor::class)] +class MonitorTest extends AbstractAsset +{ + protected static string $instance_type = BoaviztaMonitor::class; + protected static string $itemtype = GlpiMonitor::class; + protected static string $itemtype_type = GlpiMonitorType::class; + protected static string $itemtype_model = GlpiMonitorModel::class; +} From 652217a38fa33bdc850f2f06e246090fdbc2a7aa Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Thu, 12 Mar 2026 11:34:27 +0100 Subject: [PATCH 3/8] style: automated code style fix --- src/AbstractImpact.php | 4 ++-- src/CronTask.php | 2 -- src/EmbodiedImpact.php | 5 ----- src/Impact/Usage/AbstractUsageImpact.php | 3 +-- tests/src/Impact/Usage/Boavizta/AbstractAsset.php | 9 ++++----- tests/units/Impact/Usage/Boavizta/MonitorTest.php | 4 ++-- 6 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php index b965b44a..b0e9646c 100644 --- a/src/AbstractImpact.php +++ b/src/AbstractImpact.php @@ -33,10 +33,10 @@ namespace GlpiPlugin\Carbon; use CommonDBChild; -use GlpiPlugin\Carbon\Impact\Type; use CommonDBTM; use DBmysql; use DBmysqlIterator; +use GlpiPlugin\Carbon\Impact\Type; use Toolbox as GlpiToolbox; abstract class AbstractImpact extends CommonDBChild @@ -215,7 +215,7 @@ public static function getItemsToEvaluate(string $itemtype, array $crit = []): D $item_type_table . '.is_ignore' => 0, $item_type_table . '.id' => null, ], - ] + ], ], ], $crit)); diff --git a/src/CronTask.php b/src/CronTask.php index 65956533..e1f3e2db 100644 --- a/src/CronTask.php +++ b/src/CronTask.php @@ -41,12 +41,10 @@ use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientFactory; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientInterface; use GlpiPlugin\Carbon\DataSource\CronTaskProvider; -use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; use GlpiPlugin\Carbon\Impact\Embodied\Engine as EmbodiedEngine; use GlpiPlugin\Carbon\Impact\Usage\Engine as UsageEngine; use GlpiPlugin\Carbon\Impact\Usage\UsageImpactInterface as UsageImpactInterface; use Location as GlpiLocation; -use Toolbox as GlpiToolbox; class CronTask extends CommonGLPI { diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 9d391722..f1ebcb9d 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -32,11 +32,6 @@ namespace GlpiPlugin\Carbon; -use CommonDBTM; -use DBmysql; -use DBmysqlIterator; -use Toolbox as GlpiToolbox; - /** * Embodied impact of assets * diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index 0d663828..cf10fdda 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -42,7 +42,6 @@ use GlpiPlugin\Carbon\Toolbox; use GlpiPlugin\Carbon\UsageImpact; use Location as GlpiLocation; -use Toolbox as GlpiToolbox; abstract class AbstractUsageImpact implements UsageImpactInterface { @@ -230,7 +229,7 @@ public function getEvaluableQuery(string $itemtype, array $crit = [], bool $enti $item_type_table . '.is_ignore' => 0, $item_type_table . '.id' => null, ], - ['NOT' => [GlpiLocation::getTableField('id') => null]] + ['NOT' => [GlpiLocation::getTableField('id') => null]], ]; $request = [ diff --git a/tests/src/Impact/Usage/Boavizta/AbstractAsset.php b/tests/src/Impact/Usage/Boavizta/AbstractAsset.php index 44228913..ab3f6a02 100644 --- a/tests/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/tests/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -44,7 +44,6 @@ use Infocom; use Location as GlpiLocation; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; #[CoversClass(AbstractUsageImpact::class)] abstract class AbstractAsset extends DbTestCase @@ -70,17 +69,17 @@ protected function getEvaluableAsset(): array $glpi_location = $this->createItem(GlpiLocation::class); $location = $this->createItem(Location::class, [ $glpi_location->getForeignKeyField() => $glpi_location->getID(), - 'boavizta_zone' => 'FRA' + 'boavizta_zone' => 'FRA', ]); $glpi_asset_type = $this->createItem(static::$itemtype_type); $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ $glpi_asset_type->getForeignKeyField() => $glpi_asset_type->getID(), 'power_consumption' => 42, - 'category' => ComputerType::CATEGORY_DESKTOP + 'category' => ComputerType::CATEGORY_DESKTOP, ]); $asset = $this->createItem(static::$itemtype, [ $glpi_asset_type->getForeignKeyField() => $glpi_asset_type->getID(), - $glpi_location->getForeignKeyField() => $glpi_location->getID() + $glpi_location->getForeignKeyField() => $glpi_location->getID(), ]); $infocom = $this->createItem(Infocom::class, [ 'itemtype' => get_class($asset), @@ -90,7 +89,7 @@ protected function getEvaluableAsset(): array $usage_info = $this->createItem(UsageInfo::class, [ 'itemtype' => get_class($asset), 'items_id' => $asset->getID(), - ComputerUsageProfile::getForeignKeyField() => 2 // Office hours ID + ComputerUsageProfile::getForeignKeyField() => 2, // Office hours ID ]); return [ diff --git a/tests/units/Impact/Usage/Boavizta/MonitorTest.php b/tests/units/Impact/Usage/Boavizta/MonitorTest.php index f39cf11f..2683e99d 100644 --- a/tests/units/Impact/Usage/Boavizta/MonitorTest.php +++ b/tests/units/Impact/Usage/Boavizta/MonitorTest.php @@ -32,11 +32,11 @@ namespace GlpiPlugin\Carbon\Impact\Usage\Boavizta\Tests; +use GlpiPlugin\Carbon\Impact\Usage\Boavizta\Monitor as BoaviztaMonitor; +use GlpiPlugin\Carbon\Tests\Impact\Usage\Boavizta\AbstractAsset; use Monitor as GlpiMonitor; use MonitorModel as GlpiMonitorModel; use MonitorType as GlpiMonitorType; -use GlpiPlugin\Carbon\Impact\Usage\Boavizta\Monitor as BoaviztaMonitor; -use GlpiPlugin\Carbon\Tests\Impact\Usage\Boavizta\AbstractAsset; use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(BoaviztaMonitor::class)] From 41c1fa5d49196d73067f574b9faf94da2a111908 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 13 Mar 2026 13:36:32 +0100 Subject: [PATCH 4/8] refactor(Impact\Usage\Boavizta): fix and restructurate SQL queries building --- src/Impact/Usage/AbstractUsageImpact.php | 50 ----- src/Impact/Usage/Boavizta/AbstractAsset.php | 103 +++++++++ src/Impact/Usage/Boavizta/Computer.php | 122 ++--------- src/Impact/Usage/Boavizta/Monitor.php | 56 +++++ src/Impact/Usage/UsageImpactInterface.php | 3 + .../Impact/Usage/Boavizta/AbstractAsset.php | 207 ++++++++++++------ .../Impact/Usage/Boavizta/ComputerTest.php | 46 +++- .../Impact/Usage/Boavizta/MonitorTest.php | 60 +++++ 8 files changed, 420 insertions(+), 227 deletions(-) diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index cf10fdda..77498be7 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -217,56 +217,6 @@ public function evaluateItem(): bool return false; } - public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array - { - $item_table = getTableForItemType($itemtype); - $glpi_location_table = GlpiLocation::getTable(); - $item_type_table = getTableForItemType('GlpiPlugin\\Carbon\\' . $itemtype . 'Type'); - $usage_impact_table = UsageImpact::getTable(); - - $crit[] = [ - 'OR' => [ - $item_type_table . '.is_ignore' => 0, - $item_type_table . '.id' => null, - ], - ['NOT' => [GlpiLocation::getTableField('id') => null]], - ]; - - $request = [ - 'SELECT' => [ - $itemtype::getTableField('id'), - ], - 'FROM' => $item_table, - 'LEFT JOIN' => [ - $usage_impact_table => [ - 'FKEY' => [ - $usage_impact_table => 'items_id', - $item_table => 'id', - ['AND' - => [ - UsageImpact::getTableField('itemtype') => $itemtype, - ], - ], - ], - ], - $glpi_location_table => [ - 'FKEY' => [ - $glpi_location_table => 'id', - $item_table => 'locations_id', - ], - ], - ], - 'WHERE' => $crit, - ]; - - if ($entity_restrict) { - $entity_restrict = (new DbUtils())->getEntitiesRestrictCriteria($item_table, '', '', 'auto'); - $request['WHERE'] += $entity_restrict; - } - - return $request; - } - /** * Do the environmental impact evaluation of an asset * diff --git a/src/Impact/Usage/Boavizta/AbstractAsset.php b/src/Impact/Usage/Boavizta/AbstractAsset.php index 7686dd00..7dc1df24 100644 --- a/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -211,4 +211,107 @@ protected function parsePe(array $impact): ?TrackedFloat return $value; } + + public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array + { + $item_table = getTableForItemType($itemtype); + $glpi_asset_type_itemtype = $itemtype . "Type"; + $glpi_asset_model_itemtype = $itemtype . "Model"; + $glpi_assettype_table = getTableForItemType($glpi_asset_type_itemtype); + $glpi_assetmodel_table = getTableForItemType($glpi_asset_model_itemtype); + $glpi_assets_types_fk = getForeignKeyFieldForItemType($glpi_asset_type_itemtype); + $glpi_assets_models_fk = getForeignKeyFieldForItemType($glpi_asset_model_itemtype); + $asset_type_itemtype = 'GlpiPlugin\\Carbon\\' . $glpi_asset_type_itemtype; + $assettype_table = getTableForItemType($asset_type_itemtype); + $location_table = Location::getTable(); + $infocom_table = Infocom::getTable(); + $usage_impact_table = UsageImpact::getTable(); + + $request = [ + 'SELECT' => [ + $itemtype::getTableField('id'), + ], + 'FROM' => $item_table, + 'INNER JOIN' => [ + $location_table => [ + 'FKEY' => [ + $item_table => 'locations_id', + $location_table => 'locations_id', + ], + ], + ], + 'LEFT JOIN' => [ + $usage_impact_table => [ + 'FKEY' => [ + $usage_impact_table => 'items_id', + $item_table => 'id', + ['AND' + => [ + UsageImpact::getTableField('itemtype') => $itemtype, + ], + ], + ], + ], + $glpi_assettype_table => [ + 'FKEY' => [ + $glpi_assettype_table => 'id', + $item_table => $glpi_assets_types_fk, + ], + ], + $assettype_table => [ + 'FKEY' => [ + $assettype_table => $glpi_assets_types_fk, + $glpi_assettype_table => 'id', + [ + 'AND' => [ + 'NOT' => [$glpi_assettype_table . '.id' => null], + ], + ], + ], + ], + $glpi_assetmodel_table => [ + 'FKEY' => [ + $glpi_assetmodel_table => 'id', + $item_table => $glpi_assets_models_fk, + ], + ], + $infocom_table => [ + 'FKEY' => [ + $infocom_table => 'items_id', + $item_table => 'id', + ['AND' => [Infocom::getTableField('itemtype') => $itemtype]], + ], + ], + ], + 'WHERE' => [ + 'AND' => [ + $itemtype::getTableField('is_deleted') => 0, + $itemtype::getTableField('is_template') => 0, + ['NOT' => [Location::getTableField('boavizta_zone') => '']], + ['NOT' => [Location::getTableField('boavizta_zone') => null]], + [ + 'OR' => [ + $asset_type_itemtype::getTableField('power_consumption') => ['>', 0], + $glpi_asset_model_itemtype::getTableField('power_consumption') => ['>', 0], + ], + ], [ + 'OR' => [ + ['NOT' => [Infocom::getTableField('use_date') => null]], + ['NOT' => [Infocom::getTableField('delivery_date') => null]], + ['NOT' => [Infocom::getTableField('buy_date') => null]], + // ['NOT' => [Infocom::getTableField('date_creation') => null]], + // ['NOT' => [Infocom::getTableField('date_mod') => null]], + ], + ], + ], + ] + $crit, + ]; + + if ($entity_restrict) { + $entity_restrict = (new DbUtils())->getEntitiesRestrictCriteria($item_table, '', '', 'auto'); + $request['WHERE'] += $entity_restrict; + } + + return $request; + } } diff --git a/src/Impact/Usage/Boavizta/Computer.php b/src/Impact/Usage/Boavizta/Computer.php index 98e9a8ce..17a9ded7 100644 --- a/src/Impact/Usage/Boavizta/Computer.php +++ b/src/Impact/Usage/Boavizta/Computer.php @@ -62,6 +62,8 @@ class Computer extends AbstractAsset public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array { + $request = parent::getEvaluableQuery($itemtype); + $item_table = getTableForItemType($itemtype); $item_model_table = self::$model_itemtype::getTable(); $glpi_computertypes_table = GlpiComputerType::getTable(); @@ -69,113 +71,27 @@ public function getEvaluableQuery(string $itemtype, array $crit = [], bool $enti $location_table = Location::getTable(); $usage_info_table = UsageInfo::getTable(); $computerUsageProfile_table = ComputerUsageProfile::getTable(); - $infocom_table = Infocom::getTable(); - $usage_impact_table = UsageImpact::getTable(); - - $request = [ - 'SELECT' => [ - self::$itemtype::getTableField('id'), - ], - 'FROM' => $item_table, - 'INNER JOIN' => [ - $location_table => [ - 'FKEY' => [ - $item_table => 'locations_id', - $location_table => 'locations_id', - ], - ], - $usage_info_table => [ - 'FKEY' => [ - $item_table => 'id', - $usage_info_table => 'items_id', - [ - 'AND' => [UsageInfo::getTableField('itemtype') => self::$itemtype], - ], - ], - ], - $computerUsageProfile_table => [ - 'FKEY' => [ - $usage_info_table => 'plugin_carbon_computerusageprofiles_id', - $computerUsageProfile_table => 'id', - ], + $usage_info_table = UsageInfo::getTable(); + $request['INNER JOIN'][$usage_info_table] = [ + 'FKEY' => [ + $item_table => 'id', + $usage_info_table => 'items_id', + [ + 'AND' => [UsageInfo::getTableField('itemtype') => self::$itemtype], ], ], - 'LEFT JOIN' => [ - $item_model_table => [ - 'FKEY' => [ - $item_table => 'computermodels_id', - $item_model_table => 'id', - ], - ], - $glpi_computertypes_table => [ - 'FKEY' => [ - $item_table => 'computertypes_id', - $glpi_computertypes_table => 'id', - ], - ], - $computertypes_table => [ - 'FKEY' => [ - $computertypes_table => 'computertypes_id', - $glpi_computertypes_table => 'id', - [ - 'AND' => [ - 'NOT' => [GlpiComputerType::getTableField('id') => null], - ], - ], - ], - ], - $infocom_table => [ - 'FKEY' => [ - $infocom_table => 'items_id', - $item_table => 'id', - ['AND' => [Infocom::getTableField('itemtype') => self::$itemtype]], - ], - ], - $usage_impact_table => [ - 'FKEY' => [ - $usage_impact_table => 'items_id', - $item_table => 'id', - ['AND' - => [ - UsageImpact::getTableField('itemtype') => $itemtype, - ], - ], - ], - ], + ]; + $request['INNER JOIN'][$computerUsageProfile_table] = [ + 'FKEY' => [ + $usage_info_table => ComputerUsageProfile::getForeignKeyField(), + $computerUsageProfile_table => 'id', ], - 'WHERE' => [ - 'AND' => [ - self::$itemtype::getTableField('is_deleted') => 0, - self::$itemtype::getTableField('is_template') => 0, - ['NOT' => [Location::getTableField('boavizta_zone') => '']], - ['NOT' => [Location::getTableField('boavizta_zone') => null]], - [ - 'OR' => [ - ComputerType::getTableField('power_consumption') => ['>', 0], - self::$model_itemtype::getTableField('power_consumption') => ['>', 0], - ], - ], [ - 'OR' => [ - ['NOT' => [Infocom::getTableField('use_date') => null]], - ['NOT' => [Infocom::getTableField('delivery_date') => null]], - ['NOT' => [Infocom::getTableField('buy_date') => null]], - // ['NOT' => [Infocom::getTableField('date_creation') => null]], - // ['NOT' => [Infocom::getTableField('date_mod') => null]], - ], - ], [ - 'AND' => [ - ['NOT' => [ComputerType::getTableField('category') => null]], - [ComputerType::getTableField('category') => ['>', 0]], - ], - ], - ], - ] + $crit, ]; - - if ($entity_restrict) { - $entity_restrict = (new DbUtils())->getEntitiesRestrictCriteria($item_table, '', '', 'auto'); - $request['WHERE'] += $entity_restrict; - } + $request['WHERE'][] = [ + ['NOT' => [ComputerType::getTableField('category') => null]], + [ComputerType::getTableField('category') => ['>', 0]], + ]; + $request['WHERE'][] = $crit; return $request; } diff --git a/src/Impact/Usage/Boavizta/Monitor.php b/src/Impact/Usage/Boavizta/Monitor.php index 63fa0692..56ee7db3 100644 --- a/src/Impact/Usage/Boavizta/Monitor.php +++ b/src/Impact/Usage/Boavizta/Monitor.php @@ -49,6 +49,62 @@ class Monitor extends AbstractAsset protected string $endpoint = 'peripheral/monitor'; + public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array + { + // TODO : build the evaluable query from the computer evaluable query + // the location should behandled like done in History namespace + + $item_table = self::$itemtype::getTable(); + $item_model_table = self::$model_itemtype::getTable(); + $assets_items_table = Asset_PeripheralAsset::getTable(); + $computers_table = GlpiComputer::getTable(); + $glpi_monitor_types_table = GlpiMonitorType::getTable(); + $glpi_monitor_types_fk = GlpiMonitorType::getForeignKeyField(); + $monitor_types_table = MonitorType::getTable(); + + $request = parent::getEvaluableQuery($itemtype); + $parent_inner_joins = $request['INNER JOIN']; + $parent_left_joins = $request['LEFT JOIN']; + unset($request['INNER JOIN'], $request['LEFT JOIN']); + + $request['LEFT JOIN'][$assets_items_table] = [ + 'FKEY' => [ + $assets_items_table => 'items_id_peripheral', + $item_table => 'id', + [ + 'AND' => [ + Asset_PeripheralAsset::getTableField('itemtype_peripheral') => self::$itemtype, + Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class, + ], + ], + ], + ]; + $request['INNER JOIN'][$computers_table] = [ + 'FKEY' => [ + $computers_table => 'id', + $assets_items_table => 'items_id_asset', + [ + 'AND' => [Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class] + ], + ], + ]; + + // re-add inner joins of computer, after those for monitor + // Needed to join tables before theyr foreign keys are used + $request['INNER JOIN'] = array_merge($request['INNER JOIN'], $parent_inner_joins); + $request['LEFT JOIN'] = array_merge($request['LEFT JOIN'], $parent_left_joins); + + // Replace SELECT on computer by select on monitor + $request['SELECT'] = [ + self::$itemtype::getTableField('id'), + ]; + + // Append criteria to the WHERE clause + $request['WHERE'][] = $crit; + + return $request; + } + protected function doEvaluation(CommonDBTM $item): ?array { // TODO: determine if the computer is a server, a computer, a laptop, a tablet... diff --git a/src/Impact/Usage/UsageImpactInterface.php b/src/Impact/Usage/UsageImpactInterface.php index f6d4da29..55f83492 100644 --- a/src/Impact/Usage/UsageImpactInterface.php +++ b/src/Impact/Usage/UsageImpactInterface.php @@ -57,6 +57,7 @@ public function setLimit(int $limit); /** * Get query to find items we can evaluate + * An evaluable item is an item with all required data * * @template T of CommonDBTM * @param class-string $itemtype @@ -68,6 +69,8 @@ public function getEvaluableQuery(string $itemtype, array $crit = []): array; /** * Get an iterator of items to evaluate + * An item to evaluate is an item with all required data for a successful evaluation + * and without any calculation result or with an invalidated calculation result * * @template T of CommonDBTM * @param class-string $itemtype diff --git a/tests/src/Impact/Usage/Boavizta/AbstractAsset.php b/tests/src/Impact/Usage/Boavizta/AbstractAsset.php index ab3f6a02..f8bdb346 100644 --- a/tests/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/tests/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -33,16 +33,22 @@ namespace GlpiPlugin\Carbon\Tests\Impact\Usage\Boavizta; use CommonDBTM; +use Computer as GlpiComputer; +use ComputerType as GLPIComputerType; use DBmysql; +use Glpi\Asset\Asset_PeripheralAsset; use GlpiPlugin\Carbon\ComputerType; use GlpiPlugin\Carbon\ComputerUsageProfile; use GlpiPlugin\Carbon\Impact\Usage\AbstractUsageImpact; use GlpiPlugin\Carbon\Location; +use GlpiPlugin\Carbon\MonitorType; use GlpiPlugin\Carbon\Tests\DbTestCase; use GlpiPlugin\Carbon\UsageImpact; use GlpiPlugin\Carbon\UsageInfo; use Infocom; use Location as GlpiLocation; +use Monitor as GlpiMonitor; +use MonitorType as GlpiMonitorType; use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(AbstractUsageImpact::class)] @@ -59,96 +65,166 @@ abstract class AbstractAsset extends DbTestCase /** @var class-string itemtype of the model of an asset (i.e. ComputerModel, MonitorModel) */ protected static string $itemtype_model = ''; + /** + * Get an asset with all conditions to be evaluable, with all necessary objects + * + * @return array ordered list of objects + * - an asset like a computer, a monitor, a network equipment, ... + * - a location + * - a plugin location (extending the locations properties) + * - a type of asset + * - an infocom object (financial information) + * - an isage information object + * - optional: other objects for more complex case (see monitors, attached to a computer) + */ + abstract protected function getEvaluableAsset(): array; + /** * Create an asset with all required data to make it evaluable * * @return array An asset and related objects */ - protected function getEvaluableAsset(): array + protected function getEvaluableComputer(): array { $glpi_location = $this->createItem(GlpiLocation::class); $location = $this->createItem(Location::class, [ $glpi_location->getForeignKeyField() => $glpi_location->getID(), 'boavizta_zone' => 'FRA', ]); - $glpi_asset_type = $this->createItem(static::$itemtype_type); - $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ - $glpi_asset_type->getForeignKeyField() => $glpi_asset_type->getID(), + $glpi_computer_type = $this->createItem(GLPIComputerType::class); + $computer_type = $this->createItem(ComputerType::class, [ + $glpi_computer_type->getForeignKeyField() => $glpi_computer_type->getID(), 'power_consumption' => 42, 'category' => ComputerType::CATEGORY_DESKTOP, ]); - $asset = $this->createItem(static::$itemtype, [ - $glpi_asset_type->getForeignKeyField() => $glpi_asset_type->getID(), + $computer = $this->createItem(GlpiComputer::class, [ + $glpi_computer_type->getForeignKeyField() => $glpi_computer_type->getID(), $glpi_location->getForeignKeyField() => $glpi_location->getID(), ]); $infocom = $this->createItem(Infocom::class, [ - 'itemtype' => get_class($asset), - 'items_id' => $asset->getID(), + 'itemtype' => get_class($computer), + 'items_id' => $computer->getID(), 'use_date' => '2024-01-01', ]); $usage_info = $this->createItem(UsageInfo::class, [ - 'itemtype' => get_class($asset), - 'items_id' => $asset->getID(), + 'itemtype' => get_class($computer), + 'items_id' => $computer->getID(), ComputerUsageProfile::getForeignKeyField() => 2, // Office hours ID ]); return [ - $asset, + $computer, $glpi_location, $location, - $glpi_asset_type, - $asset_type, + $glpi_computer_type, + $computer_type, $infocom, $usage_info, ]; } - public function testGetItemsToEvaluate() + /** + * Create an asset with all required data to make it evaluable + * + * @return array An asset and related objects + */ + protected function getEvaluableMonitor(): array { - if (static::$itemtype === '' || static::$itemtype_type === '' || static::$itemtype_model === '') { - // Ensure that the inherited test class is properly implemented for this test - $this->fail('Itemtype propertiy not set in ' . static::class); - } + [ + $glpi_computer, + $glpi_location, + $location, + $glpi_computer_type, + $computer_type, + $glpi_computer_infocom, + $glpi_computer_usage_info, + ] = $this->getEvaluableComputer(); + + $glpi_monitor_type = $this->createItem(GlpiMonitorType::class); + $monitor_type = $this->createItem(MonitorType::class, [ + $glpi_monitor_type->getForeignKeyField() => $glpi_monitor_type->getID(), + 'power_consumption' => 42, + ]); + $glpi_monitor = $this->createItem(GlpiMonitor::class, [ + $glpi_monitor_type->getForeignKeyField() => $glpi_monitor_type->getID(), + $glpi_location->getForeignKeyField() => $glpi_location->getID(), + ]); + $infocom = $this->createItem(Infocom::class, [ + 'itemtype' => get_class($glpi_monitor), + 'items_id' => $glpi_monitor->getID(), + 'use_date' => '2024-01-01', + ]); + $usage_info = $this->createItem(UsageInfo::class, [ + 'itemtype' => get_class($glpi_monitor), + 'items_id' => $glpi_monitor->getID(), + ]); + + // Associate the monitor to the computer + $asset_peripheralasset = $this->createItem(Asset_PeripheralAsset::class, [ + 'itemtype_peripheral' => get_class($glpi_monitor), + 'itemtype_asset' => get_class($glpi_computer), + 'items_id_peripheral' => $glpi_monitor->getID(), + 'items_id_asset' => $glpi_computer->getID(), + ]); - // Test the asset is evaluable when no impact is in the DB + return [ + $glpi_monitor, + $glpi_location, + $location, + $glpi_monitor_type, + $monitor_type, + $infocom, + $usage_info, + $asset_peripheralasset, + $glpi_computer, + ]; + } + + public function test_GetItemsToEvaluate_is_evaluable_when_no_impacts_exists() + { [$asset] = $this->getEvaluableAsset(); $instance = new static::$instance_type($asset); $iterator = $instance->getItemsToEvaluate(static::$itemtype, [ $asset->getTableField('id') => $asset->getID(), ]); $this->assertEquals(1, $iterator->count()); + } - // Test the asset is no longer evaluable when there is impact in the DB + public function test_GetItemsToEvaluate_is_not_evaluable_when_impacts_exists() + { [$asset] = $this->getEvaluableAsset(); $usage_impact = $this->createItem(UsageImpact::class, [ 'itemtype' => $asset->getType(), 'items_id' => $asset->getID(), 'recalculate' => 0, ]); + $instance = new static::$instance_type($asset); $iterator = $instance->getItemsToEvaluate(static::$itemtype, [ $asset::getTableField('id') => $asset->getID(), ]); $this->assertEquals(0, $iterator->count()); + } - // Test the asset is evaluable when there is impact in the DB but recamculate is set + public function test_GetItemsToEvaluate_is_not_evaluable_when_impacts_to_recalculate_exists() + { [$asset] = $this->getEvaluableAsset(); $usage_impact = $this->createItem(UsageImpact::class, [ 'itemtype' => $asset->getType(), 'items_id' => $asset->getID(), 'recalculate' => 1, ]); + $instance = new static::$instance_type($asset); $iterator = $instance->getItemsToEvaluate(static::$itemtype, [ $asset::getTableField('id') => $asset->getID(), ]); $this->assertEquals(1, $iterator->count()); } - public function testGetEvaluableQuery() + public function test_getEvaluableQuery_returns_one_when_asset_mets_all_requirements() { /** @var DBmysql $DB */ global $DB; - // Test an asset with all requirements [$asset] = $this->getEvaluableAsset(); $instance = new static::$instance_type($asset); $request = $instance->getEvaluableQuery( @@ -159,23 +235,15 @@ public function testGetEvaluableQuery() ); $iterator = $DB->request($request); $this->assertEquals(1, $iterator->count()); + } - // Test an asset without a location - [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); - $asset->update(['locations_id' => 0] + $asset->fields); - $instance = new static::$instance_type($asset); - $request = $instance->getEvaluableQuery( - get_class($asset), - [ - $asset::getTableField('id') => $asset->getID(), - ] - ); - $iterator = $DB->request($request); - $this->assertEquals(0, $iterator->count()); + public function test_getEvaluableQuery_returns_zero_when_asset_is_in_trash_bin() + { + /** @var DBmysql $DB */ + global $DB; - // Test an asset without a boavizta_zone [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); - $location->update(['boavizta_zone' => ''] + $location->fields); + $asset->delete($asset->fields, false); $instance = new static::$instance_type($asset); $request = $instance->getEvaluableQuery( get_class($asset), @@ -185,11 +253,15 @@ public function testGetEvaluableQuery() ); $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); + } + public function test_getEvaluableQuery_returns_zero_when_asset_is_a_template() + { + /** @var DBmysql $DB */ + global $DB; - // Test an asset without usage info [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); - $usage_info->delete($usage_info->fields, true); + $asset->update(['is_template' => 1] + $asset->fields); $instance = new static::$instance_type($asset); $request = $instance->getEvaluableQuery( get_class($asset), @@ -199,23 +271,15 @@ public function testGetEvaluableQuery() ); $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); + } - // Test an asset without usage profile - [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); - $usage_info->update(['plugin_carbon_computerusageprofiles_id' => 0] + $usage_info->fields); - $instance = new static::$instance_type($asset); - $request = $instance->getEvaluableQuery( - get_class($asset), - [ - $asset::getTableField('id') => $asset->getID(), - ] - ); - $iterator = $DB->request($request); - $this->assertEquals(0, $iterator->count()); + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_location() + { + /** @var DBmysql $DB */ + global $DB; - // Test an asset in the bin [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); - $asset->delete($asset->fields, false); + $asset->update(['locations_id' => 0] + $asset->fields); $instance = new static::$instance_type($asset); $request = $instance->getEvaluableQuery( get_class($asset), @@ -225,10 +289,15 @@ public function testGetEvaluableQuery() ); $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_boavizta_zone() + { + /** @var DBmysql $DB */ + global $DB; - // Test an asset set as a template [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); - $asset->update(['is_template' => 1] + $asset->fields); + $location->update(['boavizta_zone' => ''] + $location->fields); $instance = new static::$instance_type($asset); $request = $instance->getEvaluableQuery( get_class($asset), @@ -238,8 +307,13 @@ public function testGetEvaluableQuery() ); $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_power_consumption() + { + /** @var DBmysql $DB */ + global $DB; - // Test an asset without a power consumption [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); $asset_type->update(['power_consumption' => 0] + $asset_type->fields); $instance = new static::$instance_type($asset); @@ -251,8 +325,13 @@ public function testGetEvaluableQuery() ); $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_infocom() + { + /** @var DBmysql $DB */ + global $DB; - // Test an asset without a infocom date [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); $infocom->update(['use_date' => null] + $infocom->fields); $instance = new static::$instance_type($asset); @@ -265,18 +344,4 @@ public function testGetEvaluableQuery() $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); } - - // public function testResetForItem() - // { - // $asset = $this->createItem(static::$itemtype); - // $instance = $this->createItem(UsageImpact::class, [ - // 'itemtype' => get_class($asset), - // 'items_id' => $asset->getID(), - // ]); - - // $result = AbstractUsageImpact::resetForItem($asset); - // $this->assertTrue($result); - // $result = UsageImpact::getById($instance->getID()); - // $this->assertFalse($result); - // } } diff --git a/tests/units/Impact/Usage/Boavizta/ComputerTest.php b/tests/units/Impact/Usage/Boavizta/ComputerTest.php index f964981e..b9b0af8e 100644 --- a/tests/units/Impact/Usage/Boavizta/ComputerTest.php +++ b/tests/units/Impact/Usage/Boavizta/ComputerTest.php @@ -47,12 +47,16 @@ class ComputerTest extends AbstractAsset protected static string $itemtype_type = GlpiComputerType::class; protected static string $itemtype_model = GlpiComputerModel::class; - public function testGetEvaluableQuery() + protected function getEvaluableAsset(): array { + return $this->getEvaluableComputer(); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_category_in_type() + { + /** @var DBmysql $DB */ global $DB; - parent::testGetEvaluableQuery(); - // Test an asset without a category [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); $asset_type->update(['category' => 0] + $asset_type->fields); $instance = new static::$instance_type($asset); @@ -65,4 +69,40 @@ public function testGetEvaluableQuery() $iterator = $DB->request($request); $this->assertEquals(0, $iterator->count()); } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_usage_profile() + { + /** @var DBmysql $DB */ + global $DB; + + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $usage_info->update(['plugin_carbon_computerusageprofiles_id' => 0] + $usage_info->fields); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_usage_info() + { + /** @var DBmysql $DB */ + global $DB; + + [$asset, $glpi_location, $location, $glpi_asset_type, $asset_type, $infocom, $usage_info] = $this->getEvaluableAsset(); + $usage_info->delete($usage_info->fields, true); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + } } diff --git a/tests/units/Impact/Usage/Boavizta/MonitorTest.php b/tests/units/Impact/Usage/Boavizta/MonitorTest.php index 2683e99d..b948eb25 100644 --- a/tests/units/Impact/Usage/Boavizta/MonitorTest.php +++ b/tests/units/Impact/Usage/Boavizta/MonitorTest.php @@ -46,4 +46,64 @@ class MonitorTest extends AbstractAsset protected static string $itemtype = GlpiMonitor::class; protected static string $itemtype_type = GlpiMonitorType::class; protected static string $itemtype_model = GlpiMonitorModel::class; + + protected function getEvaluableAsset(): array + { + return $this->getEvaluableMonitor(); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_no_linked_computer() + { + /** @var DBmysql $DB */ + global $DB; + + [ + $asset, + $glpi_location, + $location, + $glpi_asset_type, + $asset_type, + $infocom, + $usage_info, + $asset_peripheralasset + ] = $this->getEvaluableAsset(); + $asset_peripheralasset->delete($asset_peripheralasset->fields, true); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + } + + public function test_getEvaluableQuery_returns_zero_when_asset_has_broken_computer_link() + { + /** @var DBmysql $DB */ + global $DB; + + [ + $asset, + $glpi_location, + $location, + $glpi_asset_type, + $asset_type, + $infocom, + $usage_info, + $asset_peripheralasset, + $glpi_computer + ] = $this->getEvaluableAsset(); + $glpi_computer->delete($glpi_computer->fields, true); + $instance = new static::$instance_type($asset); + $request = $instance->getEvaluableQuery( + get_class($asset), + [ + $asset::getTableField('id') => $asset->getID(), + ] + ); + $iterator = $DB->request($request); + $this->assertEquals(0, $iterator->count()); + } } From 1a49735bd580ad7e734628436c9ab089efaad6cf Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 13 Mar 2026 13:39:37 +0100 Subject: [PATCH 5/8] refactor(Impact\Usage): align with Impact\Embodied namespace --- front/usageimpact.form.php | 4 ++-- src/CronTask.php | 5 ++--- .../CarbonIntensity/AbstractClient.php | 9 ++------- src/Impact/History/AbstractAsset.php | 7 ++----- src/Impact/History/Computer.php | 3 +++ src/Impact/History/Monitor.php | 17 +++++++++-------- src/Impact/Usage/AbstractUsageImpact.php | 5 ++--- src/Impact/Usage/Boavizta/AbstractAsset.php | 4 ++++ src/Impact/Usage/Boavizta/Computer.php | 6 +----- src/Impact/Usage/Boavizta/Monitor.php | 3 +++ 10 files changed, 30 insertions(+), 33 deletions(-) diff --git a/front/usageimpact.form.php b/front/usageimpact.form.php index b9399b6e..db13a4a6 100644 --- a/front/usageimpact.form.php +++ b/front/usageimpact.form.php @@ -126,13 +126,13 @@ } } - $usage_impact = Engine::getEngineFromItemtype($itemtype); + $usage_impact = Engine::getEngineFromItemtype($item); if ($usage_impact === null) { Session::addMessageAfterRedirect(__('Unable to find calculation engine for this asset.', 'carbon'), false, ERROR); Html::back(); } - if (!$usage_impact->evaluateItem($_POST['items_id'])) { + if (!$usage_impact->evaluateItem()) { Session::addMessageAfterRedirect(__('Update of usage impact failed.', 'carbon'), false, ERROR); } } diff --git a/src/CronTask.php b/src/CronTask.php index e1f3e2db..332199a4 100644 --- a/src/CronTask.php +++ b/src/CronTask.php @@ -37,13 +37,12 @@ use Config as GlpiConfig; use CronTask as GlpiCronTask; use Geocoder\Exception\QuotaExceeded; -use Geocoder\Geocoder; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientFactory; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientInterface; use GlpiPlugin\Carbon\DataSource\CronTaskProvider; use GlpiPlugin\Carbon\Impact\Embodied\Engine as EmbodiedEngine; +use GlpiPlugin\Carbon\Impact\History\AssetInterface; use GlpiPlugin\Carbon\Impact\Usage\Engine as UsageEngine; -use GlpiPlugin\Carbon\Impact\Usage\UsageImpactInterface as UsageImpactInterface; use Location as GlpiLocation; class CronTask extends CommonGLPI @@ -148,7 +147,7 @@ public static function cronUsageImpact(GlpiCronTask $task): int // Calculate GWP $count = 0; foreach ($usage_impacts as $usage_impact_type) { - /** @var UsageImpactInterface $usage_impact */ + /** @var AssetInterface $usage_impact */ $usage_impact = new $usage_impact_type(); $usage_impact->setLimit($limit_per_type); $count = $usage_impact->evaluateItems($usage_impact->getItemsToEvaluate()); diff --git a/src/DataSource/CarbonIntensity/AbstractClient.php b/src/DataSource/CarbonIntensity/AbstractClient.php index 22da7bf3..01b668c1 100644 --- a/src/DataSource/CarbonIntensity/AbstractClient.php +++ b/src/DataSource/CarbonIntensity/AbstractClient.php @@ -40,9 +40,9 @@ use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Source_Zone; +use GlpiPlugin\Carbon\Toolbox; use GlpiPlugin\Carbon\Zone; use Symfony\Component\Console\Helper\ProgressBar; -use Toolbox as GlpiToolbox; abstract class AbstractClient implements ClientInterface { @@ -181,12 +181,7 @@ public function fullDownload(string $zone, DateTimeImmutable $start_date, DateTi * We NEED to check memory usage to avoid running out of memory * @see DBmysql::doQuery() */ - $memory_limit = GlpiToolbox::getMemoryLimit() - 8 * 1024 * 1024; - if ($memory_limit < 0) { - // May happen in test seems that ini_get("memory_limits") returns - // enpty string in PHPUnit environment - $memory_limit = null; - } + $memory_limit = Toolbox::getMemoryLimit(); // Traverse each month from start_date to end_date $current_date = DateTime::createFromImmutable($start_date); diff --git a/src/Impact/History/AbstractAsset.php b/src/Impact/History/AbstractAsset.php index 7b6e30f2..c4834965 100644 --- a/src/Impact/History/AbstractAsset.php +++ b/src/Impact/History/AbstractAsset.php @@ -47,7 +47,6 @@ use GlpiPlugin\Carbon\Toolbox; use LogicException; use Session; -use Toolbox as GlpiToolbox; abstract class AbstractAsset extends CommonDBTM implements AssetInterface { @@ -194,10 +193,8 @@ public function evaluateItem(int $id, ?DateTime $start_date = null, ?DateTime $e * We NEED to check memory usage to avoid running out of memory * @see DBmysql::doQuery() */ - $memory_limit = GlpiToolbox::getMemoryLimit(); - if ($memory_limit) { - $memory_limit -= 8 * 1024 * 1024; - } + $memory_limit = Toolbox::getMemoryLimit(); + foreach ($gaps as $gap) { // $date_cursor = DateTime::createFromFormat('U', $gap['start']); // $date_cursor->setTimezone(new DateTimeZone($timezone)); diff --git a/src/Impact/History/Computer.php b/src/Impact/History/Computer.php index 635719d1..9444ff93 100644 --- a/src/Impact/History/Computer.php +++ b/src/Impact/History/Computer.php @@ -79,6 +79,9 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true ], 'FROM' => $item_table, 'INNER JOIN' => [ + // TODO: remove this useless join + // Could be optimized by joining the asset and plugin's location + // with locations_id FK on both sides $glpi_location_table => [ 'FKEY' => [ $item_table => 'locations_id', diff --git a/src/Impact/History/Monitor.php b/src/Impact/History/Monitor.php index 4bbab858..36a6a708 100644 --- a/src/Impact/History/Monitor.php +++ b/src/Impact/History/Monitor.php @@ -73,7 +73,7 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true $item_table = self::$itemtype::getTable(); $item_model_table = self::$model_itemtype::getTable(); $computers_table = GlpiComputer::getTable(); - $computers_items_table = Asset_PeripheralAsset::getTable(); + $assets_items_table = Asset_PeripheralAsset::getTable(); $computer_model_table = GlpiComputerModel::getTable(); $glpi_monitor_types_table = GlpiMonitorType::getTable(); $glpi_monitor_types_fk = GlpiMonitorType::getForeignKeyField(); @@ -94,21 +94,22 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true // Add joins to reach monitor from computer $request['FROM'] = $item_table; - $request['LEFT JOIN'][$computers_items_table] = [ + $request['LEFT JOIN'][$assets_items_table] = [ 'FKEY' => [ - $computers_items_table => 'items_id_peripheral', + $assets_items_table => 'items_id_peripheral', $item_table => 'id', - ['AND' => [ - Asset_PeripheralAsset::getTableField('itemtype_peripheral') => self::$itemtype, - Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class, - ], + [ + 'AND' => [ + Asset_PeripheralAsset::getTableField('itemtype_peripheral') => self::$itemtype, + Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class, + ], ], ], ]; $request['INNER JOIN'][$computers_table] = [ 'FKEY' => [ $computers_table => 'id', - $computers_items_table => 'items_id_asset', + $assets_items_table => 'items_id_asset', ['AND' => [Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class]], ], ]; diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index 77498be7..b4640f5d 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -36,12 +36,11 @@ use CommonDBTM; use DBmysql; use DBmysqlIterator; -use DbUtils; use GlpiPlugin\Carbon\DataTracking\AbstractTracked; use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Toolbox; use GlpiPlugin\Carbon\UsageImpact; -use Location as GlpiLocation; +use Toolbox as GlpiToolbox; abstract class AbstractUsageImpact implements UsageImpactInterface { @@ -110,7 +109,7 @@ public function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlI if ($itemtype === '') { throw new \LogicException('Itemtype not set'); } - if (!is_subclass_of($itemtype, CommonDBTM::class)) { + if (!GlpiToolbox::isCommonDBTM($itemtype)) { throw new \LogicException('Itemtype does not inherits from ' . CommonDBTM::class); } diff --git a/src/Impact/Usage/Boavizta/AbstractAsset.php b/src/Impact/Usage/Boavizta/AbstractAsset.php index 7dc1df24..cf685797 100644 --- a/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -34,10 +34,14 @@ namespace GlpiPlugin\Carbon\Impact\Usage\Boavizta; use CommonDBTM; +use DbUtils; use GlpiPlugin\Carbon\DataSource\Lca\Boaviztapi\Client; use GlpiPlugin\Carbon\DataTracking\TrackedFloat; use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Impact\Usage\AbstractUsageImpact; +use GlpiPlugin\Carbon\Location; +use GlpiPlugin\Carbon\UsageImpact; +use Infocom; abstract class AbstractAsset extends AbstractUsageImpact implements AssetInterface { diff --git a/src/Impact/Usage/Boavizta/Computer.php b/src/Impact/Usage/Boavizta/Computer.php index 17a9ded7..57048b40 100644 --- a/src/Impact/Usage/Boavizta/Computer.php +++ b/src/Impact/Usage/Boavizta/Computer.php @@ -51,6 +51,7 @@ use Item_DeviceProcessor; use Item_Devices; use Item_Disk; +use Location as GlpiLocation; class Computer extends AbstractAsset { @@ -65,11 +66,6 @@ public function getEvaluableQuery(string $itemtype, array $crit = [], bool $enti $request = parent::getEvaluableQuery($itemtype); $item_table = getTableForItemType($itemtype); - $item_model_table = self::$model_itemtype::getTable(); - $glpi_computertypes_table = GlpiComputerType::getTable(); - $computertypes_table = ComputerType::getTable(); - $location_table = Location::getTable(); - $usage_info_table = UsageInfo::getTable(); $computerUsageProfile_table = ComputerUsageProfile::getTable(); $usage_info_table = UsageInfo::getTable(); $request['INNER JOIN'][$usage_info_table] = [ diff --git a/src/Impact/Usage/Boavizta/Monitor.php b/src/Impact/Usage/Boavizta/Monitor.php index 56ee7db3..8c48c5c3 100644 --- a/src/Impact/Usage/Boavizta/Monitor.php +++ b/src/Impact/Usage/Boavizta/Monitor.php @@ -34,9 +34,12 @@ namespace GlpiPlugin\Carbon\Impact\Usage\Boavizta; use CommonDBTM; +use Computer as GlpiComputer; use DBmysql; use DbUtils; +use Glpi\Asset\Asset_PeripheralAsset; use Glpi\DBAL\QueryExpression; +use GlpiPlugin\Carbon\MonitorType; use Monitor as GlpiMonitor; use MonitorModel as GlpiMonitorModel; use MonitorType as GlpiMonitorType; From 1fd2d2211fdb0b04cd181b720e2997b50dc41f40 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 13 Mar 2026 13:54:04 +0100 Subject: [PATCH 6/8] style: normalize code --- src/Impact/Usage/Boavizta/AbstractAsset.php | 2 +- src/Impact/Usage/Boavizta/Computer.php | 3 --- src/Impact/Usage/Boavizta/Monitor.php | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Impact/Usage/Boavizta/AbstractAsset.php b/src/Impact/Usage/Boavizta/AbstractAsset.php index cf685797..f2f20ae9 100644 --- a/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -216,7 +216,7 @@ protected function parsePe(array $impact): ?TrackedFloat return $value; } - public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array + public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array { $item_table = getTableForItemType($itemtype); $glpi_asset_type_itemtype = $itemtype . "Type"; diff --git a/src/Impact/Usage/Boavizta/Computer.php b/src/Impact/Usage/Boavizta/Computer.php index 57048b40..63be168e 100644 --- a/src/Impact/Usage/Boavizta/Computer.php +++ b/src/Impact/Usage/Boavizta/Computer.php @@ -44,14 +44,11 @@ use GlpiPlugin\Carbon\ComputerType; use GlpiPlugin\Carbon\ComputerUsageProfile; use GlpiPlugin\Carbon\Location; -use GlpiPlugin\Carbon\UsageImpact; use GlpiPlugin\Carbon\UsageInfo; -use Infocom; use Item_DeviceMemory; use Item_DeviceProcessor; use Item_Devices; use Item_Disk; -use Location as GlpiLocation; class Computer extends AbstractAsset { diff --git a/src/Impact/Usage/Boavizta/Monitor.php b/src/Impact/Usage/Boavizta/Monitor.php index 8c48c5c3..25d0091e 100644 --- a/src/Impact/Usage/Boavizta/Monitor.php +++ b/src/Impact/Usage/Boavizta/Monitor.php @@ -87,7 +87,7 @@ public function getEvaluableQuery(string $itemtype, array $crit = [], bool $enti $computers_table => 'id', $assets_items_table => 'items_id_asset', [ - 'AND' => [Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class] + 'AND' => [Asset_PeripheralAsset::getTableField('itemtype_asset') => GlpiComputer::class], ], ], ]; From 5e7bfe8412149107f8cde8b9faaf2071f05cd43b Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 13 Mar 2026 15:13:58 +0100 Subject: [PATCH 7/8] feat(Impact\Usage\Boavizta): support all impact criteria --- install/mysql/plugin_carbon_empty.sql | 4 +- src/DataSource/Lca/Boaviztapi/Client.php | 76 ++++++++++ .../Embodied/Boavizta/AbstractAsset.php | 128 +---------------- src/Impact/Embodied/Boavizta/Computer.php | 2 +- src/Impact/Embodied/Boavizta/Monitor.php | 2 +- src/Impact/Usage/AbstractUsageImpact.php | 5 +- src/Impact/Usage/Boavizta/AbstractAsset.php | 136 +++++++----------- src/Impact/Usage/Boavizta/Computer.php | 6 +- src/Impact/Usage/Boavizta/Monitor.php | 2 +- 9 files changed, 138 insertions(+), 223 deletions(-) diff --git a/install/mysql/plugin_carbon_empty.sql b/install/mysql/plugin_carbon_empty.sql index 25f37f5c..80746571 100644 --- a/install/mysql/plugin_carbon_empty.sql +++ b/install/mysql/plugin_carbon_empty.sql @@ -160,7 +160,7 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_embodiedimpacts` ( `pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', `pocp` float DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation', `pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `wu` float DEFAULT '0' COMMENT '(unit L) Use of water resources', + `wu` float DEFAULT '0' COMMENT '(unit M^3) Use of water resources', `wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', `mips` float DEFAULT '0' COMMENT '(unit g) Material input per unit of service', `mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', @@ -289,7 +289,7 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_usageimpacts` ( `pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', `pocp` float DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation', `pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `wu` float DEFAULT '0' COMMENT '(unit L) Use of water resources', + `wu` float DEFAULT '0' COMMENT '(unit M^3) Use of water resources', `wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', `mips` float DEFAULT '0' COMMENT '(unit g) Material input per unit of service', `mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', diff --git a/src/DataSource/Lca/Boaviztapi/Client.php b/src/DataSource/Lca/Boaviztapi/Client.php index f85b8175..6ee8680a 100644 --- a/src/DataSource/Lca/Boaviztapi/Client.php +++ b/src/DataSource/Lca/Boaviztapi/Client.php @@ -37,6 +37,8 @@ use GlpiPlugin\Carbon\Config as CarbonConfig; use GlpiPlugin\Carbon\DataSource\Lca\AbstractClient; use GlpiPlugin\Carbon\DataSource\RestApiClientInterface; +use GlpiPlugin\Carbon\DataTracking\TrackedFloat; +use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Source_Zone; use GlpiPlugin\Carbon\Zone; @@ -48,6 +50,32 @@ class Client extends AbstractClient private string $base_url; private static string $source_name = 'Boaviztapi'; + /** @var array Supported impact criterias and the multiplier unit of the value returned by Boaviztapi */ + protected array $criteria_units = [ + 'gwp' => 1000, // Kg + 'adp' => 1000, // Kg + 'pe' => 1000000, // MJ + 'gwppb' => 1000, // Kg + 'gwppf' => 1000, // Kg + 'gwpplu' => 1000, // Kg + 'ir' => 1000, // Kg + 'lu' => 1, // (no unit) + 'odp' => 1000, // Kg + 'pm' => 1, // (no unit) + 'pocp' => 1000, // Kg + 'wu' => 1, // M^3 + 'mips' => 1000, // Kg + 'adpe' => 1000, // Kg + 'adpf' => 1000000, // MJ + 'ap' => 1, // mol + 'ctue' => 1, // CTUe + // 'ctuh_c' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue + // 'ctuh_nc' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue + 'epf' => 1000, // Kg + 'epm' => 1000, // Kg + 'ept' => 1, // mol + ]; + public function __construct(RestApiClientInterface $client, string $url = '') { $this->client = $client; @@ -139,6 +167,11 @@ public function queryZones(): array return $response; } + public function getCriteriaUnits(): array + { + return $this->criteria_units; + } + /** * Save zones into database * @@ -211,6 +244,49 @@ public static function getZones() return $zones; } + /** + * Read the response to find the impacts provided by Boaviztapi + * + * @param array $response + * @param string $scope (must be either embedded or use) + * @return array + */ + public function parseResponse(array $response, string $scope): array + { + $impacts = []; + $types = Type::getImpactTypes(); + foreach ($response['impacts'] as $type => $impact) { + if (!in_array($type, $types)) { + trigger_error(sprintf('Unsupported impact type %s in class %s', $type, __CLASS__)); + continue; + } + $impact_id = Type::getImpactId($type); + if ($impact_id === false) { + continue; + } + $impacts[$impact_id] = $this->parseCriteria($type, $response['impacts'][$type][$scope]); + } + + return $impacts; + } + + protected function parseCriteria(string $name, $impact): ?TrackedFloat + { + if ($impact === 'not implemented') { + return null; + } + + /** @var array $impact */ + $unit_multiplier = $this->getCriteriaUnits()[$name]; + $value = new TrackedFloat( + $impact['value'] * $unit_multiplier, + null, + TrackedFloat::DATA_QUALITY_ESTIMATED + ); + + return $value; + } + /** * Show a dropdown of zones handleed by Boaviztapi * diff --git a/src/Impact/Embodied/Boavizta/AbstractAsset.php b/src/Impact/Embodied/Boavizta/AbstractAsset.php index b27d2805..4223ce4d 100644 --- a/src/Impact/Embodied/Boavizta/AbstractAsset.php +++ b/src/Impact/Embodied/Boavizta/AbstractAsset.php @@ -34,9 +34,7 @@ namespace GlpiPlugin\Carbon\Impact\Embodied\Boavizta; use GlpiPlugin\Carbon\DataSource\Lca\Boaviztapi\Client; -use GlpiPlugin\Carbon\DataTracking\TrackedFloat; use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; -use GlpiPlugin\Carbon\Impact\Type; abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInterface { @@ -55,32 +53,6 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInte /** @var Client instance of the HTTP client */ protected ?Client $client = null; - /** @var array Supported impact criterias and the multiplier unit of the value returned by Boaviztapi */ - protected array $criteria_units = [ - 'gwp' => 1000, // Kg - 'adp' => 1000, // Kg - 'pe' => 1000000, // MJ - 'gwppb' => 1000, // Kg - 'gwppf' => 1000, // Kg - 'gwpplu' => 1000, // Kg - 'ir' => 1000, // Kg - 'lu' => 1, // (no unit) - 'odp' => 1000, // Kg - 'pm' => 1, // (no unit) - 'pocp' => 1000, // Kg - 'wu' => 1000, // M^3 - 'mips' => 1000, // Kg - 'adpe' => 1000, // Kg - 'adpf' => 1000000, // MJ - 'ap' => 1, // mol - 'ctue' => 1, // CTUe - // 'ctuh_c' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue - // 'ctuh_nc' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue - 'epf' => 1000, // Kg - 'epm' => 1000, // Kg - 'ept' => 1, // mol - ]; - // abstract public static function getEngine(CommonDBTM $item): EngineInterface; /** @@ -125,13 +97,14 @@ protected function getVersion(): string } /** - * Get the quety string specifying the impact criterias for the HTTP request + * Get the query string specifying the impact criterias for the HTTP request * * @return string */ protected function getCriteriasQueryString(): string { - return 'criteria=' . implode('&criteria=', array_keys($this->criteria_units)); + $impact_criteria = array_keys($this->client->getCriteriaUnits()); + return 'criteria=' . implode('&criteria=', $impact_criteria); } /** @@ -153,99 +126,4 @@ protected function query(array $description): array return $response; } - - /** - * Read the response to find the impacts provided by Boaviztapi - * - * @param array $response - * @return array - */ - protected function parseResponse(array $response): array - { - $impacts = []; - $types = Type::getImpactTypes(); - foreach ($response['impacts'] as $type => $impact) { - if (!in_array($type, $types)) { - trigger_error(sprintf('Unsupported impact type %s in class %s', $type, __CLASS__)); - continue; - } - $impact_id = Type::getImpactId($type); - if ($impact_id === false) { - continue; - } - $impacts[$impact_id] = $this->parseCriteria($type, $response['impacts'][$type]); - } - - return $impacts; - } - - protected function parseCriteria(string $name, array $impact): ?TrackedFloat - { - if ($impact['embedded'] === 'not implemented') { - return null; - } - - $unit_multiplier = $this->criteria_units[$name]; - $value = new TrackedFloat( - $impact['embedded']['value'] * $unit_multiplier, - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - - return $value; - } - - protected function parseGwp(array $impact): ?TrackedFloat - { - if ($impact['embedded'] === 'not implemented') { - return null; - } - - $value = new TrackedFloat( - $impact['embedded']['value'], - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - if ($impact['unit'] === 'kgCO2eq') { - $value->setValue($value->getValue() * 1000); - } - - return $value; - } - - protected function parseAdp(array $impact): ?TrackedFloat - { - if ($impact['embedded'] === 'not implemented') { - return null; - } - - $value = new TrackedFloat( - $impact['embedded']['value'], - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - if ($impact['unit'] === 'kgSbeq') { - $value->setValue($value->getValue() * 1000); - } - - return $value; - } - - protected function parsePe(array $impact): ?TrackedFloat - { - if ($impact['embedded'] === 'not implemented') { - return null; - } - - $value = new TrackedFloat( - $impact['embedded']['value'], - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - if ($impact['unit'] === 'MJ') { - $value->setValue($value->getValue() * (1000 ** 2)); - } - - return $value; - } } diff --git a/src/Impact/Embodied/Boavizta/Computer.php b/src/Impact/Embodied/Boavizta/Computer.php index 13557089..50619a25 100644 --- a/src/Impact/Embodied/Boavizta/Computer.php +++ b/src/Impact/Embodied/Boavizta/Computer.php @@ -78,7 +78,7 @@ protected function doEvaluation(): ?array ], ]; $response = $this->query($description); - $impacts = $this->parseResponse($response); + $impacts = $this->client->parseResponse($response, 'embedded'); return $impacts; } diff --git a/src/Impact/Embodied/Boavizta/Monitor.php b/src/Impact/Embodied/Boavizta/Monitor.php index 9358be67..e74d3d6c 100644 --- a/src/Impact/Embodied/Boavizta/Monitor.php +++ b/src/Impact/Embodied/Boavizta/Monitor.php @@ -56,7 +56,7 @@ protected function doEvaluation(): ?array ], ]; $response = $this->query($description); - $impacts = $this->parseResponse($response); + $impacts = $this->client->parseResponse($response, 'embedded'); return $impacts; } diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index b4640f5d..07736e09 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -106,9 +106,6 @@ public function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlI /** @var DBmysql $DB */ global $DB; - if ($itemtype === '') { - throw new \LogicException('Itemtype not set'); - } if (!GlpiToolbox::isCommonDBTM($itemtype)) { throw new \LogicException('Itemtype does not inherits from ' . CommonDBTM::class); } @@ -119,7 +116,7 @@ public function getItemsToEvaluate(string $itemtype, array $crit = []): DBmysqlI UsageImpact::getTableField('recalculate') => 1, ], ]; - $iterator = $DB->request($this->getEvaluableQuery($itemtype, $crit, false)); + $iterator = $DB->request($this->getEvaluableQuery($itemtype, $crit)); return $iterator; } diff --git a/src/Impact/Usage/Boavizta/AbstractAsset.php b/src/Impact/Usage/Boavizta/AbstractAsset.php index f2f20ae9..2226da1e 100644 --- a/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -116,6 +116,17 @@ protected function getVersion(): string return self::$engine_version; } + /** + * Get the query string specifying the impact criterias for the HTTP request + * + * @return string + */ + protected function getCriteriasQueryString(): string + { + $impact_criteria = array_keys($this->client->getCriteriaUnits()); + return 'criteria=' . implode('&criteria=', $impact_criteria); + } + protected function query($description): array { try { @@ -130,91 +141,46 @@ protected function query($description): array return $response; } - /** - * Read the response to find the impacts provided by Boaviztapi - * - * @return array - */ - protected function parseResponse(array $response): array - { - $impacts = []; - foreach ($response['impacts'] as $type => $impact) { - if (!in_array($type, Type::getImpactTypes())) { - trigger_error(sprintf('Unsupported impact type %s in class %s', $type, __CLASS__)); - continue; - } - - switch ($type) { - case 'gwp': - // Disabled as Carbon calculates itself carbon emissions - // $impacts[Type::IMPACT_GWP] = $this->parseGwp($response['impacts']['gwp']); - $impacts[Type::IMPACT_GWP] = null; - break; - case 'adp': - $impacts[Type::IMPACT_ADP] = $this->parseAdp($response['impacts']['adp']); - break; - case 'pe': - $impacts[Type::IMPACT_PE] = $this->parsePe($response['impacts']['pe']); - break; - } - } - - return $impacts; - } - - protected function parseGwp(array $impact): ?TrackedFloat - { - if ($impact['use'] === 'not implemented') { - return null; - } - - $value = new TrackedFloat( - $impact['use']['value'], - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - if ($impact['unit'] === 'kgCO2eq') { - $value->setValue($value->getValue() * 1000); - } - - return $value; - } - - protected function parseAdp(array $impact): ?TrackedFloat - { - if ($impact['use'] === 'not implemented') { - return null; - } - - $value = new TrackedFloat( - $impact['use']['value'], - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - if ($impact['unit'] === 'kgSbeq') { - $value->setValue($value->getValue() * 1000); - } - - return $value; - } - - protected function parsePe(array $impact): ?TrackedFloat - { - if ($impact['use'] === 'not implemented') { - return null; - } - - $value = new TrackedFloat( - $impact['use']['value'], - null, - TrackedFloat::DATA_QUALITY_ESTIMATED - ); - if ($impact['unit'] === 'MJ') { - $value->setValue($value->getValue() * (1000 ** 2)); - } - - return $value; - } + // /** + // * Read the response to find the impacts provided by Boaviztapi + // * + // * @return array + // */ + // protected function parseResponse(array $response): array + // { + // $impacts = []; + // $types = Type::getImpactTypes(); + // foreach ($response['impacts'] as $type => $impact) { + // if (!in_array($type, $types)) { + // trigger_error(sprintf('Unsupported impact type %s in class %s', $type, __CLASS__)); + // continue; + // } + // $impact_id = Type::getImpactId($type); + // if ($impact_id === false) { + // continue; + // } + // $impacts[$impact_id] = $this->parseCriteria($type, $response['impacts'][$type]); + + // } + + // return $impacts; + // } + + // protected function parseCriteria(string $name, array $impact): ?TrackedFloat + // { + // if ($impact['embedded'] === 'not implemented') { + // return null; + // } + + // $unit_multiplier = $this->client->getCriteriaUnits()[$name]; + // $value = new TrackedFloat( + // $impact['embedded']['value'] * $unit_multiplier, + // null, + // TrackedFloat::DATA_QUALITY_ESTIMATED + // ); + + // return $value; + // } public function getEvaluableQuery(string $itemtype, array $crit = [], bool $entity_restrict = true): array { diff --git a/src/Impact/Usage/Boavizta/Computer.php b/src/Impact/Usage/Boavizta/Computer.php index 63be168e..ea799a24 100644 --- a/src/Impact/Usage/Boavizta/Computer.php +++ b/src/Impact/Usage/Boavizta/Computer.php @@ -91,11 +91,9 @@ public function getEvaluableQuery(string $itemtype, array $crit = [], bool $enti protected function doEvaluation(CommonDBTM $item): ?array { - // TODO: determine if the computer is a server, a computer, a laptop, a tablet... - // then adapt $this->endpoint depending on the result - $type = $this->getType($item); $this->endpoint = $this->getEndpoint($type); + $this->endpoint .= '?' . $this->getCriteriasQueryString(); // Find boavizta zone code $zone_code = Location::getZoneCode($item); @@ -118,7 +116,7 @@ protected function doEvaluation(CommonDBTM $item): ?array ], ]; $response = $this->query($description); - $impacts = $this->parseResponse($response); + $impacts = $this->client->parseResponse($response, 'use'); return $impacts; } diff --git a/src/Impact/Usage/Boavizta/Monitor.php b/src/Impact/Usage/Boavizta/Monitor.php index 25d0091e..6c8d3171 100644 --- a/src/Impact/Usage/Boavizta/Monitor.php +++ b/src/Impact/Usage/Boavizta/Monitor.php @@ -123,7 +123,7 @@ protected function doEvaluation(CommonDBTM $item): ?array ], ]; $response = $this->query($description); - $impacts = $this->parseResponse($response); + $impacts = $this->client->parseResponse($response, 'use'); return $impacts; } From 56420f38db083e2a647cc70feda2759ed6e550c6 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 13 Mar 2026 15:56:18 +0100 Subject: [PATCH 8/8] fix(Impact/Usage/AbstractUsageImpact): useless argument in method call --- src/Impact/Usage/AbstractUsageImpact.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index 07736e09..94c3dbc1 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -135,7 +135,7 @@ public function evaluateItems(DBmysqlIterator $iterator): int /** @var int $count count of successfully evaluated assets */ $count = 0; foreach ($iterator as $row) { - if ($this->evaluateItem($row['id'])) { + if ($this->evaluateItem()) { $count++; } $attempts_count++;