diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 61beba3262..49a1bcf5dc 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -485,6 +485,9 @@ class Build(MPTTModel): if self.completed < self.quantity: return False + if not self.areUntrackedPartsFullyAllocated(): + return False + # No issues! return True @@ -494,7 +497,7 @@ class Build(MPTTModel): Mark this build as complete """ - if not self.can_complete: + if self.incomplete_count > 0: return self.completion_date = datetime.now().date() @@ -502,6 +505,9 @@ class Build(MPTTModel): self.status = BuildStatus.COMPLETE self.save() + # Remove untracked allocated stock + self.subtractUntrackedStock(user) + # Ensure that there are no longer any BuildItem objects # which point to thie Build Order self.allocated_stock.all().delete() @@ -775,6 +781,24 @@ class Build(MPTTModel): build_item.save() + @transaction.atomic + def subtractUntrackedStock(self, user): + """ + Called when the Build is marked as "complete", + this function removes the allocated untracked items from stock. + """ + + items = self.allocated_stock.filter( + stock_item__part__trackable=False + ) + + # Remove stock + for item in items: + item.complete_allocation(user) + + # Delete allocation + items.all().delete() + @transaction.atomic def completeBuildOutput(self, output, user, **kwargs): """ @@ -790,9 +814,6 @@ class Build(MPTTModel): # List the allocated BuildItem objects for the given output allocated_items = output.items_to_install.all() - # Ensure that only "trackable" parts are installed in the particular build output - allocated_items = allocated_items.filter(part__trackable=True) - for build_item in allocated_items: # TODO: This is VERY SLOW as each deletion from the database takes ~1 second to complete @@ -915,6 +936,13 @@ class Build(MPTTModel): # All parts must be fully allocated! return True + def areUntrackedPartsFullyAllocated(self): + """ + Returns True if the un-tracked parts are fully allocated for this BuildOrder + """ + + return self.isFullyAllocated(None) + def allocatedParts(self, output): """ Return a list of parts which have been fully allocated against a particular output diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html index a1e1ff2f8e..dee90a26a0 100644 --- a/InvenTree/build/templates/build/allocate.html +++ b/InvenTree/build/templates/build/allocate.html @@ -31,6 +31,15 @@ --> +{% if build.areUntrackedPartsFullyAllocated %} +
+ {% trans "Untracked stock has been fully allocated for this Build Order" %} +
+{% else %} +
+ {% trans "Untracked stock has not been fully allocated for this Build Order" %} +
+{% endif %} {% endif %}
{% else %} diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index 9e63208f71..c0f6e400b6 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -9,7 +9,7 @@ {% inventree_title %} | {% trans "Build Order" %} - {{ build }} {% endblock %} -{% block pre_content %} +{% block header_pre_content %} {% if build.sales_order %}
{% object_link 'so-detail' build.sales_order.id build.sales_order as link %} @@ -24,6 +24,31 @@ {% endif %} {% endblock %} +{% block header_post_content %} +{% if build.active %} +{% if build.can_complete %} +
+ {% trans "Build Order is ready to mark as completed" %} +
+{% endif %} +{% if build.incomplete_count > 0 %} +
+ {% trans "Build Order cannot be completed as outstanding outputs remain" %} +
+{% endif %} +{% if build.completed < build.quantity %} +
+ {% trans "Required build quantity has not yet been completed" %} +
+{% endif %} +{% if not build.areUntrackedPartsFullyAllocated %} +
+ {% trans "Stock has not been fully allocated to this Build Order" %} +
+{% endif %} +{% endif %} +{% endblock %} + {% block thumbnail %} {% if roles.build.change %} + {% if build.active %} + + {% endif %}
{% endif %} @@ -172,6 +201,13 @@ src="{% static 'img/blank_image.png' %}" }); $("#build-complete").on('click', function() { + + {% if build.incomplete_count > 0 %} + showAlertDialog( + '{% trans "Incomplete Outputs" %}', + '{% trans "Build Order cannot be completed as incomplete build outputs remain" %}' + ); + {% else %} launchModalForm( "{% url 'build-complete' build.id %}", { @@ -179,6 +215,7 @@ src="{% static 'img/blank_image.png' %}" submit_text: '{% trans "Complete Build" %}', } ); + {% endif %} }); $('#print-build-report').click(function() { diff --git a/InvenTree/build/templates/build/complete.html b/InvenTree/build/templates/build/complete.html index 0db6a336a5..527c0598d4 100644 --- a/InvenTree/build/templates/build/complete.html +++ b/InvenTree/build/templates/build/complete.html @@ -5,11 +5,11 @@ {% if build.can_complete %}
- {% trans "Build can be completed" %} + {% trans "Build Order is complete" %}
{% else %}
- {% trans "Build cannot be completed" %}
+ {% trans "Build Order is incomplete" %}
{% endif %} diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 5b9e0a3557..4b5a60e6e7 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -109,8 +109,6 @@ class BuildAutoAllocate(AjaxUpdateView): build = self.get_object() - form = self.get_form() - context['allocations'] = build.getAutoAllocations() context['build'] = build @@ -398,8 +396,8 @@ class BuildComplete(AjaxUpdateView): def validate(self, build, form, **kwargs): - if not build.can_complete: - form.add_error(None, _('Build order cannot be completed')) + if build.incomplete_count > 0: + form.add_error(None, _('Build order cannot be completed - incomplete outputs remain')) def save(self, build, form, **kwargs): """ diff --git a/InvenTree/templates/two_column.html b/InvenTree/templates/two_column.html index 35f084276a..76521ea367 100644 --- a/InvenTree/templates/two_column.html +++ b/InvenTree/templates/two_column.html @@ -9,8 +9,12 @@ {% block content %} +{% block header_panel %}
+ {% block header_pre_content %} + {% endblock %} +
@@ -30,7 +34,12 @@ {% endblock %}
+ + {% block header_post_content %} + {% endblock %} +
+{% endblock %} {% block content_panels %}