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

@ -188,4 +188,4 @@ def ExtractSerialNumbers(serials, expected_quantity):
if not expected_quantity == len(numbers):
raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})".format(s=len(numbers), q=expected_quantity))])
return numbers
return numbers

View File

@ -282,10 +282,9 @@ class BuildComplete(AjaxUpdateView):
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))]
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

View File

@ -280,7 +280,6 @@ class Part(models.Model):
else:
return static('/img/blank_image.png')
def validate_unique(self, exclude=None):
""" Validate that a part is 'unique'.
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 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
@ -26,6 +27,8 @@ class EditStockLocationForm(HelperForm):
class CreateStockItemForm(HelperForm):
""" Form for creating a new StockItem """
serial_numbers = forms.CharField(label='Serial numbers', required=False, help_text='Enter unique serial numbers')
class Meta:
model = StockItem
fields = [
@ -34,13 +37,30 @@ class CreateStockItemForm(HelperForm):
'location',
'quantity',
'batch',
'serial',
'serial_numbers',
'delete_on_deplete',
'status',
'notes',
'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):
""" Form for performing simple stock adjustments.

View File

@ -147,7 +147,6 @@ class StockItem(models.Model):
return True
def validate_unique(self, exclude=None):
super(StockItem, self).validate_unique(exclude)
@ -192,16 +191,23 @@ class StockItem(models.Model):
if self.part is not None:
# A trackable part must have a serial number
if self.part.trackable and not self.serial:
raise ValidationError({
'serial': _('Serial number must be set for trackable items')
})
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({
'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
if self.part.is_template:
raise ValidationError({
'part': _('Stock item cannot be created for a template Part')
})
raise ValidationError({'part': _('Stock item cannot be created for a template Part')})
except Part.DoesNotExist:
# This gets thrown if self.supplier_part is null
@ -213,13 +219,6 @@ class StockItem(models.Model):
'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):
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 -*-
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.views.generic.edit import FormMixin
from django.views.generic import DetailView, ListView
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.helpers import str2bool
from InvenTree.helpers import ExtractSerialNumbers
from datetime import datetime
from part.models import Part
@ -476,7 +478,7 @@ class StockItemCreate(AjaxCreateView):
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 form['part'].value():
@ -488,10 +490,16 @@ class StockItemCreate(AjaxCreateView):
# Hide the 'part' field (as a valid part is selected)
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 not part.purchaseable:
form.fields['supplier_part'].widget = HiddenInput()
else:
# Pre-select the allowable SupplierPart options
parts = form.fields['supplier_part'].queryset
@ -555,6 +563,63 @@ class StockItemCreate(AjaxCreateView):
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):
"""