From 9b4e1743c7ef95b8786d4b4c37fe7d9e692d0c06 Mon Sep 17 00:00:00 2001 From: Lukas <76838159+wolflu05@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:11:49 +0200 Subject: [PATCH] Feature/Tree picker (#5595) * Show only the current modal over the backdrop, move others behind * Added initial draft for tree picker * Added filters to tree picker * Added tree picker to more location fields * Fixed bug with missing input group and filters side effect * Added tree picker to part category inputs * Added missing picker for part category parent input * Fixed disabled items * Fix js linting errors * trigger: ci * Bump api_version.py * Update api_version.py --- InvenTree/InvenTree/api_version.py | 5 +- InvenTree/InvenTree/static/css/inventree.css | 4 + InvenTree/part/serializers.py | 1 + InvenTree/part/templates/part/category.html | 11 ++- InvenTree/part/templates/part/detail.html | 7 +- InvenTree/stock/serializers.py | 1 + .../stock/templates/stock/item_base.html | 4 + InvenTree/stock/templates/stock/location.html | 11 ++- .../InvenTree/settings/settings_staff_js.html | 22 ++++- InvenTree/templates/js/dynamic/nav.js | 96 ++++++++++-------- InvenTree/templates/js/translated/build.js | 16 ++- InvenTree/templates/js/translated/forms.js | 98 ++++++++++++++++++- InvenTree/templates/js/translated/modals.js | 6 ++ InvenTree/templates/js/translated/part.js | 27 ++++- .../templates/js/translated/purchase_order.js | 6 +- .../templates/js/translated/return_order.js | 6 +- InvenTree/templates/js/translated/stock.js | 20 +++- 17 files changed, 281 insertions(+), 60 deletions(-) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 2d7aefbc5f..3ccf0ba108 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 135 +INVENTREE_API_VERSION = 136 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v136 -> 2023-09-23 : https://github.com/inventree/InvenTree/pull/5595 + - Adds structural to StockLocation and PartCategory tree endpoints + v135 -> 2023-09-19 : https://github.com/inventree/InvenTree/pull/5569 - Adds location path detail to StockLocation and StockItem API endpoints - Adds category path detail to PartCategory and Part API endpoints diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 6a080cba01..912f482243 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -1097,3 +1097,7 @@ a { align-items: center; justify-content: space-between; } + +.large-treeview-icon { + font-size: 1em; +} diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index e3b2876eeb..5beb6562a2 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -113,6 +113,7 @@ class CategoryTree(InvenTree.serializers.InvenTreeModelSerializer): 'name', 'parent', 'icon', + 'structural', ] diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 1faac776f1..0dc61364d0 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -239,8 +239,17 @@ generateStocktakeReport({ category: { {% if category %}value: {{ category.pk }},{% endif %} + tree_picker: { + url: '{% url "api-part-category-tree" %}', + default_icon: global_settings.PART_CATEGORY_DEFAULT_ICON, + }, + }, + location: { + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, }, - location: {}, generate_report: {}, update_parts: {}, }); diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 32bd4ac3a8..6f9a11147b 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -436,7 +436,12 @@ part: { value: {{ part.pk }} }, - location: {}, + location: { + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, + }, generate_report: { value: false, }, diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 9328c9ce1a..aaca689cc5 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -775,6 +775,7 @@ class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer): 'name', 'parent', 'icon', + 'structural', ] diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 4704cca24c..62f9914fa7 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -650,6 +650,10 @@ $("#stock-return-from-customer").click(function() { {% if item.part.default_location %} value: {{ item.part.default_location.pk }}, {% endif %} + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, }, notes: { icon: 'fa-sticky-note', diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index b9abdb040c..3d9c0854a0 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -238,9 +238,18 @@ {% if stocktake_enable and roles.stocktake.add %} $('#location-stocktake').click(function() { generateStocktakeReport({ - category: {}, + category: { + tree_picker: { + url: '{% url "api-part-category-tree" %}', + default_icon: global_settings.PART_CATEGORY_DEFAULT_ICON, + }, + }, location: { {% if location %}value: {{ location.pk }},{% endif %} + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, }, generate_report: {}, update_parts: {}, diff --git a/InvenTree/templates/InvenTree/settings/settings_staff_js.html b/InvenTree/templates/InvenTree/settings/settings_staff_js.html index 7d8931d79c..093dacf113 100644 --- a/InvenTree/templates/InvenTree/settings/settings_staff_js.html +++ b/InvenTree/templates/InvenTree/settings/settings_staff_js.html @@ -308,6 +308,10 @@ onPanelLoad('category', function() { parameter_template: {}, category: { icon: 'fa-sitemap', + tree_picker: { + url: '{% url "api-part-category-tree" %}', + default_icon: global_settings.PART_CATEGORY_DEFAULT_ICON, + }, }, default_value: {}, }, @@ -368,6 +372,10 @@ onPanelLoad('category', function() { category: { icon: 'fa-sitemap', value: pk, + tree_picker: { + url: '{% url "api-part-category-tree" %}', + default_icon: global_settings.PART_CATEGORY_DEFAULT_ICON, + }, }, default_value: {}, }, @@ -453,8 +461,18 @@ onPanelLoad('stocktake', function() { $('#btn-generate-stocktake').click(function() { generateStocktakeReport({ part: {}, - category: {}, - location: {}, + category: { + tree_picker: { + url: '{% url "api-part-category-tree" %}', + default_icon: global_settings.PART_CATEGORY_DEFAULT_ICON, + }, + }, + location: { + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, + }, generate_report: {}, update_parts: {}, }); diff --git a/InvenTree/templates/js/dynamic/nav.js b/InvenTree/templates/js/dynamic/nav.js index 41e3fcd57d..8ce792648b 100644 --- a/InvenTree/templates/js/dynamic/nav.js +++ b/InvenTree/templates/js/dynamic/nav.js @@ -6,6 +6,7 @@ addSidebarHeader, addSidebarItem, addSidebarLink, + generateTreeStructure, enableBreadcrumbTree, enableSidebar, onPanelLoad, @@ -146,6 +147,59 @@ function enableSidebar(label, options={}) { } +/** + * Generate nested tree structure for jquery treeview from flattened list of + * tree nodes with refs to their parents + * @param {Array} data flat tree data as list of objects + * @param {Object} options custom options + * @param {Function} options.processNode Function that can change the treeview node obj + * @param {Number} options.selected pk of the node that should be preselected + */ +function generateTreeStructure(data, options) { + const nodes = {}; + const roots = []; + let node = null; + + for (var i = 0; i < data.length; i++) { + node = data[i]; + nodes[node.pk] = node; + node.selectable = false; + + node.state = { + expanded: node.pk == options.selected, + selected: node.pk == options.selected, + }; + + if (options.processNode) { + node = options.processNode(node); + } + } + + for (var i = 0; i < data.length; i++) { + node = data[i]; + + if (node.parent != null) { + if (nodes[node.parent].nodes) { + nodes[node.parent].nodes.push(node); + } else { + nodes[node.parent].nodes = [node]; + } + + if (node.state.expanded) { + while (node.parent != null) { + nodes[node.parent].state.expanded = true; + node = nodes[node.parent]; + } + } + + } else { + roots.push(node); + } + } + + return roots; +} + /** * Enable support for breadcrumb tree navigation on this page */ @@ -168,47 +222,7 @@ function enableBreadcrumbTree(options) { // Data are returned from the InvenTree server as a flattened list; // We need to convert this into a tree structure - - var nodes = {}; - var roots = []; - var node = null; - - for (var i = 0; i < data.length; i++) { - node = data[i]; - nodes[node.pk] = node; - node.selectable = false; - - if (options.processNode) { - node = options.processNode(node); - } - - node.state = { - expanded: node.pk == options.selected, - selected: node.pk == options.selected, - }; - } - - for (var i = 0; i < data.length; i++) { - node = data[i]; - - if (node.parent != null) { - if (nodes[node.parent].nodes) { - nodes[node.parent].nodes.push(node); - } else { - nodes[node.parent].nodes = [node]; - } - - if (node.state.expanded) { - while (node.parent != null) { - nodes[node.parent].state.expanded = true; - node = nodes[node.parent]; - } - } - - } else { - roots.push(node); - } - } + const roots = generateTreeStructure(data, options); $('#breadcrumb-tree').treeview({ data: roots, diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index 822f30de6b..b37c2fcab9 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -605,6 +605,10 @@ function completeBuildOutputs(build_id, outputs, options={}) { filters: { structural: false, }, + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, }, notes: { icon: 'fa-sticky-note', @@ -734,7 +738,11 @@ function scrapBuildOutputs(build_id, outputs, options={}) { location: { filters: { structural: false, - } + }, + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, }, notes: {}, discard_allocations: {}, @@ -1926,7 +1934,11 @@ function autoAllocateStockToBuild(build_id, bom_items=[], options={}) { value: options.location, filters: { structural: false, - } + }, + tree_picker: { + url: '{% url "api-location-tree" %}', + default_icon: global_settings.STOCK_LOCATION_DEFAULT_ICON, + }, }, exclude_location: {}, interchangeable: { diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 950fec6a5d..bfa2ef73e8 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -19,6 +19,8 @@ showMessage, showModalSpinner, toBool, + showQuestionDialog, + generateTreeStructure, */ /* exported @@ -2022,6 +2024,94 @@ function initializeRelatedField(field, fields, options={}) { } }); } + + if(field.tree_picker) { + // construct button + const button = $(``); + + // insert open tree picker button after select + select.parent().find(".select2").after(button); + + // save copy of filters, because of possible side effects + const filters = field.filters ? { ...field.filters } : {}; + + button.on("click", () => { + const tree_id = `${name}_tree`; + + const title = '{% trans "Select" %}' + " " + options.actions[name].label; + const content = ` +