Add option to disable the build output completion if are tests not passed ()

* Add option to disable the build output completion if required tests not passed

Fixes 

* Fix review comments

* Added tests

* Add settinsg option to PUI

* Utilize F" string concatenation

* Add validation to serializer too to being able to generate proper error message in the case if multiple outputs having incomplete tests

* Fix other build tests failing because of the new stock items

* Remove len from array empty check

* Update serializers.py

* Update models.py

Simplify error message

* Update settings.py

Formatting fix

* Update models.py

More style fixes

* Update models.py

Remove empty line

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
Miklós Márton 2024-02-18 00:28:37 +01:00 committed by GitHub
parent 7adf2e0835
commit ad1c1ae604
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 138 additions and 6 deletions
InvenTree
src/frontend/src/pages/Index/Settings

@ -915,6 +915,11 @@ class Build(InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNo
# List the allocated BuildItem objects for the given output
allocated_items = output.items_to_install.all()
if (common.settings.prevent_build_output_complete_on_incompleted_tests() and output.hasRequiredTests() and not output.passedAllRequiredTests()):
serial = output.serial
raise ValidationError(
_(f"Build output {serial} has not passed all required tests"))
for build_item in allocated_items:
# Complete the allocation of stock for that item
build_item.complete_allocation(user)

@ -27,6 +27,7 @@ from InvenTree.status_codes import BuildStatusGroups, StockStatus
from stock.models import generate_batch_code, StockItem, StockLocation
from stock.serializers import StockItemSerializerBrief, LocationSerializer
import common.models
from common.serializers import ProjectCodeSerializer
import part.filters
from part.serializers import BomItemSerializer, PartSerializer, PartBriefSerializer
@ -523,6 +524,17 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
outputs = data.get('outputs', [])
if common.settings.prevent_build_output_complete_on_incompleted_tests():
errors = []
for output in outputs:
stock_item = output['output']
if stock_item.hasRequiredTests() and not stock_item.passedAllRequiredTests():
serial = stock_item.serial
errors.append(_(f"Build output {serial} has not passed all required tests"))
if errors:
raise ValidationError(errors)
if len(outputs) == 0:
raise ValidationError(_("A list of build outputs must be provided"))

@ -1,5 +1,5 @@
"""Unit tests for the 'build' models"""
import uuid
from datetime import datetime, timedelta
from django.test import TestCase
@ -14,8 +14,8 @@ from InvenTree import status_codes as status
import common.models
import build.tasks
from build.models import Build, BuildItem, BuildLine, generate_next_build_reference
from part.models import Part, BomItem, BomItemSubstitute
from stock.models import StockItem
from part.models import Part, BomItem, BomItemSubstitute, PartTestTemplate
from stock.models import StockItem, StockItemTestResult
from users.models import Owner
import logging
@ -55,6 +55,76 @@ class BuildTestBase(TestCase):
trackable=True,
)
# create one build with one required test template
cls.tested_part_with_required_test = Part.objects.create(
name="Part having required tests",
description="Why does it matter what my description is?",
assembly=True,
trackable=True,
)
cls.test_template_required = PartTestTemplate.objects.create(
part=cls.tested_part_with_required_test,
test_name="Required test",
description="Required test template description",
required=True,
requires_value=False,
requires_attachment=False
)
ref = generate_next_build_reference()
cls.build_w_tests_trackable = Build.objects.create(
reference=ref,
title="This is a build",
part=cls.tested_part_with_required_test,
quantity=1,
issued_by=get_user_model().objects.get(pk=1),
)
cls.stockitem_with_required_test = StockItem.objects.create(
part=cls.tested_part_with_required_test,
quantity=1,
is_building=True,
serial=uuid.uuid4(),
build=cls.build_w_tests_trackable
)
# now create a part with a non-required test template
cls.tested_part_wo_required_test = Part.objects.create(
name="Part with one non.required test",
description="Why does it matter what my description is?",
assembly=True,
trackable=True,
)
cls.test_template_non_required = PartTestTemplate.objects.create(
part=cls.tested_part_wo_required_test,
test_name="Required test template",
description="Required test template description",
required=False,
requires_value=False,
requires_attachment=False
)
ref = generate_next_build_reference()
cls.build_wo_tests_trackable = Build.objects.create(
reference=ref,
title="This is a build",
part=cls.tested_part_wo_required_test,
quantity=1,
issued_by=get_user_model().objects.get(pk=1),
)
cls.stockitem_wo_required_test = StockItem.objects.create(
part=cls.tested_part_wo_required_test,
quantity=1,
is_building=True,
serial=uuid.uuid4(),
build=cls.build_wo_tests_trackable
)
cls.sub_part_1 = Part.objects.create(
name="Widget A",
description="A widget",
@ -245,7 +315,7 @@ class BuildTest(BuildTestBase):
def test_init(self):
"""Perform some basic tests before we start the ball rolling"""
self.assertEqual(StockItem.objects.count(), 10)
self.assertEqual(StockItem.objects.count(), 12)
# Build is PENDING
self.assertEqual(self.build.status, status.BuildStatus.PENDING)
@ -558,7 +628,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(BuildItem.objects.count(), 0)
# New stock items should have been created!
self.assertEqual(StockItem.objects.count(), 13)
self.assertEqual(StockItem.objects.count(), 15)
# This stock item has been marked as "consumed"
item = StockItem.objects.get(pk=self.stock_1_1.pk)
@ -573,6 +643,26 @@ class BuildTest(BuildTestBase):
for output in outputs:
self.assertFalse(output.is_building)
def test_complete_with_required_tests(self):
"""Test the prevention completion when a required test is missing feature"""
# with required tests incompleted the save should fail
common.models.InvenTreeSetting.set_setting('PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS', True, change_user=None)
with self.assertRaises(ValidationError):
self.build_w_tests_trackable.complete_build_output(self.stockitem_with_required_test, None)
# let's complete the required test and see if it could be saved
StockItemTestResult.objects.create(
stock_item=self.stockitem_with_required_test,
test=self.test_template_required.test_name,
result=True
)
self.build_w_tests_trackable.complete_build_output(self.stockitem_with_required_test, None)
# let's see if a non required test could be saved
self.build_wo_tests_trackable.complete_build_output(self.stockitem_wo_required_test, None)
def test_overdue_notification(self):
"""Test sending of notifications when a build order is overdue."""
self.build.target_date = datetime.now().date() - timedelta(days=1)

@ -1981,6 +1981,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False,
'validator': bool,
},
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
'name': _('Block Until Tests Pass'),
'description': _(
'Prevent build outputs from being completed until all required tests pass'
),
'default': False,
'validator': bool,
},
}
typ = 'inventree'

@ -56,3 +56,12 @@ def stock_expiry_enabled():
from common.models import InvenTreeSetting
return InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY', False, create=False)
def prevent_build_output_complete_on_incompleted_tests():
"""Returns True if the completion of the build outputs is disabled until the required tests are passed."""
from common.models import InvenTreeSetting
return InvenTreeSetting.get_setting(
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS', False, create=False
)

@ -13,6 +13,7 @@
<table class='table table-striped table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PATTERN" %}
{% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %}
</tbody>
</table>

@ -225,7 +225,14 @@ export default function SystemSettings() {
name: 'buildorders',
label: t`Build Orders`,
icon: <IconTools />,
content: <GlobalSettingList keys={['BUILDORDER_REFERENCE_PATTERN']} />
content: (
<GlobalSettingList
keys={[
'BUILDORDER_REFERENCE_PATTERN',
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
]}
/>
)
},
{
name: 'purchaseorders',