diff --git a/InvenTree/part/migrations/0073_auto_20211013_1048.py b/InvenTree/part/migrations/0073_auto_20211013_1048.py new file mode 100644 index 0000000000..e581af603e --- /dev/null +++ b/InvenTree/part/migrations/0073_auto_20211013_1048.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.5 on 2021-10-13 10:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0072_bomitemsubstitute'), + ] + + operations = [ + migrations.AlterModelOptions( + name='bomitemsubstitute', + options={'verbose_name': 'BOM Item Substitute'}, + ), + migrations.AlterUniqueTogether( + name='bomitemsubstitute', + unique_together={('part', 'bom_item')}, + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 0be1f5fc31..85dd9042fc 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2623,6 +2623,26 @@ class BomItemSubstitute(models.Model): part: The part which can be used as a substitute """ + class Meta: + verbose_name = _("BOM Item Substitute") + + # Prevent duplication of substitute parts + unique_together = ('part', 'bom_item') + + def validate_unique(self, exclude=None): + """ + Ensure that this BomItemSubstitute is "unique": + + - It cannot point to the same "part" as the "sub_part" of the parent "bom_item" + """ + + super().validate_unique(exclude=exclude) + + if self.part == self.bom_item.sub_part: + raise ValidationError({ + "part": _("Substitute part cannot be the same as the master part"), + }) + @staticmethod def get_api_url(): return reverse('api-bom-substitute-list') diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index a5e1153bad..19426ba1d2 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -213,11 +213,16 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) { constructForm('{% url "api-bom-substitute-list" %}', { method: 'POST', fields: { + bom_item: { + hidden: true, + value: bom_item_id, + }, part: { required: false, }, }, preFormContent: html, + cancelText: '{% trans "Close" %}', submitText: '{% trans "Add Substitute" %}', title: '{% trans "Edit BOM Item Substitutes" %}', afterRender: function(fields, opts) { @@ -243,8 +248,22 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) { }); }); }, - onSubmit: function(fields, opts) { - // TODO + preventClose: true, + onSuccess: function(response, opts) { + + // Clear the form + var field = { + type: 'related field', + }; + + updateFieldValue('part', null, field, opts); + + // Add the new substitute to the table + var row = renderSubstituteRow(response); + $(opts.modal).find('#substitute-table > tbody:last-child').append(row); + + // Re-enable the "submit" button + $(opts.modal).find('#modal-form-submit').prop('disabled', false); } });