Add build output selection to builditem creation form

This commit is contained in:
Oliver Walters 2020-10-22 23:28:15 +11:00
parent 1ca08f8bff
commit fae516b38e
7 changed files with 284 additions and 10 deletions

View File

@ -8,6 +8,8 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField
from django import forms
from .models import Build, BuildItem
from stock.models import StockLocation
@ -91,7 +93,7 @@ class CompleteBuildForm(HelperForm):
class CancelBuildForm(HelperForm):
""" Form for cancelling a build """
confirm_cancel = forms.BooleanField(required=False, help_text='Confirm build cancellation')
confirm_cancel = forms.BooleanField(required=False, help_text=_('Confirm build cancellation'))
class Meta:
model = Build
@ -101,7 +103,11 @@ class CancelBuildForm(HelperForm):
class EditBuildItemForm(HelperForm):
""" Form for adding a new BuildItem to a Build """
"""
Form for creating (or editing) a BuildItem object.
"""
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, help_text=_('Select quantity of stock to allocate'))
class Meta:
model = BuildItem
@ -109,4 +115,5 @@ class EditBuildItemForm(HelperForm):
'build',
'stock_item',
'quantity',
'install_into',
]

View File

@ -21,13 +21,38 @@ InvenTree | Allocate Parts
{% endif %}
</div>
<table class='table table-striped table-condensed' id='build-item-list' data-toolbar='#build-item-toolbar'></table>
<!--
<table class='table table-striped table-condensed' id='build-item-list' data-toolbar='#build-item-toolbar'></table>
-->
<div class="panel-group" id="build-output-accordion" role="tablist" aria-multiselectable="true">
{% for item in build.incomplete_outputs %}
{% include "build/allocation_card.html" with item=item complete=False %}
{% endfor %}
</div>
{% endblock %}
{% block js_ready %}
{{ block.super }}
{% for item in build.incomplete_outputs %}
// Get the build output as a javascript object
inventreeGet('{% url 'api-stock-detail' item.pk %}', {},
{
success: function(response) {
loadBuildOutputAllocationTable(
{{ build.pk }},
{{ build.part.pk }},
response
);
}
}
);
{% endfor %}
var buildTable = $("#build-item-list");
// Calculate sum of allocations for a particular table row

View File

@ -0,0 +1,29 @@
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading-{{ item.pk }}">
<div class="panel-title">
<div class='row'>
<div class='col-sm-6'>
<a role="button" data-toggle="collapse" data-parent="#build-output-accordion" href="#collapse-{{ item.pk }}" aria-expanded="true" aria-controls="collapse-{{ item.pk }}">
{{ item }}
</div>
</a>
<div class='col-sm-3'>
PROGRESS
</div>
<div class='col-sm-3'>
ACTIONS
<div class='btn-group float-right' id='#output-actions-{{ item.pk }}'>
<span class='fas fa-spin fa-spinner'></span>
</div>
</div>
</div>
</div>
</div>
<div id="collapse-{{ item.pk }}" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-{{ item.pk }}">
<div class="panel-body">
<table class='table table-striped table-condensed' id='allocation-table-{{ item.pk }}'></table>
</div>
</div>
</div>

View File

@ -1,9 +1,22 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
<div class='alert alert-block alert-info'>
<p>
{% trans "Select a stock item to allocate to the selected build output" %}
</p>
{% if output %}
<p>
{% trans "The allocated stock will be installed into the following build output:" %}
<br>
<i>{{ output }}</i>
</p>
{% endif %}
</div>
{% if no_stock %}
<div class='alert alert-danger alert-block' role='alert'>
No stock available for {{ part }}
{% trans "No stock available for" %} {{ part }}
</div>
{% endif %}
{% endblock %}

View File

@ -492,23 +492,37 @@ class BuildItemDelete(AjaxDeleteView):
class BuildItemCreate(AjaxCreateView):
""" View for allocating a new part to a build """
"""
View for allocating a StockItems to a build output.
"""
model = BuildItem
form_class = forms.EditBuildItemForm
ajax_template_name = 'build/create_build_item.html'
ajax_form_title = _('Allocate new Part')
ajax_form_title = _('Allocate stock to build output')
role_required = 'build.add'
# The output StockItem against which the allocation is being made
output = None
# The "part" which is being allocated to the output
part = None
available_stock = None
def get_context_data(self):
ctx = super(AjaxCreateView, self).get_context_data()
"""
Provide context data to the template which renders the form.
"""
ctx = super().get_context_data()
if self.part:
ctx['part'] = self.part
if self.output:
ctx['output'] = self.output
if self.available_stock:
ctx['stock'] = self.available_stock
else:
@ -526,7 +540,28 @@ class BuildItemCreate(AjaxCreateView):
build_id = form['build'].value()
if build_id is not None:
"""
If the build has been provided, hide the widget to change the build selection.
Additionally, update the allowable selections for other fields.
"""
form.fields['build'].widget = HiddenInput()
form.fields['install_into'].queryset = StockItem.objects.filter(build=build_id, is_building=True)
else:
"""
Build has *not* been selected
"""
pass
# If the output stock item is specified, hide the input field
output_id = form['install_into'].value()
if output_id is not None:
try:
self.output = StockItem.objects.get(pk=output_id)
form.fields['install_into'].widget = HiddenInput()
except (ValueError, StockItem.DoesNotExist):
pass
# If the sub_part is supplied, limit to matching stock items
part_id = self.get_param('part')
@ -577,12 +612,15 @@ class BuildItemCreate(AjaxCreateView):
""" Provide initial data for BomItem. Look for the folllowing in the GET data:
- build: pk of the Build object
- part: pk of the Part object which we are assigning
- output: pk of the StockItem object into which the allocated stock will be installed
"""
initials = super(AjaxCreateView, self).get_initial().copy()
build_id = self.get_param('build')
part_id = self.get_param('part')
output_id = self.get_param('install_into')
# Reference to a Part object
part = None
@ -593,6 +631,9 @@ class BuildItemCreate(AjaxCreateView):
# Reference to a Build object
build = None
# Reference to a StockItem object
output = None
if part_id:
try:
part = Part.objects.get(pk=part_id)
@ -623,7 +664,7 @@ class BuildItemCreate(AjaxCreateView):
if item_id:
try:
item = StockItem.objects.get(pk=item_id)
except:
except (ValueError, StockItem.DoesNotExist):
pass
# If a StockItem is not selected, try to auto-select one
@ -639,6 +680,17 @@ class BuildItemCreate(AjaxCreateView):
else:
quantity = min(quantity, item.unallocated_quantity())
# If the output has been specified
print("output_id:", output_id)
if output_id:
try:
output = StockItem.objects.get(pk=output_id)
initials['install_into'] = output
print("Output:", output)
except (ValueError, StockItem.DoesNotExist):
pass
print("no output found")
if quantity is not None:
initials['quantity'] = quantity

View File

@ -934,8 +934,10 @@ class Part(MPTTModel):
def required_parts(self):
""" Return a list of parts required to make this part (list of BOM items) """
parts = []
for bom in self.bom_items.all().select_related('sub_part'):
parts.append(bom.sub_part)
return parts
def get_allowed_bom_items(self):

View File

@ -29,9 +29,155 @@ function newBuildOrder(options={}) {
],
}
)
}
function loadBuildOutputAllocationTable(buildId, partId, output, options={}) {
/*
* Load the "allocation table" for a particular build output.
*
* Args:
* - buildId: The PK of the Build object
* - partId: The PK of the Part object
* - output: The StockItem object which is the "output" of the build
* - options:
* -- table: The #id of the table (will be auto-calculated if not provided)
*/
var outputId = output.pk;
var table = options.table || `#allocation-table-${outputId}`;
function reloadTable() {
// Reload the entire build allocation table
$(table).bootstrapTable('refresh');
}
function setupCallbacks() {
// Register button callbacks once table data are loaded
// Callback for 'allocate' button
$(table).find(".button-add").click(function() {
// Primary key of the 'sub_part'
var pk = $(this).attr('pk');
// Extract row data from the table
var idx = $(this).closest('tr').attr('data-index');
var row = $(table).bootstrapTable('getData')[idx];
// Launch form to allocate new stock against this output
launchModalForm("{% url 'build-item-create' %}", {
success: reloadTable,
data: {
part: pk,
build: buildId,
install_into: outputId,
},
secondary: [
{
field: 'stock_item',
label: '{% trans "New Stock Item" %}',
title: '{% trans "Create new Stock Item" %}',
url: '{% url "stock-item-create" %}',
data: {
part: pk,
},
},
]
});
});
}
// Load table of BOM items
$(table).inventreeTable({
url: "{% url 'api-bom-list' %}",
queryParams: {
part: partId,
sub_part_detail: true,
},
formatNoMatches: function() {
return "{% trans "No BOM items found" %}";
},
name: 'build-allocation',
onPostBody: setupCallbacks,
onLoadSuccess: function(tableData) {
// Once the BOM data are loaded, request allocation data for this build output
inventreeGet('/api/build/item/',
{
build: buildId,
output: outputId,
},
{
success: function(data) {
// TODO
}
}
);
},
showColumns: false,
detailViewByClick: true,
detailView: true,
detailFilter: function(index, row) {
return row.allocations != null;
},
detailFormatter: function(index, row, element) {
// TODO
return '---';
},
columns: [
{
field: 'pk',
visible: false,
},
{
field: 'sub_part_detail.full_name',
title: "{% trans "Required Part" %}",
sortable: true,
formatter: function(value, row, index, field) {
var url = `/part/${row.sub_part}/`;
var thumb = row.sub_part_detail.thumbnail;
var name = row.sub_part_detail.full_name;
var html = imageHoverIcon(thumb) + renderLink(name, url);
return html;
}
},
{
field: 'reference',
title: '{% trans "Reference" %}',
},
{
field: 'allocated',
title: '{% trans "Allocated" %}',
formatter: function(value, row, index, field) {
var allocated = value || 0;
var required = row.quantity * output.quantity;
return makeProgressBar(allocated, required);
}
},
{
field: 'actions',
title: '{% trans "Actions" %}',
formatter: function(value, row, index, field) {
// Generate action buttons for this build output
var html = `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-plus icon-green', 'button-add', row.sub_part, '{% trans "Allocate stock" %}');
html += '</div>';
return html;
}
},
]
});
}
function loadBuildTable(table, options) {
// Display a table of Build objects