mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 12:06:44 +00:00
Build completion now handles unique serial numbers
Provide a method to test if a serial number matches for a given part
This commit is contained in:
parent
9a8e4d25d2
commit
23d03d6b9b
@ -199,7 +199,7 @@ class Build(models.Model):
|
|||||||
build_item.save()
|
build_item.save()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def completeBuild(self, location, user):
|
def completeBuild(self, location, serial_numbers, user):
|
||||||
""" Mark the Build as COMPLETE
|
""" Mark the Build as COMPLETE
|
||||||
|
|
||||||
- Takes allocated items from stock
|
- Takes allocated items from stock
|
||||||
@ -227,19 +227,36 @@ class Build(models.Model):
|
|||||||
|
|
||||||
self.completed_by = user
|
self.completed_by = user
|
||||||
|
|
||||||
# Add stock of the newly created item
|
notes = 'Built {q} on {now}'.format(
|
||||||
item = StockItem.objects.create(
|
q=self.quantity,
|
||||||
part=self.part,
|
now=str(datetime.now().date())
|
||||||
location=location,
|
|
||||||
quantity=self.quantity,
|
|
||||||
batch=str(self.batch) if self.batch else '',
|
|
||||||
notes='Built {q} on {now}'.format(
|
|
||||||
q=self.quantity,
|
|
||||||
now=str(datetime.now().date())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
item.save()
|
if self.part.trackable:
|
||||||
|
# Add new serial numbers
|
||||||
|
for serial in serial_numbers:
|
||||||
|
item = StockItem.objects.create(
|
||||||
|
part=self.part,
|
||||||
|
location=location,
|
||||||
|
quantity=1,
|
||||||
|
serial=serial,
|
||||||
|
batch=str(self.batch) if self.batch else '',
|
||||||
|
notes=notes
|
||||||
|
)
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Add stock of the newly created item
|
||||||
|
item = StockItem.objects.create(
|
||||||
|
part=self.part,
|
||||||
|
location=location,
|
||||||
|
quantity=self.quantity,
|
||||||
|
batch=str(self.batch) if self.batch else '',
|
||||||
|
notes=notes
|
||||||
|
)
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
# Finally, mark the build as complete
|
# Finally, mark the build as complete
|
||||||
self.status = BuildStatus.COMPLETE
|
self.status = BuildStatus.COMPLETE
|
||||||
|
@ -225,7 +225,7 @@ class BuildComplete(AjaxUpdateView):
|
|||||||
build = Build.objects.get(id=self.kwargs['pk'])
|
build = Build.objects.get(id=self.kwargs['pk'])
|
||||||
|
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
# Build object
|
# Build object
|
||||||
context['build'] = build
|
context['build'] = build
|
||||||
|
|
||||||
@ -263,21 +263,35 @@ class BuildComplete(AjaxUpdateView):
|
|||||||
except StockLocation.DoesNotExist:
|
except StockLocation.DoesNotExist:
|
||||||
form.errors['location'] = ['Invalid location selected']
|
form.errors['location'] = ['Invalid location selected']
|
||||||
|
|
||||||
valid = False
|
serials = []
|
||||||
|
|
||||||
serials = request.POST.get('serial_numbers', '')
|
if build.part.trackable:
|
||||||
|
# A build for a trackable part must specify serial numbers
|
||||||
|
|
||||||
try:
|
sn = request.POST.get('serial_numbers', '')
|
||||||
serials = ExtractSerialNumbers(serials, build.quantity)
|
|
||||||
|
|
||||||
print(serials)
|
try:
|
||||||
|
# Exctract a list of provided serial numbers
|
||||||
|
serials = ExtractSerialNumbers(sn, build.quantity)
|
||||||
|
|
||||||
except ValidationError as e:
|
existing = []
|
||||||
form.errors['serial_numbers'] = e.messages
|
|
||||||
valid = False
|
for serial in serials:
|
||||||
|
if not StockItem.check_serial_number(build.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
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
build.completeBuild(location, request.user)
|
build.completeBuild(location, serials, request.user)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'form_valid': valid,
|
'form_valid': valid,
|
||||||
|
@ -280,6 +280,7 @@ 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:
|
||||||
|
@ -92,6 +92,7 @@ class StockItem(models.Model):
|
|||||||
location: Where this StockItem is located
|
location: Where this StockItem is located
|
||||||
quantity: Number of stocked units
|
quantity: Number of stocked units
|
||||||
batch: Batch number for this StockItem
|
batch: Batch number for this StockItem
|
||||||
|
serial: Unique serial number for this StockItem
|
||||||
URL: Optional URL to link to external resource
|
URL: Optional URL to link to external resource
|
||||||
updated: Date that this stock item was last updated (auto)
|
updated: Date that this stock item was last updated (auto)
|
||||||
stocktake_date: Date of last stocktake for this item
|
stocktake_date: Date of last stocktake for this item
|
||||||
@ -121,6 +122,32 @@ class StockItem(models.Model):
|
|||||||
system=True
|
system=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_serial_number(cls, part, serial_number):
|
||||||
|
""" Check if a new stock item can be created with the provided part_id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
part: The part to be checked
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not part.trackable:
|
||||||
|
return False
|
||||||
|
|
||||||
|
items = StockItem.objects.filter(serial=serial_number)
|
||||||
|
|
||||||
|
# Is this part a variant? If so, check S/N across all sibling variants
|
||||||
|
if part.variant_of is not None:
|
||||||
|
items = items.filter(part__variant_of=part.variant_of)
|
||||||
|
else:
|
||||||
|
items = items.filter(part=part)
|
||||||
|
|
||||||
|
# An existing serial number exists
|
||||||
|
if items.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
@ -129,11 +156,18 @@ class StockItem(models.Model):
|
|||||||
# across all variants of the same template part
|
# across all variants of the same template part
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.serial is not None and self.part.variant_of is not None:
|
if self.serial is not None:
|
||||||
if StockItem.objects.filter(part__variant_of=self.part.variant_of, serial=self.serial).exclude(id=self.id).exists():
|
# This is a variant part (check S/N across all sibling variants)
|
||||||
raise ValidationError({
|
if self.part.variant_of is not None:
|
||||||
'serial': _('A part with this serial number already exists for template part {part}'.format(part=self.part.variant_of))
|
if StockItem.objects.filter(part__variant_of=self.part.variant_of, serial=self.serial).exclude(id=self.id).exists():
|
||||||
})
|
raise ValidationError({
|
||||||
|
'serial': _('A part with this serial number already exists for template part {part}'.format(part=self.part.variant_of))
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
if StockItem.objects.filter(serial=self.serial).exclude(id=self.id).exists():
|
||||||
|
raise ValidationError({
|
||||||
|
'serial': _('A part with this serial number already exists')
|
||||||
|
})
|
||||||
except Part.DoesNotExist:
|
except Part.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user