mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add separate section for "untracked" part allocation
This commit is contained in:
parent
852cc9c4fa
commit
9e470d4064
@ -11,7 +11,7 @@ from rest_framework import generics
|
|||||||
|
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool, isNull
|
||||||
from InvenTree.status_codes import BuildStatus
|
from InvenTree.status_codes import BuildStatus
|
||||||
|
|
||||||
from .models import Build, BuildItem
|
from .models import Build, BuildItem
|
||||||
@ -194,7 +194,11 @@ class BuildItemList(generics.ListCreateAPIView):
|
|||||||
output = params.get('output', None)
|
output = params.get('output', None)
|
||||||
|
|
||||||
if output:
|
if output:
|
||||||
queryset = queryset.filter(install_into=output)
|
|
||||||
|
if isNull(output):
|
||||||
|
queryset = queryset.filter(install_into=None)
|
||||||
|
else:
|
||||||
|
queryset = queryset.filter(install_into=output)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -165,16 +165,10 @@ class AutoAllocateForm(HelperForm):
|
|||||||
|
|
||||||
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Confirm stock allocation'))
|
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Confirm stock allocation'))
|
||||||
|
|
||||||
# Keep track of which build output we are interested in
|
|
||||||
output = forms.ModelChoiceField(
|
|
||||||
queryset=StockItem.objects.all(),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Build
|
model = Build
|
||||||
fields = [
|
fields = [
|
||||||
'confirm',
|
'confirm',
|
||||||
'output',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -489,7 +489,7 @@ class Build(MPTTModel):
|
|||||||
self.status = BuildStatus.CANCELLED
|
self.status = BuildStatus.CANCELLED
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def getAutoAllocations(self, output):
|
def getAutoAllocations(self):
|
||||||
"""
|
"""
|
||||||
Return a list of StockItem objects which will be allocated
|
Return a list of StockItem objects which will be allocated
|
||||||
using the 'AutoAllocate' function.
|
using the 'AutoAllocate' function.
|
||||||
@ -521,15 +521,19 @@ class Build(MPTTModel):
|
|||||||
|
|
||||||
part = bom_item.sub_part
|
part = bom_item.sub_part
|
||||||
|
|
||||||
|
# If the part is "trackable" it cannot be auto-allocated
|
||||||
|
if part.trackable:
|
||||||
|
continue
|
||||||
|
|
||||||
# Skip any parts which are already fully allocated
|
# Skip any parts which are already fully allocated
|
||||||
if self.isPartFullyAllocated(part, output):
|
if self.isPartFullyAllocated(part, None):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# How many parts are required to complete the output?
|
# How many parts are required to complete the output?
|
||||||
required = self.unallocatedQuantity(part, output)
|
required = self.unallocatedQuantity(part, None)
|
||||||
|
|
||||||
# Grab a list of stock items which are available
|
# Grab a list of stock items which are available
|
||||||
stock_items = self.availableStockItems(part, output)
|
stock_items = self.availableStockItems(part, None)
|
||||||
|
|
||||||
# Ensure that the available stock items are in the correct location
|
# Ensure that the available stock items are in the correct location
|
||||||
if self.take_from is not None:
|
if self.take_from is not None:
|
||||||
@ -544,7 +548,6 @@ class Build(MPTTModel):
|
|||||||
build_items = BuildItem.objects.filter(
|
build_items = BuildItem.objects.filter(
|
||||||
build=self,
|
build=self,
|
||||||
stock_item=stock_item,
|
stock_item=stock_item,
|
||||||
install_into=output
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(build_items) > 0:
|
if len(build_items) > 0:
|
||||||
@ -567,24 +570,45 @@ class Build(MPTTModel):
|
|||||||
return allocations
|
return allocations
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def unallocateStock(self, output=None, part=None):
|
def unallocateOutput(self, output, part=None):
|
||||||
"""
|
"""
|
||||||
Deletes all stock allocations for this build.
|
Unallocate all stock which are allocated against the provided "output" (StockItem)
|
||||||
|
|
||||||
Args:
|
|
||||||
output: Specify which build output to delete allocations (optional)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
allocations = BuildItem.objects.filter(build=self.pk)
|
allocations = BuildItem.objects.filter(
|
||||||
|
build=self,
|
||||||
if output:
|
install_into=output
|
||||||
allocations = allocations.filter(install_into=output.pk)
|
)
|
||||||
|
|
||||||
if part:
|
if part:
|
||||||
allocations = allocations.filter(stock_item__part=part)
|
allocations = allocations.filter(stock_item__part=part)
|
||||||
|
|
||||||
# Remove all the allocations
|
allocations.delete()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def unallocateUntracked(self, part=None):
|
||||||
|
"""
|
||||||
|
Unallocate all "untracked" stock
|
||||||
|
"""
|
||||||
|
|
||||||
|
allocations = BuildItem.objects.filter(
|
||||||
|
build=self,
|
||||||
|
install_into=None
|
||||||
|
)
|
||||||
|
|
||||||
|
if part:
|
||||||
|
allocations = allocations.filter(stock_item__part=part)
|
||||||
|
|
||||||
|
allocations.delete()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def unallocateAll(self):
|
||||||
|
"""
|
||||||
|
Deletes all stock allocations for this build.
|
||||||
|
"""
|
||||||
|
|
||||||
|
allocations = BuildItem.objects.filter(build=self)
|
||||||
|
|
||||||
allocations.delete()
|
allocations.delete()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@ -685,7 +709,7 @@ class Build(MPTTModel):
|
|||||||
output.delete()
|
output.delete()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def autoAllocate(self, output):
|
def autoAllocate(self):
|
||||||
"""
|
"""
|
||||||
Run auto-allocation routine to allocate StockItems to this Build.
|
Run auto-allocation routine to allocate StockItems to this Build.
|
||||||
|
|
||||||
@ -702,7 +726,7 @@ class Build(MPTTModel):
|
|||||||
See: getAutoAllocations()
|
See: getAutoAllocations()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
allocations = self.getAutoAllocations(output)
|
allocations = self.getAutoAllocations()
|
||||||
|
|
||||||
for item in allocations:
|
for item in allocations:
|
||||||
# Create a new allocation
|
# Create a new allocation
|
||||||
@ -710,7 +734,7 @@ class Build(MPTTModel):
|
|||||||
build=self,
|
build=self,
|
||||||
stock_item=item['stock_item'],
|
stock_item=item['stock_item'],
|
||||||
quantity=item['quantity'],
|
quantity=item['quantity'],
|
||||||
install_into=output,
|
install_into=None
|
||||||
)
|
)
|
||||||
|
|
||||||
build_item.save()
|
build_item.save()
|
||||||
@ -779,7 +803,7 @@ class Build(MPTTModel):
|
|||||||
if output:
|
if output:
|
||||||
quantity *= output.quantity
|
quantity *= output.quantity
|
||||||
else:
|
else:
|
||||||
quantity *= self.remaining
|
quantity *= self.quantity
|
||||||
|
|
||||||
return quantity
|
return quantity
|
||||||
|
|
||||||
@ -1020,10 +1044,12 @@ class BuildItem(models.Model):
|
|||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if not self.install_into:
|
|
||||||
raise ValidationError(_('Build item must specify a build output'))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
# If the 'part' is trackable, then the 'install_into' field must be set!
|
||||||
|
if self.stock_item.part and self.stock_item.part.trackable and not self.install_into:
|
||||||
|
raise ValidationError(_('Build item must specify a build output, as master part is marked as trackable'))
|
||||||
|
|
||||||
# Allocated part must be in the BOM for the master part
|
# Allocated part must be in the BOM for the master part
|
||||||
if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False):
|
if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False):
|
||||||
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)]
|
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)]
|
||||||
|
@ -38,6 +38,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
<div class="panel panel-default" id='allocation-panel-untracked'>
|
||||||
|
<div class="panel-heading" role="tab" id="heading-untracked">
|
||||||
|
<div class="panel-title">
|
||||||
|
<div class='row'>
|
||||||
|
<a class='collapsed' aria-expanded='false' role="button" data-toggle="collapse" data-parent="#build-output-accordion" href="#collapse-untracked" aria-controls="collapse-untracked">
|
||||||
|
<div class='col-sm-6'>
|
||||||
|
<span class='fas fa-caret-right'></span>
|
||||||
|
{% trans "Allocated Stock" %}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class='col-sm-3'>
|
||||||
|
<div>
|
||||||
|
<div id='output-progress-untracked'>
|
||||||
|
<span class='fas fa-spin fa-spinner'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='col-sm-3'>
|
||||||
|
<div class='btn-group float-right' id='output-actions-untracked'>
|
||||||
|
<span class='fas fa-spin fa-spinner'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="collapse-untracked" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-untracked">
|
||||||
|
<div class="panel-body">
|
||||||
|
<table class='table table-striped table-condensed' id='allocation-table-untracked'></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
{% if build.incomplete_outputs %}
|
{% if build.incomplete_outputs %}
|
||||||
<div class="panel-group" id="build-output-accordion" role="tablist" aria-multiselectable="true">
|
<div class="panel-group" id="build-output-accordion" role="tablist" aria-multiselectable="true">
|
||||||
{% for item in build.incomplete_outputs %}
|
{% for item in build.incomplete_outputs %}
|
||||||
@ -66,6 +101,9 @@
|
|||||||
part: {{ build.part.pk }},
|
part: {{ build.part.pk }},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Load allocation table for un-tracked parts
|
||||||
|
loadBuildOutputAllocationTable(buildInfo, null);
|
||||||
|
|
||||||
{% for item in build.incomplete_outputs %}
|
{% for item in build.incomplete_outputs %}
|
||||||
// Get the build output as a javascript object
|
// Get the build output as a javascript object
|
||||||
inventreeGet('{% url 'api-stock-detail' item.pk %}', {},
|
inventreeGet('{% url 'api-stock-detail' item.pk %}', {},
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<li class='list-group-item {% if tab == "allocate" %}active{% endif %}' title='{% trans "In Progress" %}'>
|
<li class='list-group-item {% if tab == "allocate" %}active{% endif %}' title='{% trans "In Progress" %}'>
|
||||||
<a href='{% url "build-allocate" build.id %}'>
|
<a href='{% url "build-allocate" build.id %}'>
|
||||||
<span class='fas fa-tools'></span>
|
<span class='fas fa-tools'></span>
|
||||||
{% trans "In Progress" %}
|
{% trans "Allocate Stock" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -18,7 +18,7 @@ from stock.models import StockLocation, StockItem
|
|||||||
|
|
||||||
from InvenTree.views import AjaxUpdateView, AjaxCreateView, AjaxDeleteView
|
from InvenTree.views import AjaxUpdateView, AjaxCreateView, AjaxDeleteView
|
||||||
from InvenTree.views import InvenTreeRoleMixin
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
from InvenTree.helpers import str2bool, extract_serial_numbers, normalize
|
from InvenTree.helpers import str2bool, extract_serial_numbers, normalize, isNull
|
||||||
from InvenTree.status_codes import BuildStatus
|
from InvenTree.status_codes import BuildStatus
|
||||||
|
|
||||||
|
|
||||||
@ -98,16 +98,6 @@ class BuildAutoAllocate(AjaxUpdateView):
|
|||||||
|
|
||||||
initials = super().get_initial()
|
initials = super().get_initial()
|
||||||
|
|
||||||
# Pointing to a particular build output?
|
|
||||||
output = self.get_param('output')
|
|
||||||
|
|
||||||
if output:
|
|
||||||
try:
|
|
||||||
output = StockItem.objects.get(pk=output)
|
|
||||||
initials['output'] = output
|
|
||||||
except (ValueError, StockItem.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return initials
|
return initials
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
@ -121,16 +111,7 @@ class BuildAutoAllocate(AjaxUpdateView):
|
|||||||
|
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
|
||||||
output_id = form['output'].value()
|
context['allocations'] = build.getAutoAllocations()
|
||||||
|
|
||||||
try:
|
|
||||||
output = StockItem.objects.get(pk=output_id)
|
|
||||||
except (ValueError, StockItem.DoesNotExist):
|
|
||||||
output = None
|
|
||||||
|
|
||||||
if output:
|
|
||||||
context['output'] = output
|
|
||||||
context['allocations'] = build.getAutoAllocations(output)
|
|
||||||
|
|
||||||
context['build'] = build
|
context['build'] = build
|
||||||
|
|
||||||
@ -140,18 +121,11 @@ class BuildAutoAllocate(AjaxUpdateView):
|
|||||||
|
|
||||||
form = super().get_form()
|
form = super().get_form()
|
||||||
|
|
||||||
if form['output'].value():
|
|
||||||
# Hide the 'output' field
|
|
||||||
form.fields['output'].widget = HiddenInput()
|
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def validate(self, build, form, **kwargs):
|
def validate(self, build, form, **kwargs):
|
||||||
|
|
||||||
output = form.cleaned_data.get('output', None)
|
pass
|
||||||
|
|
||||||
if not output:
|
|
||||||
form.add_error(None, _('Build output must be specified'))
|
|
||||||
|
|
||||||
def save(self, build, form, **kwargs):
|
def save(self, build, form, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -159,9 +133,7 @@ class BuildAutoAllocate(AjaxUpdateView):
|
|||||||
perform auto-allocations
|
perform auto-allocations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output = form.cleaned_data.get('output', None)
|
build.autoAllocate()
|
||||||
|
|
||||||
build.autoAllocate(output)
|
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
@ -365,10 +337,16 @@ class BuildUnallocate(AjaxUpdateView):
|
|||||||
|
|
||||||
output_id = request.POST.get('output_id', None)
|
output_id = request.POST.get('output_id', None)
|
||||||
|
|
||||||
try:
|
if output_id:
|
||||||
output = StockItem.objects.get(pk=output_id)
|
|
||||||
except (ValueError, StockItem.DoesNotExist):
|
# If a "null" output is provided, we are trying to unallocate "untracked" stock
|
||||||
output = None
|
if isNull(output_id):
|
||||||
|
output = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
output = StockItem.objects.get(pk=output_id)
|
||||||
|
except (ValueError, StockItem.DoesNotExist):
|
||||||
|
output = None
|
||||||
|
|
||||||
part_id = request.POST.get('part_id', None)
|
part_id = request.POST.get('part_id', None)
|
||||||
|
|
||||||
@ -383,9 +361,19 @@ class BuildUnallocate(AjaxUpdateView):
|
|||||||
form.add_error('confirm', _('Confirm unallocation of build stock'))
|
form.add_error('confirm', _('Confirm unallocation of build stock'))
|
||||||
form.add_error(None, _('Check the confirmation box'))
|
form.add_error(None, _('Check the confirmation box'))
|
||||||
else:
|
else:
|
||||||
build.unallocateStock(output=output, part=part)
|
|
||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
|
# Unallocate the entire build
|
||||||
|
if not output_id:
|
||||||
|
build.unallocateAll()
|
||||||
|
# Unallocate a single output
|
||||||
|
elif output:
|
||||||
|
build.unallocateOutput(output, part=part)
|
||||||
|
# Unallocate "untracked" parts
|
||||||
|
else:
|
||||||
|
build.unallocateUntracked(part=part)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'form_valid': valid,
|
'form_valid': valid,
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,12 @@ function makeBuildOutputActionButtons(output, buildInfo) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var buildId = buildInfo.pk;
|
var buildId = buildInfo.pk;
|
||||||
var outputId = output.pk;
|
|
||||||
|
if (output) {
|
||||||
|
outputId = output.pk;
|
||||||
|
} else {
|
||||||
|
outputId = 'untracked';
|
||||||
|
}
|
||||||
|
|
||||||
var panel = `#allocation-panel-${outputId}`;
|
var panel = `#allocation-panel-${outputId}`;
|
||||||
|
|
||||||
@ -50,35 +55,40 @@ function makeBuildOutputActionButtons(output, buildInfo) {
|
|||||||
|
|
||||||
var html = `<div class='btn-group float-right' role='group'>`;
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
// Add a button to "auto allocate" against the build
|
// "Auto" allocation only works for untracked stock items
|
||||||
html += makeIconButton(
|
if (!output) {
|
||||||
'fa-magic icon-blue', 'button-output-auto', outputId,
|
html += makeIconButton(
|
||||||
'{% trans "Auto-allocate stock items to this output" %}',
|
'fa-magic icon-blue', 'button-output-auto', outputId,
|
||||||
);
|
'{% trans "Auto-allocate stock items to this output" %}',
|
||||||
|
);
|
||||||
// Add a button to "complete" the particular build output
|
}
|
||||||
html += makeIconButton(
|
|
||||||
'fa-check icon-green', 'button-output-complete', outputId,
|
|
||||||
'{% trans "Complete build output" %}',
|
|
||||||
{
|
|
||||||
//disabled: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a button to "cancel" the particular build output (unallocate)
|
// Add a button to "cancel" the particular build output (unallocate)
|
||||||
html += makeIconButton(
|
html += makeIconButton(
|
||||||
'fa-minus-circle icon-red', 'button-output-unallocate', outputId,
|
'fa-minus-circle icon-red', 'button-output-unallocate', outputId,
|
||||||
'{% trans "Unallocate stock from build output" %}',
|
'{% trans "Unallocate stock from build output" %}',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add a button to "delete" the particular build output
|
|
||||||
html += makeIconButton(
|
|
||||||
'fa-trash-alt icon-red', 'button-output-delete', outputId,
|
|
||||||
'{% trans "Delete build output" %}',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a button to "destroy" the particular build output (mark as damaged, scrap)
|
if (output) {
|
||||||
// TODO
|
|
||||||
|
// Add a button to "complete" the particular build output
|
||||||
|
html += makeIconButton(
|
||||||
|
'fa-check icon-green', 'button-output-complete', outputId,
|
||||||
|
'{% trans "Complete build output" %}',
|
||||||
|
{
|
||||||
|
//disabled: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a button to "delete" the particular build output
|
||||||
|
html += makeIconButton(
|
||||||
|
'fa-trash-alt icon-red', 'button-output-delete', outputId,
|
||||||
|
'{% trans "Delete build output" %}',
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO - Add a button to "destroy" the particular build output (mark as damaged, scrap)
|
||||||
|
}
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@ -90,7 +100,6 @@ function makeBuildOutputActionButtons(output, buildInfo) {
|
|||||||
launchModalForm(`/build/${buildId}/auto-allocate/`,
|
launchModalForm(`/build/${buildId}/auto-allocate/`,
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
output: outputId,
|
|
||||||
},
|
},
|
||||||
success: reloadTable,
|
success: reloadTable,
|
||||||
}
|
}
|
||||||
@ -115,7 +124,7 @@ function makeBuildOutputActionButtons(output, buildInfo) {
|
|||||||
{
|
{
|
||||||
success: reloadTable,
|
success: reloadTable,
|
||||||
data: {
|
data: {
|
||||||
output: outputId,
|
output: output ? outputId : 'null',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -152,13 +161,21 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
|
|
||||||
var outputId = null;
|
var outputId = null;
|
||||||
|
|
||||||
outputId = output.pk;
|
if (output) {
|
||||||
|
outputId = output.pk;
|
||||||
|
} else {
|
||||||
|
outputId = 'untracked';
|
||||||
|
}
|
||||||
|
|
||||||
var table = options.table;
|
var table = options.table;
|
||||||
|
|
||||||
if (options.table == null) {
|
if (options.table == null) {
|
||||||
table = `#allocation-table-${outputId}`;
|
table = `#allocation-table-${outputId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If an "output" is specified, then only "trackable" parts are allocated
|
||||||
|
// Otherwise, only "untrackable" parts are allowed
|
||||||
|
var trackable = ! !output;
|
||||||
|
|
||||||
function reloadTable() {
|
function reloadTable() {
|
||||||
// Reload the entire build allocation table
|
// Reload the entire build allocation table
|
||||||
@ -168,7 +185,13 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
function requiredQuantity(row) {
|
function requiredQuantity(row) {
|
||||||
// Return the requied quantity for a given row
|
// Return the requied quantity for a given row
|
||||||
|
|
||||||
return row.quantity * output.quantity;
|
if (output) {
|
||||||
|
// "Tracked" parts are calculated against individual build outputs
|
||||||
|
return row.quantity * output.quantity;
|
||||||
|
} else {
|
||||||
|
// "Untracked" parts are specified against the build itself
|
||||||
|
return row.quantity * buildInfo.quantity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sumAllocations(row) {
|
function sumAllocations(row) {
|
||||||
@ -300,6 +323,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
queryParams: {
|
queryParams: {
|
||||||
part: partId,
|
part: partId,
|
||||||
sub_part_detail: true,
|
sub_part_detail: true,
|
||||||
|
sub_part_trackable: trackable,
|
||||||
},
|
},
|
||||||
formatNoMatches: function() {
|
formatNoMatches: function() {
|
||||||
return '{% trans "No BOM items found" %}';
|
return '{% trans "No BOM items found" %}';
|
||||||
@ -310,11 +334,19 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
onLoadSuccess: function(tableData) {
|
onLoadSuccess: function(tableData) {
|
||||||
// Once the BOM data are loaded, request allocation data for this build output
|
// Once the BOM data are loaded, request allocation data for this build output
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
build: buildId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output) {
|
||||||
|
params.sub_part_trackable = true;
|
||||||
|
params.output = outputId;
|
||||||
|
} else {
|
||||||
|
params.sub_part_trackable = false;
|
||||||
|
}
|
||||||
|
|
||||||
inventreeGet('/api/build/item/',
|
inventreeGet('/api/build/item/',
|
||||||
{
|
params,
|
||||||
build: buildId,
|
|
||||||
output: outputId,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
// Iterate through the returned data, and group by the part they point to
|
// Iterate through the returned data, and group by the part they point to
|
||||||
@ -355,8 +387,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
|||||||
// Calculate the total allocated quantity
|
// Calculate the total allocated quantity
|
||||||
var allocatedQuantity = sumAllocations(tableRow);
|
var allocatedQuantity = sumAllocations(tableRow);
|
||||||
|
|
||||||
|
var requiredQuantity = 0;
|
||||||
|
|
||||||
|
if (output) {
|
||||||
|
requiredQuantity = tableRow.quantity * output.quantity;
|
||||||
|
} else {
|
||||||
|
requiredQuantity = tableRow.quantity * buildInfo.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
// Is this line item fully allocated?
|
// Is this line item fully allocated?
|
||||||
if (allocatedQuantity >= (tableRow.quantity * output.quantity)) {
|
if (allocatedQuantity >= requiredQuantity) {
|
||||||
allocatedLines += 1;
|
allocatedLines += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user