2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 04:25:42 +00:00

Build order rules (#7842)

* Add new global setting

* Check if there are open children before completing a build

* Adds management command to export settings definition

* Fix settings export

* Extract settings data into documentation

* Add global settings spec

* User settings

* Revert strict mode

* Tweak unit test

* Remove unreachable code

* Always export settings first

* Remove unused macro

* Remove old images

* Re-add missing docs strings

* Tweak docs

* Remove unused import
This commit is contained in:
Oliver
2024-08-10 09:26:03 +10:00
committed by GitHub
parent 556a3161e8
commit 42183a3a3f
20 changed files with 375 additions and 158 deletions

View File

@ -0,0 +1,53 @@
"""Custom management command to export settings definitions.
This is used to generate a JSON file which contains all of the settings,
so that they can be introspected by the InvenTree documentation system.
This in turn allows settings to be documented in the InvenTree documentation,
without having to manually duplicate the information in multiple places.
"""
import json
from django.core.management.base import BaseCommand
class Command(BaseCommand):
"""Extract settings information, and export to a JSON file."""
def add_arguments(self, parser):
"""Add custom arguments for this command."""
parser.add_argument(
'filename', type=str, help='Output filename for settings definitions'
)
def handle(self, *args, **kwargs):
"""Export settings information to a JSON file."""
from common.models import InvenTreeSetting, InvenTreeUserSetting
settings = {'global': {}, 'user': {}}
# Global settings
for key, setting in InvenTreeSetting.SETTINGS.items():
settings['global'][key] = {
'name': str(setting['name']),
'description': str(setting['description']),
'default': str(InvenTreeSetting.get_setting_default(key)),
'units': str(setting.get('units', '')),
}
# User settings
for key, setting in InvenTreeUserSetting.SETTINGS.items():
settings['user'][key] = {
'name': str(setting['name']),
'description': str(setting['description']),
'default': str(InvenTreeUserSetting.get_setting_default(key)),
'units': str(setting.get('units', '')),
}
filename = kwargs.get('filename', 'inventree_settings.json')
with open(filename, 'w') as f:
json.dump(settings, f, indent=4)
print(f"Exported InvenTree settings definitions to '{filename}'")

View File

@ -395,9 +395,9 @@ class Build(
def sub_builds(self, cascade=True):
"""Return all Build Order objects under this one."""
if cascade:
return Build.objects.filter(parent=self.pk)
descendants = self.get_descendants(include_self=True)
Build.objects.filter(parent__pk__in=[d.pk for d in descendants])
return self.get_descendants(include_self=False)
else:
return self.get_children()
def sub_build_count(self, cascade=True):
"""Return the number of sub builds under this one.
@ -407,6 +407,11 @@ class Build(
"""
return self.sub_builds(cascade=cascade).count()
@property
def has_open_child_builds(self):
"""Return True if this build order has any open child builds."""
return self.sub_builds().filter(status__in=BuildStatusGroups.ACTIVE_CODES).exists()
@property
def is_overdue(self):
"""Returns true if this build is "overdue".
@ -576,6 +581,9 @@ class Build(
- Untracked parts must be allocated
"""
if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and self.has_open_child_builds:
return False
if self.status != BuildStatus.PRODUCTION.value:
return False
@ -619,6 +627,10 @@ class Build(
trim_allocated_stock = kwargs.pop('trim_allocated_stock', False)
user = kwargs.pop('user', None)
# Prevent completion if there are open child builds
if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and self.has_open_child_builds:
return
if self.incomplete_count > 0:
return
@ -974,7 +986,10 @@ class Build(
items_to_save = []
items_to_delete = []
for build_line in self.untracked_line_items:
lines = self.untracked_line_items
lines = lines.prefetch_related('allocations')
for build_line in lines:
reduce_by = build_line.allocated_quantity() - build_line.quantity

View File

@ -27,6 +27,7 @@ from stock.serializers import StockItemSerializerBrief, LocationBriefSerializer
import common.models
from common.serializers import ProjectCodeSerializer
from common.settings import get_global_setting
from importer.mixins import DataImportExportSerializerMixin
import company.serializers
import part.filters
@ -765,6 +766,9 @@ class BuildCompleteSerializer(serializers.Serializer):
"""Perform validation of this serializer prior to saving"""
build = self.context['build']
if get_global_setting('BUILDORDER_REQUIRE_CLOSED_CHILDS') and build.has_open_child_builds:
raise ValidationError(_("Build order has open child build orders"))
if build.status != BuildStatus.PRODUCTION.value:
raise ValidationError(_("Build order must be in production state"))

View File

@ -1015,7 +1015,7 @@ class BuildOverallocationTest(BuildAPITest):
'accept_overallocated': 'trim',
},
expected_code=201,
max_query_count=555, # TODO: Come back and refactor this
max_query_count=600, # TODO: Come back and refactor this
)
self.build.refresh_from_db()

View File

@ -1838,6 +1838,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False,
'validator': bool,
},
'BUILDORDER_REQUIRE_CLOSED_CHILDS': {
'name': _('Require Closed Child Orders'),
'description': _(
'Prevent build order completion until all child orders are closed'
),
'default': False,
'validator': bool,
},
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS': {
'name': _('Block Until Tests Pass'),
'description': _(

View File

@ -17,6 +17,7 @@
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_ACTIVE_PART" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_LOCKED_PART" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_VALID_BOM" %}
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REQUIRE_CLOSED_CHILDS" %}
{% include "InvenTree/settings/setting.html" with key="PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS" %}
</tbody>
</table>

View File

@ -244,6 +244,7 @@ export default function SystemSettings() {
'BUILDORDER_REQUIRE_ACTIVE_PART',
'BUILDORDER_REQUIRE_LOCKED_PART',
'BUILDORDER_REQUIRE_VALID_BOM',
'BUILDORDER_REQUIRE_CLOSED_CHILDS',
'PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS'
]}
/>