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 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',
|
||||
]
|
||||
|
@ -20,14 +20,39 @@ InvenTree | Allocate Parts
|
||||
</div>
|
||||
{% 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
|
||||
|
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" %}
|
||||
{% 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 %}
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user