Merge pull request #1836 from SchrodingersGat/supplier-part-forms

Supplier part forms
This commit is contained in:
Oliver 2021-07-19 11:17:45 +10:00 committed by GitHub
commit fa9ef02d23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 399 additions and 801 deletions

View File

@ -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 """

View File

@ -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')

View File

@ -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 """

View File

@ -284,22 +284,8 @@
$("#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" %}',
createManufacturerPart({
manufacturer: {{ company.pk }},
onSuccess: function() {
$("#part-table").bootstrapTable("refresh");
}
@ -350,27 +336,15 @@
{% 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,
});
});
@ -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);
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));
});
var url = "{% url 'supplier-part-delete' %}"
launchModalForm(url, {
data: {
parts: parts,
},
reload: true,
$.when.apply($, requests).then(function() {
$('#supplier-part-table').bootstrapTable('refresh');
});
}
}
);
});
$("#multi-part-order").click(function() {

View File

@ -178,21 +178,15 @@ $('#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,
});
});
@ -200,18 +194,25 @@ $("#supplier-part-delete").click(function() {
var selections = $("#supplier-table").bootstrapTable("getSelections");
var parts = [];
var requests = [];
selections.forEach(function(item) {
parts.push(item.pk);
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));
});
launchModalForm("{% url 'supplier-part-delete' %}", {
data: {
parts: parts,
},
reload: true,
$.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 %}";
}
});
});

View File

@ -18,7 +18,7 @@
</li>
{% endif %}
{% if company.is_supplier or company.is_manufacturer %}
{% if company.is_supplier %}
<li class='list-group-item' title='{% trans "Supplied Parts" %}'>
<a href='#' id='select-supplier-parts' class='nav-toggle'>
<span class='fas fa-building sidebar-icon'></span>

View File

@ -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({

View File

@ -1,17 +0,0 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
{{ block.super }}
{% if part %}
<div class='alert alert-block alert-info'>
{% include "hover_image.html" with image=part.image %}
{{ part.full_name}}
<br>
<i>{{ part.description }}</i>
</div>
{% endif %}
{% endblock %}

View File

@ -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?" %}
<hr>
{% endblock %}
{% block form_data %}
<table class='table table-striped table-condensed'>
{% for part in parts %}
<tr>
<input type='hidden' name='supplier-part-{{ part.id}}' value='supplier-part-{{ part.id }}'/>
<td>
{% include "hover_image.html" with image=part.part.image %}
{{ part.part.full_name }}
</td>
<td>
{% include "hover_image.html" with image=part.supplier.image %}
{{ part.supplier.name }}
</td>
<td>
{{ part.SKU }}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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<pk>\d+)/', include(supplier_part_detail_urls)),
]

View File

@ -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=<pk> -> 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())

View File

@ -801,26 +801,15 @@
)
});
$('#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" %}',
function reloadSupplierPartTable() {
$('#supplier-part-table').bootstrapTable('refresh');
}
]
$('#supplier-create').click(function () {
createSupplierPart({
part: {{ part.pk }},
onSuccess: reloadSupplierPartTable,
});
});
@ -828,18 +817,25 @@
var selections = $("#supplier-part-table").bootstrapTable("getSelections");
var parts = [];
var requests = [];
selections.forEach(function(item) {
parts.push(item.pk);
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));
});
launchModalForm("{% url 'supplier-part-delete' %}", {
data: {
parts: parts,
},
reload: true,
$.when.apply($, requests).then(function() {
reloadSupplierPartTable();
});
}
}
);
});
loadSupplierPartTable(
@ -884,19 +880,8 @@
$('#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" %}',
createManufacturerPart({
part: {{ part.pk }},
onSuccess: function() {
$("#manufacturer-part-table").bootstrapTable("refresh");
}

View File

@ -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 = `<div class='btn-group float-right' role='group'>`;
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 += '</div>';
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 = `<div class='btn-group float-right' role='group'>`;
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 += '</div>';
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');
}
});
})
}
});
}

View File

@ -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;

View File

@ -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 += ` <span><b>${data.manufacturer_detail.name}</b> - ${data.MPN}</span>`;
html += ` - <i>${data.part_detail.full_name}</i>`;
html += `<span class='float-right'>{% trans "Manufacturer Part ID" %}: ${data.pk}</span>`;
return html;
}
// Renderer for "SupplierPart" model
function renderSupplierPart(name, data, parameters, options) {
var supplier_image = null;

View File

@ -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);
}

View File

@ -1,5 +1,11 @@
{% load i18n %}
function reloadtable(table) {
$(table).bootstrapTable('refresh');
}
function editButton(url, text='Edit') {
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
}