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:
@ -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}'")
|
@ -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
|
||||
|
||||
|
@ -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"))
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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': _(
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
]}
|
||||
/>
|
||||
|
Reference in New Issue
Block a user