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 }}, quantity: {{ build.quantity }},
completed: {{ build.completed }}, completed: {{ build.completed }},
part: {{ build.part.pk }}, part: {{ build.part.pk }},
{% if build.take_from %}
source_location: {{ build.take_from.pk }},
{% endif %}
}; };
{% for item in build.incomplete_outputs %} {% 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 %} {% if build.has_untracked_bom_items %}
// Load allocation table for un-tracked parts // Load allocation table for un-tracked parts
loadBuildOutputAllocationTable(buildInfo, null); loadBuildOutputAllocationTable(buildInfo, null);
@ -453,6 +449,9 @@ $("#btn-auto-allocate").on('click', function() {
{{ build.part.pk }}, {{ build.part.pk }},
incomplete_bom_items, incomplete_bom_items,
{ {
{% if build.take_from %}
source_location: {{ build.take_from.pk }},
{% endif %}
success: function(data) { success: function(data) {
$('#allocation-table-untracked').bootstrapTable('refresh'); $('#allocation-table-untracked').bootstrapTable('refresh');
} }
@ -479,6 +478,9 @@ $('#allocate-selected-items').click(function() {
{{ build.part.pk }}, {{ build.part.pk }},
bom_items, bom_items,
{ {
{% if build.take_from %}
source_location: {{ build.take_from.pk }},
{% endif %}
success: function(data) { success: function(data) {
$('#allocation-table-untracked').bootstrapTable('refresh'); $('#allocation-table-untracked').bootstrapTable('refresh');
} }

View File

@ -821,7 +821,7 @@ class StockList(generics.ListCreateAPIView):
if loc_id is not None: if loc_id is not None:
# Filter by 'null' location (i.e. top-level items) # 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) queryset = queryset.filter(location=None)
else: else:
try: try:

View File

@ -172,6 +172,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
partId, partId,
bom_items, bom_items,
{ {
source_location: buildInfo.source_location,
output: outputId, output: outputId,
success: reloadTable, success: reloadTable,
} }
@ -407,6 +408,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
row, row,
], ],
{ {
source_location: buildInfo.source_location,
success: function(data) { success: function(data) {
$(table).bootstrapTable('refresh'); $(table).bootstrapTable('refresh');
}, },
@ -824,7 +826,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
* - bom_items: A list of BomItem objects to be allocated * - bom_items: A list of BomItem objects to be allocated
* *
* options: * 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={}) { 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 sub_part_trackable: output_id != null
}; };
var source_location = options.source_location;
function renderBomItemRow(bom_item, quantity) { function renderBomItemRow(bom_item, quantity) {
var pk = bom_item.pk; var pk = bom_item.pk;
@ -936,8 +941,22 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
return; 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 // Create table of parts
var html = ` html += `
<table class='table table-striped table-condensed' id='stock-allocation-table'> <table class='table table-striped table-condensed' id='stock-allocation-table'>
<thead> <thead>
<tr> <tr>
@ -964,7 +983,26 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
title: '{% trans "Allocate Stock Items to Build Order" %}', title: '{% trans "Allocate Stock Items to Build Order" %}',
afterRender: function(fields, options) { 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) { bom_items.forEach(function(bom_item) {
initializeRelatedField( initializeRelatedField(
{ {
@ -981,6 +1019,21 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
render_part_detail: false, render_part_detail: false,
render_location_detail: true, render_location_detail: true,
auto_fill: 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) { noResults: function(query) {
return '{% trans "No matching stock items" %}'; 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 // Add button callbacks
$(options.modal).find('.button-row-remove').click(function() { $(options.modal).find('.button-row-remove').click(function() {
var pk = $(this).attr('pk'); 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) { function updateFieldValue(name, value, field, options) {
var el = $(options.modal).find(`#id_${name}`); var el = $(options.modal).find(`#id_${name}`);
if (!el) {
console.log(`WARNING: updateFieldValue could not find field '${name}'`);
return;
}
switch (field.type) { switch (field.type) {
case 'boolean': case 'boolean':
el.prop('checked', value); el.prop('checked', value);
@ -1105,7 +1112,14 @@ function addClearCallbacks(fields, options) {
function addClearCallback(name, field, 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); updateFieldValue(name, null, field, options);
}); });
} }
@ -1333,6 +1347,11 @@ function initializeRelatedField(field, fields, options) {
query.offset = offset; query.offset = offset;
query.limit = pageSize; query.limit = pageSize;
// Allow custom run-time filter augmentation
if ("adjustFilters" in field) {
query = field.adjustFilters(query);
}
return query; return query;
}, },
processResults: function(response) { processResults: function(response) {
@ -1453,7 +1472,6 @@ function initializeRelatedField(field, fields, options) {
} }
} }
}); });
} }
} }