Set serial numbers when creating a new stock item

This commit is contained in:
Oliver Walters 2019-07-23 10:31:34 +10:00
parent 23d03d6b9b
commit 9e5eadd6c3
6 changed files with 105 additions and 23 deletions

View File

@ -282,10 +282,9 @@ class BuildComplete(AjaxUpdateView):
if len(existing) > 0: if len(existing) > 0:
exists = ",".join([str(x) for x in existing]) exists = ",".join([str(x) for x in existing])
form.errors['serial_numbers'] = [_('The following serial numbers already exist: {sn}'.format(sn=exists))] form.errors['serial_numbers'] = [_('The following serial numbers already exist: ({sn})'.format(sn=exists))]
valid = False valid = False
except ValidationError as e: except ValidationError as e:
form.errors['serial_numbers'] = e.messages form.errors['serial_numbers'] = e.messages
valid = False valid = False

View File

@ -280,7 +280,6 @@ class Part(models.Model):
else: else:
return static('/img/blank_image.png') return static('/img/blank_image.png')
def validate_unique(self, exclude=None): def validate_unique(self, exclude=None):
""" Validate that a part is 'unique'. """ Validate that a part is 'unique'.
Uniqueness is checked across the following (case insensitive) fields: Uniqueness is checked across the following (case insensitive) fields:

View File

@ -6,8 +6,9 @@ Django Forms for interacting with Stock app
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms from django import forms
from InvenTree.forms import HelperForm from django.forms.utils import ErrorDict
from InvenTree.forms import HelperForm
from .models import StockLocation, StockItem, StockItemTracking from .models import StockLocation, StockItem, StockItemTracking
@ -26,6 +27,8 @@ class EditStockLocationForm(HelperForm):
class CreateStockItemForm(HelperForm): class CreateStockItemForm(HelperForm):
""" Form for creating a new StockItem """ """ Form for creating a new StockItem """
serial_numbers = forms.CharField(label='Serial numbers', required=False, help_text='Enter unique serial numbers')
class Meta: class Meta:
model = StockItem model = StockItem
fields = [ fields = [
@ -34,13 +37,30 @@ class CreateStockItemForm(HelperForm):
'location', 'location',
'quantity', 'quantity',
'batch', 'batch',
'serial', 'serial_numbers',
'delete_on_deplete', 'delete_on_deplete',
'status', 'status',
'notes', 'notes',
'URL', 'URL',
] ]
# Custom clean to prevent complex StockItem.clean() logic from running (yet)
def full_clean(self):
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
# Don't run _post_clean() as this will run StockItem.clean()
self._clean_fields()
self._clean_form()
class AdjustStockForm(forms.ModelForm): class AdjustStockForm(forms.ModelForm):
""" Form for performing simple stock adjustments. """ Form for performing simple stock adjustments.

View File

@ -147,7 +147,6 @@ class StockItem(models.Model):
return True return True
def validate_unique(self, exclude=None): def validate_unique(self, exclude=None):
super(StockItem, self).validate_unique(exclude) super(StockItem, self).validate_unique(exclude)
@ -192,16 +191,23 @@ class StockItem(models.Model):
if self.part is not None: if self.part is not None:
# A trackable part must have a serial number # A trackable part must have a serial number
if self.part.trackable and not self.serial: if self.part.trackable:
if not self.serial:
raise ValidationError({'serial': _('Serial number must be set for trackable items')})
if self.delete_on_deplete:
raise ValidationError({'delete_on_deplete': _("Must be set to False for trackable items")})
# Serial number cannot be set for items with quantity greater than 1
if not self.quantity == 1:
raise ValidationError({ raise ValidationError({
'serial': _('Serial number must be set for trackable items') 'quantity': _("Quantity must be set to 1 for item with a serial number"),
'serial': _("Serial number cannot be set if quantity > 1")
}) })
# A template part cannot be instantiated as a StockItem # A template part cannot be instantiated as a StockItem
if self.part.is_template: if self.part.is_template:
raise ValidationError({ raise ValidationError({'part': _('Stock item cannot be created for a template Part')})
'part': _('Stock item cannot be created for a template Part')
})
except Part.DoesNotExist: except Part.DoesNotExist:
# This gets thrown if self.supplier_part is null # This gets thrown if self.supplier_part is null
@ -213,13 +219,6 @@ class StockItem(models.Model):
'belongs_to': _('Item cannot belong to itself') 'belongs_to': _('Item cannot belong to itself')
}) })
# Serial number cannot be set for items with quantity greater than 1
if not self.quantity == 1 and self.serial:
raise ValidationError({
'quantity': _("Quantity must be set to 1 for item with a serial number"),
'serial': _("Serial number cannot be set if quantity > 1")
})
def get_absolute_url(self): def get_absolute_url(self):
return reverse('stock-item-detail', kwargs={'pk': self.id}) return reverse('stock-item-detail', kwargs={'pk': self.id})

View File

@ -5,6 +5,7 @@ Django views for interacting with Stock app
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
@ -17,6 +18,7 @@ from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
from InvenTree.views import QRCodeView from InvenTree.views import QRCodeView
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.helpers import ExtractSerialNumbers
from datetime import datetime from datetime import datetime
from part.models import Part from part.models import Part
@ -476,7 +478,7 @@ class StockItemCreate(AjaxCreateView):
ForeignKey choices based on other selections ForeignKey choices based on other selections
""" """
form = super(AjaxCreateView, self).get_form() form = super().get_form()
# If the user has selected a Part, limit choices for SupplierPart # If the user has selected a Part, limit choices for SupplierPart
if form['part'].value(): if form['part'].value():
@ -488,10 +490,16 @@ class StockItemCreate(AjaxCreateView):
# Hide the 'part' field (as a valid part is selected) # Hide the 'part' field (as a valid part is selected)
form.fields['part'].widget = HiddenInput() form.fields['part'].widget = HiddenInput()
# trackable parts get special consideration
if part.trackable:
form.fields['delete_on_deplete'].widget = HiddenInput()
form.fields['delete_on_deplete'].initial = False
else:
form.fields.pop('serial_numbers')
# If the part is NOT purchaseable, hide the supplier_part field # If the part is NOT purchaseable, hide the supplier_part field
if not part.purchaseable: if not part.purchaseable:
form.fields['supplier_part'].widget = HiddenInput() form.fields['supplier_part'].widget = HiddenInput()
else: else:
# Pre-select the allowable SupplierPart options # Pre-select the allowable SupplierPart options
parts = form.fields['supplier_part'].queryset parts = form.fields['supplier_part'].queryset
@ -555,6 +563,63 @@ class StockItemCreate(AjaxCreateView):
return initials return initials
def post(self, request, *args, **kwargs):
""" Handle POST of StockItemCreate form.
- Manage serial-number valdiation for tracked parts
"""
form = self.get_form()
valid = form.is_valid()
if valid:
part_id = form['part'].value()
try:
part = Part.objects.get(id=part_id)
quantity = int(form['quantity'].value())
except (Part.DoesNotExist, ValueError):
part = None
quantity = 1
valid = False
if part is None:
form.errors['part'] = [_('Invalid part selection')]
else:
# A trackable part must provide serial numbesr
if part.trackable:
sn = request.POST.get('serial_numbers', '')
try:
serials = ExtractSerialNumbers(sn, quantity)
existing = []
for serial in serials:
if not StockItem.check_serial_number(part, serial):
existing.append(serial)
if len(existing) > 0:
exists = ",".join([str(x) for x in existing])
form.errors['serial_numbers'] = [_('The following serial numbers already exist: ({sn})'.format(sn=exists))]
valid = False
except ValidationError as e:
form.errors['serial_numbers'] = e.messages
valid = False
print("Valid?", valid)
valid = False
print("valid:", valid)
data = {
'form_valid': valid,
}
return self.renderJsonResponse(request, form, data=data)
class StockLocationDelete(AjaxDeleteView): class StockLocationDelete(AjaxDeleteView):
""" """