mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
[Build] Add extra validation options (#7560)
* Add new setting: BUILDORDER_REQUIRE_VALID_BOM - Prevent build orders from being created if the assembly BOM has not been validated * Add validation check when creating a build order * Add unit tests
This commit is contained in:
parent
720651602b
commit
13dbfd0b14
@ -120,8 +120,23 @@ class Build(
|
|||||||
self.validate_reference_field(self.reference)
|
self.validate_reference_field(self.reference)
|
||||||
self.reference_int = self.rebuild_reference_field(self.reference)
|
self.reference_int = self.rebuild_reference_field(self.reference)
|
||||||
|
|
||||||
|
if get_global_setting('BUILDORDER_REQUIRE_VALID_BOM'):
|
||||||
|
# Check that the BOM is valid
|
||||||
|
if not self.part.is_bom_valid():
|
||||||
|
raise ValidationError({
|
||||||
|
'part': _('Assembly BOM has not been validated')
|
||||||
|
})
|
||||||
|
|
||||||
|
if get_global_setting('BUILDORDER_REQUIRE_ACTIVE_PART'):
|
||||||
|
# Check that the part is active
|
||||||
|
if not self.part.active:
|
||||||
|
raise ValidationError({
|
||||||
|
'part': _('Part is not active')
|
||||||
|
})
|
||||||
|
|
||||||
# On first save (i.e. creation), run some extra checks
|
# On first save (i.e. creation), run some extra checks
|
||||||
if self.pk is None:
|
if self.pk is None:
|
||||||
|
|
||||||
# Set the destination location (if not specified)
|
# Set the destination location (if not specified)
|
||||||
if not self.destination:
|
if not self.destination:
|
||||||
self.destination = self.part.get_default_location()
|
self.destination = self.part.get_default_location()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Basic unit tests for the BuildOrder app"""
|
"""Basic unit tests for the BuildOrder app"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import tag
|
from django.test import tag
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -9,8 +10,10 @@ from datetime import datetime, timedelta
|
|||||||
from InvenTree.unit_test import InvenTreeTestCase
|
from InvenTree.unit_test import InvenTreeTestCase
|
||||||
|
|
||||||
from .models import Build
|
from .models import Build
|
||||||
|
from part.models import Part, BomItem
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
|
|
||||||
|
from common.settings import get_global_setting, set_global_setting
|
||||||
from build.status_codes import BuildStatus
|
from build.status_codes import BuildStatus
|
||||||
|
|
||||||
|
|
||||||
@ -88,6 +91,64 @@ class BuildTestSimple(InvenTreeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(build.status, BuildStatus.CANCELLED)
|
self.assertEqual(build.status, BuildStatus.CANCELLED)
|
||||||
|
|
||||||
|
def test_build_create(self):
|
||||||
|
"""Test creation of build orders via API."""
|
||||||
|
|
||||||
|
n = Build.objects.count()
|
||||||
|
|
||||||
|
# Find an assembly part
|
||||||
|
assembly = Part.objects.filter(assembly=True).first()
|
||||||
|
|
||||||
|
self.assertEqual(assembly.get_bom_items().count(), 0)
|
||||||
|
|
||||||
|
# Let's create some BOM items for this assembly
|
||||||
|
for component in Part.objects.filter(assembly=False, component=True)[:15]:
|
||||||
|
|
||||||
|
try:
|
||||||
|
BomItem.objects.create(
|
||||||
|
part=assembly,
|
||||||
|
sub_part=component,
|
||||||
|
reference='xxx',
|
||||||
|
quantity=5
|
||||||
|
)
|
||||||
|
except ValidationError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# The assembly has a BOM, and is now *invalid*
|
||||||
|
self.assertGreater(assembly.get_bom_items().count(), 0)
|
||||||
|
self.assertFalse(assembly.is_bom_valid())
|
||||||
|
|
||||||
|
# Create a build for an assembly with an *invalid* BOM
|
||||||
|
set_global_setting('BUILDORDER_REQUIRE_VALID_BOM', False)
|
||||||
|
set_global_setting('BUILDORDER_REQUIRE_ACTIVE_PART', True)
|
||||||
|
|
||||||
|
bo = Build.objects.create(part=assembly, quantity=10, reference='BO-9990')
|
||||||
|
bo.save()
|
||||||
|
|
||||||
|
# Now, require a *valid* BOM
|
||||||
|
set_global_setting('BUILDORDER_REQUIRE_VALID_BOM', True)
|
||||||
|
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
bo = Build.objects.create(part=assembly, quantity=10, reference='BO-9991')
|
||||||
|
|
||||||
|
# Now, validate the BOM, and try again
|
||||||
|
assembly.validate_bom(None)
|
||||||
|
self.assertTrue(assembly.is_bom_valid())
|
||||||
|
|
||||||
|
bo = Build.objects.create(part=assembly, quantity=10, reference='BO-9992')
|
||||||
|
|
||||||
|
# Now, try and create a build for an inactive assembly
|
||||||
|
assembly.active = False
|
||||||
|
assembly.save()
|
||||||
|
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
bo = Build.objects.create(part=assembly, quantity=10, reference='BO-9993')
|
||||||
|
|
||||||
|
set_global_setting('BUILDORDER_REQUIRE_ACTIVE_PART', False)
|
||||||
|
Build.objects.create(part=assembly, quantity=10, reference='BO-9994')
|
||||||
|
|
||||||
|
# Check that expected quantity of new builds is created
|
||||||
|
self.assertEqual(Build.objects.count(), n + 3)
|
||||||
|
|
||||||
class TestBuildViews(InvenTreeTestCase):
|
class TestBuildViews(InvenTreeTestCase):
|
||||||
"""Tests for Build app views."""
|
"""Tests for Build app views."""
|
||||||
|
@ -1786,6 +1786,20 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
'BUILDORDER_REQUIRE_ACTIVE_PART': {
|
||||||
|
'name': _('Require Active Part'),
|
||||||
|
'description': _('Prevent build order creation for inactive parts'),
|
||||||
|
'default': False,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
|
'BUILDORDER_REQUIRE_VALID_BOM': {
|
||||||
|
'name': _('Require Valid BOM'),
|
||||||
|
'description': _(
|
||||||
|
'Prevent build order creation unless BOM has been validated'
|
||||||
|
),
|
||||||
|
'default': False,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
|
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
|
||||||
'name': _('Block Until Tests Pass'),
|
'name': _('Block Until Tests Pass'),
|
||||||
'description': _(
|
'description': _(
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PATTERN" %}
|
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PATTERN" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_RESPONSIBLE" %}
|
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_RESPONSIBLE" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_ACTIVE_PART" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_VALID_BOM" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %}
|
{% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -245,6 +245,8 @@ export default function SystemSettings() {
|
|||||||
keys={[
|
keys={[
|
||||||
'BUILDORDER_REFERENCE_PATTERN',
|
'BUILDORDER_REFERENCE_PATTERN',
|
||||||
'BUILDORDER_REQUIRE_RESPONSIBLE',
|
'BUILDORDER_REQUIRE_RESPONSIBLE',
|
||||||
|
'BUILDORDER_REQUIRE_ACTIVE_PART',
|
||||||
|
'BUILDORDER_REQUIRE_VALID_BOM',
|
||||||
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
|
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user