diff --git a/InvenTree/part/migrations/0003_auto_20190525_2226.py b/InvenTree/part/migrations/0003_auto_20190525_2226.py new file mode 100644 index 0000000000..3e805d460d --- /dev/null +++ b/InvenTree/part/migrations/0003_auto_20190525_2226.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2 on 2019-05-25 12:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0002_auto_20190520_2204'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='has_variants', + field=models.BooleanField(default=False, help_text='Is this part a template part?'), + ), + migrations.AddField( + model_name='part', + name='variant_of', + field=models.ForeignKey(blank=True, help_text='Is this part a variant of another part?', limit_choices_to={'active': True, 'has_variants': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='variants', to='part.Part'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 385bae7bf4..d03c6030e3 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -192,6 +192,7 @@ class Part(models.Model): description: Longer form description of the part keywords: Optional keywords for improving part search results IPN: Internal part number (optional) + has_variants: If True, this part is a 'template' part and cannot be instantiated as a StockItem URL: Link to an external page with more information about this part (e.g. internal Wiki) image: Image of this part default_location: Where the item is normally stored (may be null) @@ -258,6 +259,17 @@ class Part(models.Model): variant = models.CharField(max_length=32, blank=True, help_text='Part variant or revision code') + has_variants = models.BooleanField(default=False, help_text='Is this part a template part?') + + variant_of = models.ForeignKey('part.Part', related_name='variants', + null=True, blank=True, + limit_choices_to={ + 'has_variants': True, + 'active': True, + }, + on_delete=models.SET_NULL, + help_text='Is this part a variant of another part?') + description = models.CharField(max_length=250, blank=False, help_text='Part description') keywords = models.CharField(max_length=250, blank=True, help_text='Part keywords to improve visibility in search results') diff --git a/InvenTree/stock/migrations/0002_auto_20190525_2226.py b/InvenTree/stock/migrations/0002_auto_20190525_2226.py new file mode 100644 index 0000000000..b8607c3ec7 --- /dev/null +++ b/InvenTree/stock/migrations/0002_auto_20190525_2226.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2 on 2019-05-25 12:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'has_variants': True}, 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 3680079b30..fdf88d5473 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -135,11 +135,18 @@ 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') }) + # A template part cannot be instantiated as a StockItem + if self.part.has_variants: + raise ValidationError({ + 'part': _('Stock item cannot be created for a template Part') + }) + except Part.DoesNotExist: # This gets thrown if self.supplier_part is null # TODO - Find a test than can be perfomed... @@ -186,7 +193,12 @@ class StockItem(models.Model): } ) - part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='stock_items', help_text='Base part') + part = models.ForeignKey('part.Part', on_delete=models.CASCADE, + related_name='stock_items', help_text='Base part', + limit_choices_to={ + 'has_variants': True, + 'active': True, + }) supplier_part = models.ForeignKey('company.SupplierPart', blank=True, null=True, on_delete=models.SET_NULL, help_text='Select a matching supplier part for this stock item')