Add ability to specify "source location" for stock allocations

- Defaults to build.take_from
- User-selectable at run-time
- Selected value affects select2 query
This commit is contained in:
Oliver 2021-10-05 10:38:18 +11:00
parent 76668b0d54
commit 563deb5ffa
4 changed files with 94 additions and 14 deletions

View File

@ -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');
}

View File

@ -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:

View File

@ -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 += `
<table class='table table-striped table-condensed' id='stock-allocation-table'>
<thead>
<tr>
@ -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');

View File

@ -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);
});
}
@ -1333,6 +1347,11 @@ function initializeRelatedField(field, fields, options) {
query.offset = offset;
query.limit = pageSize;
// Allow custom run-time filter augmentation
if ("adjustFilters" in field) {
query = field.adjustFilters(query);
}
return query;
},
processResults: function(response) {
@ -1453,7 +1472,6 @@ function initializeRelatedField(field, fields, options) {
}
}
});
}
}