mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Set serial numbers when creating a new stock item
This commit is contained in:
parent
23d03d6b9b
commit
9e5eadd6c3
@ -188,4 +188,4 @@ def ExtractSerialNumbers(serials, expected_quantity):
|
|||||||
if not expected_quantity == len(numbers):
|
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))])
|
raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})".format(s=len(numbers), q=expected_quantity))])
|
||||||
|
|
||||||
return numbers
|
return numbers
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
raise ValidationError({
|
if not self.serial:
|
||||||
'serial': _('Serial number must be set for trackable items')
|
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
|
# 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})
|
||||||
|
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user