diff --git a/CHANGELOG.md b/CHANGELOG.md index 0693377..1b85795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [unreleased] +## [Unreleased] ### Fixed - Fixed the issue where a ticket could be solved without a solution diff --git a/public/css/moreoptions.scss b/public/css/moreoptions.scss new file mode 100644 index 0000000..276327f --- /dev/null +++ b/public/css/moreoptions.scss @@ -0,0 +1,113 @@ +/* + * ------------------------------------------------------------------------- + * MoreOptions plugin for GLPI + * ------------------------------------------------------------------------- + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2025 by the MoreOptions plugin team. + * @license MIT https://opensource.org/licenses/mit-license.php + * @link https://github.com/pluginsGLPI/moreoptions + * @link https://gitlab.teclib.com/glpi-network/moreoptions/ + * ------------------------------------------------------------------------- + */ + +// Everything below is scoped under .mo-config so it can never leak onto the +// rest of the Entity form -- this stylesheet is loaded on every GLPI page +// (see the add_css hook in setup.php), not just this one. +.mo-config { + .mo-seg { + display: inline-flex; + gap: 2px; + padding: 2px; + background: var(--tblr-bg-surface-tertiary); + border: 1px solid var(--tblr-border-color); + border-radius: 5px; + + input { + position: absolute; + opacity: 0; + pointer-events: none; + } + + label { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 15px; + padding: 5px 11px; + border-radius: 3px; + white-space: nowrap; + cursor: pointer; + color: var(--tblr-secondary); + margin: 0; + + i { + font-size: 15px; + } + + &:hover { + color: var(--tblr-body-color); + font-weight: 500; + } + } + + input:checked + label { + opacity: unset; + background: color-mix(in srgb, var(--glpi-palette-color-1), var(--tblr-bg-surface) 90%); + font-weight: 600; + } + } + + // GLPI's own inherited-value badge, sitting outside the button group, to + // its left -- only worth showing while "Inherit" is actually selected. + .mo-inherited { + display: none; + } + + .form-field:has(.mo-seg-inherit:checked) .mo-inherited { + display: inline-flex; + align-items: center; + } + + .mo-pane { + display: none; + + &.active { + display: block; + } + } + + .mo-row-warn { + display: flex; + align-items: flex-start; + gap: 6px; + font-size: 13px; + color: var(--tblr-warning); + margin-top: 6px; + + i { + font-size: 15px; + flex: none; + margin-top: 1px; + } + } +} diff --git a/setup.php b/setup.php index 2ad112c..e04a13a 100644 --- a/setup.php +++ b/setup.php @@ -64,6 +64,8 @@ function plugin_init_moreoptions(): void Plugin::registerClass(Config::class, ['addtabon' => 'Entity']); + $PLUGIN_HOOKS[Hooks::ADD_CSS]['moreoptions'][] = 'css/moreoptions.scss'; + $PLUGIN_HOOKS[Hooks::ITEM_ADD]['moreoptions'][Entity::class] = [ Config::class, 'addConfig', ]; diff --git a/src/Config.php b/src/Config.php index 00c53e1..3796051 100644 --- a/src/Config.php +++ b/src/Config.php @@ -120,42 +120,7 @@ public static function preItemUpdate(CommonDBTM $item): CommonDBTM */ public static function getItilConfigFields(): array { - return [ - 'take_item_group_ticket', - 'take_item_group_change', - 'take_item_group_problem', - 'prevent_closure_ticket', - 'prevent_closure_change', - 'prevent_closure_problem', - 'require_technician_to_close_ticket', - 'require_technicians_group_to_close_ticket', - 'require_category_to_close_ticket', - 'require_location_to_close_ticket', - 'require_solution_to_close_ticket', - 'require_technician_to_close_change', - 'require_technicians_group_to_close_change', - 'require_category_to_close_change', - 'require_location_to_close_change', - 'require_solution_to_close_change', - 'require_technician_to_close_problem', - 'require_technicians_group_to_close_problem', - 'require_category_to_close_problem', - 'require_location_to_close_problem', - 'require_solution_to_close_problem', - 'assign_technical_manager_when_changing_category_ticket', - 'assign_technical_group_when_changing_category_ticket', - 'assign_technical_manager_when_changing_category_change', - 'assign_technical_group_when_changing_category_change', - 'assign_technical_manager_when_changing_category_problem', - 'assign_technical_group_when_changing_category_problem', - 'mandatory_task_category', - 'mandatory_task_duration', - 'mandatory_task_user', - 'mandatory_task_group', - 'assign_technician_from_task_ticket', - 'assign_technician_from_task_change', - 'assign_technician_from_task_problem', - ]; + return self::getConfigFieldsByKind('yes_no'); } /** @@ -163,14 +128,7 @@ public static function getItilConfigFields(): array */ private static function getActorGroupConfigFields(): array { - return [ - 'take_requester_group_ticket', - 'take_requester_group_change', - 'take_requester_group_problem', - 'take_technician_group_ticket', - 'take_technician_group_change', - 'take_technician_group_problem', - ]; + return self::getConfigFieldsByKind('actor'); } /** @@ -181,6 +139,30 @@ private static function getAllConfigFields(): array return array_merge(self::getItilConfigFields(), self::getActorGroupConfigFields()); } + /** + * Every field name of the given kind, resolved across all four tabs -- + * i.e. the same list `getItilConfigFields()`/`getActorGroupConfigFields()` + * used to hardcode, but read off `getScreenSections()` so a new setting + * only needs to be added there. + * + * @return array + */ + private static function getConfigFieldsByKind(string $kind): array + { + $fields = []; + foreach (self::getScreenTabs() as $tab) { + foreach (self::getSectionsForTab($tab['id']) as $section) { + foreach ($section['rows'] as $row) { + if ($row['kind'] === $kind) { + $fields[] = $row['field']; + } + } + } + } + + return array_values(array_unique($fields)); + } + /** * @return array */ @@ -303,23 +285,21 @@ public static function showForEntity(Entity $item): void 'entities_id' => $item->getID(), ]); - $inheritance_labels = []; - if ($item->getID() > 0) { - $parentConfig = self::getConfig($item->fields['entities_id'], true); - foreach (self::getItilConfigFields() as $field) { - $inheritance_labels[$field] = self::getInheritedValueBadge($parentConfig->fields[$field] ?? 0); - } - foreach (self::getActorGroupConfigFields() as $field) { - $inheritance_labels[$field] = self::getInheritedValueBadgeForActorGroup($parentConfig->fields[$field] ?? 0); - } + $tabs = self::getScreenTabs(); + $sections_by_tab = []; + foreach ($tabs as $tab) { + $sections_by_tab[$tab['id']] = self::getSectionsForTab($tab['id']); } TemplateRenderer::getInstance()->display( '@moreoptions/config.html.twig', [ 'item' => $moconfig, + 'tabs' => $tabs, + 'sections_by_tab' => $sections_by_tab, + 'parent_entity_id' => $item->getID() > 0 ? (int) $item->fields['entities_id'] : null, + 'parent_badges' => self::getParentValueBadges($item), 'dropdown_options' => self::getSelectableActorGroup(), - 'inheritance_labels' => $inheritance_labels, 'config_parent' => self::CONFIG_PARENT, 'escalade_takes_technician_group' => self::isTechnicianGroupHandledByEscalade(), 'params' => [ @@ -334,20 +314,149 @@ public static function getIcon(): string return "ti ti-send"; } - private static function getInheritedValueBadge(mixed $value): string + /** + * The four tabs the config screen is split into. + * + * @return array + */ + public static function getScreenTabs(): array { - $text = match ((int) $value) { - 1 => __('Yes'), - default => __('No'), - }; - return Entity::inheritedValue(htmlescape($text), false, false); + return [ + ['id' => 'ticket', 'label' => __('Ticket'), 'icon' => 'ti-ticket'], + ['id' => 'change', 'label' => __('Change'), 'icon' => 'ti-git-branch'], + ['id' => 'problem', 'label' => __('Problem'), 'icon' => 'ti-alert-circle'], + ['id' => 'task', 'label' => _n('Task', 'Tasks', 2), 'icon' => 'ti-checklist'], + ]; } - private static function getInheritedValueBadgeForActorGroup(mixed $value): string + /** + * Settings shared by the Ticket / Change / Problem tabs ('itil'), plus the + * task-only ones ('task'), which have no per-type variant: their field + * name carries no suffix. + * + * This is the single place to add a new setting or section. A row + * applies to all three ITIL types unless it lists the ones it is + * restricted to in `only` (e.g. `['ticket']`, for a ticket-specific + * setting), and warns instead of being hidden on a single type with + * `warn_on`. Adding a field here is not enough on its own: it also needs + * a column (see install()) and, for a brand new one, a default value + * wired into addConfig(). + * + * @return array>> + */ + private static function getScreenSections(): array { - $options = self::getSelectableActorGroup(); - $text = $options[(int) $value] ?? __('No'); - return Entity::inheritedValue(htmlescape($text), false, false); + return [ + 'itil' => [ + [ + 'title' => __('Actors and groups', 'moreoptions'), + 'icon' => 'ti-users-group', + 'rows' => [ + ['key' => 'take_item_group', 'kind' => 'yes_no', 'label' => __('Take the group of associated item', 'moreoptions')], + ['key' => 'take_requester_group', 'kind' => 'actor', 'label' => __('Take the requester group', 'moreoptions')], + ['key' => 'take_technician_group', 'kind' => 'actor', 'label' => __('Take the technician group', 'moreoptions'), 'warn_on' => 'ticket'], + ['key' => 'assign_technical_manager_when_changing_category', 'kind' => 'yes_no', 'label' => __('Assign technical manager when changing category', 'moreoptions')], + ['key' => 'assign_technical_group_when_changing_category', 'kind' => 'yes_no', 'label' => __('Assign technical group when changing category', 'moreoptions')], + ['key' => 'assign_technician_from_task', 'kind' => 'yes_no', 'label' => __('Assign technician from task to parent item', 'moreoptions')], + ], + ], + [ + 'title' => __('Closure', 'moreoptions'), + 'icon' => 'ti-lock', + 'rows' => [ + ['key' => 'prevent_closure', 'kind' => 'yes_no', 'label' => __('Prevent closure with tasks in To Do status', 'moreoptions')], + ], + ], + [ + 'title' => __('Mandatory fields to Solve and Close ITILs', 'moreoptions'), + 'icon' => 'ti-help-circle', + 'rows' => [ + ['key' => 'require_technician_to_close', 'kind' => 'yes_no', 'label' => __('Technician')], + ['key' => 'require_technicians_group_to_close', 'kind' => 'yes_no', 'label' => __('Technicians group')], + ['key' => 'require_category_to_close', 'kind' => 'yes_no', 'label' => __('Category')], + ['key' => 'require_location_to_close', 'kind' => 'yes_no', 'label' => __('Location')], + ['key' => 'require_solution_to_close', 'kind' => 'yes_no', 'label' => __('Solution')], + ], + ], + ], + 'task' => [ + [ + 'title' => __('Mandatory fields for Tasks creation', 'moreoptions'), + 'icon' => 'ti-checklist', + 'note' => __('These settings are shared by tickets, changes and problems.', 'moreoptions'), + 'rows' => [ + ['key' => 'mandatory_task_category', 'kind' => 'yes_no', 'label' => __('Category')], + ['key' => 'mandatory_task_duration', 'kind' => 'yes_no', 'label' => __('Duration')], + ['key' => 'mandatory_task_user', 'kind' => 'yes_no', 'label' => __('User')], + ['key' => 'mandatory_task_group', 'kind' => 'yes_no', 'label' => __('Group')], + ], + ], + ], + ]; + } + + /** + * Sections to render for one tab: each row's field name resolved (`key` + * plus the tab's suffix, task settings getting none), and rows + * restricted with `only` dropped where they do not apply. + * + * @return array> + */ + private static function getSectionsForTab(string $tab_id): array + { + $group = $tab_id === 'task' ? 'task' : 'itil'; + $suffix = $tab_id === 'task' ? '' : ('_' . $tab_id); + + $sections = []; + foreach (self::getScreenSections()[$group] as $section) { + $rows = array_values(array_filter( + $section['rows'], + static fn(array $row): bool => empty($row['only']) || in_array($tab_id, $row['only'], true), + )); + + if ($rows === []) { + continue; + } + + $section['rows'] = array_map( + static fn(array $row): array => $row + ['field' => $row['key'] . $suffix], + $rows, + ); + $sections[] = $section; + } + + return $sections; + } + + /** + * GLPI's own "inherited value" badges, one per field, shown right on the + * "Inherit" option so it doubles as what that option currently resolves + * to. + * + * Returns an empty array for the root entity, which inherits from nothing. + * + * @return array + */ + private static function getParentValueBadges(Entity $item): array + { + if ($item->getID() <= 0) { + return []; + } + + $parent_config = self::getConfig((int) $item->fields['entities_id'], true); + $actor_options = self::getSelectableActorGroup(); + + $badges = []; + foreach (self::getItilConfigFields() as $field) { + $text = ((int) ($parent_config->fields[$field] ?? 0)) === 1 ? __('Yes') : __('No'); + $badges[$field] = Entity::inheritedValue(htmlescape($text), false, false); + } + foreach (self::getActorGroupConfigFields() as $field) { + $text = $actor_options[(int) ($parent_config->fields[$field] ?? 0)] ?? __('No'); + $badges[$field] = Entity::inheritedValue(htmlescape($text), false, false); + } + + return $badges; } public static function addConfig(CommonDBTM $item): void diff --git a/templates/components/segmented_control.html.twig b/templates/components/segmented_control.html.twig new file mode 100644 index 0000000..e38423b --- /dev/null +++ b/templates/components/segmented_control.html.twig @@ -0,0 +1,72 @@ +{# + # ------------------------------------------------------------------------- + # MoreOptions plugin for GLPI + # ------------------------------------------------------------------------- + # + # MIT License + # + # Permission is hereby granted, free of charge, to any person obtaining a copy + # of this software and associated documentation files (the "Software"), to deal + # in the Software without restriction, including without limitation the rights + # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + # copies of the Software, and to permit persons to whom the Software is + # furnished to do so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be included in all + # copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + # SOFTWARE. + # ------------------------------------------------------------------------- + # @copyright Copyright (C) 2025 by the MoreOptions plugin team. + # @license MIT https://opensource.org/licenses/mit-license.php + # @link https://github.com/pluginsGLPI/moreoptions + # @link https://gitlab.teclib.com/glpi-network/moreoptions/ + # ------------------------------------------------------------------------- + #} + +{# + # A single field's worth of options, rendered as a compact button group + # instead of a dropdown: every possible value -- and the one currently + # active -- is readable at a glance, without opening anything. + # + # Backed by plain radio inputs sharing `field` as their name, so the value + # they send on submit is exactly what a + + {% endfor %} + +{% endmacro %} diff --git a/templates/config.html.twig b/templates/config.html.twig index 302387a..53ddcbd 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -32,173 +32,105 @@ # ------------------------------------------------------------------------- #} -{% import 'components/form/fields_macros.html.twig' as fields %} +{% import '@moreoptions/components/segmented_control.html.twig' as mo %} -{# Layout options for fields #} -{% set field_options = {'label_class': 'col-8', 'input_class': 'col-4', 'field_class': 'col-6', 'full_width': false} %} -{% set inherit_option = item.fields['entities_id'] != 0 ? {(config_parent): __('Inheritance of the parent entity')} : {} %} -{% set yes_no_options = inherit_option + {0: __('No'), 1: __('Yes')} %} -{% set dropdown_options_with_inherit = inherit_option + dropdown_options %} -{% set cell_options = {'no_label': true, 'field_class': 'col-12'} %} +{% set rand = random() %} -{# Escalade owns the same feature with a global configuration: our per-entity option is then inoperative #} -{% set escalade_takes_technician_group = escalade_takes_technician_group|default(false) %} -{% set escalade_technician_group_warning %} - {% if escalade_takes_technician_group %} -
- {{ __('The "Use the technician\'s group" option from Escalade is enabled and takes precedence over this one.', 'moreoptions') }} -
- {% endif %} -{% endset %} +{% set canedit = params['canedit'] ?? false %} +{% set can_inherit = parent_entity_id is not null %} -
-
- {% set item_has_pictures = item.hasItemtypeOrModelPictures() %} +{# Option lists, built once: the inherit choice only exists below the root entity. #} +{% set yes_no_set = [] %} +{% if can_inherit %} + {% set yes_no_set = [{'v': config_parent, 'label': __('Inherit', 'moreoptions'), 'inherit': true}] %} +{% endif %} +{% set yes_no_set = yes_no_set|merge([ + {'v': 0, 'label': __('No'), 'inherit': false}, + {'v': 1, 'label': __('Yes'), 'inherit': false}, +]) %} -
-
-
-
-
+{% set actor_set = [] %} +{% if can_inherit %} + {% set actor_set = [{'v': config_parent, 'label': __('Inherit', 'moreoptions'), 'inherit': true}] %} +{% endif %} +{% for value, label in dropdown_options %} + {% set actor_set = actor_set|merge([{'v': value, 'label': label, 'inherit': false}]) %} +{% endfor %} -
- - {{ __('Itil items') }} -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  {{ __('Ticket') }}{{ __('Change') }}{{ __('Problem') }}
{{ __('Take the group of associated item', 'moreoptions') }}{{ fields.dropdownArrayField('take_item_group_ticket', item.fields['take_item_group_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['take_item_group_ticket']|default(null)})) }}{{ fields.dropdownArrayField('take_item_group_change', item.fields['take_item_group_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['take_item_group_change']|default(null)})) }}{{ fields.dropdownArrayField('take_item_group_problem', item.fields['take_item_group_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['take_item_group_problem']|default(null)})) }}
{{ __('Take the requester group', 'moreoptions') }}{{ fields.dropdownArrayField('take_requester_group_ticket', item.fields['take_requester_group_ticket'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_requester_group_ticket']|default(null)})) }}{{ fields.dropdownArrayField('take_requester_group_change', item.fields['take_requester_group_change'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_requester_group_change']|default(null)})) }}{{ fields.dropdownArrayField('take_requester_group_problem', item.fields['take_requester_group_problem'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_requester_group_problem']|default(null)})) }}
{{ __('Take the technician group', 'moreoptions') }}{{ fields.dropdownArrayField('take_technician_group_ticket', item.fields['take_technician_group_ticket'], dropdown_options_with_inherit, '', cell_options|merge({'disabled': escalade_takes_technician_group, 'add_field_html': (inheritance_labels['take_technician_group_ticket']|default('')) ~ escalade_technician_group_warning})) }}{{ fields.dropdownArrayField('take_technician_group_change', item.fields['take_technician_group_change'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_technician_group_change']|default(null)})) }}{{ fields.dropdownArrayField('take_technician_group_problem', item.fields['take_technician_group_problem'], dropdown_options_with_inherit, '', cell_options|merge({'add_field_html': inheritance_labels['take_technician_group_problem']|default(null)})) }}
{{ __('Prevent closure with tasks in To Do status', 'moreoptions') }}{{ fields.dropdownArrayField('prevent_closure_ticket', item.fields['prevent_closure_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['prevent_closure_ticket']|default(null)})) }}{{ fields.dropdownArrayField('prevent_closure_change', item.fields['prevent_closure_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['prevent_closure_change']|default(null)})) }}{{ fields.dropdownArrayField('prevent_closure_problem', item.fields['prevent_closure_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['prevent_closure_problem']|default(null)})) }}
{{ __('Assign technical manager when changing category', 'moreoptions') }}{{ fields.dropdownArrayField('assign_technical_manager_when_changing_category_ticket', item.fields['assign_technical_manager_when_changing_category_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_manager_when_changing_category_ticket']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_manager_when_changing_category_change', item.fields['assign_technical_manager_when_changing_category_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_manager_when_changing_category_change']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_manager_when_changing_category_problem', item.fields['assign_technical_manager_when_changing_category_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_manager_when_changing_category_problem']|default(null)})) }}
{{ __('Assign technical group when changing category', 'moreoptions') }}{{ fields.dropdownArrayField('assign_technical_group_when_changing_category_ticket', item.fields['assign_technical_group_when_changing_category_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_ticket']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_group_when_changing_category_change', item.fields['assign_technical_group_when_changing_category_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_change']|default(null)})) }}{{ fields.dropdownArrayField('assign_technical_group_when_changing_category_problem', item.fields['assign_technical_group_when_changing_category_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technical_group_when_changing_category_problem']|default(null)})) }}
{{ __('Assign technician from task to parent item', 'moreoptions') }}{{ fields.dropdownArrayField('assign_technician_from_task_ticket', item.fields['assign_technician_from_task_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technician_from_task_ticket']|default(null)})) }}{{ fields.dropdownArrayField('assign_technician_from_task_change', item.fields['assign_technician_from_task_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technician_from_task_change']|default(null)})) }}{{ fields.dropdownArrayField('assign_technician_from_task_problem', item.fields['assign_technician_from_task_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['assign_technician_from_task_problem']|default(null)})) }}
+{# + # The tabs themselves, and which sections/settings go in each, are built by + # Config::getScreenTabs() / Config::showForEntity() -- see `tabs` and + # `sections_by_tab`. Keeping that definition in PHP rather than here is what + # lets a new setting (including one restricted to a single ITIL type) or a + # whole new section get added in one place, without touching this template. + #} -
- - {{ __('Mandatory fields to Solve and Close ITILs', 'moreoptions') }} -
+
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  {{ __('Ticket') }}{{ __('Change') }}{{ __('Problem') }}
{{ __('Technician') }}{{ fields.dropdownArrayField('require_technician_to_close_ticket', item.fields['require_technician_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technician_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_technician_to_close_change', item.fields['require_technician_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technician_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_technician_to_close_problem', item.fields['require_technician_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technician_to_close_problem']|default(null)})) }}
{{ __('Technicians group') }}{{ fields.dropdownArrayField('require_technicians_group_to_close_ticket', item.fields['require_technicians_group_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technicians_group_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_technicians_group_to_close_change', item.fields['require_technicians_group_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technicians_group_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_technicians_group_to_close_problem', item.fields['require_technicians_group_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_technicians_group_to_close_problem']|default(null)})) }}
{{ __('Category') }}{{ fields.dropdownArrayField('require_category_to_close_ticket', item.fields['require_category_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_category_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_category_to_close_change', item.fields['require_category_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_category_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_category_to_close_problem', item.fields['require_category_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_category_to_close_problem']|default(null)})) }}
{{ __('Location') }}{{ fields.dropdownArrayField('require_location_to_close_ticket', item.fields['require_location_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_location_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_location_to_close_change', item.fields['require_location_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_location_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_location_to_close_problem', item.fields['require_location_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_location_to_close_problem']|default(null)})) }}
{{ __('Solution') }}{{ fields.dropdownArrayField('require_solution_to_close_ticket', item.fields['require_solution_to_close_ticket'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_solution_to_close_ticket']|default(null)})) }}{{ fields.dropdownArrayField('require_solution_to_close_change', item.fields['require_solution_to_close_change'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_solution_to_close_change']|default(null)})) }}{{ fields.dropdownArrayField('require_solution_to_close_problem', item.fields['require_solution_to_close_problem'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['require_solution_to_close_problem']|default(null)})) }}
+
+ {% for tab in tabs %} +
+ {% for section in sections_by_tab[tab.id] %} +
+ + {{ section.title }} +
+ {% if section.note is defined %} +
{{ section.note }}
+ {% endif %} -
- - {{ __('Mandatory fields for Tasks creation', 'moreoptions') }} -
+
+ {% for row in section.rows %} + {% set options = row.kind == 'actor' ? actor_set : yes_no_set %} + {% set current = item.fields[row.field]|default(can_inherit ? config_parent : 0) %} + {% set disabled = (row.warn_on|default(null) == tab.id) and escalade_takes_technician_group %} - - - - - - - - - - - - - - - - - -
{{ __('Category') }}{{ __('Duration') }}{{ __('User') }}{{ __('Group') }}
{{ fields.dropdownArrayField('mandatory_task_category', item.fields['mandatory_task_category'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_category']|default(null)})) }}{{ fields.dropdownArrayField('mandatory_task_duration', item.fields['mandatory_task_duration'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_duration']|default(null)})) }}{{ fields.dropdownArrayField('mandatory_task_user', item.fields['mandatory_task_user'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_user']|default(null)})) }}{{ fields.dropdownArrayField('mandatory_task_group', item.fields['mandatory_task_group'], yes_no_options, '', cell_options|merge({'add_field_html': inheritance_labels['mandatory_task_group']|default(null)})) }}
+
+
+ + {% if disabled %} +
+ + {{ __('The "Use the technician\'s group" option from Escalade is enabled and takes precedence over this one.', 'moreoptions') }} +
+ {% endif %} +
+
+ {{ mo.render(row.field, options, current, parent_badges[row.field]|default(null), disabled, canedit, rand) }} +
+
+
+ {% endfor %}
-
+ {% endfor %}
-
+ {% endfor %}
-
- {{ include('components/form/buttons.html.twig') }} + + {{ include('components/form/buttons.html.twig') }} -
\ No newline at end of file +