From 22a5f921b80089ab64e4ad57e4387916f54e4ed3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 27 Oct 2020 23:09:17 +1100 Subject: [PATCH] Auto-generate build outputs when a build is created --- InvenTree/InvenTree/views.py | 2 +- InvenTree/build/forms.py | 8 +++++++ InvenTree/build/models.py | 46 ++++++++++++++++++++++++++++-------- InvenTree/build/views.py | 44 +++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 87c517f9e2..4fc4de20ae 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -387,7 +387,7 @@ class AjaxCreateView(AjaxMixin, CreateView): self.pre_save(self.form, request) self.object = self.form.save() - self.post_save(new_object=self.object, request=request) + self.post_save(new_object=self.object, request=request, form=self.form, data=self.form.cleaned_data) # Return the PK of the newly-created object data['pk'] = self.object.pk diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index f671324195..f83d836c6a 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -23,6 +23,7 @@ class EditBuildForm(HelperForm): 'reference': 'BO', 'link': 'fa-link', 'batch': 'fa-layer-group', + 'serial-numbers': 'fa-hashtag', 'location': 'fa-map-marker-alt', } @@ -30,6 +31,12 @@ class EditBuildForm(HelperForm): 'reference': _('Build Order reference') } + serial_numbers = forms.CharField( + label=_('Serial Numbers'), + help_text=_('Serial numbers for build outputs'), + required=False, + ) + class Meta: model = Build fields = [ @@ -38,6 +45,7 @@ class EditBuildForm(HelperForm): 'part', 'quantity', 'batch', + 'serial_numbers', 'take_from', 'destination', 'parent', diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 5567856c2d..c2e772042d 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -24,6 +24,7 @@ from mptt.models import MPTTModel, TreeForeignKey from InvenTree.status_codes import BuildStatus from InvenTree.helpers import increment, getSetting, normalize +from InvenTree.helpers import ExtractSerialNumbers from InvenTree.validators import validate_build_order_reference from InvenTree.models import InvenTreeAttachment @@ -269,21 +270,46 @@ class Build(MPTTModel): return new_ref - def createInitialStockItem(self, user): + def createInitialStockItem(self, serial_numbers, user): """ Create an initial output StockItem to be completed by this build. """ - output = StockModels.StockItem.objects.create( - part=self.part, # Link to the parent part - location=None, # No location (yet) until it is completed - quantity=self.quantity, - batch='', # The 'batch' code is not set until the item is completed - build=self, # Point back to this build - is_building=True, # Mark this StockItem as building - ) + if self.part.trackable: + # Trackable part? Create individual build outputs? - output.save() + # Serial numbers specified for the build? + if serial_numbers: + serials = ExtractSerialNumbers(serial_numbers, self.quantity) + else: + serials = [None] * self.quantity + + for serial in serials: + output = StockModels.StockItem.objects.create( + part=self.part, + location=self.destination, + quantity=1, + batch=self.batch, + serial=serial, + build=self, + is_building=True + ) + + output.save() + + else: + # Create a single build output + + output = StockModels.StockItem.objects.create( + part=self.part, # Link to the parent part + location=None, # No location (yet) until it is completed + quantity=self.quantity, + batch='', # The 'batch' code is not set until the item is completed + build=self, # Point back to this build + is_building=True, # Mark this StockItem as building + ) + + output.save() # TODO - Add a transaction note to the new StockItem diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 071a902bea..0c9cf11a4d 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -512,6 +512,18 @@ class BuildCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' role_required = 'build.add' + def get_form(self): + form = super().get_form() + + if form['part'].value(): + part = Part.objects.get(pk=form['part'].value()) + + # Part is not trackable - hide serial numbers + if not part.trackable: + form.fields['serial_numbers'].widget = HiddenInput() + + return form + def get_initial(self): """ Get initial parameters for Build creation. @@ -547,6 +559,33 @@ class BuildCreate(AjaxCreateView): return { 'success': _('Created new build'), } + + def validate(self, cleaned_data, **kwargs): + """ + Perform extra form validation. + + - If part is trackable, check that either batch or serial numbers are calculated + + By this point form.is_valid() has been executed + """ + + part = cleaned_data['part'] + + if part.trackable: + # For a trackable part, either batch or serial nubmber must be specified + if not cleaned_data['batch'] and not cleaned_data['serial_numbers']: + raise ValidationError({ + 'part': _('Trackable part must have either batch or serial number specified') + }) + + # If serial numbers are set... + serials = cleaned_data['serial_numbers'] + quantity = cleaned_data['quantity'] + + extracted = ExtractSerialNumbers(serials, quantity) + + # Ok, no errors... + return True def post_save(self, **kwargs): """ @@ -555,8 +594,11 @@ class BuildCreate(AjaxCreateView): build = kwargs['new_object'] request = kwargs['request'] + data = kwargs['data'] + + serials = data['serial_numbers'] - build.createInitialStockItem(request.user) + build.createInitialStockItem(serials, request.user) class BuildUpdate(AjaxUpdateView):