From 0c8a047bc22b527b491e9239023bff7d6d38f366 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 21 Dec 2021 17:16:27 +1100 Subject: [PATCH] Adds simply endpoint for BOM duplication --- InvenTree/part/api.py | 23 ++++++++ InvenTree/part/forms.py | 33 ------------ InvenTree/part/serializers.py | 51 ++++++++++++++++++ .../part/templates/part/bom_duplicate.html | 17 ------ InvenTree/part/templates/part/detail.html | 12 ++--- InvenTree/part/urls.py | 1 - InvenTree/part/views.py | 52 ------------------- InvenTree/templates/js/translated/part.js | 24 +++++++++ 8 files changed, 102 insertions(+), 111 deletions(-) delete mode 100644 InvenTree/part/templates/part/bom_duplicate.html diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index a672e79051..8f31eb0b77 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -454,6 +454,26 @@ class PartSerialNumberDetail(generics.RetrieveAPIView): return Response(data) +class PartCopyBOM(generics.CreateAPIView): + """ + API endpoint for duplicating a BOM + """ + + queryset = Part.objects.all() + serializer_class = part_serializers.PartCopyBOMSerializer + + def get_serializer_context(self): + + ctx = super().get_serializer_context() + + try: + ctx['part'] = Part.objects.get(pk=self.kwargs.get('pk', None)) + except: + pass + + return ctx + + class PartDetail(generics.RetrieveUpdateDestroyAPIView): """ API endpoint for detail view of a single Part object """ @@ -1585,6 +1605,9 @@ 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'), + # 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 2d9ae4dc30..5afd037c63 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -55,39 +55,6 @@ class PartImageDownloadForm(HelperForm): ] -class BomDuplicateForm(HelperForm): - """ - Simple confirmation form for BOM duplication. - - Select which parent to select from. - """ - - parent = PartModelChoiceField( - label=_('Parent Part'), - help_text=_('Select parent part to copy BOM from'), - queryset=Part.objects.filter(is_template=True), - ) - - clear = forms.BooleanField( - required=False, initial=True, - help_text=_('Clear existing BOM items') - ) - - confirm = forms.BooleanField( - required=False, initial=False, - label=_('Confirm'), - help_text=_('Confirm BOM duplication') - ) - - class Meta: - model = Part - fields = [ - 'parent', - 'clear', - 'confirm', - ] - - class BomValidateForm(HelperForm): """ Simple confirmation form for BOM validation. User is presented with a single checkbox input, diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 820962b613..79e6596124 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -9,6 +9,7 @@ from django.urls import reverse_lazy from django.db import models from django.db.models import Q from django.db.models.functions import Coalesce +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from sql_util.utils import SubqueryCount, SubquerySum @@ -636,3 +637,53 @@ class CategoryParameterTemplateSerializer(InvenTreeModelSerializer): 'parameter_template', 'default_value', ] + + +class PartCopyBOMSerializer(serializers.Serializer): + """ + Serializer for copying a BOM from another part + """ + + class Meta: + fields = [ + 'part', + 'remove_existing', + ] + + part = serializers.PrimaryKeyRelatedField( + queryset=Part.objects.all(), + many=False, + required=True, + allow_null=False, + label=_('Part'), + help_text=_('Select part to copy BOM from'), + ) + + def validate_part(self, part): + """ + Check that a 'valid' part was selected + """ + + # Check if the BOM can be copied from the provided part + base_part = self.context['part'] + + return part + + remove_existing = serializers.BooleanField( + label=_('Remove Existing Data'), + help_text=_('Remove existing BOM items before copying') + ) + + def save(self): + """ + Actually duplicate the BOM + """ + + base_part = self.context['part'] + + data = self.validated_data + + part = data['part'] + clear = data.get('remove_existing', True) + + base_part.copy_bom_from(part, clear=clear) diff --git a/InvenTree/part/templates/part/bom_duplicate.html b/InvenTree/part/templates/part/bom_duplicate.html deleted file mode 100644 index 1d8ccc7d1a..0000000000 --- a/InvenTree/part/templates/part/bom_duplicate.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "modal_form.html" %} -{% load i18n %} - -{% block pre_form_content %} - -

- {% trans "Select parent part to copy BOM from" %} -

- -{% if part.has_bom %} -
- {% trans "Warning" %}
- {% trans "This part already has a Bill of Materials" %}
-
-{% endif %} - -{% endblock %} diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index d16de22e1b..52c18b04c5 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -580,14 +580,10 @@ }); $('#bom-duplicate').click(function() { - launchModalForm( - "{% url 'duplicate-bom' part.id %}", - { - success: function() { - $('#bom-table').bootstrapTable('refresh'); - } - } - ); + + duplicateBom({{ part.pk }}, { + + }); }); $("#bom-item-new").click(function () { diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index e5907e15e2..a10da3f587 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -40,7 +40,6 @@ part_detail_urls = [ url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'), url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'), - url(r'^bom-duplicate/?', views.BomDuplicate.as_view(), name='duplicate-bom'), url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index af35cf9c1f..2aa2d5324a 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -694,58 +694,6 @@ class PartImageSelect(AjaxUpdateView): return self.renderJsonResponse(request, form, data) -class BomDuplicate(AjaxUpdateView): - """ - View for duplicating BOM from a parent item. - """ - - model = Part - context_object_name = 'part' - ajax_form_title = _('Duplicate BOM') - ajax_template_name = 'part/bom_duplicate.html' - form_class = part_forms.BomDuplicateForm - - def get_form(self): - - form = super().get_form() - - # Limit choices to parents of the current part - parents = self.get_object().get_ancestors() - - form.fields['parent'].queryset = parents - - return form - - def get_initial(self): - initials = super().get_initial() - - parents = self.get_object().get_ancestors() - - if parents.count() == 1: - initials['parent'] = parents[0] - - return initials - - def validate(self, part, form): - - confirm = str2bool(form.cleaned_data.get('confirm', False)) - - if not confirm: - form.add_error('confirm', _('Confirm duplication of BOM from parent')) - - def save(self, part, form): - """ - Duplicate BOM from the specified parent - """ - - parent = form.cleaned_data.get('parent', None) - - clear = str2bool(form.cleaned_data.get('clear', True)) - - if parent: - part.copy_bom_from(parent, clear=clear) - - class BomValidate(AjaxUpdateView): """ Modal form view for validating a part BOM diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 7d9ba454e8..f8a019d0e6 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -21,6 +21,7 @@ */ /* exported + duplicateBom, duplicatePart, editCategory, editPart, @@ -428,6 +429,29 @@ function toggleStar(options) { } +/* Duplicate a BOM */ +function duplicateBom(part_id, options={}) { + constructForm(`/api/part/${part_id}/copy-bom/`, { + method: 'POST', + fields: { + part: { + icon: 'fa-shapes', + filters: { + assembly: true, + ancestor: part_id, + } + }, + remove_existing: { + value: true, + }, + }, + confirm: true, + title: '{% trans "Copy Bill of Materials" %}', + }); + +} + + function partStockLabel(part, options={}) { if (part.in_stock) {