mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Add option to disable the build output completion if are tests not passed (#6057)
* Add option to disable the build output completion if required tests not passed Fixes #5037 * 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:
		| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user