mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user