mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add build output selection to builditem creation form
This commit is contained in:
parent
1ca08f8bff
commit
fae516b38e
@ -8,6 +8,8 @@ from __future__ import unicode_literals
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from InvenTree.forms import HelperForm
|
from InvenTree.forms import HelperForm
|
||||||
|
from InvenTree.fields import RoundingDecimalFormField
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Build, BuildItem
|
from .models import Build, BuildItem
|
||||||
from stock.models import StockLocation
|
from stock.models import StockLocation
|
||||||
@ -91,7 +93,7 @@ class CompleteBuildForm(HelperForm):
|
|||||||
class CancelBuildForm(HelperForm):
|
class CancelBuildForm(HelperForm):
|
||||||
""" Form for cancelling a build """
|
""" 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:
|
class Meta:
|
||||||
model = Build
|
model = Build
|
||||||
@ -101,7 +103,11 @@ class CancelBuildForm(HelperForm):
|
|||||||
|
|
||||||
|
|
||||||
class EditBuildItemForm(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:
|
class Meta:
|
||||||
model = BuildItem
|
model = BuildItem
|
||||||
@ -109,4 +115,5 @@ class EditBuildItemForm(HelperForm):
|
|||||||
'build',
|
'build',
|
||||||
'stock_item',
|
'stock_item',
|
||||||
'quantity',
|
'quantity',
|
||||||
|
'install_into',
|
||||||
]
|
]
|
||||||
|
@ -20,14 +20,39 @@ InvenTree | Allocate Parts
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ 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");
|
var buildTable = $("#build-item-list");
|
||||||
|
|
||||||
// Calculate sum of allocations for a particular table row
|
// Calculate sum of allocations for a particular table row
|
||||||
|
29
InvenTree/build/templates/build/allocation_card.html
Normal file
29
InvenTree/build/templates/build/allocation_card.html
Normal 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>
|
@ -1,9 +1,22 @@
|
|||||||
{% extends "modal_form.html" %}
|
{% extends "modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block pre_form_content %}
|
{% 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 %}
|
{% if no_stock %}
|
||||||
<div class='alert alert-danger alert-block' role='alert'>
|
<div class='alert alert-danger alert-block' role='alert'>
|
||||||
No stock available for {{ part }}
|
{% trans "No stock available for" %} {{ part }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -492,23 +492,37 @@ class BuildItemDelete(AjaxDeleteView):
|
|||||||
|
|
||||||
|
|
||||||
class BuildItemCreate(AjaxCreateView):
|
class BuildItemCreate(AjaxCreateView):
|
||||||
""" View for allocating a new part to a build """
|
"""
|
||||||
|
View for allocating a StockItems to a build output.
|
||||||
|
"""
|
||||||
|
|
||||||
model = BuildItem
|
model = BuildItem
|
||||||
form_class = forms.EditBuildItemForm
|
form_class = forms.EditBuildItemForm
|
||||||
ajax_template_name = 'build/create_build_item.html'
|
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'
|
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
|
part = None
|
||||||
|
|
||||||
available_stock = None
|
available_stock = None
|
||||||
|
|
||||||
def get_context_data(self):
|
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:
|
if self.part:
|
||||||
ctx['part'] = self.part
|
ctx['part'] = self.part
|
||||||
|
|
||||||
|
if self.output:
|
||||||
|
ctx['output'] = self.output
|
||||||
|
|
||||||
if self.available_stock:
|
if self.available_stock:
|
||||||
ctx['stock'] = self.available_stock
|
ctx['stock'] = self.available_stock
|
||||||
else:
|
else:
|
||||||
@ -526,7 +540,28 @@ class BuildItemCreate(AjaxCreateView):
|
|||||||
build_id = form['build'].value()
|
build_id = form['build'].value()
|
||||||
|
|
||||||
if build_id is not None:
|
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['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
|
# If the sub_part is supplied, limit to matching stock items
|
||||||
part_id = self.get_param('part')
|
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:
|
""" Provide initial data for BomItem. Look for the folllowing in the GET data:
|
||||||
|
|
||||||
- build: pk of the Build object
|
- 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()
|
initials = super(AjaxCreateView, self).get_initial().copy()
|
||||||
|
|
||||||
build_id = self.get_param('build')
|
build_id = self.get_param('build')
|
||||||
part_id = self.get_param('part')
|
part_id = self.get_param('part')
|
||||||
|
output_id = self.get_param('install_into')
|
||||||
|
|
||||||
# Reference to a Part object
|
# Reference to a Part object
|
||||||
part = None
|
part = None
|
||||||
@ -593,6 +631,9 @@ class BuildItemCreate(AjaxCreateView):
|
|||||||
# Reference to a Build object
|
# Reference to a Build object
|
||||||
build = None
|
build = None
|
||||||
|
|
||||||
|
# Reference to a StockItem object
|
||||||
|
output = None
|
||||||
|
|
||||||
if part_id:
|
if part_id:
|
||||||
try:
|
try:
|
||||||
part = Part.objects.get(pk=part_id)
|
part = Part.objects.get(pk=part_id)
|
||||||
@ -623,7 +664,7 @@ class BuildItemCreate(AjaxCreateView):
|
|||||||
if item_id:
|
if item_id:
|
||||||
try:
|
try:
|
||||||
item = StockItem.objects.get(pk=item_id)
|
item = StockItem.objects.get(pk=item_id)
|
||||||
except:
|
except (ValueError, StockItem.DoesNotExist):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# If a StockItem is not selected, try to auto-select one
|
# If a StockItem is not selected, try to auto-select one
|
||||||
@ -639,6 +680,17 @@ class BuildItemCreate(AjaxCreateView):
|
|||||||
else:
|
else:
|
||||||
quantity = min(quantity, item.unallocated_quantity())
|
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:
|
if quantity is not None:
|
||||||
initials['quantity'] = quantity
|
initials['quantity'] = quantity
|
||||||
|
|
||||||
|
@ -934,8 +934,10 @@ class Part(MPTTModel):
|
|||||||
def required_parts(self):
|
def required_parts(self):
|
||||||
""" Return a list of parts required to make this part (list of BOM items) """
|
""" Return a list of parts required to make this part (list of BOM items) """
|
||||||
parts = []
|
parts = []
|
||||||
|
|
||||||
for bom in self.bom_items.all().select_related('sub_part'):
|
for bom in self.bom_items.all().select_related('sub_part'):
|
||||||
parts.append(bom.sub_part)
|
parts.append(bom.sub_part)
|
||||||
|
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
def get_allowed_bom_items(self):
|
def get_allowed_bom_items(self):
|
||||||
|
@ -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) {
|
function loadBuildTable(table, options) {
|
||||||
// Display a table of Build objects
|
// Display a table of Build objects
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user