diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index d18b1a2a8b..89f9103187 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -493,6 +493,22 @@ class BomItemSerializer(InvenTreeModelSerializer): purchase_price_range = serializers.SerializerMethodField() + def validate(self, data): + + # Check for duplicate BOM items + part = data['part'] + sub_part = data['sub_part'] + + if BomItem.objects.get(part=part, sub_part=sub_part).exists(): + raise serializers.ValidationError({ + 'part': _("Duplicate BOM item already exists"), + 'sub_part': _("Duplicate BOM items already exists"), + }) + + data = super().validate(data) + + return data + def __init__(self, *args, **kwargs): # part_detail and sub_part_detail serializers are only included if requested. # This saves a bunch of database requests @@ -962,8 +978,20 @@ class BomUploadSerializer(serializers.Serializer): items = data['items'] - with transaction.atomic(): + try: + with transaction.atomic(): - for item in items: - print(item) - \ No newline at end of file + for item in items: + + part = item['part'] + sub_part = item['sub_part'] + + # Ignore duplicate BOM items + if BomItem.objects.filter(part=part, sub_part=sub_part).exists(): + continue + + # Create a new BomItem object + BomItem.objects.create(**item) + + except Exception as e: + raise serializers.ValidationError(detail=serializers.as_serializer_error(e)) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 512cb0c46a..d5391ab70a 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -43,6 +43,7 @@ function constructBomUploadTable(data, options={}) { var field_options = { hideLabels: true, hideClearButton: true, + form_classes: 'bom-form-group', }; function constructRowField(field_name) { @@ -77,7 +78,7 @@ function constructBomUploadTable(data, options={}) { buttons += ``; var html = ` - + ${sub_part} ${quantity} ${reference} diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 67a162ff2b..2742e3f0ca 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -1056,6 +1056,7 @@ function handleNestedErrors(errors, field_name, options={}) { // Here, error_item is a map of field names to error messages for (sub_field_name in error_item) { + var errors = error_item[sub_field_name]; // Find the target (nested) field @@ -1919,12 +1920,12 @@ function constructField(name, parameters, options) { options.current_group = group; } - var form_classes = 'form-group'; + var form_classes = options.form_classes || 'form-group'; if (parameters.errors) { form_classes += ' form-field-error'; } - + // Optional content to render before the field if (parameters.before) { html += parameters.before; diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index ee4c4cb5ef..68ba496309 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -161,7 +161,7 @@ function renderPart(name, data, parameters, options) { html += ` ${data.full_name || data.name}`; if (data.description) { - html += ` - ${data.description}`; + html += ` - ${data.description}`; } var extra = '';