From 563deb5ffad84d23cda49b7c3d54dafea7912c88 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 5 Oct 2021 10:38:18 +1100 Subject: [PATCH] Add ability to specify "source location" for stock allocations - Defaults to build.take_from - User-selectable at run-time - Selected value affects select2 query --- InvenTree/build/templates/build/detail.html | 16 ++--- InvenTree/stock/api.py | 2 +- InvenTree/templates/js/translated/build.js | 66 ++++++++++++++++++++- InvenTree/templates/js/translated/forms.js | 24 +++++++- 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 5e7f3d7c5f..2f707dcc40 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -317,6 +317,9 @@ var buildInfo = { quantity: {{ build.quantity }}, completed: {{ build.completed }}, part: {{ build.part.pk }}, + {% if build.take_from %} + source_location: {{ build.take_from.pk }}, + {% endif %} }; {% for item in build.incomplete_outputs %} @@ -412,13 +415,6 @@ $('#edit-notes').click(function() { }); }); -var buildInfo = { - pk: {{ build.pk }}, - quantity: {{ build.quantity }}, - completed: {{ build.completed }}, - part: {{ build.part.pk }}, -}; - {% if build.has_untracked_bom_items %} // Load allocation table for un-tracked parts loadBuildOutputAllocationTable(buildInfo, null); @@ -453,6 +449,9 @@ $("#btn-auto-allocate").on('click', function() { {{ build.part.pk }}, incomplete_bom_items, { + {% if build.take_from %} + source_location: {{ build.take_from.pk }}, + {% endif %} success: function(data) { $('#allocation-table-untracked').bootstrapTable('refresh'); } @@ -479,6 +478,9 @@ $('#allocate-selected-items').click(function() { {{ build.part.pk }}, bom_items, { + {% if build.take_from %} + source_location: {{ build.take_from.pk }}, + {% endif %} success: function(data) { $('#allocation-table-untracked').bootstrapTable('refresh'); } diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index e03d821bae..27c2426d53 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -821,7 +821,7 @@ class StockList(generics.ListCreateAPIView): if loc_id is not None: # Filter by 'null' location (i.e. top-level items) - if isNull(loc_id): + if isNull(loc_id) and not cascade: queryset = queryset.filter(location=None) else: try: diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index 325cb962a2..4e385d9f9a 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -172,6 +172,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) { partId, bom_items, { + source_location: buildInfo.source_location, output: outputId, success: reloadTable, } @@ -407,6 +408,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { row, ], { + source_location: buildInfo.source_location, success: function(data) { $(table).bootstrapTable('refresh'); }, @@ -824,7 +826,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { * - bom_items: A list of BomItem objects to be allocated * * options: - * - outputId: ID / PK of the associated build output (or null for untracked items) + * - output: ID / PK of the associated build output (or null for untracked items) + * - source_location: ID / PK of the top-level StockLocation to take parts from (or null) */ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { @@ -837,6 +840,8 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { sub_part_trackable: output_id != null }; + var source_location = options.source_location; + function renderBomItemRow(bom_item, quantity) { var pk = bom_item.pk; @@ -936,8 +941,22 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { return; } + var html = ``; + + // Render a "take from" input + html += constructField( + 'take_from', + { + type: 'related field', + label: '{% trans "Source Location" %}', + help_text: '{% trans "Select source location (leave blank to take from all locations)" %}', + required: false, + }, + {}, + ); + // Create table of parts - var html = ` + html += ` @@ -964,7 +983,26 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { title: '{% trans "Allocate Stock Items to Build Order" %}', afterRender: function(fields, options) { - // Initialize select2 fields + var take_from_field = { + name: 'take_from', + model: 'stocklocation', + api_url: '{% url "api-location-list" %}', + required: false, + type: 'related field', + value: source_location, + noResults: function(query) { + return '{% trans "No matching stock locations" %}'; + }, + }; + + // Initialize "take from" field + initializeRelatedField( + take_from_field, + null, + options, + ); + + // Initialize stock item fields bom_items.forEach(function(bom_item) { initializeRelatedField( { @@ -981,6 +1019,21 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { render_part_detail: false, render_location_detail: true, auto_fill: true, + adjustFilters: function(filters) { + // Restrict query to the selected location + var location = getFormFieldValue( + 'take_from', + {}, + { + modal: options.modal, + } + ); + + filters.location = location; + filters.cascade = true; + + return filters; + }, noResults: function(query) { return '{% trans "No matching stock items" %}'; } @@ -990,6 +1043,13 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) { ); }); + // Add callback to "clear" button for take_from field + addClearCallback( + "take_from", + take_from_field, + options, + ); + // Add button callbacks $(options.modal).find('.button-row-remove').click(function() { var pk = $(this).attr('pk'); diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index ee30c4ea1c..2d8a563550 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -728,10 +728,17 @@ function updateFieldValues(fields, options) { } } - +/* + * Update the value of a named field + */ function updateFieldValue(name, value, field, options) { var el = $(options.modal).find(`#id_${name}`); + if (!el) { + console.log(`WARNING: updateFieldValue could not find field '${name}'`); + return; + } + switch (field.type) { case 'boolean': el.prop('checked', value); @@ -1105,7 +1112,14 @@ function addClearCallbacks(fields, options) { function addClearCallback(name, field, options) { - $(options.modal).find(`#clear_${name}`).click(function() { + var el = $(options.modal).find(`#clear_${name}`); + + if (!el) { + console.log(`WARNING: addClearCallback could not find field '${name}'`); + return; + } + + el.click(function() { updateFieldValue(name, null, field, options); }); } @@ -1332,6 +1346,11 @@ function initializeRelatedField(field, fields, options) { query.search = params.term; query.offset = offset; query.limit = pageSize; + + // Allow custom run-time filter augmentation + if ("adjustFilters" in field) { + query = field.adjustFilters(query); + } return query; }, @@ -1453,7 +1472,6 @@ function initializeRelatedField(field, fields, options) { } } }); - } }