From d70110690bc61313dca7484b73361416424cdc27 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 25 May 2019 23:09:04 +1000 Subject: [PATCH] Validate uniqueness for StockItems - If the Part is a variant of a template, ensure that the serial numbers are unique across all instances of the template - Prevent instantiation of a StockItem for a part which has variants --- .../migrations/0003_auto_20190525_2303.py | 19 ++++++++++++++ InvenTree/stock/models.py | 19 +++++++++++++- InvenTree/stock/views.py | 25 +++++++++++-------- 3 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 InvenTree/stock/migrations/0003_auto_20190525_2303.py diff --git a/InvenTree/stock/migrations/0003_auto_20190525_2303.py b/InvenTree/stock/migrations/0003_auto_20190525_2303.py new file mode 100644 index 0000000000..e8f7708cfe --- /dev/null +++ b/InvenTree/stock/migrations/0003_auto_20190525_2303.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 13:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0002_auto_20190525_2226'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'has_variants': False}, on_delete=django.db.models.deletion.CASCADE, related_name='stock_items', to='part.Part'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index fdf88d5473..46bab9ae73 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -115,6 +115,23 @@ class StockItem(models.Model): system=True ) + def validate_unique(self, exclude=None): + super(StockItem, self).validate_unique(exclude) + + # If the Part object is a variant (of a template part), + # ensure that the serial number is unique + # across all variants of the same template part + + + try: + if self.serial is not None and self.part.variant_of is not None: + 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)) + }) + except Part.DoesNotExist: + pass + def clean(self): """ Validate the StockItem object (separate to field validation) @@ -196,7 +213,7 @@ class StockItem(models.Model): part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='stock_items', help_text='Base part', limit_choices_to={ - 'has_variants': True, + 'has_variants': False, 'active': True, }) diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index cd9fb615af..d729cb83fc 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -208,28 +208,31 @@ class StockItemCreate(AjaxCreateView): try: part = Part.objects.get(id=part_id) - parts = form.fields['supplier_part'].queryset - parts = parts.filter(part=part.id) + # Hide the 'part' field (as a valid part is selected) + form.fields['part'].widget = HiddenInput() + # If the part is NOT purchaseable, hide the supplier_part field if not part.purchaseable: form.fields['supplier_part'].widget = HiddenInput() - form.fields['supplier_part'].queryset = parts + else: + # Pre-select the allowable SupplierPart options + parts = form.fields['supplier_part'].queryset + parts = parts.filter(part=part.id) - # If there is one (and only one) supplier part available, pre-select it - all_parts = parts.all() - if len(all_parts) == 1: + form.fields['supplier_part'].queryset = parts - # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate - form.fields['supplier_part'].initial = all_parts[0].id + # If there is one (and only one) supplier part available, pre-select it + all_parts = parts.all() + if len(all_parts) == 1: + + # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate + form.fields['supplier_part'].initial = all_parts[0].id except Part.DoesNotExist: pass - # Hide the 'part' field - form.fields['part'].widget = HiddenInput() - # Otherwise if the user has selected a SupplierPart, we know what Part they meant! elif form['supplier_part'].value() is not None: pass