mirror of
https://github.com/inventree/InvenTree.git
synced 2026-05-13 21:17:33 +00:00
External order checks (#11935)
* Add new global settings Co-authored-by: Copilot <copilot@github.com> * Validation logic Co-authored-by: Copilot <copilot@github.com> * Remove one setting - Already covered if build order requires validation * Add unit test Co-authored-by: Copilot <copilot@github.com> --------- Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -304,10 +304,11 @@ The following [global settings](../settings/global.md) are available for adjusti
|
|||||||
| Name | Description | Default | Units |
|
| Name | Description | Default | Units |
|
||||||
| ---- | ----------- | ------- | ----- |
|
| ---- | ----------- | ------- | ----- |
|
||||||
{{ globalsetting("BUILDORDER_REFERENCE_PATTERN") }}
|
{{ globalsetting("BUILDORDER_REFERENCE_PATTERN") }}
|
||||||
{{ globalsetting("BUILDORDER_EXTERNAL_BUILDS") }}
|
|
||||||
{{ globalsetting("BUILDORDER_REQUIRE_RESPONSIBLE") }}
|
{{ globalsetting("BUILDORDER_REQUIRE_RESPONSIBLE") }}
|
||||||
{{ globalsetting("BUILDORDER_REQUIRE_ACTIVE_PART") }}
|
{{ globalsetting("BUILDORDER_REQUIRE_ACTIVE_PART") }}
|
||||||
{{ globalsetting("BUILDORDER_REQUIRE_LOCKED_PART") }}
|
{{ globalsetting("BUILDORDER_REQUIRE_LOCKED_PART") }}
|
||||||
{{ globalsetting("BUILDORDER_REQUIRE_VALID_BOM") }}
|
{{ globalsetting("BUILDORDER_REQUIRE_VALID_BOM") }}
|
||||||
{{ globalsetting("BUILDORDER_REQUIRE_CLOSED_CHILDS") }}
|
{{ globalsetting("BUILDORDER_REQUIRE_CLOSED_CHILDS") }}
|
||||||
{{ globalsetting("PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS") }}
|
{{ globalsetting("PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS") }}
|
||||||
|
{{ globalsetting("BUILDORDER_EXTERNAL_BUILDS") }}
|
||||||
|
{{ globalsetting("BUILDORDER_EXTERNAL_REQUIRED") }}
|
||||||
|
|||||||
@@ -932,7 +932,10 @@ class ExternalBuildTest(InvenTreeAPITestCase):
|
|||||||
def test_validation(self):
|
def test_validation(self):
|
||||||
"""Test validation of external build logic."""
|
"""Test validation of external build logic."""
|
||||||
part = Part.objects.create(
|
part = Part.objects.create(
|
||||||
name='Test part', description='A test part', purchaseable=False
|
name='Test part',
|
||||||
|
description='A test part',
|
||||||
|
assembly=True,
|
||||||
|
purchaseable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a build order
|
# Create a build order
|
||||||
@@ -949,6 +952,47 @@ class ExternalBuildTest(InvenTreeAPITestCase):
|
|||||||
str(err.exception.messages),
|
str(err.exception.messages),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_build_requirement(self):
|
||||||
|
"""Test the global 'BUILDORDER_EXTERNAL_REQUIRED' setting."""
|
||||||
|
# Create required test data
|
||||||
|
part = Part.objects.create(
|
||||||
|
name='Test part',
|
||||||
|
description='A test part',
|
||||||
|
assembly=True,
|
||||||
|
purchaseable=True,
|
||||||
|
)
|
||||||
|
supplier = company.models.Company.objects.create(
|
||||||
|
name='Test supplier', active=True, is_supplier=True
|
||||||
|
)
|
||||||
|
supplier_part = company.models.SupplierPart.objects.create(
|
||||||
|
part=part, supplier=supplier, SKU='TEST-123'
|
||||||
|
)
|
||||||
|
|
||||||
|
po = PurchaseOrder.objects.create(supplier=supplier, reference='PO-9999')
|
||||||
|
po_line = PurchaseOrderLineItem.objects.create(
|
||||||
|
order=po, part=supplier_part, quantity=10
|
||||||
|
)
|
||||||
|
|
||||||
|
set_global_setting('BUILDORDER_EXTERNAL_REQUIRED', False)
|
||||||
|
po_line.clean() # Should not raise an error
|
||||||
|
|
||||||
|
set_global_setting('BUILDORDER_EXTERNAL_BUILDS', True)
|
||||||
|
set_global_setting('BUILDORDER_EXTERNAL_REQUIRED', True)
|
||||||
|
|
||||||
|
# Expect failure, there is no linked build order
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
po_line.clean()
|
||||||
|
|
||||||
|
# Create and link a build order
|
||||||
|
build = Build.objects.create(
|
||||||
|
part=part, title='Test build order', quantity=10, external=True
|
||||||
|
)
|
||||||
|
po_line.build_order = build
|
||||||
|
po_line.save()
|
||||||
|
|
||||||
|
# Clean step now passes
|
||||||
|
po_line.clean()
|
||||||
|
|
||||||
def test_logic(self):
|
def test_logic(self):
|
||||||
"""Test external build logic."""
|
"""Test external build logic."""
|
||||||
# Create a purchaseable assembly part
|
# Create a purchaseable assembly part
|
||||||
|
|||||||
@@ -832,6 +832,14 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
|
|||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
'BUILDORDER_EXTERNAL_REQUIRED': {
|
||||||
|
'name': _('Require External Build Orders'),
|
||||||
|
'description': _(
|
||||||
|
'Require an external build order when ordering assembled parts from an external supplier'
|
||||||
|
),
|
||||||
|
'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': _(
|
||||||
|
|||||||
@@ -1955,13 +1955,16 @@ class PurchaseOrderLineItem(OrderLineItem):
|
|||||||
if self.part.supplier != self.order.supplier:
|
if self.part.supplier != self.order.supplier:
|
||||||
raise ValidationError({'part': _('Supplier part must match supplier')})
|
raise ValidationError({'part': _('Supplier part must match supplier')})
|
||||||
|
|
||||||
|
# Link to the base part
|
||||||
|
part = self.part.part
|
||||||
|
|
||||||
if self.build_order:
|
if self.build_order:
|
||||||
if not self.build_order.external:
|
if not self.build_order.external:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'build_order': _('Build order must be marked as external')
|
'build_order': _('Build order must be marked as external')
|
||||||
})
|
})
|
||||||
|
|
||||||
if part := self.part.part:
|
if part:
|
||||||
if not part.assembly:
|
if not part.assembly:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'build_order': _(
|
'build_order': _(
|
||||||
@@ -1974,6 +1977,17 @@ class PurchaseOrderLineItem(OrderLineItem):
|
|||||||
'build_order': _('Build order part must match line item part')
|
'build_order': _('Build order part must match line item part')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Extra checks for external builds
|
||||||
|
if part and part.assembly and get_global_setting('BUILDORDER_EXTERNAL_BUILDS'):
|
||||||
|
if not self.build_order and get_global_setting(
|
||||||
|
'BUILDORDER_EXTERNAL_REQUIRED'
|
||||||
|
):
|
||||||
|
raise ValidationError({
|
||||||
|
'build_order': _(
|
||||||
|
'An external build order is required for assembly parts'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Render a string representation of a PurchaseOrderLineItem instance."""
|
"""Render a string representation of a PurchaseOrderLineItem instance."""
|
||||||
return '{n} x {part} - {po}'.format(
|
return '{n} x {part} - {po}'.format(
|
||||||
|
|||||||
@@ -290,11 +290,11 @@ export default function SystemSettings() {
|
|||||||
label: t`Manufacturing`,
|
label: t`Manufacturing`,
|
||||||
icon: <IconBuildingFactory2 />,
|
icon: <IconBuildingFactory2 />,
|
||||||
content: (
|
content: (
|
||||||
|
<>
|
||||||
<GlobalSettingList
|
<GlobalSettingList
|
||||||
heading={t`Build Orders`}
|
heading={t`Build Orders`}
|
||||||
keys={[
|
keys={[
|
||||||
'BUILDORDER_REFERENCE_PATTERN',
|
'BUILDORDER_REFERENCE_PATTERN',
|
||||||
'BUILDORDER_EXTERNAL_BUILDS',
|
|
||||||
'BUILDORDER_REQUIRE_RESPONSIBLE',
|
'BUILDORDER_REQUIRE_RESPONSIBLE',
|
||||||
'BUILDORDER_REQUIRE_ACTIVE_PART',
|
'BUILDORDER_REQUIRE_ACTIVE_PART',
|
||||||
'BUILDORDER_REQUIRE_LOCKED_PART',
|
'BUILDORDER_REQUIRE_LOCKED_PART',
|
||||||
@@ -303,6 +303,14 @@ export default function SystemSettings() {
|
|||||||
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
|
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<GlobalSettingList
|
||||||
|
heading={t`External Build Orders`}
|
||||||
|
keys={[
|
||||||
|
'BUILDORDER_EXTERNAL_BUILDS',
|
||||||
|
'BUILDORDER_EXTERNAL_REQUIRED'
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user