From f6baf5d2ae9f19794d72cc06c3fa9b825841590f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 15 May 2019 00:16:34 +1000 Subject: [PATCH] Add 'overage' field to BOM item - Accepts absolute or percentage numbers - Default = blank - Now with custom validator! (for limited time only, limit one per customer) --- InvenTree/InvenTree/validators.py | 51 ++++++++++++++++++- InvenTree/part/forms.py | 1 + .../migrations/0025_auto_20190515_0012.py | 46 +++++++++++++++++ InvenTree/part/models.py | 5 ++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 InvenTree/part/migrations/0025_auto_20190515_0012.py diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index f3fd9ef306..0e1622a49a 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -7,9 +7,56 @@ from django.utils.translation import gettext_lazy as _ def validate_part_name(value): - # Prevent some illegal characters in part names - for c in ['|', '#', '$']: + """ Prevent some illegal characters in part names. + """ + + for c in ['|', '#', '$', '{', '}']: if c in str(value): raise ValidationError( _('Invalid character in part name') ) + + +def validate_overage(value): + """ Validate that a BOM overage string is properly formatted. + + An overage string can look like: + + - An integer number ('1' / 3 / 4) + - A percentage ('5%' / '10 %') + """ + + value = str(value).lower().strip() + + # First look for a simple integer value + try: + i = int(value) + + if i < 0: + raise ValidationError(_("Overage value must not be negative")) + + # Looks like an integer! + return True + except ValueError: + pass + + # Now look for a percentage value + if value.endswith('%'): + v = value[:-1].strip() + + # Does it look like a number? + try: + f = float(v) + + if f < 0: + raise ValidationError(_("Overage value must not be negative")) + elif f > 100: + raise ValidationError(_("Overage must not exceed 100%")) + + return True + except ValueError: + pass + + raise ValidationError( + _("Overage must be an integer value or a percentage") + ) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 88c6c11385..d4e70ee47a 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -133,6 +133,7 @@ class EditBomItemForm(HelperForm): 'part', 'sub_part', 'quantity', + 'overage', 'note' ] diff --git a/InvenTree/part/migrations/0025_auto_20190515_0012.py b/InvenTree/part/migrations/0025_auto_20190515_0012.py new file mode 100644 index 0000000000..aaeb8ea1a3 --- /dev/null +++ b/InvenTree/part/migrations/0025_auto_20190515_0012.py @@ -0,0 +1,46 @@ +# Generated by Django 2.2 on 2019-05-14 14:12 + +import InvenTree.validators +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0024_partcategory_default_keywords'), + ] + + operations = [ + migrations.AddField( + model_name='bomitem', + name='overage', + field=models.CharField(blank=True, help_text='Estimated build wastage quantity (absolute or percentage)', max_length=24, validators=[InvenTree.validators.validate_overage]), + ), + migrations.AlterField( + model_name='bomitem', + name='note', + field=models.CharField(blank=True, help_text='BOM item notes', max_length=100), + ), + migrations.AlterField( + model_name='bomitem', + name='part', + field=models.ForeignKey(help_text='Select parent part', limit_choices_to={'active': True, 'buildable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='bom_items', to='part.Part'), + ), + migrations.AlterField( + model_name='bomitem', + name='quantity', + field=models.PositiveIntegerField(default=1, help_text='BOM quantity for this BOM item', validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='bomitem', + name='sub_part', + field=models.ForeignKey(help_text='Select part to be used in BOM', limit_choices_to={'active': True, 'consumable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='used_in', to='part.Part'), + ), + migrations.AlterField( + model_name='supplierpart', + name='URL', + field=models.URLField(blank=True, help_text='URL for external supplier part link'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index d255a319f3..b316da2813 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -661,6 +661,7 @@ class BomItem(models.Model): part: Link to the parent part (the part that will be produced) sub_part: Link to the child part (the part that will be consumed) quantity: Number of 'sub_parts' consumed to produce one 'part' + overage: Estimated losses for a Build. Can be expressed as absolute value (e.g. '7') or a percentage (e.g. '2%') note: Note field for this BOM item """ @@ -688,6 +689,10 @@ class BomItem(models.Model): # Quantity required quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)], help_text='BOM quantity for this BOM item') + overage = models.CharField(max_length=24, blank=True, validators=[validators.validate_overage], + help_text='Estimated build wastage quantity (absolute or percentage)' + ) + # Note attached to this BOM line item note = models.CharField(max_length=100, blank=True, help_text='BOM item notes')