From 2c0da25cbc9e123f4b108295d4a5836cb374ddcb Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@gmail.com>
Date: Tue, 21 Dec 2021 22:45:59 +1100
Subject: [PATCH] "Validate BOM" now uses the API also

---
 InvenTree/part/api.py                         | 57 ++++++++++++++++++-
 InvenTree/part/forms.py                       | 15 -----
 .../part/templates/part/bom_validate.html     | 12 ----
 InvenTree/part/templates/part/detail.html     | 10 ++--
 InvenTree/part/urls.py                        |  1 -
 InvenTree/part/views.py                       | 42 --------------
 InvenTree/templates/js/translated/api.js      |  5 ++
 InvenTree/templates/js/translated/part.js     | 28 ++++++++-
 8 files changed, 91 insertions(+), 79 deletions(-)
 delete mode 100644 InvenTree/part/templates/part/bom_validate.html

diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py
index 8f31eb0b77..0625a717a5 100644
--- a/InvenTree/part/api.py
+++ b/InvenTree/part/api.py
@@ -474,6 +474,56 @@ class PartCopyBOM(generics.CreateAPIView):
         return ctx
 
 
+class PartValidateBOM(generics.RetrieveUpdateAPIView):
+    """
+    API endpoint for 'validating' the BOM for a given Part
+    """
+
+    class BOMValidateSerializer(serializers.ModelSerializer):
+
+        class Meta:
+            model = Part
+            fields = [
+                'checksum',
+                'valid',
+            ]
+
+        checksum = serializers.CharField(
+            read_only=True,
+            source='bom_checksum',
+        )
+
+        valid = serializers.BooleanField(
+            write_only=True,
+            default=False,
+            label=_('Valid'),
+            help_text=_('Validate entire Bill of Materials'),
+        )
+
+        def validate_valid(self, valid):
+            if not valid:
+                raise ValidationError(_('This option must be selected'))
+
+    queryset = Part.objects.all()
+
+    serializer_class = BOMValidateSerializer
+
+    def update(self, request, *args, **kwargs):
+
+        part = self.get_object()
+
+        partial = kwargs.pop('partial', False)
+
+        serializer = self.get_serializer(part, data=request.data, partial=partial)
+        serializer.is_valid(raise_exception=True)
+
+        part.validate_bom(request.user)
+
+        return Response({
+            'checksum': part.bom_checksum,
+        })
+
+
 class PartDetail(generics.RetrieveUpdateDestroyAPIView):
     """ API endpoint for detail view of a single Part object """
 
@@ -1605,8 +1655,11 @@ part_api_urls = [
         # Endpoint for extra serial number information
         url(r'^serial-numbers/', PartSerialNumberDetail.as_view(), name='api-part-serial-number-detail'),
 
-        # Endpoint for duplicating a BOM
-        url(r'^copy-bom/', PartCopyBOM.as_view(), name='api-part-copy-bom'),
+        # Endpoint for duplicating a BOM for the specific Part
+        url(r'^bom-copy/', PartCopyBOM.as_view(), name='api-part-bom-copy'),
+
+        # Endpoint for validating a BOM for the specific Part
+        url(r'^bom-validate/', PartValidateBOM.as_view(), name='api-part-bom-validate'),
 
         # Part detail endpoint
         url(r'^.*$', PartDetail.as_view(), name='api-part-detail'),
diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py
index 5afd037c63..c4c7d29228 100644
--- a/InvenTree/part/forms.py
+++ b/InvenTree/part/forms.py
@@ -55,21 +55,6 @@ class PartImageDownloadForm(HelperForm):
         ]
 
 
-class BomValidateForm(HelperForm):
-    """ Simple confirmation form for BOM validation.
-    User is presented with a single checkbox input,
-    to confirm that the BOM for this part is valid
-    """
-
-    validate = forms.BooleanField(required=False, initial=False, label=_('validate'), help_text=_('Confirm that the BOM is correct'))
-
-    class Meta:
-        model = Part
-        fields = [
-            'validate'
-        ]
-
-
 class BomMatchItemForm(MatchItemForm):
     """ Override MatchItemForm fields """
 
diff --git a/InvenTree/part/templates/part/bom_validate.html b/InvenTree/part/templates/part/bom_validate.html
deleted file mode 100644
index 3554f9c56a..0000000000
--- a/InvenTree/part/templates/part/bom_validate.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{% extends "modal_form.html" %}
-
-{% load i18n %}
-
-{% block pre_form_content %}
-{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><em>{{ part }}</em>{% endblocktrans %}
-
-<div class='alert alert-warning alert-block'>
-    {% trans 'This will validate each line in the BOM.' %}
-</div>
-
-{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html
index c768c61257..63bd747d0c 100644
--- a/InvenTree/part/templates/part/detail.html
+++ b/InvenTree/part/templates/part/detail.html
@@ -609,12 +609,10 @@
         });
 
         $("#validate-bom").click(function() {
-            launchModalForm(
-                "{% url 'bom-validate' part.id %}",
-                {
-                    reload: true,
-                }
-            );
+
+            validateBom({{ part.id }}, {
+                reload: true
+            });
         });
 
         $("#download-bom").click(function () {
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index a10da3f587..14f3e28b24 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -35,7 +35,6 @@ part_detail_urls = [
     url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
     url(r'^bom-export/?', views.BomExport.as_view(), name='bom-export'),
     url(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'),
-    url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
 
     url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
 
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 2aa2d5324a..4304d2a95d 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -694,48 +694,6 @@ class PartImageSelect(AjaxUpdateView):
         return self.renderJsonResponse(request, form, data)
 
 
-class BomValidate(AjaxUpdateView):
-    """
-    Modal form view for validating a part BOM
-    """
-
-    model = Part
-    ajax_form_title = _("Validate BOM")
-    ajax_template_name = 'part/bom_validate.html'
-    context_object_name = 'part'
-    form_class = part_forms.BomValidateForm
-
-    def get_context(self):
-        return {
-            'part': self.get_object(),
-        }
-
-    def get(self, request, *args, **kwargs):
-
-        form = self.get_form()
-
-        return self.renderJsonResponse(request, form, context=self.get_context())
-
-    def validate(self, part, form, **kwargs):
-
-        confirm = str2bool(form.cleaned_data.get('validate', False))
-
-        if not confirm:
-            form.add_error('validate', _('Confirm that the BOM is valid'))
-
-    def save(self, part, form, **kwargs):
-        """
-        Mark the BOM as validated
-        """
-
-        part.validate_bom(self.request.user)
-
-    def get_data(self):
-        return {
-            'success': _('Validated Bill of Materials')
-        }
-
-
 class BomUpload(InvenTreeRoleMixin, FileManagementFormView):
     """ View for uploading a BOM file, and handling BOM data importing.
 
diff --git a/InvenTree/templates/js/translated/api.js b/InvenTree/templates/js/translated/api.js
index d9c23f035f..eadf2e2afc 100644
--- a/InvenTree/templates/js/translated/api.js
+++ b/InvenTree/templates/js/translated/api.js
@@ -207,6 +207,11 @@ function showApiError(xhr, url) {
         title = '{% trans "Error 404: Resource Not Found" %}';
         message = '{% trans "The requested resource could not be located on the server" %}';
         break;
+    // Method not allowed
+    case 405:
+        title = '{% trans "Error 405: Method Not Allowed" %}';
+        message = '{% trans "HTTP method not allowed at URL" %}';
+        break;
     // Timeout
     case 408:
         title = '{% trans "Error 408: Timeout" %}';
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index 1cfd50d66c..fafbfb37c8 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -40,6 +40,7 @@
     loadStockPricingChart,
     partStockLabel,
     toggleStar,
+    validateBom,
 */
 
 /* Part API functions
@@ -429,9 +430,34 @@ function toggleStar(options) {
 }
 
 
+/* Validate a BOM */
+function validateBom(part_id, options={}) {
+
+    var html = `
+    <div class='alert alert-block alert-success'>
+    {% trans "Validating the BOM will mark each line item as valid" %}
+    </div>
+    `;
+
+    constructForm(`/api/part/${part_id}/bom-validate/`, {
+        method: 'PUT',
+        fields: {
+            valid: {},
+        },
+        preFormContent: html,
+        title: '{% trans "Validate Bill of Materials" %}',
+        reload: options.reload,
+        onSuccess: function(response) {
+            showMessage('{% trans "Validated Bill of Materials" %}');
+        }
+    });
+}
+
+
 /* Duplicate a BOM */
 function duplicateBom(part_id, options={}) {
-    constructForm(`/api/part/${part_id}/copy-bom/`, {
+
+    constructForm(`/api/part/${part_id}/bom-copy/`, {
         method: 'POST',
         fields: {
             part: {