diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 079e871b84..506133df00 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -6,13 +6,12 @@ Django Forms for interacting with Company app from __future__ import unicode_literals from InvenTree.forms import HelperForm -from InvenTree.fields import InvenTreeMoneyField, RoundingDecimalFormField +from InvenTree.fields import RoundingDecimalFormField from django.utils.translation import ugettext_lazy as _ import django.forms from .models import Company -from .models import SupplierPart from .models import SupplierPriceBreak @@ -34,67 +33,6 @@ class CompanyImageDownloadForm(HelperForm): ] -class EditSupplierPartForm(HelperForm): - """ Form for editing a SupplierPart object """ - - field_prefix = { - 'link': 'fa-link', - 'SKU': 'fa-hashtag', - 'note': 'fa-pencil-alt', - } - - single_pricing = InvenTreeMoneyField( - label=_('Single Price'), - help_text=_('Single quantity price'), - decimal_places=4, - max_digits=19, - required=False, - ) - - manufacturer = django.forms.ChoiceField( - required=False, - help_text=_('Select manufacturer'), - choices=[], - ) - - MPN = django.forms.CharField( - required=False, - help_text=_('Manufacturer Part Number'), - max_length=100, - label=_('MPN'), - ) - - class Meta: - model = SupplierPart - fields = [ - 'part', - 'supplier', - 'SKU', - 'manufacturer', - 'MPN', - 'description', - 'link', - 'note', - 'single_pricing', - # 'base_cost', - # 'multiple', - 'packaging', - ] - - def get_manufacturer_choices(self): - """ Returns tuples for all manufacturers """ - empty_choice = [('', '----------')] - - manufacturers = [(manufacturer.id, manufacturer.name) for manufacturer in Company.objects.filter(is_manufacturer=True)] - - return empty_choice + manufacturers - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.fields['manufacturer'].choices = self.get_manufacturer_choices() - - class EditPriceBreakForm(HelperForm): """ Form for creating / editing a supplier price break """ diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 778e00cde1..3b731381b8 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -9,9 +9,7 @@ import os from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator -from django.core.exceptions import ValidationError from django.db import models -from django.db.utils import IntegrityError from django.db.models import Sum, Q, UniqueConstraint from django.apps import apps @@ -475,57 +473,6 @@ class SupplierPart(models.Model): def get_absolute_url(self): return reverse('supplier-part-detail', kwargs={'pk': self.id}) - def save(self, *args, **kwargs): - """ Overriding save method to process the linked ManufacturerPart - """ - - if 'manufacturer' in kwargs: - manufacturer_id = kwargs.pop('manufacturer') - - try: - manufacturer = Company.objects.get(pk=int(manufacturer_id)) - except (ValueError, Company.DoesNotExist): - manufacturer = None - else: - manufacturer = None - if 'MPN' in kwargs: - MPN = kwargs.pop('MPN') - else: - MPN = None - - if manufacturer or MPN: - if not self.manufacturer_part: - # Create ManufacturerPart - manufacturer_part = ManufacturerPart.create(part=self.part, - manufacturer=manufacturer, - mpn=MPN, - description=self.description) - self.manufacturer_part = manufacturer_part - else: - # Update ManufacturerPart (if ID exists) - try: - manufacturer_part_id = self.manufacturer_part.id - except AttributeError: - manufacturer_part_id = None - - if manufacturer_part_id: - try: - (manufacturer_part, created) = ManufacturerPart.objects.update_or_create(part=self.part, - manufacturer=manufacturer, - MPN=MPN) - except IntegrityError: - manufacturer_part = None - raise ValidationError(f'ManufacturerPart linked to {self.part} from manufacturer {manufacturer.name}' - f'with part number {MPN} already exists!') - - if manufacturer_part: - self.manufacturer_part = manufacturer_part - - self.clean() - self.validate_unique() - - super().save(*args, **kwargs) - class Meta: unique_together = ('part', 'supplier', 'SKU') diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 6e5ef08d6e..e9f13d021d 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -96,7 +96,9 @@ class CompanySerializer(InvenTreeModelSerializer): class ManufacturerPartSerializer(InvenTreeModelSerializer): - """ Serializer for ManufacturerPart object """ + """ + Serializer for ManufacturerPart object + """ part_detail = PartBriefSerializer(source='part', many=False, read_only=True) @@ -106,8 +108,8 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer): def __init__(self, *args, **kwargs): - part_detail = kwargs.pop('part_detail', False) - manufacturer_detail = kwargs.pop('manufacturer_detail', False) + part_detail = kwargs.pop('part_detail', True) + manufacturer_detail = kwargs.pop('manufacturer_detail', True) prettify = kwargs.pop('pretty', False) super(ManufacturerPartSerializer, self).__init__(*args, **kwargs) @@ -229,25 +231,6 @@ class SupplierPartSerializer(InvenTreeModelSerializer): 'supplier_detail', ] - def create(self, validated_data): - """ Extract manufacturer data and process ManufacturerPart """ - - # Create SupplierPart - supplier_part = super().create(validated_data) - - # Get ManufacturerPart raw data (unvalidated) - manufacturer_id = self.initial_data.get('manufacturer', None) - MPN = self.initial_data.get('MPN', None) - - if manufacturer_id and MPN: - kwargs = { - 'manufacturer': manufacturer_id, - 'MPN': MPN, - } - supplier_part.save(**kwargs) - - return supplier_part - class SupplierPriceBreakSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPriceBreak object """ diff --git a/InvenTree/company/templates/company/detail.html b/InvenTree/company/templates/company/detail.html index 105e0f82ae..b45289eb8c 100644 --- a/InvenTree/company/templates/company/detail.html +++ b/InvenTree/company/templates/company/detail.html @@ -284,23 +284,9 @@ $("#manufacturer-part-create").click(function () { - constructForm('{% url "api-manufacturer-part-list" %}', { - fields: { - part: {}, - manufacturer: { - value: {{ company.pk }}, - }, - MPN: { - icon: 'fa-hashtag', - }, - description: {}, - link: { - icon: 'fa-link', - }, - }, - method: 'POST', - title: '{% trans "Add Manufacturer Part" %}', - onSuccess: function() { + createManufacturerPart({ + manufacturer: {{ company.pk }}, + onSuccess: function() { $("#part-table").bootstrapTable("refresh"); } }); @@ -350,28 +336,16 @@ {% if company.is_supplier %} + function reloadSupplierPartTable() { + $('#supplier-part-table').bootstrapTable('refresh'); + } + $("#supplier-part-create").click(function () { - launchModalForm( - "{% url 'supplier-part-create' %}", - { - data: { - supplier: {{ company.id }}, - }, - reload: true, - secondary: [ - { - field: 'part', - label: '{% trans "New Part" %}', - title: '{% trans "Create new Part" %}', - url: "{% url 'part-create' %}" - }, - { - field: 'supplier', - label: "{% trans 'New Supplier' %}", - title: "{% trans 'Create new Supplier' %}", - }, - ] - }); + + createSupplierPart({ + supplier: {{ company.pk }}, + onSuccess: reloadSupplierPartTable, + }); }); loadSupplierPartTable( @@ -390,22 +364,27 @@ {% endif %} $("#multi-part-delete").click(function() { - var selections = $("#part-table").bootstrapTable("getSelections"); + var selections = $("#supplier-part-table").bootstrapTable("getSelections"); - var parts = []; + var requests = []; - selections.forEach(function(item) { - parts.push(item.pk); - }); - - var url = "{% url 'supplier-part-delete' %}" - - launchModalForm(url, { - data: { - parts: parts, - }, - reload: true, - }); + showQuestionDialog( + '{% trans "Delete Supplier Parts?" %}', + '{% trans "All selected supplier parts will be deleted" %}', + { + accept: function() { + selections.forEach(function(part) { + var url = `/api/company/part/${part.pk}/`; + + requests.push(inventreeDelete(url)); + }); + + $.when.apply($, requests).then(function() { + $('#supplier-part-table').bootstrapTable('refresh'); + }); + } + } + ); }); $("#multi-part-order").click(function() { diff --git a/InvenTree/company/templates/company/manufacturer_part.html b/InvenTree/company/templates/company/manufacturer_part.html index da5ea36173..94ff64440f 100644 --- a/InvenTree/company/templates/company/manufacturer_part.html +++ b/InvenTree/company/templates/company/manufacturer_part.html @@ -178,40 +178,41 @@ $('#parameter-create').click(function() { }); }); +function reloadSupplierPartTable() { + $('#supplier-table').bootstrapTable('refresh'); +} + $('#supplier-create').click(function () { - launchModalForm( - "{% url 'supplier-part-create' %}", - { - reload: true, - data: { - manufacturer_part: {{ part.id }} - }, - secondary: [ - { - field: 'supplier', - label: '{% trans "New Supplier" %}', - title: '{% trans "Create new supplier" %}', - }, - ] - }); + createSupplierPart({ + manufacturer_part: {{ part.pk }}, + part: {{ part.part.pk }}, + onSuccess: reloadSupplierPartTable, + }); }); $("#supplier-part-delete").click(function() { var selections = $("#supplier-table").bootstrapTable("getSelections"); - var parts = []; + var requests = []; - selections.forEach(function(item) { - parts.push(item.pk); - }); - - launchModalForm("{% url 'supplier-part-delete' %}", { - data: { - parts: parts, - }, - reload: true, - }); + showQuestionDialog( + '{% trans "Delete Supplier Parts?" %}', + '{% trans "All selected supplier parts will be deleted" %}', + { + accept: function() { + selections.forEach(function(part) { + var url = `/api/company/part/${part.pk}/`; + + requests.push(inventreeDelete(url)); + }); + + $.when.apply($, requests).then(function() { + reloadSupplierPartTable(); + }); + } + } + ); }); $("#multi-parameter-delete").click(function() { @@ -296,29 +297,19 @@ $('#order-part, #order-part2').click(function() { $('#edit-part').click(function () { - constructForm('{% url "api-manufacturer-part-detail" part.pk %}', { - fields: { - part: {}, - manufacturer: {}, - MPN: { - icon: 'fa-hashtag', - }, - description: {}, - link: { - icon: 'fa-link', - }, - }, - title: '{% trans "Edit Manufacturer Part" %}', - reload: true, + editManufacturerPart({{ part.pk }}, { + onSuccess: function() { + location.reload(); + } }); }); $('#delete-part').click(function() { - constructForm('{% url "api-manufacturer-part-detail" part.pk %}', { - method: 'DELETE', - title: '{% trans "Delete Manufacturer Part" %}', - redirect: "{% url 'company-detail' part.manufacturer.id %}", + deleteManufacturerPart({{ part.pk }}, { + onSuccess: function() { + window.location.href = "{% url 'company-detail' part.manufacturer.id %}"; + } }); }); diff --git a/InvenTree/company/templates/company/navbar.html b/InvenTree/company/templates/company/navbar.html index 025b1c6b4a..b652d6b603 100644 --- a/InvenTree/company/templates/company/navbar.html +++ b/InvenTree/company/templates/company/navbar.html @@ -18,7 +18,7 @@ {% endif %} - {% if company.is_supplier or company.is_manufacturer %} + {% if company.is_supplier %}
  • diff --git a/InvenTree/company/templates/company/supplier_part.html b/InvenTree/company/templates/company/supplier_part.html index c3c2f89aa7..f751665e56 100644 --- a/InvenTree/company/templates/company/supplier_part.html +++ b/InvenTree/company/templates/company/supplier_part.html @@ -327,21 +327,21 @@ $('#order-part, #order-part2').click(function() { }); $('#edit-part').click(function () { - launchModalForm( - "{% url 'supplier-part-edit' part.id %}", - { - reload: true - } - ); + + editSupplierPart({{ part.pk }}, { + onSuccess: function() { + location.reload(); + } + }); }); $('#delete-part').click(function() { - launchModalForm( - "{% url 'supplier-part-delete' %}?part={{ part.id }}", - { - redirect: "{% url 'company-detail' part.supplier.id %}" + + deleteSupplierPart({{ part.pk }}, { + onSuccess: function() { + window.location.href = "{% url 'company-detail' part.supplier.id %}"; } - ); + }); }); attachNavCallbacks({ diff --git a/InvenTree/company/templates/company/supplier_part_create.html b/InvenTree/company/templates/company/supplier_part_create.html deleted file mode 100644 index 21c23f9075..0000000000 --- a/InvenTree/company/templates/company/supplier_part_create.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "modal_form.html" %} - -{% load i18n %} - -{% block pre_form_content %} -{{ block.super }} - -{% if part %} -
    - {% include "hover_image.html" with image=part.image %} - {{ part.full_name}} -
    - {{ part.description }} -
    -{% endif %} - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/company/templates/company/supplier_part_delete.html b/InvenTree/company/templates/company/supplier_part_delete.html deleted file mode 100644 index 40d9ce42de..0000000000 --- a/InvenTree/company/templates/company/supplier_part_delete.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "modal_delete_form.html" %} -{% load i18n %} - -{% block pre_form_content %} -{% trans "Are you sure you want to delete the following Supplier Parts?" %} - -
    -{% endblock %} - -{% block form_data %} - -{% for part in parts %} - - - - - - - -{% endfor %} -
    - {% include "hover_image.html" with image=part.part.image %} - {{ part.part.full_name }} - - {% include "hover_image.html" with image=part.supplier.image %} - {{ part.supplier.name }} - - {{ part.SKU }} -
    - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/company/test_api.py b/InvenTree/company/test_api.py index 2da6d29198..a915ad1bf5 100644 --- a/InvenTree/company/test_api.py +++ b/InvenTree/company/test_api.py @@ -218,14 +218,27 @@ class ManufacturerTest(InvenTreeAPITestCase): def test_supplier_part_create(self): url = reverse('api-supplier-part-list') - # Create supplier part + # Create a manufacturer part + response = self.post( + reverse('api-manufacturer-part-list'), + { + 'part': 1, + 'manufacturer': 7, + 'MPN': 'PART_NUMBER', + }, + expected_code=201 + ) + + pk = response.data['pk'] + + # Create a supplier part (associated with the new manufacturer part) data = { 'part': 1, 'supplier': 1, 'SKU': 'SKU_TEST', - 'manufacturer': 7, - 'MPN': 'PART_NUMBER', + 'manufacturer_part': pk, } + response = self.client.post(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index 6fc4281f2b..89968081c3 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -10,9 +10,6 @@ from django.urls import reverse from django.contrib.auth import get_user_model from django.contrib.auth.models import Group -from .models import ManufacturerPart -from .models import SupplierPart - class CompanyViewTestBase(TestCase): @@ -75,108 +72,6 @@ class CompanyViewTestBase(TestCase): return json_data, form_errors -class SupplierPartViewTests(CompanyViewTestBase): - """ - Tests for the SupplierPart views. - """ - - def test_supplier_part_create(self): - """ - Test the SupplierPartCreate view. - - This view allows some additional functionality, - specifically it allows the user to create a single-quantity price break - automatically, when saving the new SupplierPart model. - """ - - url = reverse('supplier-part-create') - - # First check that we can GET the form - response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # How many supplier parts are already in the database? - n = SupplierPart.objects.all().count() - - data = { - 'part': 1, - 'supplier': 1, - } - - # SKU is required! (form should fail) - (response, errors) = self.post(url, data, valid=False) - - self.assertIsNotNone(errors.get('SKU', None)) - - data['SKU'] = 'TEST-ME-123' - - (response, errors) = self.post(url, data, valid=True) - - # Check that the SupplierPart was created! - self.assertEqual(n + 1, SupplierPart.objects.all().count()) - - # Check that it was created *without* a price-break - supplier_part = SupplierPart.objects.get(pk=response['pk']) - - self.assertEqual(supplier_part.price_breaks.count(), 0) - - # Duplicate SKU is prohibited - (response, errors) = self.post(url, data, valid=False) - - self.assertIsNotNone(errors.get('__all__', None)) - - # Add with a different SKU, *and* a single-quantity price - data['SKU'] = 'TEST-ME-1234' - data['single_pricing_0'] = '123.4' - data['single_pricing_1'] = 'CAD' - - (response, errors) = self.post(url, data, valid=True) - - pk = response.get('pk') - - # Check that *another* SupplierPart was created - self.assertEqual(n + 2, SupplierPart.objects.all().count()) - - supplier_part = SupplierPart.objects.get(pk=pk) - - # Check that a price-break has been created! - self.assertEqual(supplier_part.price_breaks.count(), 1) - - price_break = supplier_part.price_breaks.first() - - self.assertEqual(price_break.quantity, 1) - - def test_supplier_part_delete(self): - """ - Test the SupplierPartDelete view - """ - - url = reverse('supplier-part-delete') - - # Get form using 'part' argument - response = self.client.get(url, {'part': '1'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # Get form using 'parts' argument - response = self.client.get(url + '?parts[]=1&parts[]=2', HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # POST to delete two parts - n = SupplierPart.objects.count() - response = self.client.post( - url, - { - 'supplier-part-2': 'supplier-part-2', - 'supplier-part-3': 'supplier-part-3', - 'confirm_delete': True - }, - HTTP_X_REQUESTED_WITH='XMLHttpRequest') - - self.assertEqual(response.status_code, 200) - - self.assertEqual(n - 2, SupplierPart.objects.count()) - - class CompanyViewTest(CompanyViewTestBase): """ Tests for various 'Company' views @@ -187,36 +82,3 @@ class CompanyViewTest(CompanyViewTestBase): response = self.client.get(reverse('company-index')) self.assertEqual(response.status_code, 200) - - -class ManufacturerPartViewTests(CompanyViewTestBase): - """ - Tests for the ManufacturerPart views. - """ - - def test_supplier_part_create(self): - """ - Test that the SupplierPartCreate view creates Manufacturer Part. - """ - - url = reverse('supplier-part-create') - - # First check that we can GET the form - response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # How many manufacturer parts are already in the database? - n = ManufacturerPart.objects.all().count() - - data = { - 'part': 1, - 'supplier': 1, - 'SKU': 'SKU_TEST', - 'manufacturer': 6, - 'MPN': 'MPN_TEST', - } - - (response, errors) = self.post(url, data, valid=True) - - # Check that the ManufacturerPart was created! - self.assertEqual(n + 1, ManufacturerPart.objects.all().count()) diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index e4a70b077a..79ffc34af5 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -192,18 +192,14 @@ class ManufacturerPartSimpleTest(TestCase): SKU='SKU_TEST', ) - kwargs = { - 'manufacturer': manufacturer.id, - 'MPN': 'MPN_TEST', - } - supplier_part.save(**kwargs) + supplier_part.save() def test_exists(self): - self.assertEqual(ManufacturerPart.objects.count(), 5) + self.assertEqual(ManufacturerPart.objects.count(), 4) # Check that manufacturer part was created from supplier part creation manufacturer_parts = ManufacturerPart.objects.filter(manufacturer=1) - self.assertEqual(manufacturer_parts.count(), 2) + self.assertEqual(manufacturer_parts.count(), 1) def test_delete(self): # Remove a part diff --git a/InvenTree/company/urls.py b/InvenTree/company/urls.py index 7d2b6fb609..901e7f7089 100644 --- a/InvenTree/company/urls.py +++ b/InvenTree/company/urls.py @@ -35,16 +35,6 @@ manufacturer_part_urls = [ ])), ] -supplier_part_detail_urls = [ - url(r'^edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'), - +supplier_part_urls = [ url('^.*$', views.SupplierPartDetail.as_view(template_name='company/supplier_part.html'), name='supplier-part-detail'), ] - -supplier_part_urls = [ - url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'), - - url(r'delete/', views.SupplierPartDelete.as_view(), name='supplier-part-delete'), - - url(r'^(?P\d+)/', include(supplier_part_detail_urls)), -] diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index ab6344e810..f3a9af7628 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -10,31 +10,22 @@ from django.utils.translation import ugettext_lazy as _ from django.views.generic import DetailView, ListView from django.urls import reverse -from django.forms import HiddenInput from django.core.files.base import ContentFile -from moneyed import CURRENCIES - from PIL import Image import requests import io -from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView -from InvenTree.helpers import str2bool +from InvenTree.views import AjaxUpdateView from InvenTree.views import InvenTreeRoleMixin from .models import Company from .models import ManufacturerPart from .models import SupplierPart -from part.models import Part -from .forms import EditSupplierPartForm from .forms import CompanyImageDownloadForm -import common.models -import common.settings - class CompanyIndex(InvenTreeRoleMixin, ListView): """ View for displaying list of companies @@ -231,272 +222,3 @@ class SupplierPartDetail(DetailView): ctx = super().get_context_data(**kwargs) return ctx - - -class SupplierPartEdit(AjaxUpdateView): - """ Update view for editing SupplierPart """ - - model = SupplierPart - context_object_name = 'part' - form_class = EditSupplierPartForm - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Edit Supplier Part') - - def save(self, supplier_part, form, **kwargs): - """ Process ManufacturerPart data """ - - manufacturer = form.cleaned_data.get('manufacturer', None) - MPN = form.cleaned_data.get('MPN', None) - kwargs = {'manufacturer': manufacturer, - 'MPN': MPN, - } - supplier_part.save(**kwargs) - - def get_form(self): - form = super().get_form() - - supplier_part = self.get_object() - - # Hide Manufacturer fields - form.fields['manufacturer'].widget = HiddenInput() - form.fields['MPN'].widget = HiddenInput() - - # It appears that hiding a MoneyField fails validation - # Therefore the idea to set the value before hiding - if form.is_valid(): - form.cleaned_data['single_pricing'] = supplier_part.unit_pricing - # Hide the single-pricing field (only for creating a new SupplierPart!) - form.fields['single_pricing'].widget = HiddenInput() - - return form - - def get_initial(self): - """ Fetch data from ManufacturerPart """ - - initials = super(SupplierPartEdit, self).get_initial().copy() - - supplier_part = self.get_object() - - if supplier_part.manufacturer_part: - if supplier_part.manufacturer_part.manufacturer: - initials['manufacturer'] = supplier_part.manufacturer_part.manufacturer.id - initials['MPN'] = supplier_part.manufacturer_part.MPN - - return initials - - -class SupplierPartCreate(AjaxCreateView): - """ Create view for making new SupplierPart """ - - model = SupplierPart - form_class = EditSupplierPartForm - ajax_template_name = 'company/supplier_part_create.html' - ajax_form_title = _('Create new Supplier Part') - context_object_name = 'part' - - def validate(self, part, form): - - single_pricing = form.cleaned_data.get('single_pricing', None) - - if single_pricing: - # TODO - What validation steps can be performed on the single_pricing field? - pass - - def get_context_data(self): - """ - Supply context data to the form - """ - - ctx = super().get_context_data() - - # Add 'part' object - form = self.get_form() - - part = form['part'].value() - - try: - part = Part.objects.get(pk=part) - except (ValueError, Part.DoesNotExist): - part = None - - ctx['part'] = part - - return ctx - - def save(self, form): - """ - If single_pricing is defined, add a price break for quantity=1 - """ - - # Save the supplier part object - supplier_part = super().save(form) - - # Process manufacturer data - manufacturer = form.cleaned_data.get('manufacturer', None) - MPN = form.cleaned_data.get('MPN', None) - kwargs = {'manufacturer': manufacturer, - 'MPN': MPN, - } - supplier_part.save(**kwargs) - - single_pricing = form.cleaned_data.get('single_pricing', None) - - if single_pricing: - - supplier_part.add_price_break(1, single_pricing) - - return supplier_part - - def get_form(self): - """ Create Form instance to create a new SupplierPart object. - Hide some fields if they are not appropriate in context - """ - form = super(AjaxCreateView, self).get_form() - - if form.initial.get('part', None): - # Hide the part field - form.fields['part'].widget = HiddenInput() - - if form.initial.get('manufacturer', None): - # Hide the manufacturer field - form.fields['manufacturer'].widget = HiddenInput() - # Hide the MPN field - form.fields['MPN'].widget = HiddenInput() - - return form - - def get_initial(self): - """ Provide initial data for new SupplierPart: - - - If 'supplier_id' provided, pre-fill supplier field - - If 'part_id' provided, pre-fill part field - """ - initials = super(SupplierPartCreate, self).get_initial().copy() - - manufacturer_id = self.get_param('manufacturer') - supplier_id = self.get_param('supplier') - part_id = self.get_param('part') - manufacturer_part_id = self.get_param('manufacturer_part') - - supplier = None - - if supplier_id: - try: - supplier = Company.objects.get(pk=supplier_id) - initials['supplier'] = supplier - except (ValueError, Company.DoesNotExist): - supplier = None - - if manufacturer_id: - try: - initials['manufacturer'] = Company.objects.get(pk=manufacturer_id) - except (ValueError, Company.DoesNotExist): - pass - - if manufacturer_part_id: - try: - # Get ManufacturerPart instance information - manufacturer_part_obj = ManufacturerPart.objects.get(pk=manufacturer_part_id) - initials['part'] = Part.objects.get(pk=manufacturer_part_obj.part.id) - initials['manufacturer'] = manufacturer_part_obj.manufacturer.id - initials['MPN'] = manufacturer_part_obj.MPN - except (ValueError, ManufacturerPart.DoesNotExist, Part.DoesNotExist, Company.DoesNotExist): - pass - - if part_id: - try: - initials['part'] = Part.objects.get(pk=part_id) - except (ValueError, Part.DoesNotExist): - pass - - # Initial value for single pricing - if supplier: - currency_code = supplier.currency_code - else: - currency_code = common.settings.currency_code_default() - - currency = CURRENCIES.get(currency_code, None) - - if currency_code: - initials['single_pricing'] = ('', currency) - - return initials - - -class SupplierPartDelete(AjaxDeleteView): - """ Delete view for removing a SupplierPart. - - SupplierParts can be deleted using a variety of 'selectors'. - - - ?part= -> Delete a single SupplierPart object - - ?parts=[] -> Delete a list of SupplierPart objects - - """ - - success_url = '/supplier/' - ajax_template_name = 'company/supplier_part_delete.html' - ajax_form_title = _('Delete Supplier Part') - - role_required = 'purchase_order.delete' - - parts = [] - - def get_context_data(self): - ctx = {} - - ctx['parts'] = self.parts - - return ctx - - def get_parts(self): - """ Determine which SupplierPart object(s) the user wishes to delete. - """ - - self.parts = [] - - # User passes a single SupplierPart ID - if 'part' in self.request.GET: - try: - self.parts.append(SupplierPart.objects.get(pk=self.request.GET.get('part'))) - except (ValueError, SupplierPart.DoesNotExist): - pass - - elif 'parts[]' in self.request.GET: - - part_id_list = self.request.GET.getlist('parts[]') - - self.parts = SupplierPart.objects.filter(id__in=part_id_list) - - def get(self, request, *args, **kwargs): - self.request = request - self.get_parts() - - return self.renderJsonResponse(request, form=self.get_form()) - - def post(self, request, *args, **kwargs): - """ Handle the POST action for deleting supplier parts. - """ - - self.request = request - self.parts = [] - - for item in self.request.POST: - if item.startswith('supplier-part-'): - pk = item.replace('supplier-part-', '') - - try: - self.parts.append(SupplierPart.objects.get(pk=pk)) - except (ValueError, SupplierPart.DoesNotExist): - pass - - confirm = str2bool(self.request.POST.get('confirm_delete', False)) - - data = { - 'form_valid': confirm, - } - - if confirm: - for part in self.parts: - part.delete() - - return self.renderJsonResponse(self.request, data=data, form=self.get_form()) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 0ba539baea..0666108e0b 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -801,45 +801,41 @@ ) }); + function reloadSupplierPartTable() { + $('#supplier-part-table').bootstrapTable('refresh'); + } + $('#supplier-create').click(function () { - launchModalForm( - "{% url 'supplier-part-create' %}", - { - reload: true, - data: { - part: {{ part.id }} - }, - secondary: [ - { - field: 'supplier', - label: '{% trans "New Supplier" %}', - title: '{% trans "Create new supplier" %}', - }, - { - field: 'manufacturer', - label: '{% trans "New Manufacturer" %}', - title: '{% trans "Create new manufacturer" %}', - } - ] - }); + + createSupplierPart({ + part: {{ part.pk }}, + onSuccess: reloadSupplierPartTable, + }); }); $("#supplier-part-delete").click(function() { var selections = $("#supplier-part-table").bootstrapTable("getSelections"); - var parts = []; + var requests = []; - selections.forEach(function(item) { - parts.push(item.pk); - }); - - launchModalForm("{% url 'supplier-part-delete' %}", { - data: { - parts: parts, - }, - reload: true, - }); + showQuestionDialog( + '{% trans "Delete Supplier Parts?" %}', + '{% trans "All selected supplier parts will be deleted" %}', + { + accept: function() { + selections.forEach(function(part) { + var url = `/api/company/part/${part.pk}/`; + + requests.push(inventreeDelete(url)); + }); + + $.when.apply($, requests).then(function() { + reloadSupplierPartTable(); + }); + } + } + ); }); loadSupplierPartTable( @@ -883,21 +879,10 @@ }); $('#manufacturer-create').click(function () { - - constructForm('{% url "api-manufacturer-part-list" %}', { - fields: { - part: { - value: {{ part.pk }}, - hidden: true, - }, - manufacturer: {}, - MPN: {}, - description: {}, - link: {}, - }, - method: 'POST', - title: '{% trans "Add Manufacturer Part" %}', - onSuccess: function() { + + createManufacturerPart({ + part: {{ part.pk }}, + onSuccess: function() { $("#manufacturer-part-table").bootstrapTable("refresh"); } }); diff --git a/InvenTree/templates/js/company.js b/InvenTree/templates/js/company.js index 7daa99e1c6..b202fbcd52 100644 --- a/InvenTree/templates/js/company.js +++ b/InvenTree/templates/js/company.js @@ -1,6 +1,142 @@ {% load i18n %} +function manufacturerPartFields() { + + return { + part: {}, + manufacturer: {}, + MPN: { + icon: 'fa-hashtag', + }, + description: {}, + link: { + icon: 'fa-link', + } + }; +} + + +function createManufacturerPart(options={}) { + + var fields = manufacturerPartFields(); + + if (options.part) { + fields.part.value = options.part; + fields.part.hidden = true; + } + + if (options.manufacturer) { + fields.manufacturer.value = options.manufacturer; + } + + constructForm('{% url "api-manufacturer-part-list" %}', { + fields: fields, + method: 'POST', + title: '{% trans "Add Manufacturer Part" %}', + onSuccess: options.onSuccess + }); +} + + +function editManufacturerPart(part, options={}) { + + var url = `/api/company/part/manufacturer/${part}/`; + + constructForm(url, { + fields: manufacturerPartFields(), + title: '{% trans "Edit Manufacturer Part" %}', + onSuccess: options.onSuccess + }); +} + +function deleteManufacturerPart(part, options={}) { + + constructForm(`/api/company/part/manufacturer/${part}/`, { + method: 'DELETE', + title: '{% trans "Delete Manufacturer Part" %}', + onSuccess: options.onSuccess, + }); +} + + +function supplierPartFields() { + + return { + part: {}, + supplier: {}, + SKU: { + icon: 'fa-hashtag', + }, + manufacturer_part: { + filters: { + part_detail: true, + manufacturer_detail: true, + } + }, + description: {}, + link: { + icon: 'fa-link', + }, + note: { + icon: 'fa-pencil-alt', + }, + packaging: { + icon: 'fa-box', + } + }; +} + +/* + * Launch a form to create a new ManufacturerPart + */ +function createSupplierPart(options={}) { + + var fields = supplierPartFields(); + + if (options.part) { + fields.manufacturer_part.filters.part = options.part; + fields.part.hidden = true; + fields.part.value = options.part; + } + + if (options.supplier) { + fields.supplier.value = options.supplier; + } + + if (options.manufacturer_part) { + fields.manufacturer_part.value = options.manufacturer_part; + } + + constructForm('{% url "api-supplier-part-list" %}', { + fields: fields, + method: 'POST', + title: '{% trans "Add Supplier Part" %}', + onSuccess: options.onSuccess, + }); +} + + +function editSupplierPart(part, options={}) { + + constructForm(`/api/company/part/${part}/`, { + fields: supplierPartFields(), + title: '{% trans "Edit Supplier Part" %}', + onSuccess: options.onSuccess + }); +} + + +function deleteSupplierPart(part, options={}) { + + constructForm(`/api/company/part/${part}/`, { + method: 'DELETE', + title: '{% trans "Delete Supplier Part" %}', + onSuccess: options.onSuccess, + }); +} + + // Returns a default form-set for creating / editing a Company object function companyFormFields(options={}) { @@ -323,8 +459,52 @@ function loadManufacturerPartTable(table, url, options) { title: '{% trans "Description" %}', sortable: false, switchable: true, + }, + { + field: 'actions', + title: '', + sortable: false, + switchable: false, + formatter: function(value, row) { + var pk = row.pk; + + var html = `
    `; + + html += makeIconButton('fa-edit icon-blue', 'button-manufacturer-part-edit', pk, '{% trans "Edit manufacturer part" %}'); + html += makeIconButton('fa-trash-alt icon-red', 'button-manufacturer-part-delete', pk, '{% trans "Delete manufacturer part" %}'); + + html += '
    '; + + return html; + } } ], + onPostBody: function() { + // Callbacks + $(table).find('.button-manufacturer-part-edit').click(function() { + var pk = $(this).attr('pk'); + + editManufacturerPart( + pk, + { + onSuccess: function() { + $(table).bootstrapTable('refresh'); + } + }); + }); + + $(table).find('.button-manufacturer-part-delete').click(function() { + var pk = $(this).attr('pk'); + + deleteManufacturerPart( + pk, + { + onSuccess: function() { + $(table).bootstrapTable('refresh'); + } + }); + }) + } }); } @@ -570,7 +750,51 @@ function loadSupplierPartTable(table, url, options) { field: 'packaging', title: '{% trans "Packaging" %}', sortable: false, + }, + { + field: 'actions', + title: '', + sortable: false, + switchable: false, + formatter: function(value, row) { + var pk = row.pk; + + var html = `
    `; + + html += makeIconButton('fa-edit icon-blue', 'button-supplier-part-edit', pk, '{% trans "Edit supplier part" %}'); + html += makeIconButton('fa-trash-alt icon-red', 'button-supplier-part-delete', pk, '{% trans "Delete supplier part" %}'); + + html += '
    '; + + return html; + } } ], + onPostBody: function() { + // Callbacks + $(table).find('.button-supplier-part-edit').click(function() { + var pk = $(this).attr('pk'); + + editSupplierPart( + pk, + { + onSuccess: function() { + $(table).bootstrapTable('refresh'); + } + }); + }); + + $(table).find('.button-supplier-part-delete').click(function() { + var pk = $(this).attr('pk'); + + deleteSupplierPart( + pk, + { + onSuccess: function() { + $(table).bootstrapTable('refresh'); + } + }); + }) + } }); } \ No newline at end of file diff --git a/InvenTree/templates/js/forms.js b/InvenTree/templates/js/forms.js index 6dd7dbd968..568ca5ac58 100644 --- a/InvenTree/templates/js/forms.js +++ b/InvenTree/templates/js/forms.js @@ -1113,7 +1113,7 @@ function initializeRelatedField(name, field, options) { var pk = field.value; var url = `${field.api_url}/${pk}/`.replace('//', '/'); - inventreeGet(url, {}, { + inventreeGet(url, field.filters || {}, { success: function(data) { setRelatedFieldData(name, data, options); } @@ -1211,6 +1211,9 @@ function renderModelData(name, model, data, parameters, options) { case 'partparametertemplate': renderer = renderPartParameterTemplate; break; + case 'manufacturerpart': + renderer = renderManufacturerPart; + break; case 'supplierpart': renderer = renderSupplierPart; break; diff --git a/InvenTree/templates/js/model_renderers.js b/InvenTree/templates/js/model_renderers.js index ddb4897d8d..12e00cab2c 100644 --- a/InvenTree/templates/js/model_renderers.js +++ b/InvenTree/templates/js/model_renderers.js @@ -174,7 +174,35 @@ function renderPartParameterTemplate(name, data, parameters, options) { } -// Rendered for "SupplierPart" model +// Renderer for "ManufacturerPart" model +function renderManufacturerPart(name, data, parameters, options) { + + var manufacturer_image = null; + var part_image = null; + + if (data.manufacturer_detail) { + manufacturer_image = data.manufacturer_detail.image; + } + + if (data.part_detail) { + part_image = data.part_detail.thumbnail || data.part_detail.image; + } + + var html = ''; + + html += select2Thumbnail(manufacturer_image); + html += select2Thumbnail(part_image); + + html += ` ${data.manufacturer_detail.name} - ${data.MPN}`; + html += ` - ${data.part_detail.full_name}`; + + html += `{% trans "Manufacturer Part ID" %}: ${data.pk}`; + + return html; +} + + +// Renderer for "SupplierPart" model function renderSupplierPart(name, data, parameters, options) { var supplier_image = null; diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 754d3d5b59..1d38b631a5 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -1616,27 +1616,6 @@ function createNewStockItem(options) { }, ]; - options.secondary = [ - { - field: 'part', - label: '{% trans "New Part" %}', - title: '{% trans "Create New Part" %}', - url: "{% url 'part-create' %}", - }, - { - field: 'supplier_part', - label: '{% trans "New Supplier Part" %}', - title: '{% trans "Create new Supplier Part" %}', - url: "{% url 'supplier-part-create' %}" - }, - { - field: 'location', - label: '{% trans "New Location" %}', - title: '{% trans "Create New Location" %}', - url: "{% url 'stock-location-create' %}", - }, - ]; - launchModalForm("{% url 'stock-item-create' %}", options); } diff --git a/InvenTree/templates/js/tables.js b/InvenTree/templates/js/tables.js index 8fedeb8f9e..afe1fefbc9 100644 --- a/InvenTree/templates/js/tables.js +++ b/InvenTree/templates/js/tables.js @@ -1,5 +1,11 @@ {% load i18n %} + +function reloadtable(table) { + $(table).bootstrapTable('refresh'); +} + + function editButton(url, text='Edit') { return ""; }