mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 15:15:42 +00:00 
			
		
		
		
	Merge remote-tracking branch 'inventree/master'
This commit is contained in:
		@@ -280,11 +280,25 @@ def MakeBarcode(object_name, object_pk, object_data={}, **kwargs):
 | 
			
		||||
        json string of the supplied data plus some other data
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    url = kwargs.get('url', False)
 | 
			
		||||
    brief = kwargs.get('brief', True)
 | 
			
		||||
 | 
			
		||||
    data = {}
 | 
			
		||||
 | 
			
		||||
    if brief:
 | 
			
		||||
    if url:
 | 
			
		||||
        request = object_data.get('request', None)
 | 
			
		||||
        item_url = object_data.get('item_url', None)
 | 
			
		||||
        absolute_url = None
 | 
			
		||||
 | 
			
		||||
        if request and item_url:
 | 
			
		||||
            absolute_url = request.build_absolute_uri(item_url)
 | 
			
		||||
            # Return URL (No JSON)
 | 
			
		||||
            return absolute_url
 | 
			
		||||
 | 
			
		||||
        if item_url:
 | 
			
		||||
            # Return URL (No JSON)
 | 
			
		||||
            return item_url
 | 
			
		||||
    elif brief:
 | 
			
		||||
        data[object_name] = object_pk
 | 
			
		||||
    else:
 | 
			
		||||
        data['tool'] = 'InvenTree'
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ from django.contrib import admin
 | 
			
		||||
from django.contrib.auth import views as auth_views
 | 
			
		||||
 | 
			
		||||
from company.urls import company_urls
 | 
			
		||||
from company.urls import manufacturer_part_urls
 | 
			
		||||
from company.urls import supplier_part_urls
 | 
			
		||||
from company.urls import price_break_urls
 | 
			
		||||
 | 
			
		||||
@@ -115,6 +116,7 @@ dynamic_javascript_urls = [
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'^part/', include(part_urls)),
 | 
			
		||||
    url(r'^manufacturer-part/', include(manufacturer_part_urls)),
 | 
			
		||||
    url(r'^supplier-part/', include(supplier_part_urls)),
 | 
			
		||||
    url(r'^price-break/', include(price_break_urls)),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,9 +15,11 @@ from django.db.models import Q
 | 
			
		||||
from InvenTree.helpers import str2bool
 | 
			
		||||
 | 
			
		||||
from .models import Company
 | 
			
		||||
from .models import ManufacturerPart
 | 
			
		||||
from .models import SupplierPart, SupplierPriceBreak
 | 
			
		||||
 | 
			
		||||
from .serializers import CompanySerializer
 | 
			
		||||
from .serializers import ManufacturerPartSerializer
 | 
			
		||||
from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -80,8 +82,105 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
 | 
			
		||||
        queryset = CompanySerializer.annotate_queryset(queryset)
 | 
			
		||||
 | 
			
		||||
        return queryset
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartList(generics.ListCreateAPIView):
 | 
			
		||||
    """ API endpoint for list view of ManufacturerPart object
 | 
			
		||||
 | 
			
		||||
    - GET: Return list of ManufacturerPart objects
 | 
			
		||||
    - POST: Create a new ManufacturerPart object
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    queryset = ManufacturerPart.objects.all().prefetch_related(
 | 
			
		||||
        'part',
 | 
			
		||||
        'manufacturer',
 | 
			
		||||
        'supplier_parts',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    serializer_class = ManufacturerPartSerializer
 | 
			
		||||
 | 
			
		||||
    def get_serializer(self, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
        # Do we wish to include extra detail?
 | 
			
		||||
        try:
 | 
			
		||||
            kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', None))
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            kwargs['manufacturer_detail'] = str2bool(self.request.query_params.get('manufacturer_detail', None))
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            kwargs['pretty'] = str2bool(self.request.query_params.get('pretty', None))
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
        
 | 
			
		||||
        kwargs['context'] = self.get_serializer_context()
 | 
			
		||||
 | 
			
		||||
        return self.serializer_class(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def filter_queryset(self, queryset):
 | 
			
		||||
        """
 | 
			
		||||
        Custom filtering for the queryset.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        queryset = super().filter_queryset(queryset)
 | 
			
		||||
 | 
			
		||||
        params = self.request.query_params
 | 
			
		||||
 | 
			
		||||
        # Filter by manufacturer
 | 
			
		||||
        manufacturer = params.get('company', None)
 | 
			
		||||
 | 
			
		||||
        if manufacturer is not None:
 | 
			
		||||
            queryset = queryset.filter(manufacturer=manufacturer)
 | 
			
		||||
 | 
			
		||||
        # Filter by parent part?
 | 
			
		||||
        part = params.get('part', None)
 | 
			
		||||
 | 
			
		||||
        if part is not None:
 | 
			
		||||
            queryset = queryset.filter(part=part)
 | 
			
		||||
 | 
			
		||||
        # Filter by 'active' status of the part?
 | 
			
		||||
        active = params.get('active', None)
 | 
			
		||||
 | 
			
		||||
        if active is not None:
 | 
			
		||||
            active = str2bool(active)
 | 
			
		||||
            queryset = queryset.filter(part__active=active)
 | 
			
		||||
 | 
			
		||||
        return queryset
 | 
			
		||||
 | 
			
		||||
    filter_backends = [
 | 
			
		||||
        DjangoFilterBackend,
 | 
			
		||||
        filters.SearchFilter,
 | 
			
		||||
        filters.OrderingFilter,
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    filter_fields = [
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    search_fields = [
 | 
			
		||||
        'manufacturer__name',
 | 
			
		||||
        'description',
 | 
			
		||||
        'MPN',
 | 
			
		||||
        'part__name',
 | 
			
		||||
        'part__description',
 | 
			
		||||
    ]
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartDetail(generics.RetrieveUpdateDestroyAPIView):
 | 
			
		||||
    """ API endpoint for detail view of ManufacturerPart object
 | 
			
		||||
 | 
			
		||||
    - GET: Retrieve detail view
 | 
			
		||||
    - PATCH: Update object
 | 
			
		||||
    - DELETE: Delete object
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    queryset = ManufacturerPart.objects.all()
 | 
			
		||||
    serializer_class = ManufacturerPartSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SupplierPartList(generics.ListCreateAPIView):
 | 
			
		||||
    """ API endpoint for list view of SupplierPart object
 | 
			
		||||
 | 
			
		||||
@@ -92,7 +191,7 @@ class SupplierPartList(generics.ListCreateAPIView):
 | 
			
		||||
    queryset = SupplierPart.objects.all().prefetch_related(
 | 
			
		||||
        'part',
 | 
			
		||||
        'supplier',
 | 
			
		||||
        'manufacturer'
 | 
			
		||||
        'manufacturer_part__manufacturer',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
@@ -114,7 +213,7 @@ class SupplierPartList(generics.ListCreateAPIView):
 | 
			
		||||
        manufacturer = params.get('manufacturer', None)
 | 
			
		||||
 | 
			
		||||
        if manufacturer is not None:
 | 
			
		||||
            queryset = queryset.filter(manufacturer=manufacturer)
 | 
			
		||||
            queryset = queryset.filter(manufacturer_part__manufacturer=manufacturer)
 | 
			
		||||
 | 
			
		||||
        # Filter by supplier
 | 
			
		||||
        supplier = params.get('supplier', None)
 | 
			
		||||
@@ -126,7 +225,7 @@ class SupplierPartList(generics.ListCreateAPIView):
 | 
			
		||||
        company = params.get('company', None)
 | 
			
		||||
 | 
			
		||||
        if company is not None:
 | 
			
		||||
            queryset = queryset.filter(Q(manufacturer=company) | Q(supplier=company))
 | 
			
		||||
            queryset = queryset.filter(Q(manufacturer_part__manufacturer=company) | Q(supplier=company))
 | 
			
		||||
 | 
			
		||||
        # Filter by parent part?
 | 
			
		||||
        part = params.get('part', None)
 | 
			
		||||
@@ -134,6 +233,12 @@ class SupplierPartList(generics.ListCreateAPIView):
 | 
			
		||||
        if part is not None:
 | 
			
		||||
            queryset = queryset.filter(part=part)
 | 
			
		||||
 | 
			
		||||
        # Filter by manufacturer part?
 | 
			
		||||
        manufacturer_part = params.get('manufacturer_part', None)
 | 
			
		||||
 | 
			
		||||
        if manufacturer_part is not None:
 | 
			
		||||
            queryset = queryset.filter(manufacturer_part=manufacturer_part)
 | 
			
		||||
 | 
			
		||||
        # Filter by 'active' status of the part?
 | 
			
		||||
        active = params.get('active', None)
 | 
			
		||||
 | 
			
		||||
@@ -184,9 +289,9 @@ class SupplierPartList(generics.ListCreateAPIView):
 | 
			
		||||
    search_fields = [
 | 
			
		||||
        'SKU',
 | 
			
		||||
        'supplier__name',
 | 
			
		||||
        'manufacturer__name',
 | 
			
		||||
        'manufacturer_part__manufacturer__name',
 | 
			
		||||
        'description',
 | 
			
		||||
        'MPN',
 | 
			
		||||
        'manufacturer_part__MPN',
 | 
			
		||||
        'part__name',
 | 
			
		||||
        'part__description',
 | 
			
		||||
    ]
 | 
			
		||||
@@ -197,7 +302,7 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
 | 
			
		||||
 | 
			
		||||
    - GET: Retrieve detail view
 | 
			
		||||
    - PATCH: Update object
 | 
			
		||||
    - DELETE: Delete objec
 | 
			
		||||
    - DELETE: Delete object
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    queryset = SupplierPart.objects.all()
 | 
			
		||||
@@ -226,6 +331,15 @@ class SupplierPriceBreakList(generics.ListCreateAPIView):
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
manufacturer_part_api_urls = [
 | 
			
		||||
 | 
			
		||||
    url(r'^(?P<pk>\d+)/?', ManufacturerPartDetail.as_view(), name='api-manufacturer-part-detail'),
 | 
			
		||||
 | 
			
		||||
    # Catch anything else
 | 
			
		||||
    url(r'^.*$', ManufacturerPartList.as_view(), name='api-manufacturer-part-list'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
supplier_part_api_urls = [
 | 
			
		||||
 | 
			
		||||
    url(r'^(?P<pk>\d+)/?', SupplierPartDetail.as_view(), name='api-supplier-part-detail'),
 | 
			
		||||
@@ -236,7 +350,8 @@ supplier_part_api_urls = [
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
company_api_urls = [
 | 
			
		||||
    
 | 
			
		||||
    url(r'^part/manufacturer/', include(manufacturer_part_api_urls)),
 | 
			
		||||
 | 
			
		||||
    url(r'^part/', include(supplier_part_api_urls)),
 | 
			
		||||
 | 
			
		||||
    url(r'^price-break/', SupplierPriceBreakList.as_view(), name='api-part-supplier-price'),
 | 
			
		||||
 
 | 
			
		||||
@@ -31,3 +31,17 @@
 | 
			
		||||
    name: Another customer!
 | 
			
		||||
    description: Yet another company
 | 
			
		||||
    is_customer: True
 | 
			
		||||
 | 
			
		||||
- model: company.company
 | 
			
		||||
  pk: 6
 | 
			
		||||
  fields:
 | 
			
		||||
    name: A manufacturer
 | 
			
		||||
    description: A company that makes parts!
 | 
			
		||||
    is_manufacturer: True
 | 
			
		||||
 | 
			
		||||
- model: company.company
 | 
			
		||||
  pk: 7
 | 
			
		||||
  fields:
 | 
			
		||||
    name: Another manufacturer
 | 
			
		||||
    description: They build things and sell it to us
 | 
			
		||||
    is_manufacturer: True
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								InvenTree/company/fixtures/manufacturer_part.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								InvenTree/company/fixtures/manufacturer_part.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
# Manufacturer Parts
 | 
			
		||||
 | 
			
		||||
- model: company.manufacturerpart
 | 
			
		||||
  pk: 1
 | 
			
		||||
  fields:
 | 
			
		||||
    part: 5
 | 
			
		||||
    manufacturer: 6
 | 
			
		||||
    MPN: 'MPN123'
 | 
			
		||||
 | 
			
		||||
- model: company.manufacturerpart
 | 
			
		||||
  pk: 2
 | 
			
		||||
  fields:
 | 
			
		||||
    part: 3
 | 
			
		||||
    manufacturer: 7
 | 
			
		||||
    MPN: 'MPN456'
 | 
			
		||||
 | 
			
		||||
- model: company.manufacturerpart
 | 
			
		||||
  pk: 3
 | 
			
		||||
  fields:
 | 
			
		||||
    part: 5
 | 
			
		||||
    manufacturer: 7
 | 
			
		||||
    MPN: 'MPN789'
 | 
			
		||||
 | 
			
		||||
# Supplier parts linked to Manufacturer parts
 | 
			
		||||
- model: company.supplierpart
 | 
			
		||||
  pk: 10
 | 
			
		||||
  fields:
 | 
			
		||||
    part: 3
 | 
			
		||||
    manufacturer_part: 2
 | 
			
		||||
    supplier: 2
 | 
			
		||||
    SKU: 'MPN456-APPEL'
 | 
			
		||||
 | 
			
		||||
- model: company.supplierpart
 | 
			
		||||
  pk: 11
 | 
			
		||||
  fields:
 | 
			
		||||
    part: 3
 | 
			
		||||
    manufacturer_part: 2
 | 
			
		||||
    supplier: 3
 | 
			
		||||
    SKU: 'MPN456-ZERG'
 | 
			
		||||
@@ -17,6 +17,7 @@ from djmoney.forms.fields import MoneyField
 | 
			
		||||
import common.settings
 | 
			
		||||
 | 
			
		||||
from .models import Company
 | 
			
		||||
from .models import ManufacturerPart
 | 
			
		||||
from .models import SupplierPart
 | 
			
		||||
from .models import SupplierPriceBreak
 | 
			
		||||
 | 
			
		||||
@@ -85,12 +86,30 @@ class CompanyImageDownloadForm(HelperForm):
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditManufacturerPartForm(HelperForm):
 | 
			
		||||
    """ Form for editing a ManufacturerPart object """
 | 
			
		||||
 | 
			
		||||
    field_prefix = {
 | 
			
		||||
        'link': 'fa-link',
 | 
			
		||||
        'MPN': 'fa-hashtag',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = ManufacturerPart
 | 
			
		||||
        fields = [
 | 
			
		||||
            'part',
 | 
			
		||||
            'manufacturer',
 | 
			
		||||
            'MPN',
 | 
			
		||||
            'description',
 | 
			
		||||
            'link',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditSupplierPartForm(HelperForm):
 | 
			
		||||
    """ Form for editing a SupplierPart object """
 | 
			
		||||
 | 
			
		||||
    field_prefix = {
 | 
			
		||||
        'link': 'fa-link',
 | 
			
		||||
        'MPN': 'fa-hashtag',
 | 
			
		||||
        'SKU': 'fa-hashtag',
 | 
			
		||||
        'note': 'fa-pencil-alt',
 | 
			
		||||
    }
 | 
			
		||||
@@ -104,15 +123,28 @@ class EditSupplierPartForm(HelperForm):
 | 
			
		||||
        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',
 | 
			
		||||
            'description',
 | 
			
		||||
            'manufacturer',
 | 
			
		||||
            'MPN',
 | 
			
		||||
            'description',
 | 
			
		||||
            'link',
 | 
			
		||||
            'note',
 | 
			
		||||
            'single_pricing',
 | 
			
		||||
@@ -121,6 +153,19 @@ class EditSupplierPartForm(HelperForm):
 | 
			
		||||
            '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 """
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								InvenTree/company/migrations/0034_manufacturerpart.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								InvenTree/company/migrations/0034_manufacturerpart.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import InvenTree.fields
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('company', '0033_auto_20210410_1528'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='ManufacturerPart',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('MPN', models.CharField(help_text='Manufacturer Part Number', max_length=100, null=True, verbose_name='MPN')),
 | 
			
		||||
                ('link', InvenTree.fields.InvenTreeURLField(blank=True, help_text='URL for external manufacturer part link', null=True, verbose_name='Link')),
 | 
			
		||||
                ('description', models.CharField(blank=True, help_text='Manufacturer part description', max_length=250, null=True, verbose_name='Description')),
 | 
			
		||||
                ('manufacturer', models.ForeignKey(help_text='Select manufacturer', limit_choices_to={'is_manufacturer': True}, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='manufactured_parts', to='company.Company', verbose_name='Manufacturer')),
 | 
			
		||||
                ('part', models.ForeignKey(help_text='Select part', limit_choices_to={'purchaseable': True}, on_delete=django.db.models.deletion.CASCADE, related_name='manufacturer_parts', to='part.Part', verbose_name='Base Part')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'unique_together': {('part', 'manufacturer', 'MPN')},
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										18
									
								
								InvenTree/company/migrations/0035_supplierpart_update_1.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								InvenTree/company/migrations/0035_supplierpart_update_1.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import InvenTree.fields
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('company', '0034_manufacturerpart'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='supplierpart',
 | 
			
		||||
            name='manufacturer_part',
 | 
			
		||||
            field=models.ForeignKey(blank=True, help_text='Select manufacturer part', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supplier_parts', to='company.ManufacturerPart', verbose_name='Manufacturer Part'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										110
									
								
								InvenTree/company/migrations/0036_supplierpart_update_2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								InvenTree/company/migrations/0036_supplierpart_update_2.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
import InvenTree.fields
 | 
			
		||||
from django.db import migrations, models, transaction
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
from django.db.utils import IntegrityError
 | 
			
		||||
 | 
			
		||||
def supplierpart_make_manufacturer_parts(apps, schema_editor):
 | 
			
		||||
    Part = apps.get_model('part', 'Part')
 | 
			
		||||
    ManufacturerPart = apps.get_model('company', 'ManufacturerPart')
 | 
			
		||||
    SupplierPart = apps.get_model('company', 'SupplierPart')
 | 
			
		||||
 | 
			
		||||
    supplier_parts = SupplierPart.objects.all()
 | 
			
		||||
    
 | 
			
		||||
    if supplier_parts:
 | 
			
		||||
        print(f'\nCreating ManufacturerPart Objects\n{"-"*10}')
 | 
			
		||||
        for supplier_part in supplier_parts:
 | 
			
		||||
            print(f'{supplier_part.supplier.name[:15].ljust(15)} | {supplier_part.SKU[:15].ljust(15)}\t', end='')
 | 
			
		||||
 | 
			
		||||
            if supplier_part.manufacturer_part:
 | 
			
		||||
                print(f'[ERROR: MANUFACTURER PART ALREADY EXISTS]')
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            part = supplier_part.part
 | 
			
		||||
            if not part:
 | 
			
		||||
                print(f'[ERROR: SUPPLIER PART IS NOT CONNECTED TO PART]')
 | 
			
		||||
                continue
 | 
			
		||||
            
 | 
			
		||||
            manufacturer = supplier_part.manufacturer
 | 
			
		||||
            MPN = supplier_part.MPN
 | 
			
		||||
            link = supplier_part.link
 | 
			
		||||
            description = supplier_part.description
 | 
			
		||||
 | 
			
		||||
            if manufacturer or MPN:
 | 
			
		||||
                print(f' | {part.name[:15].ljust(15)}', end='')
 | 
			
		||||
                
 | 
			
		||||
                try:
 | 
			
		||||
                    print(f' | {manufacturer.name[:15].ljust(15)}', end='')
 | 
			
		||||
                except AttributeError:
 | 
			
		||||
                    print(f' | {"EMPTY MANUF".ljust(15)}', end='')
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    print(f' | {MPN[:15].ljust(15)}', end='')
 | 
			
		||||
                except TypeError:
 | 
			
		||||
                    print(f' | {"EMPTY MPN".ljust(15)}', end='')
 | 
			
		||||
 | 
			
		||||
                print('\t', end='')
 | 
			
		||||
 | 
			
		||||
                # Create ManufacturerPart
 | 
			
		||||
                manufacturer_part = ManufacturerPart(part=part, manufacturer=manufacturer, MPN=MPN, description=description, link=link)
 | 
			
		||||
                created = False
 | 
			
		||||
                try:
 | 
			
		||||
                    with transaction.atomic():
 | 
			
		||||
                        manufacturer_part.save()
 | 
			
		||||
                    created = True
 | 
			
		||||
                except IntegrityError:
 | 
			
		||||
                    manufacturer_part = ManufacturerPart.objects.get(part=part, manufacturer=manufacturer, MPN=MPN)
 | 
			
		||||
 | 
			
		||||
                # Link it to SupplierPart
 | 
			
		||||
                supplier_part.manufacturer_part = manufacturer_part
 | 
			
		||||
                supplier_part.save()
 | 
			
		||||
 | 
			
		||||
                if created:
 | 
			
		||||
                    print(f'[SUCCESS: MANUFACTURER PART CREATED]')
 | 
			
		||||
                else:
 | 
			
		||||
                    print(f'[IGNORED: MANUFACTURER PART ALREADY EXISTS]')
 | 
			
		||||
            else:
 | 
			
		||||
                print(f'[IGNORED: MISSING MANUFACTURER DATA]')
 | 
			
		||||
 | 
			
		||||
        print(f'{"-"*10}\nDone\n')
 | 
			
		||||
 | 
			
		||||
def supplierpart_populate_manufacturer_info(apps, schema_editor):
 | 
			
		||||
    Part = apps.get_model('part', 'Part')
 | 
			
		||||
    ManufacturerPart = apps.get_model('company', 'ManufacturerPart')
 | 
			
		||||
    SupplierPart = apps.get_model('company', 'SupplierPart')
 | 
			
		||||
 | 
			
		||||
    supplier_parts = SupplierPart.objects.all()
 | 
			
		||||
    
 | 
			
		||||
    if supplier_parts:
 | 
			
		||||
        print(f'\nSupplierPart: Populating Manufacturer Information\n{"-"*10}')
 | 
			
		||||
        for supplier_part in supplier_parts:
 | 
			
		||||
            print(f'{supplier_part.supplier.name[:15].ljust(15)} | {supplier_part.SKU[:15].ljust(15)}\t', end='')
 | 
			
		||||
            
 | 
			
		||||
            manufacturer_part = supplier_part.manufacturer_part
 | 
			
		||||
 | 
			
		||||
            if manufacturer_part:
 | 
			
		||||
                if manufacturer_part.manufacturer:
 | 
			
		||||
                    supplier_part.manufacturer = manufacturer_part.manufacturer
 | 
			
		||||
 | 
			
		||||
                if manufacturer_part.MPN:
 | 
			
		||||
                    supplier_part.MPN = manufacturer_part.MPN
 | 
			
		||||
 | 
			
		||||
                supplier_part.save()
 | 
			
		||||
                
 | 
			
		||||
                print(f'[SUCCESS: UPDATED MANUFACTURER INFO]')
 | 
			
		||||
            else:
 | 
			
		||||
                print(f'[IGNORED: NO MANUFACTURER PART]')
 | 
			
		||||
 | 
			
		||||
        print(f'{"-"*10}\nDone\n')
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('company', '0035_supplierpart_update_1'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        # Make new ManufacturerPart with SupplierPart "manufacturer" and "MPN"
 | 
			
		||||
        # fields, then link it to the new SupplierPart "manufacturer_part" field
 | 
			
		||||
        migrations.RunPython(supplierpart_make_manufacturer_parts, reverse_code=supplierpart_populate_manufacturer_info),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										21
									
								
								InvenTree/company/migrations/0037_supplierpart_update_3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								InvenTree/company/migrations/0037_supplierpart_update_3.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import InvenTree.fields
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('company', '0036_supplierpart_update_2'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='supplierpart',
 | 
			
		||||
            name='MPN',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='supplierpart',
 | 
			
		||||
            name='manufacturer',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -11,7 +11,9 @@ import math
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
@@ -208,7 +210,7 @@ class Company(models.Model):
 | 
			
		||||
    @property
 | 
			
		||||
    def parts(self):
 | 
			
		||||
        """ Return SupplierPart objects which are supplied or manufactured by this company """
 | 
			
		||||
        return SupplierPart.objects.filter(Q(supplier=self.id) | Q(manufacturer=self.id))
 | 
			
		||||
        return SupplierPart.objects.filter(Q(supplier=self.id) | Q(manufacturer_part__manufacturer=self.id))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def part_count(self):
 | 
			
		||||
@@ -223,7 +225,7 @@ class Company(models.Model):
 | 
			
		||||
    def stock_items(self):
 | 
			
		||||
        """ Return a list of all stock items supplied or manufactured by this company """
 | 
			
		||||
        stock = apps.get_model('stock', 'StockItem')
 | 
			
		||||
        return stock.objects.filter(Q(supplier_part__supplier=self.id) | Q(supplier_part__manufacturer=self.id)).all()
 | 
			
		||||
        return stock.objects.filter(Q(supplier_part__supplier=self.id) | Q(supplier_part__manufacturer_part__manufacturer=self.id)).all()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def stock_count(self):
 | 
			
		||||
@@ -284,19 +286,106 @@ class Contact(models.Model):
 | 
			
		||||
                                on_delete=models.CASCADE)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SupplierPart(models.Model):
 | 
			
		||||
    """ Represents a unique part as provided by a Supplier
 | 
			
		||||
    Each SupplierPart is identified by a MPN (Manufacturer Part Number)
 | 
			
		||||
    Each SupplierPart is also linked to a Part object.
 | 
			
		||||
    A Part may be available from multiple suppliers
 | 
			
		||||
class ManufacturerPart(models.Model):
 | 
			
		||||
    """ Represents a unique part as provided by a Manufacturer
 | 
			
		||||
    Each ManufacturerPart is identified by a MPN (Manufacturer Part Number)
 | 
			
		||||
    Each ManufacturerPart is also linked to a Part object.
 | 
			
		||||
    A Part may be available from multiple manufacturers
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        part: Link to the master Part
 | 
			
		||||
        manufacturer: Company that manufactures the ManufacturerPart
 | 
			
		||||
        MPN: Manufacture part number
 | 
			
		||||
        link: Link to external website for this manufacturer part
 | 
			
		||||
        description: Descriptive notes field
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        unique_together = ('part', 'manufacturer', 'MPN')
 | 
			
		||||
    
 | 
			
		||||
    part = models.ForeignKey('part.Part', on_delete=models.CASCADE,
 | 
			
		||||
                             related_name='manufacturer_parts',
 | 
			
		||||
                             verbose_name=_('Base Part'),
 | 
			
		||||
                             limit_choices_to={
 | 
			
		||||
                                 'purchaseable': True,
 | 
			
		||||
                             },
 | 
			
		||||
                             help_text=_('Select part'),
 | 
			
		||||
                             )
 | 
			
		||||
    
 | 
			
		||||
    manufacturer = models.ForeignKey(
 | 
			
		||||
        Company,
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
        null=True,
 | 
			
		||||
        related_name='manufactured_parts',
 | 
			
		||||
        limit_choices_to={
 | 
			
		||||
            'is_manufacturer': True
 | 
			
		||||
        },
 | 
			
		||||
        verbose_name=_('Manufacturer'),
 | 
			
		||||
        help_text=_('Select manufacturer'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    MPN = models.CharField(
 | 
			
		||||
        null=True,
 | 
			
		||||
        max_length=100,
 | 
			
		||||
        verbose_name=_('MPN'),
 | 
			
		||||
        help_text=_('Manufacturer Part Number')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    link = InvenTreeURLField(
 | 
			
		||||
        blank=True, null=True,
 | 
			
		||||
        verbose_name=_('Link'),
 | 
			
		||||
        help_text=_('URL for external manufacturer part link')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    description = models.CharField(
 | 
			
		||||
        max_length=250, blank=True, null=True,
 | 
			
		||||
        verbose_name=_('Description'),
 | 
			
		||||
        help_text=_('Manufacturer part description')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create(cls, part, manufacturer, mpn, description, link=None):
 | 
			
		||||
        """ Check if ManufacturerPart instance does not already exist
 | 
			
		||||
            then create it
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        manufacturer_part = None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            manufacturer_part = ManufacturerPart.objects.get(part=part, manufacturer=manufacturer, MPN=mpn)
 | 
			
		||||
        except ManufacturerPart.DoesNotExist:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if not manufacturer_part:
 | 
			
		||||
            manufacturer_part = ManufacturerPart(part=part, manufacturer=manufacturer, MPN=mpn, description=description, link=link)
 | 
			
		||||
            manufacturer_part.save()
 | 
			
		||||
            
 | 
			
		||||
        return manufacturer_part
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        s = ''
 | 
			
		||||
 | 
			
		||||
        if self.manufacturer:
 | 
			
		||||
            s += f'{self.manufacturer.name}'
 | 
			
		||||
            s += ' | '
 | 
			
		||||
 | 
			
		||||
        s += f'{self.MPN}'
 | 
			
		||||
 | 
			
		||||
        return s
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SupplierPart(models.Model):
 | 
			
		||||
    """ Represents a unique part as provided by a Supplier
 | 
			
		||||
    Each SupplierPart is identified by a SKU (Supplier Part Number)
 | 
			
		||||
    Each SupplierPart is also linked to a Part or ManufacturerPart object.
 | 
			
		||||
    A Part may be available from multiple suppliers
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        part: Link to the master Part (Obsolete)
 | 
			
		||||
        source_item: The sourcing item linked to this SupplierPart instance
 | 
			
		||||
        supplier: Company that supplies this SupplierPart object
 | 
			
		||||
        SKU: Stock keeping unit (supplier part number)
 | 
			
		||||
        manufacturer: Company that manufactures the SupplierPart (leave blank if it is the sample as the Supplier!)
 | 
			
		||||
        MPN: Manufacture part number
 | 
			
		||||
        link: Link to external website for this part
 | 
			
		||||
        link: Link to external website for this supplier part
 | 
			
		||||
        description: Descriptive notes field
 | 
			
		||||
        note: Longer form note field
 | 
			
		||||
        base_cost: Base charge added to order independent of quantity e.g. "Reeling Fee"
 | 
			
		||||
@@ -308,6 +397,57 @@ 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')
 | 
			
		||||
 | 
			
		||||
@@ -336,23 +476,12 @@ class SupplierPart(models.Model):
 | 
			
		||||
        help_text=_('Supplier stock keeping unit')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    manufacturer = models.ForeignKey(
 | 
			
		||||
        Company,
 | 
			
		||||
        on_delete=models.SET_NULL,
 | 
			
		||||
        related_name='manufactured_parts',
 | 
			
		||||
        limit_choices_to={
 | 
			
		||||
            'is_manufacturer': True
 | 
			
		||||
        },
 | 
			
		||||
        verbose_name=_('Manufacturer'),
 | 
			
		||||
        help_text=_('Select manufacturer'),
 | 
			
		||||
        null=True, blank=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    MPN = models.CharField(
 | 
			
		||||
        max_length=100, blank=True, null=True,
 | 
			
		||||
        verbose_name=_('MPN'),
 | 
			
		||||
        help_text=_('Manufacturer part number')
 | 
			
		||||
    )
 | 
			
		||||
    manufacturer_part = models.ForeignKey(ManufacturerPart, on_delete=models.CASCADE,
 | 
			
		||||
                                          blank=True, null=True,
 | 
			
		||||
                                          related_name='supplier_parts',
 | 
			
		||||
                                          verbose_name=_('Manufacturer Part'),
 | 
			
		||||
                                          help_text=_('Select manufacturer part'),
 | 
			
		||||
                                          )
 | 
			
		||||
 | 
			
		||||
    link = InvenTreeURLField(
 | 
			
		||||
        blank=True, null=True,
 | 
			
		||||
@@ -389,10 +518,11 @@ class SupplierPart(models.Model):
 | 
			
		||||
 | 
			
		||||
        items = []
 | 
			
		||||
 | 
			
		||||
        if self.manufacturer:
 | 
			
		||||
            items.append(self.manufacturer.name)
 | 
			
		||||
        if self.MPN:
 | 
			
		||||
            items.append(self.MPN)
 | 
			
		||||
        if self.manufacturer_part:
 | 
			
		||||
            if self.manufacturer_part.manufacturer:
 | 
			
		||||
                items.append(self.manufacturer_part.manufacturer.name)
 | 
			
		||||
            if self.manufacturer_part.MPN:
 | 
			
		||||
                items.append(self.manufacturer_part.MPN)
 | 
			
		||||
 | 
			
		||||
        return ' | '.join(items)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ from rest_framework import serializers
 | 
			
		||||
from sql_util.utils import SubqueryCount
 | 
			
		||||
 | 
			
		||||
from .models import Company
 | 
			
		||||
from .models import ManufacturerPart
 | 
			
		||||
from .models import SupplierPart, SupplierPriceBreak
 | 
			
		||||
 | 
			
		||||
from InvenTree.serializers import InvenTreeModelSerializer
 | 
			
		||||
@@ -80,6 +81,49 @@ class CompanySerializer(InvenTreeModelSerializer):
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartSerializer(InvenTreeModelSerializer):
 | 
			
		||||
    """ Serializer for ManufacturerPart object """
 | 
			
		||||
 | 
			
		||||
    part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
 | 
			
		||||
 | 
			
		||||
    manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True)
 | 
			
		||||
 | 
			
		||||
    pretty_name = serializers.CharField(read_only=True)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
        part_detail = kwargs.pop('part_detail', False)
 | 
			
		||||
        manufacturer_detail = kwargs.pop('manufacturer_detail', False)
 | 
			
		||||
        prettify = kwargs.pop('pretty', False)
 | 
			
		||||
 | 
			
		||||
        super(ManufacturerPartSerializer, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        if part_detail is not True:
 | 
			
		||||
            self.fields.pop('part_detail')
 | 
			
		||||
 | 
			
		||||
        if manufacturer_detail is not True:
 | 
			
		||||
            self.fields.pop('manufacturer_detail')
 | 
			
		||||
 | 
			
		||||
        if prettify is not True:
 | 
			
		||||
            self.fields.pop('pretty_name')
 | 
			
		||||
 | 
			
		||||
    manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True))
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = ManufacturerPart
 | 
			
		||||
        fields = [
 | 
			
		||||
            'pk',
 | 
			
		||||
            'part',
 | 
			
		||||
            'part_detail',
 | 
			
		||||
            'pretty_name',
 | 
			
		||||
            'manufacturer',
 | 
			
		||||
            'manufacturer_detail',
 | 
			
		||||
            'description',
 | 
			
		||||
            'MPN',
 | 
			
		||||
            'link',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SupplierPartSerializer(InvenTreeModelSerializer):
 | 
			
		||||
    """ Serializer for SupplierPart object """
 | 
			
		||||
 | 
			
		||||
@@ -87,7 +131,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
 | 
			
		||||
 | 
			
		||||
    supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True)
 | 
			
		||||
 | 
			
		||||
    manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True)
 | 
			
		||||
    manufacturer_detail = CompanyBriefSerializer(source='manufacturer_part.manufacturer', many=False, read_only=True)
 | 
			
		||||
 | 
			
		||||
    pretty_name = serializers.CharField(read_only=True)
 | 
			
		||||
 | 
			
		||||
@@ -113,8 +157,12 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
 | 
			
		||||
            self.fields.pop('pretty_name')
 | 
			
		||||
 | 
			
		||||
    supplier = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_supplier=True))
 | 
			
		||||
    
 | 
			
		||||
    manufacturer = serializers.PrimaryKeyRelatedField(source='manufacturer_part.manufacturer', read_only=True)
 | 
			
		||||
    
 | 
			
		||||
    MPN = serializers.StringRelatedField(source='manufacturer_part.MPN')
 | 
			
		||||
 | 
			
		||||
    manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True))
 | 
			
		||||
    manufacturer_part = ManufacturerPartSerializer(read_only=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = SupplierPart
 | 
			
		||||
@@ -127,12 +175,31 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
 | 
			
		||||
            'supplier_detail',
 | 
			
		||||
            'SKU',
 | 
			
		||||
            'manufacturer',
 | 
			
		||||
            'manufacturer_detail',
 | 
			
		||||
            'description',
 | 
			
		||||
            'MPN',
 | 
			
		||||
            'manufacturer_detail',
 | 
			
		||||
            'manufacturer_part',
 | 
			
		||||
            'description',
 | 
			
		||||
            'link',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    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 or MPN:
 | 
			
		||||
            kwargs = {'manufacturer': manufacturer_id,
 | 
			
		||||
                      'MPN': MPN,
 | 
			
		||||
                      }
 | 
			
		||||
            supplier_part.save(**kwargs)
 | 
			
		||||
 | 
			
		||||
        return supplier_part
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
 | 
			
		||||
    """ Serializer for SupplierPriceBreak object """
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,13 @@
 | 
			
		||||
                <td>{% trans "Company Name" %}</td>
 | 
			
		||||
                <td>{{ company.name }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            {% if company.description %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td><span class='fas fa-info'></span></td>
 | 
			
		||||
                <td>{% trans "Description" %}</td>
 | 
			
		||||
                <td>{{ company.description }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td><span class='fas fa-globe'></span></td>
 | 
			
		||||
                <td>{% trans "Website" %}</td>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,127 @@
 | 
			
		||||
{% extends "company/company_base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include 'company/navbar.html' with tab='manufacturer_parts' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
{% trans "Manufacturer Parts" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block details %}
 | 
			
		||||
 | 
			
		||||
{% if roles.purchase_order.change %}
 | 
			
		||||
<div id='button-toolbar'>
 | 
			
		||||
    <div class='button-toolbar container-fluid'>
 | 
			
		||||
        <div class='btn-group role='group'>
 | 
			
		||||
            {% if roles.purchase_order.add %}
 | 
			
		||||
                <button class="btn btn-success" id='manufacturer-part-create' title='{% trans "Create new manufacturer part" %}'>
 | 
			
		||||
                    <span class='fas fa-plus-circle'></span> {% trans "New Manufacturer Part" %}
 | 
			
		||||
                </button>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                <div class="dropdown" style="float: right;">
 | 
			
		||||
                    <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}
 | 
			
		||||
                        <span class="caret"></span>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <ul class="dropdown-menu">
 | 
			
		||||
                        {% if roles.purchase_order.add %}
 | 
			
		||||
                        <li><a href='#' id='multi-part-order' title='{% trans "Order parts" %}'>{% trans "Order Parts" %}</a></li>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        {% if roles.purchase_order.delete %}
 | 
			
		||||
                        <li><a href='#' id='multi-part-delete' title='{% trans "Delete parts" %}'>{% trans "Delete Parts" %}</a></li>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>  
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class='filter-list' id='filter-list-supplier-part'>
 | 
			
		||||
            <!-- Empty div (will be filled out with available BOM filters) -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
<table class='table table-striped table-condensed' id='part-table' data-toolbar='#button-toolbar'>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
    $("#manufacturer-part-create").click(function () {
 | 
			
		||||
        launchModalForm(
 | 
			
		||||
            "{% url 'manufacturer-part-create' %}",
 | 
			
		||||
            {
 | 
			
		||||
                data: {
 | 
			
		||||
                    manufacturer: {{ company.id }},
 | 
			
		||||
                },
 | 
			
		||||
                reload: true,
 | 
			
		||||
                secondary: [
 | 
			
		||||
                    {
 | 
			
		||||
                        field: 'part',
 | 
			
		||||
                        label: '{% trans "New Part" %}',
 | 
			
		||||
                        title: '{% trans "Create new Part" %}',
 | 
			
		||||
                        url: "{% url 'part-create' %}"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        field: 'manufacturer',
 | 
			
		||||
                        label: '{% trans "New Manufacturer" %}',
 | 
			
		||||
                        title: '{% trans "Create new Manufacturer" %}',
 | 
			
		||||
                        url: "{% url 'manufacturer-create' %}",
 | 
			
		||||
                    },
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    loadManufacturerPartTable(
 | 
			
		||||
        "#part-table",
 | 
			
		||||
        "{% url 'api-manufacturer-part-list' %}",
 | 
			
		||||
        {
 | 
			
		||||
            params: {
 | 
			
		||||
                part_detail: true,
 | 
			
		||||
                manufacturer_detail: true,
 | 
			
		||||
                company: {{ company.id }},
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $("#multi-part-delete").click(function() {
 | 
			
		||||
        var selections = $("#part-table").bootstrapTable("getSelections");
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            parts.push(item.pk);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var url = "{% url 'manufacturer-part-delete' %}"
 | 
			
		||||
 | 
			
		||||
        launchModalForm(url, {
 | 
			
		||||
            data: {
 | 
			
		||||
                parts: parts,
 | 
			
		||||
            },
 | 
			
		||||
            reload: true,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#multi-part-order").click(function() {
 | 
			
		||||
        var selections = $("#part-table").bootstrapTable("getSelections");
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            parts.push(item.part);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        launchModalForm("/order/purchase-order/order-parts/", {
 | 
			
		||||
            data: {
 | 
			
		||||
                parts: parts,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
{% extends "company/company_base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include 'company/navbar.html' with tab='parts' %}
 | 
			
		||||
{% include 'company/navbar.html' with tab='supplier_parts' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
@@ -17,9 +18,9 @@
 | 
			
		||||
    <div class='button-toolbar container-fluid'>
 | 
			
		||||
        <div class='btn-group' role='group'>
 | 
			
		||||
            {% if roles.purchase_order.add %}
 | 
			
		||||
            <button class="btn btn-success" id='part-create' title='{% trans "Create new supplier part" %}'>
 | 
			
		||||
                <span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %}
 | 
			
		||||
            </button>
 | 
			
		||||
                <button class="btn btn-success" id='supplier-part-create' title='{% trans "Create new supplier part" %}'>
 | 
			
		||||
                    <span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %}
 | 
			
		||||
                </button>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            <div class='btn-group'>
 | 
			
		||||
                <div class="dropdown" style="float: right;">
 | 
			
		||||
@@ -51,13 +52,12 @@
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
    $("#part-create").click(function () {
 | 
			
		||||
    $("#supplier-part-create").click(function () {
 | 
			
		||||
        launchModalForm(
 | 
			
		||||
            "{% url 'supplier-part-create' %}",
 | 
			
		||||
            {
 | 
			
		||||
                data: {
 | 
			
		||||
                    {% if company.is_supplier %}supplier: {{ company.id }},{% endif %}
 | 
			
		||||
                    {% if company.is_manufacturer %}manufacturer: {{ company.id }},{% endif %}
 | 
			
		||||
                    supplier: {{ company.id }},
 | 
			
		||||
                },
 | 
			
		||||
                reload: true,
 | 
			
		||||
                secondary: [
 | 
			
		||||
@@ -73,12 +73,6 @@
 | 
			
		||||
                        title: "{% trans 'Create new Supplier' %}",
 | 
			
		||||
                        url: "{% url 'supplier-create' %}",
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        field: 'manufacturer',
 | 
			
		||||
                        label: '{% trans "New Manufacturer" %}',
 | 
			
		||||
                        title: '{% trans "Create new Manufacturer" %}',
 | 
			
		||||
                        url: "{% url 'manufacturer-create' %}",
 | 
			
		||||
                    },
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
@@ -105,7 +99,9 @@
 | 
			
		||||
            parts.push(item.pk);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        launchModalForm("{% url 'supplier-part-delete' %}", {
 | 
			
		||||
        var url = "{% url 'supplier-part-delete' %}"
 | 
			
		||||
 | 
			
		||||
        launchModalForm(url, {
 | 
			
		||||
            data: {
 | 
			
		||||
                parts: parts,
 | 
			
		||||
            },
 | 
			
		||||
							
								
								
									
										133
									
								
								InvenTree/company/templates/company/manufacturer_part_base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								InvenTree/company/templates/company/manufacturer_part_base.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
{% extends "two_column.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block page_title %}
 | 
			
		||||
InvenTree | {% trans "Manufacturer Part" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block thumbnail %}
 | 
			
		||||
<img class='part-thumb'
 | 
			
		||||
{% if part.part.image %}
 | 
			
		||||
src='{{ part.part.image.url }}'
 | 
			
		||||
{% else %}
 | 
			
		||||
src="{% static 'img/blank_image.png' %}"
 | 
			
		||||
{% endif %}/>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block page_data %}
 | 
			
		||||
<h3>{% trans "Manufacturer Part" %}</h3>
 | 
			
		||||
<hr>
 | 
			
		||||
<h4>
 | 
			
		||||
    {{ part.part.full_name }}
 | 
			
		||||
    {% if user.is_staff and perms.company.change_company %}
 | 
			
		||||
    <a href="{% url 'admin:company_supplierpart_change' part.pk %}">
 | 
			
		||||
        <span title='{% trans "Admin view" %}' class='fas fa-user-shield'></span>
 | 
			
		||||
    </a>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</h4>
 | 
			
		||||
<p>{{ part.manufacturer.name }} - {{ part.MPN }}</p>
 | 
			
		||||
 | 
			
		||||
{% if roles.purchase_order.change %}
 | 
			
		||||
<div class='btn-row'>
 | 
			
		||||
    <div class='btn-group action-buttons' role='group'>
 | 
			
		||||
        {% comment "for later" %}
 | 
			
		||||
        {% if roles.purchase_order.add %}
 | 
			
		||||
        <button type='button' class='btn btn-default btn-glyph' id='order-part' title='{% trans "Order part" %}'>
 | 
			
		||||
            <span class='fas fa-shopping-cart'></span>
 | 
			
		||||
        </button>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% endcomment %}
 | 
			
		||||
        <button type='button' class='btn btn-default btn-glyph' id='edit-part' title='{% trans "Edit manufacturer part" %}'>
 | 
			
		||||
            <span class='fas fa-edit icon-green'/>
 | 
			
		||||
        </button>
 | 
			
		||||
        {% if roles.purchase_order.delete %}
 | 
			
		||||
        <button type='button' class='btn btn-default btn-glyph' id='delete-part' title='{% trans "Delete manufacturer part" %}'>
 | 
			
		||||
            <span class='fas fa-trash-alt icon-red'/>
 | 
			
		||||
        </button>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block page_details %}
 | 
			
		||||
 | 
			
		||||
<h4>{% trans "Manufacturer Part Details" %}</h4>
 | 
			
		||||
<table class="table table-striped table-condensed">
 | 
			
		||||
    <col width='25'>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-shapes'></span></td>
 | 
			
		||||
            <td>{% trans "Internal Part" %}</td>
 | 
			
		||||
            <td>
 | 
			
		||||
                {% if part.part %}
 | 
			
		||||
                <a href="{% url 'part-manufacturers' part.part.id %}">{{ part.part.full_name }}</a>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% if part.description %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td></td>
 | 
			
		||||
            <td>{% trans "Description" %}</td>
 | 
			
		||||
            <td>{{ part.description }}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if part.link %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-link'></span></td>
 | 
			
		||||
            <td>{% trans "External Link" %}</td>
 | 
			
		||||
            <td><a href="{{ part.link }}">{{ part.link }}</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-industry'></span></td>
 | 
			
		||||
            <td>{% trans "Manufacturer" %}</td>
 | 
			
		||||
            <td><a href="{% url 'company-detail-manufacturer-parts' part.manufacturer.id %}">{{ part.manufacturer.name }}</a></td></tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-hashtag'></span></td>
 | 
			
		||||
            <td>{% trans "MPN" %}</td>
 | 
			
		||||
            <td>{{ part.MPN }}</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
</table>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
enableNavbar({
 | 
			
		||||
    label: 'manufacturer-part',
 | 
			
		||||
    toggleId: '#manufacturer-part-menu-toggle'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
$('#order-part, #order-part2').click(function() {
 | 
			
		||||
    launchModalForm(
 | 
			
		||||
        "{% url 'order-parts' %}",
 | 
			
		||||
        {
 | 
			
		||||
            data: {
 | 
			
		||||
                part: {{ part.part.id }},
 | 
			
		||||
            },
 | 
			
		||||
            reload: true,
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('#edit-part').click(function () {
 | 
			
		||||
    launchModalForm(
 | 
			
		||||
                    "{% url 'manufacturer-part-edit' part.id %}",
 | 
			
		||||
                    {
 | 
			
		||||
                        reload: true
 | 
			
		||||
                    }
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$('#delete-part').click(function() {
 | 
			
		||||
    launchModalForm(
 | 
			
		||||
        "{% url 'manufacturer-part-delete' %}?part={{ part.id }}",
 | 
			
		||||
        {
 | 
			
		||||
            redirect: "{% url 'company-detail-manufacturer-parts' part.manufacturer.id %}"
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
{% 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 %}
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
{% extends "modal_delete_form.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block pre_form_content %}
 | 
			
		||||
<div class='alert alert-block alert-warning'>
 | 
			
		||||
    {% trans "Are you sure you want to delete the following Manufacturer Parts?" %}
 | 
			
		||||
</div>
 | 
			
		||||
{% for part in parts %}
 | 
			
		||||
    
 | 
			
		||||
{% endfor %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block form_data %}
 | 
			
		||||
 | 
			
		||||
{% for part in parts %}
 | 
			
		||||
<table class='table table-striped table-condensed'>
 | 
			
		||||
<tr>
 | 
			
		||||
    <input type='hidden' name='manufacturer-part-{{ part.id}}' value='manufacturer-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.manufacturer.image %}
 | 
			
		||||
        {{ part.manufacturer.name }}
 | 
			
		||||
    </td>
 | 
			
		||||
    <td>
 | 
			
		||||
        {{ part.MPN }}
 | 
			
		||||
    </td>
 | 
			
		||||
</tr>
 | 
			
		||||
</table>
 | 
			
		||||
{% if part.supplier_parts.all|length > 0 %}
 | 
			
		||||
<div class='alert alert-block alert-danger'>
 | 
			
		||||
<p>There are {{ part.supplier_parts.all|length }} suppliers defined for this manufacturer part. If you delete it, the following supplier parts will also be deleted:
 | 
			
		||||
</p>
 | 
			
		||||
<ul class='list-group' style='margin-top:10px'>
 | 
			
		||||
    {% for spart in part.supplier_parts.all %}
 | 
			
		||||
    <li class='list-group-item'>{{ spart.supplier.name }} - {{ spart.SKU }}</li>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</ul>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
{% extends "company/manufacturer_part_base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include "company/manufacturer_part_navbar.html" with tab='details' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
{% trans "Manufacturer Part Details" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block details %}
 | 
			
		||||
 | 
			
		||||
<table class="table table-striped table-condensed">
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>{% trans "Internal Part" %}</td>
 | 
			
		||||
        <td>
 | 
			
		||||
            {% if part.part %}
 | 
			
		||||
            <a href="{% url 'part-manufacturers' part.part.id %}">{{ part.part.full_name }}</a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr><td>{% trans "Manufacturer" %}</td><td><a href="{% url 'company-detail-manufacturer-parts' part.manufacturer.id %}">{{ part.manufacturer.name }}</a></td></tr>
 | 
			
		||||
    <tr><td>{% trans "MPN" %}</td><td>{{ part.MPN }}</tr></tr>
 | 
			
		||||
{% if part.link %}
 | 
			
		||||
    <tr><td>{% trans "External Link" %}</td><td><a href="{{ part.link }}">{{ part.link }}</a></td></tr>
 | 
			
		||||
{% endif %}
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<ul class='list-group'>
 | 
			
		||||
 | 
			
		||||
    <li class='list-group-item'>
 | 
			
		||||
        <a href='#' id='manufacturer-part-menu-toggle'>
 | 
			
		||||
            <span class='menu-tab-icon fas fa-expand-arrows-alt'></span>
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
 | 
			
		||||
    <li class='list-group-item {% if tab == "suppliers" %}active{% endif %}' title='{% trans "Supplier Parts" %}'>
 | 
			
		||||
        <a href='{% url "manufacturer-part-suppliers" part.id %}'>
 | 
			
		||||
            <span class='fas fa-building'></span>
 | 
			
		||||
            {% trans "Suppliers" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
 | 
			
		||||
    {% comment "for later" %}
 | 
			
		||||
    <li class='list-group-item {% if tab == "stock" %}active{% endif %}' title='{% trans "Manufacturer Part Stock" %}'>
 | 
			
		||||
        <a href='{% url "manufacturer-part-stock" part.id %}'>
 | 
			
		||||
            <span class='fas fa-boxes'></span>
 | 
			
		||||
            {% trans "Stock" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
 | 
			
		||||
    <li class='list-group-item {% if tab == "orders" %}active{% endif %}' title='{% trans "Manufacturer Part Orders" %}'>
 | 
			
		||||
        <a href='{% url "manufacturer-part-orders" part.id %}'>
 | 
			
		||||
            <span class='fas fa-shopping-cart'></span>
 | 
			
		||||
            {% trans "Orders" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
    {% endcomment %}
 | 
			
		||||
 | 
			
		||||
</ul>
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
{% extends "company/manufacturer_part_base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include "company/manufacturer_part_navbar.html" with tab='suppliers' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
{% trans "Supplier Parts" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block details %}
 | 
			
		||||
<div id='button-toolbar'>
 | 
			
		||||
    <div class='btn-group'>
 | 
			
		||||
        <button class="btn btn-success" id='supplier-create'>
 | 
			
		||||
            <span class='fas fa-plus-circle'></span> {% trans "New Supplier Part" %}
 | 
			
		||||
        </button>
 | 
			
		||||
        <div id='opt-dropdown' class="btn-group">
 | 
			
		||||
            <button id='supplier-part-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
 | 
			
		||||
            <ul class="dropdown-menu">
 | 
			
		||||
                <li><a href='#' id='supplier-part-delete' title='{% trans "Delete supplier parts" %}'>{% trans "Delete" %}</a></li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<table class="table table-striped table-condensed" id='supplier-table' data-toolbar='#button-toolbar'>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
 | 
			
		||||
$('#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" %}',
 | 
			
		||||
                    url: "{% url 'supplier-create' %}"
 | 
			
		||||
                },
 | 
			
		||||
            ]
 | 
			
		||||
        });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$("#supplier-part-delete").click(function() {
 | 
			
		||||
    
 | 
			
		||||
    var selections = $("#supplier-table").bootstrapTable("getSelections");
 | 
			
		||||
 | 
			
		||||
    var parts = [];
 | 
			
		||||
 | 
			
		||||
    selections.forEach(function(item) {
 | 
			
		||||
        parts.push(item.pk);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    launchModalForm("{% url 'supplier-part-delete' %}", {
 | 
			
		||||
        data: {
 | 
			
		||||
            parts: parts,
 | 
			
		||||
        },
 | 
			
		||||
        reload: true,
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
loadSupplierPartTable(
 | 
			
		||||
    "#supplier-table",
 | 
			
		||||
    "{% url 'api-supplier-part-list' %}",
 | 
			
		||||
    {
 | 
			
		||||
        params: {
 | 
			
		||||
            part: {{ part.part.id }},
 | 
			
		||||
            manufacturer_part: {{ part.id }},
 | 
			
		||||
            part_detail: false,
 | 
			
		||||
            supplier_detail: true,
 | 
			
		||||
            manufacturer_detail: false,
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options'])
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -16,14 +16,25 @@
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
 | 
			
		||||
    {% if company.is_supplier or company.is_manufacturer %}
 | 
			
		||||
    <li class='list-group-item {% if tab == "parts" %}active{% endif %}' title='{% trans "Supplied Parts" %}'>
 | 
			
		||||
        <a href='{% url "company-detail-parts" company.id %}'>
 | 
			
		||||
            <span class='fas fa-shapes'></span>
 | 
			
		||||
            {% trans "Parts" %}
 | 
			
		||||
    {% if company.is_manufacturer %}
 | 
			
		||||
    <li class='list-group-item {% if tab == "manufacturer_parts" %}active{% endif %}' title='{% trans "Manufactured Parts" %}'>
 | 
			
		||||
        <a href='{% url "company-detail-manufacturer-parts" company.id %}'>
 | 
			
		||||
            <span class='fas fa-industry'></span>
 | 
			
		||||
            {% trans "Manufactured Parts" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    
 | 
			
		||||
    {% if company.is_supplier or company.is_manufacturer %}
 | 
			
		||||
    <li class='list-group-item {% if tab == "supplier_parts" %}active{% endif %}' title='{% trans "Supplied Parts" %}'>
 | 
			
		||||
        <a href='{% url "company-detail-supplier-parts" company.id %}'>
 | 
			
		||||
            <span class='fas fa-building'></span>
 | 
			
		||||
            {% trans "Supplied Parts" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if company.is_manufacturer or company.is_supplier %}
 | 
			
		||||
    <li class='list-group-item {% if tab == "stock" %}active{% endif %}' title='{% trans "Stock Items" %}'>
 | 
			
		||||
        <a href='{% url "company-detail-stock" company.id %}'>
 | 
			
		||||
            <span class='fas fa-boxes'></span>
 | 
			
		||||
 
 | 
			
		||||
@@ -81,23 +81,24 @@ src="{% static 'img/blank_image.png' %}"
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-building'></span></td>
 | 
			
		||||
            <td>{% trans "Supplier" %}</td>
 | 
			
		||||
            <td><a href="{% url 'company-detail-parts' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
 | 
			
		||||
            <td><a href="{% url 'company-detail-supplier-parts' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-hashtag'></span></td>
 | 
			
		||||
            <td>{% trans "SKU" %}</td>
 | 
			
		||||
            <td>{{ part.SKU }}</tr>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% if part.manufacturer %}
 | 
			
		||||
        {% if part.manufacturer_part.manufacturer %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-industry'></span></td>
 | 
			
		||||
            <td>{% trans "Manufacturer" %}</td>
 | 
			
		||||
            <td><a href="{% url 'company-detail-parts' part.manufacturer.id %}">{{ part.manufacturer.name }}</a></td></tr>
 | 
			
		||||
            <td><a href="{% url 'company-detail-manufacturer-parts' part.manufacturer_part.manufacturer.id %}">{{ part.manufacturer_part.manufacturer.name }}</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if part.MPN %}
 | 
			
		||||
        {% if part.manufacturer_part.MPN %}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><span class='fas fa-hashtag'></span></td>
 | 
			
		||||
            <td>{% trans "MPN" %}</td>
 | 
			
		||||
            <td>{{ part.MPN }}</td>
 | 
			
		||||
            <td><a href="{% url 'manufacturer-part-detail' part.manufacturer_part.id %}">{{ part.manufacturer_part.MPN }}</a></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if part.packaging %}
 | 
			
		||||
@@ -150,7 +151,7 @@ $('#delete-part').click(function() {
 | 
			
		||||
    launchModalForm(
 | 
			
		||||
        "{% url 'supplier-part-delete' %}?part={{ part.id }}",
 | 
			
		||||
        {
 | 
			
		||||
            redirect: "{% url 'company-detail-parts' part.supplier.id %}"
 | 
			
		||||
            redirect: "{% url 'company-detail-supplier-parts' part.supplier.id %}"
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,16 @@
 | 
			
		||||
<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>
 | 
			
		||||
        {% include "hover_image.html" with image=part.part.image %}
 | 
			
		||||
        {{ part.part.full_name }}
 | 
			
		||||
        {{ part.SKU }}
 | 
			
		||||
    </td>
 | 
			
		||||
</tr>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include "company/part_navbar.html" with tab='details' %}
 | 
			
		||||
{% include "company/supplier_part_navbar.html" with tab='details' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr><td>{% trans "Supplier" %}</td><td><a href="{% url 'company-detail-parts' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
 | 
			
		||||
    <tr><td>{% trans "Supplier" %}</td><td><a href="{% url 'company-detail-supplier-parts' part.supplier.id %}">{{ part.supplier.name }}</a></td></tr>
 | 
			
		||||
    <tr><td>{% trans "SKU" %}</td><td>{{ part.SKU }}</tr></tr>
 | 
			
		||||
{% if part.link %}
 | 
			
		||||
    <tr><td>{% trans "External Link" %}</td><td><a href="{{ part.link }}">{{ part.link }}</a></td></tr>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
 | 
			
		||||
<ul class='list-group'>
 | 
			
		||||
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include "company/part_navbar.html" with tab='orders' %}
 | 
			
		||||
{% include "company/supplier_part_navbar.html" with tab='orders' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include "company/part_navbar.html" with tab='pricing' %}
 | 
			
		||||
{% include "company/supplier_part_navbar.html" with tab='pricing' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include "company/part_navbar.html" with tab='stock' %}
 | 
			
		||||
{% include "company/supplier_part_navbar.html" with tab='stock' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ class CompanyTest(InvenTreeAPITestCase):
 | 
			
		||||
    def test_company_list(self):
 | 
			
		||||
        url = reverse('api-company-list')
 | 
			
		||||
 | 
			
		||||
        # There should be two companies
 | 
			
		||||
        # There should be three companies
 | 
			
		||||
        response = self.get(url)
 | 
			
		||||
        self.assertEqual(len(response.data), 3)
 | 
			
		||||
 | 
			
		||||
@@ -62,3 +62,90 @@ class CompanyTest(InvenTreeAPITestCase):
 | 
			
		||||
        data = {'search': 'cup'}
 | 
			
		||||
        response = self.get(url, data)
 | 
			
		||||
        self.assertEqual(len(response.data), 2)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerTest(InvenTreeAPITestCase):
 | 
			
		||||
    """
 | 
			
		||||
    Series of tests for the Manufacturer DRF API
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    fixtures = [
 | 
			
		||||
        'category',
 | 
			
		||||
        'part',
 | 
			
		||||
        'location',
 | 
			
		||||
        'company',
 | 
			
		||||
        'manufacturer_part',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    roles = [
 | 
			
		||||
        'part.add',
 | 
			
		||||
        'part.change',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def test_manufacturer_part_list(self):
 | 
			
		||||
        url = reverse('api-manufacturer-part-list')
 | 
			
		||||
 | 
			
		||||
        # There should be three manufacturer parts
 | 
			
		||||
        response = self.get(url)
 | 
			
		||||
        self.assertEqual(len(response.data), 3)
 | 
			
		||||
 | 
			
		||||
        # Create manufacturer part
 | 
			
		||||
        data = {
 | 
			
		||||
            'part': 1,
 | 
			
		||||
            'manufacturer': 7,
 | 
			
		||||
            'MPN': 'MPN_TEST',
 | 
			
		||||
        }
 | 
			
		||||
        response = self.client.post(url, data, format='json')
 | 
			
		||||
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
 | 
			
		||||
        self.assertEqual(response.data['MPN'], 'MPN_TEST')
 | 
			
		||||
 | 
			
		||||
        # Filter by manufacturer
 | 
			
		||||
        data = {'company': 7}
 | 
			
		||||
        response = self.get(url, data)
 | 
			
		||||
        self.assertEqual(len(response.data), 3)
 | 
			
		||||
 | 
			
		||||
        # Filter by part
 | 
			
		||||
        data = {'part': 5}
 | 
			
		||||
        response = self.get(url, data)
 | 
			
		||||
        self.assertEqual(len(response.data), 2)
 | 
			
		||||
 | 
			
		||||
    def test_manufacturer_part_detail(self):
 | 
			
		||||
        url = reverse('api-manufacturer-part-detail', kwargs={'pk': 1})
 | 
			
		||||
 | 
			
		||||
        response = self.get(url)
 | 
			
		||||
        self.assertEqual(response.data['MPN'], 'MPN123')
 | 
			
		||||
 | 
			
		||||
        # Change the MPN
 | 
			
		||||
        data = {
 | 
			
		||||
            'MPN': 'MPN-TEST-123',
 | 
			
		||||
        }
 | 
			
		||||
        response = self.client.patch(url, data, format='json')
 | 
			
		||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
			
		||||
        self.assertEqual(response.data['MPN'], 'MPN-TEST-123')
 | 
			
		||||
 | 
			
		||||
    def test_manufacturer_part_search(self):
 | 
			
		||||
        # Test search functionality in manufacturer list
 | 
			
		||||
        url = reverse('api-manufacturer-part-list')
 | 
			
		||||
        data = {'search': 'MPN'}
 | 
			
		||||
        response = self.get(url, data)
 | 
			
		||||
        self.assertEqual(len(response.data), 3)
 | 
			
		||||
 | 
			
		||||
    def test_supplier_part_create(self):
 | 
			
		||||
        url = reverse('api-supplier-part-list')
 | 
			
		||||
 | 
			
		||||
        # Create supplier part
 | 
			
		||||
        data = {
 | 
			
		||||
            'part': 1,
 | 
			
		||||
            'supplier': 1,
 | 
			
		||||
            'SKU': 'SKU_TEST',
 | 
			
		||||
            'manufacturer': 7,
 | 
			
		||||
            'MPN': 'PART_NUMBER',
 | 
			
		||||
        }
 | 
			
		||||
        response = self.client.post(url, data, format='json')
 | 
			
		||||
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
 | 
			
		||||
 | 
			
		||||
        # Check manufacturer part
 | 
			
		||||
        manufacturer_part_id = int(response.data['manufacturer_part']['pk'])
 | 
			
		||||
        url = reverse('api-manufacturer-part-detail', kwargs={'pk': manufacturer_part_id})
 | 
			
		||||
        response = self.get(url)
 | 
			
		||||
        self.assertEqual(response.data['MPN'], 'PART_NUMBER')
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ class TestManufacturerField(MigratorTestCase):
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier,
 | 
			
		||||
            SKU='SCREW.002',
 | 
			
		||||
            manufacturer_name='Zero Corp'
 | 
			
		||||
            manufacturer_name='Zero Corp',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(Company.objects.count(), 1)
 | 
			
		||||
@@ -107,6 +107,136 @@ class TestManufacturerField(MigratorTestCase):
 | 
			
		||||
        self.assertEqual(part.manufacturer.name, 'ACME')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestManufacturerPart(MigratorTestCase):
 | 
			
		||||
    """
 | 
			
		||||
    Tests for migration 0034-0037 which added and transitioned to the ManufacturerPart model
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    migrate_from = ('company', '0033_auto_20210410_1528')
 | 
			
		||||
    migrate_to = ('company', '0037_supplierpart_update_3')
 | 
			
		||||
 | 
			
		||||
    def prepare(self):
 | 
			
		||||
        """
 | 
			
		||||
        Prepare the database by adding some test data 'before' the change:
 | 
			
		||||
 | 
			
		||||
        - Part object
 | 
			
		||||
        - Company object (supplier)
 | 
			
		||||
        - SupplierPart object
 | 
			
		||||
        """
 | 
			
		||||
        
 | 
			
		||||
        Part = self.old_state.apps.get_model('part', 'part')
 | 
			
		||||
        Company = self.old_state.apps.get_model('company', 'company')
 | 
			
		||||
        SupplierPart = self.old_state.apps.get_model('company', 'supplierpart')
 | 
			
		||||
 | 
			
		||||
        # Create an initial part
 | 
			
		||||
        part = Part.objects.create(
 | 
			
		||||
            name='CAP CER 0.1UF 10V X5R 0402',
 | 
			
		||||
            description='CAP CER 0.1UF 10V X5R 0402',
 | 
			
		||||
            purchaseable=True,
 | 
			
		||||
            level=0,
 | 
			
		||||
            tree_id=0,
 | 
			
		||||
            lft=0,
 | 
			
		||||
            rght=0,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Create a manufacturer
 | 
			
		||||
        manufacturer = Company.objects.create(
 | 
			
		||||
            name='Murata',
 | 
			
		||||
            description='Makes capacitors',
 | 
			
		||||
            is_manufacturer=True,
 | 
			
		||||
            is_supplier=False,
 | 
			
		||||
            is_customer=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Create suppliers
 | 
			
		||||
        supplier_1 = Company.objects.create(
 | 
			
		||||
            name='Digi-Key',
 | 
			
		||||
            description='A supplier of components',
 | 
			
		||||
            is_manufacturer=False,
 | 
			
		||||
            is_supplier=True,
 | 
			
		||||
            is_customer=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        supplier_2 = Company.objects.create(
 | 
			
		||||
            name='Mouser',
 | 
			
		||||
            description='We sell components',
 | 
			
		||||
            is_manufacturer=False,
 | 
			
		||||
            is_supplier=True,
 | 
			
		||||
            is_customer=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Add some SupplierPart objects
 | 
			
		||||
        SupplierPart.objects.create(
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier_1,
 | 
			
		||||
            SKU='DK-MUR-CAP-123456-ND',
 | 
			
		||||
            manufacturer=manufacturer,
 | 
			
		||||
            MPN='MUR-CAP-123456',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        SupplierPart.objects.create(
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier_1,
 | 
			
		||||
            SKU='DK-MUR-CAP-987654-ND',
 | 
			
		||||
            manufacturer=manufacturer,
 | 
			
		||||
            MPN='MUR-CAP-987654',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        SupplierPart.objects.create(
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier_2,
 | 
			
		||||
            SKU='CAP-CER-01UF',
 | 
			
		||||
            manufacturer=manufacturer,
 | 
			
		||||
            MPN='MUR-CAP-123456',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # No MPN
 | 
			
		||||
        SupplierPart.objects.create(
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier_2,
 | 
			
		||||
            SKU='CAP-CER-01UF-1',
 | 
			
		||||
            manufacturer=manufacturer,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # No Manufacturer
 | 
			
		||||
        SupplierPart.objects.create(
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier_2,
 | 
			
		||||
            SKU='CAP-CER-01UF-2',
 | 
			
		||||
            MPN='MUR-CAP-123456',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # No Manufacturer data
 | 
			
		||||
        SupplierPart.objects.create(
 | 
			
		||||
            part=part,
 | 
			
		||||
            supplier=supplier_2,
 | 
			
		||||
            SKU='CAP-CER-01UF-3',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_manufacturer_part_objects(self):
 | 
			
		||||
        """
 | 
			
		||||
        Test that the new companies have been created successfully
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # Check on the SupplierPart objects
 | 
			
		||||
        SupplierPart = self.new_state.apps.get_model('company', 'supplierpart')
 | 
			
		||||
        
 | 
			
		||||
        supplier_parts = SupplierPart.objects.all()
 | 
			
		||||
        self.assertEqual(supplier_parts.count(), 6)
 | 
			
		||||
 | 
			
		||||
        supplier_parts = SupplierPart.objects.filter(supplier__name='Mouser')
 | 
			
		||||
        self.assertEqual(supplier_parts.count(), 4)
 | 
			
		||||
 | 
			
		||||
        # Check on the ManufacturerPart objects
 | 
			
		||||
        ManufacturerPart = self.new_state.apps.get_model('company', 'manufacturerpart')
 | 
			
		||||
        
 | 
			
		||||
        manufacturer_parts = ManufacturerPart.objects.all()
 | 
			
		||||
        self.assertEqual(manufacturer_parts.count(), 4)
 | 
			
		||||
        
 | 
			
		||||
        manufacturer_part = manufacturer_parts.first()
 | 
			
		||||
        self.assertEqual(manufacturer_part.MPN, 'MUR-CAP-123456')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCurrencyMigration(MigratorTestCase):
 | 
			
		||||
    """
 | 
			
		||||
    Tests for upgrade from basic currency support to django-money
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -20,6 +21,7 @@ class CompanyViewTestBase(TestCase):
 | 
			
		||||
        'part',
 | 
			
		||||
        'location',
 | 
			
		||||
        'company',
 | 
			
		||||
        'manufacturer_part',
 | 
			
		||||
        'supplier_part',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@@ -200,3 +202,105 @@ class CompanyViewTest(CompanyViewTestBase):
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse('customer-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
			
		||||
        self.assertContains(response, 'Create new Customer')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartViewTests(CompanyViewTestBase):
 | 
			
		||||
    """
 | 
			
		||||
    Tests for the ManufacturerPart views.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def test_manufacturer_part_create(self):
 | 
			
		||||
        """
 | 
			
		||||
        Test the ManufacturerPartCreate view.
 | 
			
		||||
        """
 | 
			
		||||
        
 | 
			
		||||
        url = reverse('manufacturer-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 manufaturer parts are already in the database?
 | 
			
		||||
        n = ManufacturerPart.objects.all().count()
 | 
			
		||||
 | 
			
		||||
        data = {
 | 
			
		||||
            'part': 1,
 | 
			
		||||
            'manufacturer': 6,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # MPN is required! (form should fail)
 | 
			
		||||
        (response, errors) = self.post(url, data, valid=False)
 | 
			
		||||
 | 
			
		||||
        self.assertIsNotNone(errors.get('MPN', None))
 | 
			
		||||
 | 
			
		||||
        data['MPN'] = 'TEST-ME-123'
 | 
			
		||||
 | 
			
		||||
        (response, errors) = self.post(url, data, valid=True)
 | 
			
		||||
 | 
			
		||||
        # Check that the ManufacturerPart was created!
 | 
			
		||||
        self.assertEqual(n + 1, ManufacturerPart.objects.all().count())
 | 
			
		||||
 | 
			
		||||
        # Try to create duplicate ManufacturerPart
 | 
			
		||||
        (response, errors) = self.post(url, data, valid=False)
 | 
			
		||||
 | 
			
		||||
        self.assertIsNotNone(errors.get('__all__', None))
 | 
			
		||||
 | 
			
		||||
        # Check that the ManufacturerPart count stayed the same
 | 
			
		||||
        self.assertEqual(n + 1, ManufacturerPart.objects.all().count())
 | 
			
		||||
 | 
			
		||||
    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())
 | 
			
		||||
 | 
			
		||||
    def test_manufacturer_part_delete(self):
 | 
			
		||||
        """
 | 
			
		||||
        Test the ManufacturerPartDelete view
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        url = reverse('manufacturer-part-delete')
 | 
			
		||||
 | 
			
		||||
        # Get form using 'part' argument
 | 
			
		||||
        response = self.client.get(url, {'part': '2'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        # POST to delete manufacturer part
 | 
			
		||||
        n = ManufacturerPart.objects.count()
 | 
			
		||||
        m = SupplierPart.objects.count()
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            url,
 | 
			
		||||
            {
 | 
			
		||||
                'manufacturer-part-2': 'manufacturer-part-2',
 | 
			
		||||
                'confirm_delete': True
 | 
			
		||||
            },
 | 
			
		||||
            HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
			
		||||
        
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        # Check that the ManufacturerPart was deleted
 | 
			
		||||
        self.assertEqual(n - 1, ManufacturerPart.objects.count())
 | 
			
		||||
        # Check that the SupplierParts were deleted
 | 
			
		||||
        self.assertEqual(m - 2, SupplierPart.objects.count())
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from .models import Company, Contact, SupplierPart
 | 
			
		||||
from .models import Company, Contact, ManufacturerPart, SupplierPart
 | 
			
		||||
from .models import rename_company_image
 | 
			
		||||
from part.models import Part
 | 
			
		||||
 | 
			
		||||
@@ -22,6 +22,7 @@ class CompanySimpleTest(TestCase):
 | 
			
		||||
        'part',
 | 
			
		||||
        'location',
 | 
			
		||||
        'bom',
 | 
			
		||||
        'manufacturer_part',
 | 
			
		||||
        'supplier_part',
 | 
			
		||||
        'price_breaks',
 | 
			
		||||
    ]
 | 
			
		||||
@@ -74,10 +75,10 @@ class CompanySimpleTest(TestCase):
 | 
			
		||||
        self.assertEqual(acme.supplied_part_count, 4)
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(appel.has_parts)
 | 
			
		||||
        self.assertEqual(appel.supplied_part_count, 2)
 | 
			
		||||
        self.assertEqual(appel.supplied_part_count, 3)
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(zerg.has_parts)
 | 
			
		||||
        self.assertEqual(zerg.supplied_part_count, 1)
 | 
			
		||||
        self.assertEqual(zerg.supplied_part_count, 2)
 | 
			
		||||
 | 
			
		||||
    def test_price_breaks(self):
 | 
			
		||||
        
 | 
			
		||||
@@ -166,3 +167,53 @@ class ContactSimpleTest(TestCase):
 | 
			
		||||
        # Remove the parent company
 | 
			
		||||
        Company.objects.get(pk=self.c.pk).delete()
 | 
			
		||||
        self.assertEqual(Contact.objects.count(), 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartSimpleTest(TestCase):
 | 
			
		||||
 | 
			
		||||
    fixtures = [
 | 
			
		||||
        'category',
 | 
			
		||||
        'company',
 | 
			
		||||
        'location',
 | 
			
		||||
        'part',
 | 
			
		||||
        'manufacturer_part',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        # Create a manufacturer part
 | 
			
		||||
        self.part = Part.objects.get(pk=1)
 | 
			
		||||
        manufacturer = Company.objects.get(pk=1)
 | 
			
		||||
        
 | 
			
		||||
        self.mp = ManufacturerPart.create(
 | 
			
		||||
            part=self.part,
 | 
			
		||||
            manufacturer=manufacturer,
 | 
			
		||||
            mpn='PART_NUMBER',
 | 
			
		||||
            description='THIS IS A MANUFACTURER PART',
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        # Create a supplier part
 | 
			
		||||
        supplier = Company.objects.get(pk=5)
 | 
			
		||||
        supplier_part = SupplierPart.objects.create(
 | 
			
		||||
            part=self.part,
 | 
			
		||||
            supplier=supplier,
 | 
			
		||||
            SKU='SKU_TEST',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'manufacturer': manufacturer.id,
 | 
			
		||||
            'MPN': 'MPN_TEST',
 | 
			
		||||
        }
 | 
			
		||||
        supplier_part.save(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def test_exists(self):
 | 
			
		||||
        self.assertEqual(ManufacturerPart.objects.count(), 5)
 | 
			
		||||
 | 
			
		||||
        # Check that manufacturer part was created from supplier part creation
 | 
			
		||||
        manufacturer_parts = ManufacturerPart.objects.filter(manufacturer=1)
 | 
			
		||||
        self.assertEqual(manufacturer_parts.count(), 2)
 | 
			
		||||
 | 
			
		||||
    def test_delete(self):
 | 
			
		||||
        # Remove a part
 | 
			
		||||
        Part.objects.get(pk=self.part.id).delete()
 | 
			
		||||
        # Check that ManufacturerPart was deleted
 | 
			
		||||
        self.assertEqual(ManufacturerPart.objects.count(), 3)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ company_detail_urls = [
 | 
			
		||||
 | 
			
		||||
    # url(r'orders/?', views.CompanyDetail.as_view(template_name='company/orders.html'), name='company-detail-orders'),
 | 
			
		||||
 | 
			
		||||
    url(r'^parts/', views.CompanyDetail.as_view(template_name='company/detail_part.html'), name='company-detail-parts'),
 | 
			
		||||
    url(r'^supplier-parts/', views.CompanyDetail.as_view(template_name='company/detail_supplier_part.html'), name='company-detail-supplier-parts'),
 | 
			
		||||
    url(r'^manufacturer-parts/', views.CompanyDetail.as_view(template_name='company/detail_manufacturer_part.html'), name='company-detail-manufacturer-parts'),
 | 
			
		||||
    url(r'^stock/', views.CompanyDetail.as_view(template_name='company/detail_stock.html'), name='company-detail-stock'),
 | 
			
		||||
    url(r'^purchase-orders/', views.CompanyDetail.as_view(template_name='company/purchase_orders.html'), name='company-detail-purchase-orders'),
 | 
			
		||||
    url(r'^assigned-stock/', views.CompanyDetail.as_view(template_name='company/assigned_stock.html'), name='company-detail-assigned-stock'),
 | 
			
		||||
@@ -52,9 +53,26 @@ price_break_urls = [
 | 
			
		||||
    url(r'^(?P<pk>\d+)/delete/', views.PriceBreakDelete.as_view(), name='price-break-delete'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
manufacturer_part_detail_urls = [
 | 
			
		||||
    url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='manufacturer-part-edit'),
 | 
			
		||||
    
 | 
			
		||||
    url(r'^suppliers/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-suppliers'),
 | 
			
		||||
 | 
			
		||||
    url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-detail'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
manufacturer_part_urls = [
 | 
			
		||||
    url(r'^new/?', views.ManufacturerPartCreate.as_view(), name='manufacturer-part-create'),
 | 
			
		||||
 | 
			
		||||
    url(r'delete/', views.ManufacturerPartDelete.as_view(), name='manufacturer-part-delete'),
 | 
			
		||||
 | 
			
		||||
    url(r'^(?P<pk>\d+)/', include(manufacturer_part_detail_urls)),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
supplier_part_detail_urls = [
 | 
			
		||||
    url(r'^edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'),
 | 
			
		||||
 | 
			
		||||
    url(r'^manufacturers/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_manufacturers.html'), name='supplier-part-manufacturers'),
 | 
			
		||||
    url(r'^pricing/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_pricing.html'), name='supplier-part-pricing'),
 | 
			
		||||
    url(r'^orders/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_orders.html'), name='supplier-part-orders'),
 | 
			
		||||
    url(r'^stock/', views.SupplierPartDetail.as_view(template_name='company/supplier_part_stock.html'), name='supplier-part-stock'),
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ from InvenTree.helpers import str2bool
 | 
			
		||||
from InvenTree.views import InvenTreeRoleMixin
 | 
			
		||||
 | 
			
		||||
from .models import Company
 | 
			
		||||
from .models import ManufacturerPart
 | 
			
		||||
from .models import SupplierPart
 | 
			
		||||
from .models import SupplierPriceBreak
 | 
			
		||||
 | 
			
		||||
@@ -31,6 +32,7 @@ from part.models import Part
 | 
			
		||||
 | 
			
		||||
from .forms import EditCompanyForm
 | 
			
		||||
from .forms import CompanyImageForm
 | 
			
		||||
from .forms import EditManufacturerPartForm
 | 
			
		||||
from .forms import EditSupplierPartForm
 | 
			
		||||
from .forms import EditPriceBreakForm
 | 
			
		||||
from .forms import CompanyImageDownloadForm
 | 
			
		||||
@@ -331,6 +333,177 @@ class CompanyDelete(AjaxDeleteView):
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartDetail(DetailView):
 | 
			
		||||
    """ Detail view for ManufacturerPart """
 | 
			
		||||
    model = ManufacturerPart
 | 
			
		||||
    template_name = 'company/manufacturer_part_detail.html'
 | 
			
		||||
    context_object_name = 'part'
 | 
			
		||||
    queryset = ManufacturerPart.objects.all()
 | 
			
		||||
    permission_required = 'purchase_order.view'
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        ctx = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        return ctx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartEdit(AjaxUpdateView):
 | 
			
		||||
    """ Update view for editing ManufacturerPart """
 | 
			
		||||
 | 
			
		||||
    model = ManufacturerPart
 | 
			
		||||
    context_object_name = 'part'
 | 
			
		||||
    form_class = EditManufacturerPartForm
 | 
			
		||||
    ajax_template_name = 'modal_form.html'
 | 
			
		||||
    ajax_form_title = _('Edit Manufacturer Part')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartCreate(AjaxCreateView):
 | 
			
		||||
    """ Create view for making new ManufacturerPart """
 | 
			
		||||
 | 
			
		||||
    model = ManufacturerPart
 | 
			
		||||
    form_class = EditManufacturerPartForm
 | 
			
		||||
    ajax_template_name = 'company/manufacturer_part_create.html'
 | 
			
		||||
    ajax_form_title = _('Create New Manufacturer Part')
 | 
			
		||||
    context_object_name = 'part'
 | 
			
		||||
 | 
			
		||||
    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 get_form(self):
 | 
			
		||||
        """ Create Form instance to create a new ManufacturerPart 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()
 | 
			
		||||
 | 
			
		||||
        return form
 | 
			
		||||
 | 
			
		||||
    def get_initial(self):
 | 
			
		||||
        """ Provide initial data for new ManufacturerPart:
 | 
			
		||||
 | 
			
		||||
        - If 'manufacturer_id' provided, pre-fill manufacturer field
 | 
			
		||||
        - If 'part_id' provided, pre-fill part field
 | 
			
		||||
        """
 | 
			
		||||
        initials = super(ManufacturerPartCreate, self).get_initial().copy()
 | 
			
		||||
 | 
			
		||||
        manufacturer_id = self.get_param('manufacturer')
 | 
			
		||||
        part_id = self.get_param('part')
 | 
			
		||||
 | 
			
		||||
        if manufacturer_id:
 | 
			
		||||
            try:
 | 
			
		||||
                initials['manufacturer'] = Company.objects.get(pk=manufacturer_id)
 | 
			
		||||
            except (ValueError, Company.DoesNotExist):
 | 
			
		||||
                pass
 | 
			
		||||
        
 | 
			
		||||
        if part_id:
 | 
			
		||||
            try:
 | 
			
		||||
                initials['part'] = Part.objects.get(pk=part_id)
 | 
			
		||||
            except (ValueError, Part.DoesNotExist):
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        return initials
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManufacturerPartDelete(AjaxDeleteView):
 | 
			
		||||
    """ Delete view for removing a ManufacturerPart.
 | 
			
		||||
    
 | 
			
		||||
    ManufacturerParts can be deleted using a variety of 'selectors'.
 | 
			
		||||
 | 
			
		||||
    - ?part=<pk> -> Delete a single ManufacturerPart object
 | 
			
		||||
    - ?parts=[] -> Delete a list of ManufacturerPart objects
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    success_url = '/manufacturer/'
 | 
			
		||||
    ajax_template_name = 'company/manufacturer_part_delete.html'
 | 
			
		||||
    ajax_form_title = _('Delete Manufacturer 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 ManufacturerPart object(s) the user wishes to delete.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        self.parts = []
 | 
			
		||||
 | 
			
		||||
        # User passes a single ManufacturerPart ID
 | 
			
		||||
        if 'part' in self.request.GET:
 | 
			
		||||
            try:
 | 
			
		||||
                self.parts.append(ManufacturerPart.objects.get(pk=self.request.GET.get('part')))
 | 
			
		||||
            except (ValueError, ManufacturerPart.DoesNotExist):
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        elif 'parts[]' in self.request.GET:
 | 
			
		||||
 | 
			
		||||
            part_id_list = self.request.GET.getlist('parts[]')
 | 
			
		||||
 | 
			
		||||
            self.parts = ManufacturerPart.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 ManufacturerPart object.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        self.request = request
 | 
			
		||||
        self.parts = []
 | 
			
		||||
 | 
			
		||||
        for item in self.request.POST:
 | 
			
		||||
            if item.startswith('manufacturer-part-'):
 | 
			
		||||
                pk = item.replace('manufacturer-part-', '')
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    self.parts.append(ManufacturerPart.objects.get(pk=pk))
 | 
			
		||||
                except (ValueError, ManufacturerPart.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())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SupplierPartDetail(DetailView):
 | 
			
		||||
    """ Detail view for SupplierPart """
 | 
			
		||||
    model = SupplierPart
 | 
			
		||||
@@ -354,11 +527,25 @@ class SupplierPartEdit(AjaxUpdateView):
 | 
			
		||||
    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():
 | 
			
		||||
@@ -368,6 +555,19 @@ class SupplierPartEdit(AjaxUpdateView):
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
            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 """
 | 
			
		||||
@@ -415,6 +615,14 @@ class SupplierPartCreate(AjaxCreateView):
 | 
			
		||||
        # 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:
 | 
			
		||||
@@ -433,6 +641,12 @@ class SupplierPartCreate(AjaxCreateView):
 | 
			
		||||
            # 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):
 | 
			
		||||
@@ -446,6 +660,7 @@ class SupplierPartCreate(AjaxCreateView):
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
@@ -461,6 +676,16 @@ class SupplierPartCreate(AjaxCreateView):
 | 
			
		||||
                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:
 | 
			
		||||
@@ -493,7 +718,7 @@ class SupplierPartDelete(AjaxDeleteView):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    success_url = '/supplier/'
 | 
			
		||||
    ajax_template_name = 'company/partdelete.html'
 | 
			
		||||
    ajax_template_name = 'company/supplier_part_delete.html'
 | 
			
		||||
    ajax_form_title = _('Delete Supplier Part')
 | 
			
		||||
 | 
			
		||||
    role_required = 'purchase_order.delete'
 | 
			
		||||
 
 | 
			
		||||
@@ -253,10 +253,12 @@ class StockItemLabel(LabelTemplate):
 | 
			
		||||
            'part': stock_item.part,
 | 
			
		||||
            'name': stock_item.part.full_name,
 | 
			
		||||
            'ipn': stock_item.part.IPN,
 | 
			
		||||
            'revision': stock_item.part.revision,
 | 
			
		||||
            'quantity': normalize(stock_item.quantity),
 | 
			
		||||
            'serial': stock_item.serial,
 | 
			
		||||
            'uid': stock_item.uid,
 | 
			
		||||
            'qr_data': stock_item.format_barcode(brief=True),
 | 
			
		||||
            'qr_url': stock_item.format_barcode(url=True, request=request),
 | 
			
		||||
            'tests': stock_item.testResultMap()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: \n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2021-04-11 22:07+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2021-04-15 10:07+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2021-03-28 17:47+0200\n"
 | 
			
		||||
"Last-Translator: Andreas Kaiser <kaiser.vocote@gmail.com>, Matthias "
 | 
			
		||||
"MAIR<matmair@live.de>\n"
 | 
			
		||||
@@ -190,11 +190,15 @@ msgstr "Polnisch"
 | 
			
		||||
msgid "Turkish"
 | 
			
		||||
msgstr "Türkisch"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:57
 | 
			
		||||
#: InvenTree/status.py:84
 | 
			
		||||
msgid "Background worker check failed"
 | 
			
		||||
msgstr "Hintergrund-Prozess-Kontrolle fehlgeschlagen"
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:60
 | 
			
		||||
#: InvenTree/status.py:88
 | 
			
		||||
msgid "Email backend not configured"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:91
 | 
			
		||||
msgid "InvenTree system health checks failed"
 | 
			
		||||
msgstr "InvenTree Status-Überprüfung fehlgeschlagen"
 | 
			
		||||
 | 
			
		||||
@@ -2045,28 +2049,29 @@ msgid "Supplied Parts"
 | 
			
		||||
msgstr "Zulieferer-Teile"
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:23
 | 
			
		||||
#: order/templates/order/receive_parts.html:14 part/models.py:322
 | 
			
		||||
#: part/templates/part/cat_link.html:7 part/templates/part/category.html:95
 | 
			
		||||
#: order/templates/order/receive_parts.html:14 part/api.py:40
 | 
			
		||||
#: part/models.py:322 part/templates/part/cat_link.html:7
 | 
			
		||||
#: part/templates/part/category.html:95
 | 
			
		||||
#: part/templates/part/category_navbar.html:11
 | 
			
		||||
#: part/templates/part/category_navbar.html:14
 | 
			
		||||
#: part/templates/part/category_partlist.html:10
 | 
			
		||||
#: templates/InvenTree/index.html:96 templates/InvenTree/search.html:113
 | 
			
		||||
#: templates/InvenTree/settings/tabs.html:25 templates/navbar.html:23
 | 
			
		||||
#: templates/stats.html:48 templates/stats.html:57 users/models.py:38
 | 
			
		||||
#: templates/stats.html:59 templates/stats.html:68 users/models.py:38
 | 
			
		||||
msgid "Parts"
 | 
			
		||||
msgstr "Teile"
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:27 part/templates/part/navbar.html:33
 | 
			
		||||
#: stock/templates/stock/location.html:100
 | 
			
		||||
#: stock/templates/stock/location.html:115 templates/InvenTree/search.html:182
 | 
			
		||||
#: templates/stats.html:61 templates/stats.html:70 users/models.py:40
 | 
			
		||||
#: templates/stats.html:72 templates/stats.html:81 users/models.py:40
 | 
			
		||||
msgid "Stock Items"
 | 
			
		||||
msgstr "BestandsObjekte"
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:30
 | 
			
		||||
#: company/templates/company/part_navbar.html:14
 | 
			
		||||
#: part/templates/part/navbar.html:36 stock/templates/stock/loc_link.html:7
 | 
			
		||||
#: stock/templates/stock/location.html:29
 | 
			
		||||
#: part/templates/part/navbar.html:36 stock/api.py:51
 | 
			
		||||
#: stock/templates/stock/loc_link.html:7 stock/templates/stock/location.html:29
 | 
			
		||||
#: stock/templates/stock/stock_app_base.html:9
 | 
			
		||||
#: templates/InvenTree/index.html:127 templates/InvenTree/search.html:180
 | 
			
		||||
#: templates/InvenTree/search.html:216
 | 
			
		||||
@@ -3274,7 +3279,7 @@ msgstr "Teil-Kategorie"
 | 
			
		||||
 | 
			
		||||
#: part/models.py:83 part/templates/part/category.html:19
 | 
			
		||||
#: part/templates/part/category.html:90 part/templates/part/category.html:141
 | 
			
		||||
#: templates/InvenTree/search.html:126 templates/stats.html:52
 | 
			
		||||
#: templates/InvenTree/search.html:126 templates/stats.html:63
 | 
			
		||||
#: users/models.py:37
 | 
			
		||||
msgid "Part Categories"
 | 
			
		||||
msgstr "Teil-Kategorien"
 | 
			
		||||
@@ -5333,7 +5338,7 @@ msgid "Stock Details"
 | 
			
		||||
msgstr "Objekt-Details"
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/location.html:110 templates/InvenTree/search.html:263
 | 
			
		||||
#: templates/stats.html:65 users/models.py:39
 | 
			
		||||
#: templates/stats.html:76 users/models.py:39
 | 
			
		||||
msgid "Stock Locations"
 | 
			
		||||
msgstr "Bestand-Lagerorte"
 | 
			
		||||
 | 
			
		||||
@@ -6137,6 +6142,14 @@ msgstr "Vorlagenteil"
 | 
			
		||||
msgid "Assembled part"
 | 
			
		||||
msgstr "Baugruppe"
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:167 templates/js/filters.js:397
 | 
			
		||||
msgid "true"
 | 
			
		||||
msgstr "ja"
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:171 templates/js/filters.js:398
 | 
			
		||||
msgid "false"
 | 
			
		||||
msgstr "nein"
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:193
 | 
			
		||||
msgid "Select filter"
 | 
			
		||||
msgstr "Filter auswählen"
 | 
			
		||||
@@ -6470,6 +6483,22 @@ msgstr "Auftrag zugewiesen"
 | 
			
		||||
msgid "No stock items matching query"
 | 
			
		||||
msgstr "Keine zur Anfrage passenden BestandsObjekte"
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:357
 | 
			
		||||
msgid "items"
 | 
			
		||||
msgstr "Teile"
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:449
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Batch"
 | 
			
		||||
msgid "batches"
 | 
			
		||||
msgstr "Los"
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:476
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Allocations"
 | 
			
		||||
msgid "locations"
 | 
			
		||||
msgstr "Zuweisungen"
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:478
 | 
			
		||||
msgid "Undefined location"
 | 
			
		||||
msgstr "unbekannter Lagerort"
 | 
			
		||||
@@ -6653,7 +6682,7 @@ msgstr "Elemente, die in Produktion sind, anzeigen"
 | 
			
		||||
 | 
			
		||||
#: templates/js/table_filters.js:144
 | 
			
		||||
msgid "Include Variants"
 | 
			
		||||
msgstr "Varianten hinzufügen"
 | 
			
		||||
msgstr "Varianten einschließen"
 | 
			
		||||
 | 
			
		||||
#: templates/js/table_filters.js:145
 | 
			
		||||
msgid "Include stock items for variant parts"
 | 
			
		||||
@@ -6792,7 +6821,7 @@ msgstr "Barcode scannen"
 | 
			
		||||
msgid "Admin"
 | 
			
		||||
msgstr "Admin"
 | 
			
		||||
 | 
			
		||||
#: templates/navbar.html:73 templates/registration/logout.html:5
 | 
			
		||||
#: templates/navbar.html:73
 | 
			
		||||
msgid "Logout"
 | 
			
		||||
msgstr "Ausloggen"
 | 
			
		||||
 | 
			
		||||
@@ -6808,6 +6837,18 @@ msgstr "Über InvenBaum"
 | 
			
		||||
msgid "QR data not provided"
 | 
			
		||||
msgstr "QR Daten nicht angegeben"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logged_out.html:50
 | 
			
		||||
msgid "You have been logged out"
 | 
			
		||||
msgstr "Sie wurden abgemeldet"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logged_out.html:51
 | 
			
		||||
#: templates/registration/password_reset_complete.html:51
 | 
			
		||||
#: templates/registration/password_reset_done.html:58
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Returned to location"
 | 
			
		||||
msgid "Return to login screen"
 | 
			
		||||
msgstr "zurück ins Lager"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/login.html:64
 | 
			
		||||
msgid "Enter username"
 | 
			
		||||
msgstr "Benutzername eingeben"
 | 
			
		||||
@@ -6820,17 +6861,61 @@ msgstr "Passwort"
 | 
			
		||||
msgid "Username / password combination is incorrect"
 | 
			
		||||
msgstr "Benutzername / Passwort Kombination ist falsch"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:6
 | 
			
		||||
msgid "You have been logged out"
 | 
			
		||||
msgstr "Sie wurden abgemeldet"
 | 
			
		||||
#: templates/registration/login.html:95
 | 
			
		||||
#: templates/registration/password_reset_form.html:51
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Enter password"
 | 
			
		||||
msgid "Forgotten your password?"
 | 
			
		||||
msgstr "Passwort eingeben"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:7
 | 
			
		||||
msgid "Click"
 | 
			
		||||
msgstr "Klick"
 | 
			
		||||
#: templates/registration/login.html:95
 | 
			
		||||
msgid "Click here to reset"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:7
 | 
			
		||||
msgid "here</a> to log in</p>"
 | 
			
		||||
msgstr "hier</a> zum abmelden</p>"
 | 
			
		||||
#: templates/registration/password_reset_complete.html:50
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Purchase order completed"
 | 
			
		||||
msgid "Password reset complete"
 | 
			
		||||
msgstr "Bestellung als vollständig markieren"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:52
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:56
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Change Password"
 | 
			
		||||
msgid "Change password"
 | 
			
		||||
msgstr "Passwort ändern"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:60
 | 
			
		||||
msgid ""
 | 
			
		||||
"The password reset link was invalid, possibly because it has already been "
 | 
			
		||||
"used. Please request a new password reset."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_done.html:51
 | 
			
		||||
msgid ""
 | 
			
		||||
"We've emailed you instructions for setting your password, if an account "
 | 
			
		||||
"exists with the email you entered. You should receive them shortly."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_done.html:54
 | 
			
		||||
msgid ""
 | 
			
		||||
"If you don't receive an email, please make sure you've entered the address "
 | 
			
		||||
"you registered with, and check your spam folder."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:52
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Contact email address"
 | 
			
		||||
msgid "Enter your email address below."
 | 
			
		||||
msgstr "Kontakt-Email"
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:53
 | 
			
		||||
msgid "An email will be sent with password reset instructions."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:58
 | 
			
		||||
msgid "Send email"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:9
 | 
			
		||||
msgid "Server"
 | 
			
		||||
@@ -6852,17 +6937,25 @@ msgstr "Gesund"
 | 
			
		||||
msgid "Issues detected"
 | 
			
		||||
msgstr "Probleme erkannt"
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:30
 | 
			
		||||
#: templates/stats.html:31
 | 
			
		||||
msgid "Background Worker"
 | 
			
		||||
msgstr "Hintergrund-Prozess"
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:33
 | 
			
		||||
msgid "Operational"
 | 
			
		||||
msgstr "Betriebsbereit"
 | 
			
		||||
#: templates/stats.html:34
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Background Worker"
 | 
			
		||||
msgid "Background worker not running"
 | 
			
		||||
msgstr "Hintergrund-Prozess"
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:35
 | 
			
		||||
msgid "Not running"
 | 
			
		||||
msgstr "Läuft nicht"
 | 
			
		||||
#: templates/stats.html:42
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Part Settings"
 | 
			
		||||
msgid "Email Settings"
 | 
			
		||||
msgstr "Teil-Einstellungen"
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:45
 | 
			
		||||
msgid "Email settings not configured"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stock_table.html:14
 | 
			
		||||
msgid "Export Stock Information"
 | 
			
		||||
@@ -6980,6 +7073,28 @@ msgstr "Berechtigungen Einträge zu ändern"
 | 
			
		||||
msgid "Permission to delete items"
 | 
			
		||||
msgstr "Berechtigung Einträge zu löschen"
 | 
			
		||||
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#~| msgid "Part Pricing"
 | 
			
		||||
#~ msgid "Stock Pricing"
 | 
			
		||||
#~ msgstr "Teilbepreisung"
 | 
			
		||||
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#~| msgid "No pricing information is available for this part."
 | 
			
		||||
#~ msgid "No stock pricing history is available for this part."
 | 
			
		||||
#~ msgstr "Keine Preise für dieses Teil verfügbar"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Click"
 | 
			
		||||
#~ msgstr "Klick"
 | 
			
		||||
 | 
			
		||||
#~ msgid "here</a> to log in</p>"
 | 
			
		||||
#~ msgstr "hier</a> zum abmelden</p>"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Operational"
 | 
			
		||||
#~ msgstr "Betriebsbereit"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Not running"
 | 
			
		||||
#~ msgstr "Läuft nicht"
 | 
			
		||||
 | 
			
		||||
#~ msgid "InvenTree server issues detected"
 | 
			
		||||
#~ msgstr "InvenTree Server Fehler aufgetreten"
 | 
			
		||||
 | 
			
		||||
@@ -7009,9 +7124,6 @@ msgstr "Berechtigung Einträge zu löschen"
 | 
			
		||||
#~ msgid "customer"
 | 
			
		||||
#~ msgstr "Kunde"
 | 
			
		||||
 | 
			
		||||
#~ msgid "items"
 | 
			
		||||
#~ msgstr "Teile"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Create purchase order"
 | 
			
		||||
#~ msgstr "Neue Bestellung anlegen"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2021-04-11 22:07+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2021-04-15 10:07+0000\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@@ -188,11 +188,15 @@ msgstr ""
 | 
			
		||||
msgid "Turkish"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:57
 | 
			
		||||
#: InvenTree/status.py:84
 | 
			
		||||
msgid "Background worker check failed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:60
 | 
			
		||||
#: InvenTree/status.py:88
 | 
			
		||||
msgid "Email backend not configured"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:91
 | 
			
		||||
msgid "InvenTree system health checks failed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -2022,28 +2026,29 @@ msgid "Supplied Parts"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:23
 | 
			
		||||
#: order/templates/order/receive_parts.html:14 part/models.py:322
 | 
			
		||||
#: part/templates/part/cat_link.html:7 part/templates/part/category.html:95
 | 
			
		||||
#: order/templates/order/receive_parts.html:14 part/api.py:40
 | 
			
		||||
#: part/models.py:322 part/templates/part/cat_link.html:7
 | 
			
		||||
#: part/templates/part/category.html:95
 | 
			
		||||
#: part/templates/part/category_navbar.html:11
 | 
			
		||||
#: part/templates/part/category_navbar.html:14
 | 
			
		||||
#: part/templates/part/category_partlist.html:10
 | 
			
		||||
#: templates/InvenTree/index.html:96 templates/InvenTree/search.html:113
 | 
			
		||||
#: templates/InvenTree/settings/tabs.html:25 templates/navbar.html:23
 | 
			
		||||
#: templates/stats.html:48 templates/stats.html:57 users/models.py:38
 | 
			
		||||
#: templates/stats.html:59 templates/stats.html:68 users/models.py:38
 | 
			
		||||
msgid "Parts"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:27 part/templates/part/navbar.html:33
 | 
			
		||||
#: stock/templates/stock/location.html:100
 | 
			
		||||
#: stock/templates/stock/location.html:115 templates/InvenTree/search.html:182
 | 
			
		||||
#: templates/stats.html:61 templates/stats.html:70 users/models.py:40
 | 
			
		||||
#: templates/stats.html:72 templates/stats.html:81 users/models.py:40
 | 
			
		||||
msgid "Stock Items"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:30
 | 
			
		||||
#: company/templates/company/part_navbar.html:14
 | 
			
		||||
#: part/templates/part/navbar.html:36 stock/templates/stock/loc_link.html:7
 | 
			
		||||
#: stock/templates/stock/location.html:29
 | 
			
		||||
#: part/templates/part/navbar.html:36 stock/api.py:51
 | 
			
		||||
#: stock/templates/stock/loc_link.html:7 stock/templates/stock/location.html:29
 | 
			
		||||
#: stock/templates/stock/stock_app_base.html:9
 | 
			
		||||
#: templates/InvenTree/index.html:127 templates/InvenTree/search.html:180
 | 
			
		||||
#: templates/InvenTree/search.html:216
 | 
			
		||||
@@ -3242,7 +3247,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:83 part/templates/part/category.html:19
 | 
			
		||||
#: part/templates/part/category.html:90 part/templates/part/category.html:141
 | 
			
		||||
#: templates/InvenTree/search.html:126 templates/stats.html:52
 | 
			
		||||
#: templates/InvenTree/search.html:126 templates/stats.html:63
 | 
			
		||||
#: users/models.py:37
 | 
			
		||||
msgid "Part Categories"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -5266,7 +5271,7 @@ msgid "Stock Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/location.html:110 templates/InvenTree/search.html:263
 | 
			
		||||
#: templates/stats.html:65 users/models.py:39
 | 
			
		||||
#: templates/stats.html:76 users/models.py:39
 | 
			
		||||
msgid "Stock Locations"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -6063,6 +6068,14 @@ msgstr ""
 | 
			
		||||
msgid "Assembled part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:167 templates/js/filters.js:397
 | 
			
		||||
msgid "true"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:171 templates/js/filters.js:398
 | 
			
		||||
msgid "false"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:193
 | 
			
		||||
msgid "Select filter"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -6395,6 +6408,18 @@ msgstr ""
 | 
			
		||||
msgid "No stock items matching query"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:357
 | 
			
		||||
msgid "items"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:449
 | 
			
		||||
msgid "batches"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:476
 | 
			
		||||
msgid "locations"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:478
 | 
			
		||||
msgid "Undefined location"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -6717,7 +6742,7 @@ msgstr ""
 | 
			
		||||
msgid "Admin"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/navbar.html:73 templates/registration/logout.html:5
 | 
			
		||||
#: templates/navbar.html:73
 | 
			
		||||
msgid "Logout"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -6733,6 +6758,16 @@ msgstr ""
 | 
			
		||||
msgid "QR data not provided"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logged_out.html:50
 | 
			
		||||
msgid "You have been logged out"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logged_out.html:51
 | 
			
		||||
#: templates/registration/password_reset_complete.html:51
 | 
			
		||||
#: templates/registration/password_reset_done.html:58
 | 
			
		||||
msgid "Return to login screen"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/login.html:64
 | 
			
		||||
msgid "Enter username"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -6745,16 +6780,52 @@ msgstr ""
 | 
			
		||||
msgid "Username / password combination is incorrect"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:6
 | 
			
		||||
msgid "You have been logged out"
 | 
			
		||||
#: templates/registration/login.html:95
 | 
			
		||||
#: templates/registration/password_reset_form.html:51
 | 
			
		||||
msgid "Forgotten your password?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:7
 | 
			
		||||
msgid "Click"
 | 
			
		||||
#: templates/registration/login.html:95
 | 
			
		||||
msgid "Click here to reset"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:7
 | 
			
		||||
msgid "here</a> to log in</p>"
 | 
			
		||||
#: templates/registration/password_reset_complete.html:50
 | 
			
		||||
msgid "Password reset complete"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:52
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:56
 | 
			
		||||
msgid "Change password"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:60
 | 
			
		||||
msgid ""
 | 
			
		||||
"The password reset link was invalid, possibly because it has already been "
 | 
			
		||||
"used. Please request a new password reset."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_done.html:51
 | 
			
		||||
msgid ""
 | 
			
		||||
"We've emailed you instructions for setting your password, if an account "
 | 
			
		||||
"exists with the email you entered. You should receive them shortly."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_done.html:54
 | 
			
		||||
msgid ""
 | 
			
		||||
"If you don't receive an email, please make sure you've entered the address "
 | 
			
		||||
"you registered with, and check your spam folder."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:52
 | 
			
		||||
msgid "Enter your email address below."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:53
 | 
			
		||||
msgid "An email will be sent with password reset instructions."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:58
 | 
			
		||||
msgid "Send email"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:9
 | 
			
		||||
@@ -6777,16 +6848,20 @@ msgstr ""
 | 
			
		||||
msgid "Issues detected"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:30
 | 
			
		||||
#: templates/stats.html:31
 | 
			
		||||
msgid "Background Worker"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:33
 | 
			
		||||
msgid "Operational"
 | 
			
		||||
#: templates/stats.html:34
 | 
			
		||||
msgid "Background worker not running"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:35
 | 
			
		||||
msgid "Not running"
 | 
			
		||||
#: templates/stats.html:42
 | 
			
		||||
msgid "Email Settings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:45
 | 
			
		||||
msgid "Email settings not configured"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stock_table.html:14
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2021-04-11 22:07+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2021-04-15 10:07+0000\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@@ -188,11 +188,15 @@ msgstr ""
 | 
			
		||||
msgid "Turkish"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:57
 | 
			
		||||
#: InvenTree/status.py:84
 | 
			
		||||
msgid "Background worker check failed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:60
 | 
			
		||||
#: InvenTree/status.py:88
 | 
			
		||||
msgid "Email backend not configured"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: InvenTree/status.py:91
 | 
			
		||||
msgid "InvenTree system health checks failed"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -2022,28 +2026,29 @@ msgid "Supplied Parts"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:23
 | 
			
		||||
#: order/templates/order/receive_parts.html:14 part/models.py:322
 | 
			
		||||
#: part/templates/part/cat_link.html:7 part/templates/part/category.html:95
 | 
			
		||||
#: order/templates/order/receive_parts.html:14 part/api.py:40
 | 
			
		||||
#: part/models.py:322 part/templates/part/cat_link.html:7
 | 
			
		||||
#: part/templates/part/category.html:95
 | 
			
		||||
#: part/templates/part/category_navbar.html:11
 | 
			
		||||
#: part/templates/part/category_navbar.html:14
 | 
			
		||||
#: part/templates/part/category_partlist.html:10
 | 
			
		||||
#: templates/InvenTree/index.html:96 templates/InvenTree/search.html:113
 | 
			
		||||
#: templates/InvenTree/settings/tabs.html:25 templates/navbar.html:23
 | 
			
		||||
#: templates/stats.html:48 templates/stats.html:57 users/models.py:38
 | 
			
		||||
#: templates/stats.html:59 templates/stats.html:68 users/models.py:38
 | 
			
		||||
msgid "Parts"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:27 part/templates/part/navbar.html:33
 | 
			
		||||
#: stock/templates/stock/location.html:100
 | 
			
		||||
#: stock/templates/stock/location.html:115 templates/InvenTree/search.html:182
 | 
			
		||||
#: templates/stats.html:61 templates/stats.html:70 users/models.py:40
 | 
			
		||||
#: templates/stats.html:72 templates/stats.html:81 users/models.py:40
 | 
			
		||||
msgid "Stock Items"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: company/templates/company/navbar.html:30
 | 
			
		||||
#: company/templates/company/part_navbar.html:14
 | 
			
		||||
#: part/templates/part/navbar.html:36 stock/templates/stock/loc_link.html:7
 | 
			
		||||
#: stock/templates/stock/location.html:29
 | 
			
		||||
#: part/templates/part/navbar.html:36 stock/api.py:51
 | 
			
		||||
#: stock/templates/stock/loc_link.html:7 stock/templates/stock/location.html:29
 | 
			
		||||
#: stock/templates/stock/stock_app_base.html:9
 | 
			
		||||
#: templates/InvenTree/index.html:127 templates/InvenTree/search.html:180
 | 
			
		||||
#: templates/InvenTree/search.html:216
 | 
			
		||||
@@ -3242,7 +3247,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: part/models.py:83 part/templates/part/category.html:19
 | 
			
		||||
#: part/templates/part/category.html:90 part/templates/part/category.html:141
 | 
			
		||||
#: templates/InvenTree/search.html:126 templates/stats.html:52
 | 
			
		||||
#: templates/InvenTree/search.html:126 templates/stats.html:63
 | 
			
		||||
#: users/models.py:37
 | 
			
		||||
msgid "Part Categories"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -5266,7 +5271,7 @@ msgid "Stock Details"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: stock/templates/stock/location.html:110 templates/InvenTree/search.html:263
 | 
			
		||||
#: templates/stats.html:65 users/models.py:39
 | 
			
		||||
#: templates/stats.html:76 users/models.py:39
 | 
			
		||||
msgid "Stock Locations"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -6063,6 +6068,14 @@ msgstr ""
 | 
			
		||||
msgid "Assembled part"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:167 templates/js/filters.js:397
 | 
			
		||||
msgid "true"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:171 templates/js/filters.js:398
 | 
			
		||||
msgid "false"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/filters.js:193
 | 
			
		||||
msgid "Select filter"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -6395,6 +6408,18 @@ msgstr ""
 | 
			
		||||
msgid "No stock items matching query"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:357
 | 
			
		||||
msgid "items"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:449
 | 
			
		||||
msgid "batches"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:476
 | 
			
		||||
msgid "locations"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/js/stock.js:478
 | 
			
		||||
msgid "Undefined location"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -6717,7 +6742,7 @@ msgstr ""
 | 
			
		||||
msgid "Admin"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/navbar.html:73 templates/registration/logout.html:5
 | 
			
		||||
#: templates/navbar.html:73
 | 
			
		||||
msgid "Logout"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -6733,6 +6758,16 @@ msgstr ""
 | 
			
		||||
msgid "QR data not provided"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logged_out.html:50
 | 
			
		||||
msgid "You have been logged out"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logged_out.html:51
 | 
			
		||||
#: templates/registration/password_reset_complete.html:51
 | 
			
		||||
#: templates/registration/password_reset_done.html:58
 | 
			
		||||
msgid "Return to login screen"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/login.html:64
 | 
			
		||||
msgid "Enter username"
 | 
			
		||||
msgstr ""
 | 
			
		||||
@@ -6745,16 +6780,52 @@ msgstr ""
 | 
			
		||||
msgid "Username / password combination is incorrect"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:6
 | 
			
		||||
msgid "You have been logged out"
 | 
			
		||||
#: templates/registration/login.html:95
 | 
			
		||||
#: templates/registration/password_reset_form.html:51
 | 
			
		||||
msgid "Forgotten your password?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:7
 | 
			
		||||
msgid "Click"
 | 
			
		||||
#: templates/registration/login.html:95
 | 
			
		||||
msgid "Click here to reset"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/logout.html:7
 | 
			
		||||
msgid "here</a> to log in</p>"
 | 
			
		||||
#: templates/registration/password_reset_complete.html:50
 | 
			
		||||
msgid "Password reset complete"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:52
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:56
 | 
			
		||||
msgid "Change password"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_confirm.html:60
 | 
			
		||||
msgid ""
 | 
			
		||||
"The password reset link was invalid, possibly because it has already been "
 | 
			
		||||
"used. Please request a new password reset."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_done.html:51
 | 
			
		||||
msgid ""
 | 
			
		||||
"We've emailed you instructions for setting your password, if an account "
 | 
			
		||||
"exists with the email you entered. You should receive them shortly."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_done.html:54
 | 
			
		||||
msgid ""
 | 
			
		||||
"If you don't receive an email, please make sure you've entered the address "
 | 
			
		||||
"you registered with, and check your spam folder."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:52
 | 
			
		||||
msgid "Enter your email address below."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:53
 | 
			
		||||
msgid "An email will be sent with password reset instructions."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/registration/password_reset_form.html:58
 | 
			
		||||
msgid "Send email"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:9
 | 
			
		||||
@@ -6777,16 +6848,20 @@ msgstr ""
 | 
			
		||||
msgid "Issues detected"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:30
 | 
			
		||||
#: templates/stats.html:31
 | 
			
		||||
msgid "Background Worker"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:33
 | 
			
		||||
msgid "Operational"
 | 
			
		||||
#: templates/stats.html:34
 | 
			
		||||
msgid "Background worker not running"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:35
 | 
			
		||||
msgid "Not running"
 | 
			
		||||
#: templates/stats.html:42
 | 
			
		||||
msgid "Email Settings"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stats.html:45
 | 
			
		||||
msgid "Email settings not configured"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/stock_table.html:14
 | 
			
		||||
 
 | 
			
		||||
@@ -181,6 +181,13 @@ $("#po-table").inventreeTable({
 | 
			
		||||
            sortName: 'part__MPN',
 | 
			
		||||
            field: 'supplier_part_detail.MPN',
 | 
			
		||||
            title: '{% trans "MPN" %}',
 | 
			
		||||
            formatter: function(value, row, index, field) {
 | 
			
		||||
                if (row.supplier_part_detail.manufacturer_part) {
 | 
			
		||||
                    return renderLink(value, `/manufacturer-part/${row.supplier_part_detail.manufacturer_part.pk}/`);
 | 
			
		||||
                } else {
 | 
			
		||||
                    return "";
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            sortable: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ from __future__ import unicode_literals
 | 
			
		||||
from django_filters.rest_framework import DjangoFilterBackend
 | 
			
		||||
from django.http import JsonResponse
 | 
			
		||||
from django.db.models import Q, F, Count, Prefetch, Sum
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from rest_framework import status
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
@@ -36,7 +37,7 @@ from InvenTree.status_codes import BuildStatus
 | 
			
		||||
 | 
			
		||||
class PartCategoryTree(TreeSerializer):
 | 
			
		||||
 | 
			
		||||
    title = "Parts"
 | 
			
		||||
    title = _("Parts")
 | 
			
		||||
    model = PartCategory
 | 
			
		||||
 | 
			
		||||
    queryset = PartCategory.objects.all()
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ from InvenTree.helpers import DownloadFile, GetExportFormats
 | 
			
		||||
 | 
			
		||||
from .admin import BomItemResource
 | 
			
		||||
from .models import BomItem
 | 
			
		||||
from company.models import SupplierPart
 | 
			
		||||
from company.models import ManufacturerPart, SupplierPart
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def IsValidBOMFormat(fmt):
 | 
			
		||||
@@ -49,7 +49,7 @@ def MakeBomTemplate(fmt):
 | 
			
		||||
    return DownloadFile(data, filename)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=False, stock_data=False, supplier_data=False):
 | 
			
		||||
def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=False, stock_data=False, supplier_data=False, manufacturer_data=False):
 | 
			
		||||
    """ Export a BOM (Bill of Materials) for a given part.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
@@ -160,7 +160,123 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
 | 
			
		||||
        # Add stock columns to dataset
 | 
			
		||||
        add_columns_to_dataset(stock_cols, len(bom_items))
 | 
			
		||||
 | 
			
		||||
    if supplier_data:
 | 
			
		||||
    if manufacturer_data and supplier_data:
 | 
			
		||||
        """
 | 
			
		||||
        If requested, add extra columns for each SupplierPart and ManufacturerPart associated with each line item
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # Expand dataset with manufacturer parts
 | 
			
		||||
        manufacturer_headers = [
 | 
			
		||||
            _('Manufacturer'),
 | 
			
		||||
            _('MPN'),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        supplier_headers = [
 | 
			
		||||
            _('Supplier'),
 | 
			
		||||
            _('SKU'),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        manufacturer_cols = {}
 | 
			
		||||
 | 
			
		||||
        for b_idx, bom_item in enumerate(bom_items):
 | 
			
		||||
            # Get part instance
 | 
			
		||||
            b_part = bom_item.sub_part
 | 
			
		||||
 | 
			
		||||
            # Filter manufacturer parts
 | 
			
		||||
            manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk)
 | 
			
		||||
            manufacturer_parts = manufacturer_parts.prefetch_related('supplier_parts')
 | 
			
		||||
            
 | 
			
		||||
            # Process manufacturer part
 | 
			
		||||
            for manufacturer_idx, manufacturer_part in enumerate(manufacturer_parts):
 | 
			
		||||
 | 
			
		||||
                if manufacturer_part:
 | 
			
		||||
                    manufacturer_name = manufacturer_part.manufacturer.name
 | 
			
		||||
                else:
 | 
			
		||||
                    manufacturer_name = ''
 | 
			
		||||
 | 
			
		||||
                manufacturer_mpn = manufacturer_part.MPN
 | 
			
		||||
 | 
			
		||||
                # Generate column names for this manufacturer
 | 
			
		||||
                k_man = manufacturer_headers[0] + "_" + str(manufacturer_idx)
 | 
			
		||||
                k_mpn = manufacturer_headers[1] + "_" + str(manufacturer_idx)
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    manufacturer_cols[k_man].update({b_idx: manufacturer_name})
 | 
			
		||||
                    manufacturer_cols[k_mpn].update({b_idx: manufacturer_mpn})
 | 
			
		||||
                except KeyError:
 | 
			
		||||
                    manufacturer_cols[k_man] = {b_idx: manufacturer_name}
 | 
			
		||||
                    manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn}
 | 
			
		||||
 | 
			
		||||
                # Process supplier parts
 | 
			
		||||
                for supplier_idx, supplier_part in enumerate(manufacturer_part.supplier_parts.all()):
 | 
			
		||||
 | 
			
		||||
                    if supplier_part.supplier:
 | 
			
		||||
                        supplier_name = supplier_part.supplier.name
 | 
			
		||||
                    else:
 | 
			
		||||
                        supplier_name = ''
 | 
			
		||||
 | 
			
		||||
                    supplier_sku = supplier_part.SKU
 | 
			
		||||
 | 
			
		||||
                    # Generate column names for this supplier
 | 
			
		||||
                    k_sup = str(supplier_headers[0]) + "_" + str(manufacturer_idx) + "_" + str(supplier_idx)
 | 
			
		||||
                    k_sku = str(supplier_headers[1]) + "_" + str(manufacturer_idx) + "_" + str(supplier_idx)
 | 
			
		||||
 | 
			
		||||
                    try:
 | 
			
		||||
                        manufacturer_cols[k_sup].update({b_idx: supplier_name})
 | 
			
		||||
                        manufacturer_cols[k_sku].update({b_idx: supplier_sku})
 | 
			
		||||
                    except KeyError:
 | 
			
		||||
                        manufacturer_cols[k_sup] = {b_idx: supplier_name}
 | 
			
		||||
                        manufacturer_cols[k_sku] = {b_idx: supplier_sku}
 | 
			
		||||
 | 
			
		||||
        # Add manufacturer columns to dataset
 | 
			
		||||
        add_columns_to_dataset(manufacturer_cols, len(bom_items))
 | 
			
		||||
 | 
			
		||||
    elif manufacturer_data:
 | 
			
		||||
        """
 | 
			
		||||
        If requested, add extra columns for each ManufacturerPart associated with each line item
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # Expand dataset with manufacturer parts
 | 
			
		||||
        manufacturer_headers = [
 | 
			
		||||
            _('Manufacturer'),
 | 
			
		||||
            _('MPN'),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        manufacturer_cols = {}
 | 
			
		||||
 | 
			
		||||
        for b_idx, bom_item in enumerate(bom_items):
 | 
			
		||||
            # Get part instance
 | 
			
		||||
            b_part = bom_item.sub_part
 | 
			
		||||
 | 
			
		||||
            # Filter supplier parts
 | 
			
		||||
            manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk)
 | 
			
		||||
            
 | 
			
		||||
            for idx, manufacturer_part in enumerate(manufacturer_parts):
 | 
			
		||||
 | 
			
		||||
                if manufacturer_part:
 | 
			
		||||
                    manufacturer_name = manufacturer_part.manufacturer.name
 | 
			
		||||
                else:
 | 
			
		||||
                    manufacturer_name = ''
 | 
			
		||||
 | 
			
		||||
                manufacturer_mpn = manufacturer_part.MPN
 | 
			
		||||
 | 
			
		||||
                # Add manufacturer data to the manufacturer columns
 | 
			
		||||
 | 
			
		||||
                # Generate column names for this manufacturer
 | 
			
		||||
                k_man = manufacturer_headers[0] + "_" + str(idx)
 | 
			
		||||
                k_mpn = manufacturer_headers[1] + "_" + str(idx)
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    manufacturer_cols[k_man].update({b_idx: manufacturer_name})
 | 
			
		||||
                    manufacturer_cols[k_mpn].update({b_idx: manufacturer_mpn})
 | 
			
		||||
                except KeyError:
 | 
			
		||||
                    manufacturer_cols[k_man] = {b_idx: manufacturer_name}
 | 
			
		||||
                    manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn}
 | 
			
		||||
 | 
			
		||||
        # Add manufacturer columns to dataset
 | 
			
		||||
        add_columns_to_dataset(manufacturer_cols, len(bom_items))
 | 
			
		||||
 | 
			
		||||
    elif supplier_data:
 | 
			
		||||
        """
 | 
			
		||||
        If requested, add extra columns for each SupplierPart associated with each line item
 | 
			
		||||
        """
 | 
			
		||||
@@ -169,8 +285,6 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
 | 
			
		||||
        manufacturer_headers = [
 | 
			
		||||
            _('Supplier'),
 | 
			
		||||
            _('SKU'),
 | 
			
		||||
            _('Manufacturer'),
 | 
			
		||||
            _('MPN'),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        manufacturer_cols = {}
 | 
			
		||||
@@ -191,31 +305,18 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
 | 
			
		||||
 | 
			
		||||
                supplier_sku = supplier_part.SKU
 | 
			
		||||
 | 
			
		||||
                if supplier_part.manufacturer:
 | 
			
		||||
                    manufacturer_name = supplier_part.manufacturer.name
 | 
			
		||||
                else:
 | 
			
		||||
                    manufacturer_name = ''
 | 
			
		||||
 | 
			
		||||
                manufacturer_mpn = supplier_part.MPN
 | 
			
		||||
 | 
			
		||||
                # Add manufacturer data to the manufacturer columns
 | 
			
		||||
 | 
			
		||||
                # Generate column names for this supplier
 | 
			
		||||
                k_sup = manufacturer_headers[0] + "_" + str(idx)
 | 
			
		||||
                k_sku = manufacturer_headers[1] + "_" + str(idx)
 | 
			
		||||
                k_man = manufacturer_headers[2] + "_" + str(idx)
 | 
			
		||||
                k_mpn = manufacturer_headers[3] + "_" + str(idx)
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    manufacturer_cols[k_sup].update({b_idx: supplier_name})
 | 
			
		||||
                    manufacturer_cols[k_sku].update({b_idx: supplier_sku})
 | 
			
		||||
                    manufacturer_cols[k_man].update({b_idx: manufacturer_name})
 | 
			
		||||
                    manufacturer_cols[k_mpn].update({b_idx: manufacturer_mpn})
 | 
			
		||||
                except KeyError:
 | 
			
		||||
                    manufacturer_cols[k_sup] = {b_idx: supplier_name}
 | 
			
		||||
                    manufacturer_cols[k_sku] = {b_idx: supplier_sku}
 | 
			
		||||
                    manufacturer_cols[k_man] = {b_idx: manufacturer_name}
 | 
			
		||||
                    manufacturer_cols[k_mpn] = {b_idx: manufacturer_mpn}
 | 
			
		||||
 | 
			
		||||
        # Add manufacturer columns to dataset
 | 
			
		||||
        add_columns_to_dataset(manufacturer_cols, len(bom_items))
 | 
			
		||||
 
 | 
			
		||||
@@ -95,9 +95,11 @@ class BomExportForm(forms.Form):
 | 
			
		||||
    parameter_data = forms.BooleanField(label=_("Include Parameter Data"), required=False, initial=False, help_text=_("Include part parameters data in exported BOM"))
 | 
			
		||||
 | 
			
		||||
    stock_data = forms.BooleanField(label=_("Include Stock Data"), required=False, initial=False, help_text=_("Include part stock data in exported BOM"))
 | 
			
		||||
    
 | 
			
		||||
    manufacturer_data = forms.BooleanField(label=_("Include Manufacturer Data"), required=False, initial=True, help_text=_("Include part manufacturer data in exported BOM"))
 | 
			
		||||
 | 
			
		||||
    supplier_data = forms.BooleanField(label=_("Include Supplier Data"), required=False, initial=True, help_text=_("Include part supplier data in exported BOM"))
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    def get_choices(self):
 | 
			
		||||
        """ BOM export format choices """
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								InvenTree/part/templates/part/manufacturer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								InvenTree/part/templates/part/manufacturer.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
{% extends "part/part_base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
 | 
			
		||||
{% block menubar %}
 | 
			
		||||
{% include 'part/navbar.html' with tab='manufacturers' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block heading %}
 | 
			
		||||
{% trans "Part Manufacturers" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block details %}
 | 
			
		||||
 | 
			
		||||
<div id='button-toolbar'>
 | 
			
		||||
    <div class='btn-group'>
 | 
			
		||||
        <button class="btn btn-success" id='manufacturer-create'>
 | 
			
		||||
            <span class='fas fa-plus-circle'></span> {% trans "New Manufacturer Part" %}
 | 
			
		||||
        </button>
 | 
			
		||||
        <div id='opt-dropdown' class="btn-group">
 | 
			
		||||
            <button id='manufacturer-part-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
 | 
			
		||||
            <ul class="dropdown-menu">
 | 
			
		||||
                <li><a href='#' id='manufacturer-part-delete' title='{% trans "Delete manufacturer parts" %}'>{% trans "Delete" %}</a></li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<table class="table table-striped table-condensed" id='manufacturer-table' data-toolbar='#button-toolbar'>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block js_load %}
 | 
			
		||||
{{ block.super }}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block js_ready %}
 | 
			
		||||
    {{ block.super }}
 | 
			
		||||
 | 
			
		||||
    $('#manufacturer-create').click(function () {
 | 
			
		||||
        launchModalForm(
 | 
			
		||||
            "{% url 'manufacturer-part-create' %}",
 | 
			
		||||
            {
 | 
			
		||||
                reload: true,
 | 
			
		||||
                data: {
 | 
			
		||||
                    part: {{ part.id }}
 | 
			
		||||
                },
 | 
			
		||||
                secondary: [
 | 
			
		||||
                    {
 | 
			
		||||
                        field: 'manufacturer',
 | 
			
		||||
                        label: '{% trans "New Manufacturer" %}',
 | 
			
		||||
                        title: '{% trans "Create new manufacturer" %}',
 | 
			
		||||
                        url: "{% url 'manufacturer-create' %}",
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#manufacturer-part-delete").click(function() {
 | 
			
		||||
        
 | 
			
		||||
        var selections = $("#manufacturer-table").bootstrapTable("getSelections");
 | 
			
		||||
 | 
			
		||||
        var parts = [];
 | 
			
		||||
 | 
			
		||||
        selections.forEach(function(item) {
 | 
			
		||||
            parts.push(item.pk);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        launchModalForm("{% url 'manufacturer-part-delete' %}", {
 | 
			
		||||
            data: {
 | 
			
		||||
                parts: parts,
 | 
			
		||||
            },
 | 
			
		||||
            reload: true,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    loadManufacturerPartTable(
 | 
			
		||||
        "#manufacturer-table",
 | 
			
		||||
        "{% url 'api-manufacturer-part-list' %}",
 | 
			
		||||
        {
 | 
			
		||||
            params: {
 | 
			
		||||
                part: {{ part.id }},
 | 
			
		||||
                part_detail: false,
 | 
			
		||||
                manufacturer_detail: true,
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    linkButtonsToSelection($("#manufacturer-table"), ['#manufacturer-part-options'])
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -69,6 +69,12 @@
 | 
			
		||||
    </li>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% if part.purchaseable and roles.purchase_order.view %}
 | 
			
		||||
    <li class='list-group-item {% if tab == "manufacturers" %}active{% endif %}' title='{% trans "Manufacturers" %}'>
 | 
			
		||||
        <a href='{% url "part-manufacturers" part.id %}'>
 | 
			
		||||
            <span class='menu-tab-icon fas fa-industry'></span>
 | 
			
		||||
            {% trans "Manufacturers" %}
 | 
			
		||||
        </a>
 | 
			
		||||
    </li>
 | 
			
		||||
    <li class='list-group-item {% if tab == "suppliers" %}active{% endif %}' title='{% trans "Suppliers" %}'>
 | 
			
		||||
        <a href='{% url "part-suppliers" part.id %}'>
 | 
			
		||||
            <span class='menu-tab-icon fas fa-building'></span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,15 @@
 | 
			
		||||
{% extends "modal_form.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block pre_form_content %}
 | 
			
		||||
 | 
			
		||||
<div class='alert alert-block alert-danger'>
 | 
			
		||||
    Are you sure you want to delete part '<b>{{ part.full_name }}</b>'?
 | 
			
		||||
    {% trans "Are you sure you want to delete part" %} '<b>{{ part.full_name }}</b>'?
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% if part.used_in_count %}
 | 
			
		||||
<hr>
 | 
			
		||||
<p>This part is used in BOMs for {{ part.used_in_count }} other parts. If you delete this part, the BOMs for the following parts will be updated:
 | 
			
		||||
<p>{% trans "This part is used in BOMs for" %} {{ part.used_in_count }} {% trans "other parts. If you delete this part, the BOMs for the following parts will be updated" %}:
 | 
			
		||||
<ul class="list-group">
 | 
			
		||||
    {% for child in part.used_in.all %}
 | 
			
		||||
    <li class='list-group-item'>{{ child.part.full_name }} - {{ child.part.description }}</li>
 | 
			
		||||
@@ -18,7 +19,7 @@
 | 
			
		||||
 | 
			
		||||
{% if part.stock_items.all|length > 0 %}
 | 
			
		||||
<hr>
 | 
			
		||||
<p>There are {{ part.stock_items.all|length }} stock entries defined for this part. If you delete this part, the following stock entries will also be deleted:
 | 
			
		||||
<p>{% trans "There are" %} {{ part.stock_items.all|length }} {% trans "stock entries defined for this part. If you delete this part, the following stock entries will also be deleted" %}:
 | 
			
		||||
    <ul class='list-group'>
 | 
			
		||||
        {% for stock in part.stock_items.all %}
 | 
			
		||||
        <li class='list-group-item'>{{ stock }}</li>
 | 
			
		||||
@@ -27,9 +28,20 @@
 | 
			
		||||
</p>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% if part.manufacturer_parts.all|length > 0 %}
 | 
			
		||||
<hr>
 | 
			
		||||
<p>{% trans "There are" %} {{ part.manufacturer_parts.all|length }} {% trans "manufacturers defined for this part. If you delete this part, the following manufacturer parts will also be deleted" %}:
 | 
			
		||||
    <ul class='list-group'>
 | 
			
		||||
        {% for spart in part.manufacturer_parts.all %}
 | 
			
		||||
        <li class='list-group-item'>{{ spart.manufacturer.name }} - {{ spart.MPN }}</li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </ul>
 | 
			
		||||
</p>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% if part.supplier_parts.all|length > 0 %}
 | 
			
		||||
<hr>
 | 
			
		||||
<p>There are {{ part.supplier_parts.all|length }} suppliers defined for this part. If you delete this part, the following supplier parts will also be deleted.
 | 
			
		||||
<p>{% trans "There are" %} {{ part.supplier_parts.all|length }} {% trans "suppliers defined for this part. If you delete this part, the following supplier parts will also be deleted" %}:
 | 
			
		||||
    <ul class='list-group'>
 | 
			
		||||
        {% for spart in part.supplier_parts.all %}
 | 
			
		||||
        <li class='list-group-item'>{{ spart.supplier.name }} - {{ spart.SKU }}</li>
 | 
			
		||||
@@ -40,7 +52,7 @@
 | 
			
		||||
 | 
			
		||||
{% if part.serials.all|length > 0 %}
 | 
			
		||||
<hr>
 | 
			
		||||
<p>There are {{ part.serials.all|length }} unique parts tracked for '{{ part.full_name }}'. Deleting this part will permanently remove this tracking information.</p>
 | 
			
		||||
<p>{% trans "There are" %} {{ part.serials.all|length }} {% trans "unique parts tracked for" %} '{{ part.full_name }}'. {% trans "Deleting this part will permanently remove this tracking information" %}.</p>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -85,7 +85,7 @@
 | 
			
		||||
        {
 | 
			
		||||
            params: {
 | 
			
		||||
                part: {{ part.id }},
 | 
			
		||||
                part_detail: true,
 | 
			
		||||
                part_detail: false,
 | 
			
		||||
                supplier_detail: true,
 | 
			
		||||
                manufacturer_detail: true,
 | 
			
		||||
            },
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ part_detail_urls = [
 | 
			
		||||
    url(r'^bom/?', views.PartDetail.as_view(template_name='part/bom.html'), name='part-bom'),
 | 
			
		||||
    url(r'^build/?', views.PartDetail.as_view(template_name='part/build.html'), name='part-build'),
 | 
			
		||||
    url(r'^used/?', views.PartDetail.as_view(template_name='part/used_in.html'), name='part-used-in'),
 | 
			
		||||
    url(r'^manufacturers/?', views.PartDetail.as_view(template_name='part/manufacturer.html'), name='part-manufacturers'),
 | 
			
		||||
    url(r'^suppliers/?', views.PartDetail.as_view(template_name='part/supplier.html'), name='part-suppliers'),
 | 
			
		||||
    url(r'^orders/?', views.PartDetail.as_view(template_name='part/orders.html'), name='part-orders'),
 | 
			
		||||
    url(r'^sales-orders/', views.PartDetail.as_view(template_name='part/sales_orders.html'), name='part-sales-orders'),
 | 
			
		||||
 
 | 
			
		||||
@@ -1845,6 +1845,8 @@ class BomDownload(AjaxView):
 | 
			
		||||
 | 
			
		||||
        supplier_data = str2bool(request.GET.get('supplier_data', False))
 | 
			
		||||
 | 
			
		||||
        manufacturer_data = str2bool(request.GET.get('manufacturer_data', False))
 | 
			
		||||
 | 
			
		||||
        levels = request.GET.get('levels', None)
 | 
			
		||||
 | 
			
		||||
        if levels is not None:
 | 
			
		||||
@@ -1866,7 +1868,9 @@ class BomDownload(AjaxView):
 | 
			
		||||
                         max_levels=levels,
 | 
			
		||||
                         parameter_data=parameter_data,
 | 
			
		||||
                         stock_data=stock_data,
 | 
			
		||||
                         supplier_data=supplier_data)
 | 
			
		||||
                         supplier_data=supplier_data,
 | 
			
		||||
                         manufacturer_data=manufacturer_data,
 | 
			
		||||
                         )
 | 
			
		||||
 | 
			
		||||
    def get_data(self):
 | 
			
		||||
        return {
 | 
			
		||||
@@ -1896,6 +1900,7 @@ class BomExport(AjaxView):
 | 
			
		||||
        parameter_data = str2bool(request.POST.get('parameter_data', False))
 | 
			
		||||
        stock_data = str2bool(request.POST.get('stock_data', False))
 | 
			
		||||
        supplier_data = str2bool(request.POST.get('supplier_data', False))
 | 
			
		||||
        manufacturer_data = str2bool(request.POST.get('manufacturer_data', False))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            part = Part.objects.get(pk=self.kwargs['pk'])
 | 
			
		||||
@@ -1913,6 +1918,7 @@ class BomExport(AjaxView):
 | 
			
		||||
        url += '¶meter_data=' + str(parameter_data)
 | 
			
		||||
        url += '&stock_data=' + str(stock_data)
 | 
			
		||||
        url += '&supplier_data=' + str(supplier_data)
 | 
			
		||||
        url += '&manufacturer_data=' + str(manufacturer_data)
 | 
			
		||||
 | 
			
		||||
        if levels:
 | 
			
		||||
            url += '&levels=' + str(levels)
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ from rest_framework import generics, filters, permissions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StockCategoryTree(TreeSerializer):
 | 
			
		||||
    title = 'Stock'
 | 
			
		||||
    title = _('Stock')
 | 
			
		||||
    model = StockLocation
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@@ -774,7 +774,7 @@ class StockList(generics.ListCreateAPIView):
 | 
			
		||||
        company = params.get('company', None)
 | 
			
		||||
 | 
			
		||||
        if company is not None:
 | 
			
		||||
            queryset = queryset.filter(Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer=company))
 | 
			
		||||
            queryset = queryset.filter(Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer_part__manufacturer=company))
 | 
			
		||||
 | 
			
		||||
        # Filter by supplier
 | 
			
		||||
        supplier = params.get('supplier', None)
 | 
			
		||||
@@ -786,7 +786,7 @@ class StockList(generics.ListCreateAPIView):
 | 
			
		||||
        manufacturer = params.get('manufacturer', None)
 | 
			
		||||
 | 
			
		||||
        if manufacturer is not None:
 | 
			
		||||
            queryset = queryset.filter(supplier_part__manufacturer=manufacturer)
 | 
			
		||||
            queryset = queryset.filter(supplier_part__manufacturer_part__manufacturer=manufacturer)
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        Filter by the 'last updated' date of the stock item(s):
 | 
			
		||||
 
 | 
			
		||||
@@ -344,6 +344,8 @@ class StockItem(MPTTModel):
 | 
			
		||||
            "stockitem",
 | 
			
		||||
            self.id,
 | 
			
		||||
            {
 | 
			
		||||
                "request": kwargs.get('request', None),
 | 
			
		||||
                "item_url": reverse('stock-item-detail', kwargs={'pk': self.id}),
 | 
			
		||||
                "url": reverse('api-stock-detail', kwargs={'pk': self.id}),
 | 
			
		||||
            },
 | 
			
		||||
            **kwargs
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
 | 
			
		||||
            'sales_order',
 | 
			
		||||
            'supplier_part',
 | 
			
		||||
            'supplier_part__supplier',
 | 
			
		||||
            'supplier_part__manufacturer',
 | 
			
		||||
            'supplier_part__manufacturer_part__manufacturer',
 | 
			
		||||
            'allocations',
 | 
			
		||||
            'sales_order_allocations',
 | 
			
		||||
            'location',
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,16 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
 | 
			
		||||
    {% if item.supplier_part %}
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><span class='fas fa-industry'></span></td>
 | 
			
		||||
        <td>{% trans "Manufacturer" %}</td>
 | 
			
		||||
        <td><a href="{% url 'company-detail' item.supplier_part.manufacturer_part.manufacturer.id %}">{{ item.supplier_part.manufacturer_part.manufacturer.name }}</a></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><span class='fas fa-hashtag'></span></td>
 | 
			
		||||
        <td>{% trans "Manufacturer Part" %}</td>
 | 
			
		||||
        <td><a href="{% url 'manufacturer-part-detail' item.supplier_part.manufacturer_part.id %}">{{ item.supplier_part.manufacturer_part.MPN }}</a></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><span class='fas fa-building'></span></td>
 | 
			
		||||
        <td>{% trans "Supplier" %}</td>
 | 
			
		||||
        <td><a href="{% url 'company-detail' item.supplier_part.supplier.id %}">{{ item.supplier_part.supplier.name }}</a></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load inventree_extras %}
 | 
			
		||||
 | 
			
		||||
{% block page_title %}
 | 
			
		||||
InvenTree | {% trans "Search Results" %}
 | 
			
		||||
@@ -145,6 +146,21 @@ InvenTree | {% trans "Search Results" %}
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    addItem('manufacturer-part', '{% trans "Manufacturer Parts" %}', 'fa-toolbox');
 | 
			
		||||
 | 
			
		||||
    loadManufacturerPartTable(
 | 
			
		||||
        "#table-manufacturer-part",
 | 
			
		||||
        "{% url 'api-manufacturer-part-list' %}",
 | 
			
		||||
        {
 | 
			
		||||
            params: {
 | 
			
		||||
                search: "{{ query }}",
 | 
			
		||||
                part_detail: true,
 | 
			
		||||
                supplier_detail: true,
 | 
			
		||||
                manufacturer_detail: true
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    addItem('supplier-part', '{% trans "Supplier Parts" %}', 'fa-pallet');
 | 
			
		||||
 | 
			
		||||
    loadSupplierPartTable(
 | 
			
		||||
@@ -287,6 +303,15 @@ InvenTree | {% trans "Search Results" %}
 | 
			
		||||
    {% if roles.purchase_order.view or roles.sales_order.view %}
 | 
			
		||||
    addItemTitle('{% trans "Company" %}');
 | 
			
		||||
 | 
			
		||||
    addItem('manufacturer', '{% trans "Manufacturers" %}', 'fa-industry');
 | 
			
		||||
 | 
			
		||||
    loadCompanyTable('#table-manufacturer', "{% url 'api-company-list' %}", {
 | 
			
		||||
        params: {
 | 
			
		||||
            search: "{{ query }}",
 | 
			
		||||
            is_manufacturer: "true",
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    {% if roles.purchase_order.view %}
 | 
			
		||||
    addItem('supplier', '{% trans "Suppliers" %}', 'fa-building');
 | 
			
		||||
 | 
			
		||||
@@ -305,16 +330,6 @@ InvenTree | {% trans "Search Results" %}
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    addItem('manufacturer', '{% trans "Manufacturers" %}', 'fa-industry');
 | 
			
		||||
 | 
			
		||||
    loadCompanyTable('#table-manufacturer', "{% url 'api-company-list' %}", {
 | 
			
		||||
        params: {
 | 
			
		||||
            search: "{{ query }}",
 | 
			
		||||
            is_manufacturer: "true",
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if roles.sales_order.view %}
 | 
			
		||||
 
 | 
			
		||||
@@ -101,6 +101,104 @@ function loadCompanyTable(table, url, options={}) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function loadManufacturerPartTable(table, url, options) {
 | 
			
		||||
    /*
 | 
			
		||||
     * Load manufacturer part table
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    // Query parameters
 | 
			
		||||
    var params = options.params || {};
 | 
			
		||||
 | 
			
		||||
    // Load filters
 | 
			
		||||
    var filters = loadTableFilters("manufacturer-part");
 | 
			
		||||
 | 
			
		||||
    for (var key in params) {
 | 
			
		||||
        filters[key] = params[key];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupFilterList("manufacturer-part", $(table));
 | 
			
		||||
 | 
			
		||||
    $(table).inventreeTable({
 | 
			
		||||
        url: url,
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        original: params,
 | 
			
		||||
        queryParams: filters,
 | 
			
		||||
        name: 'manufacturerparts',
 | 
			
		||||
        groupBy: false,
 | 
			
		||||
        formatNoMatches: function() { return "{% trans "No manufacturer parts found" %}"; },
 | 
			
		||||
        columns: [
 | 
			
		||||
            {
 | 
			
		||||
                checkbox: true,
 | 
			
		||||
                switchable: false,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                visible: params['part_detail'],
 | 
			
		||||
                switchable: params['part_detail'],
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                field: 'part_detail.full_name',
 | 
			
		||||
                title: '{% trans "Part" %}',
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
 | 
			
		||||
                    var url = `/part/${row.part}/`;
 | 
			
		||||
 | 
			
		||||
                    var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, url);
 | 
			
		||||
 | 
			
		||||
                    if (row.part_detail.is_template) {
 | 
			
		||||
                        html += `<span class='fas fa-clone label-right' title='{% trans "Template part" %}'></span>`;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (row.part_detail.assembly) {
 | 
			
		||||
                        html += `<span class='fas fa-tools label-right' title='{% trans "Assembled part" %}'></span>`;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!row.part_detail.active) {
 | 
			
		||||
                        html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return html;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                field: 'manufacturer',
 | 
			
		||||
                title: '{% trans "Manufacturer" %}',
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
                    if (value && row.manufacturer_detail) {
 | 
			
		||||
                        var name = row.manufacturer_detail.name;
 | 
			
		||||
                        var url = `/company/${value}/`;
 | 
			
		||||
                        var html = imageHoverIcon(row.manufacturer_detail.image) + renderLink(name, url);
 | 
			
		||||
 | 
			
		||||
                        return html;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return "-";
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                field: 'MPN',
 | 
			
		||||
                title: '{% trans "MPN" %}',
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
                    return renderLink(value, `/manufacturer-part/${row.pk}/`);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                field: 'link',
 | 
			
		||||
                title: '{% trans "Link" %}',
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
                    if (value) {
 | 
			
		||||
                        return renderLink(value, value);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return '';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function loadSupplierPartTable(table, url, options) {
 | 
			
		||||
    /*
 | 
			
		||||
     * Load supplier part table
 | 
			
		||||
@@ -133,10 +231,11 @@ function loadSupplierPartTable(table, url, options) {
 | 
			
		||||
                switchable: false,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                visible: params['part_detail'],
 | 
			
		||||
                switchable: params['part_detail'],
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                field: 'part_detail.full_name',
 | 
			
		||||
                title: '{% trans "Part" %}',
 | 
			
		||||
                switchable: false,
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
 | 
			
		||||
                    var url = `/part/${row.part}/`;
 | 
			
		||||
@@ -183,6 +282,8 @@ function loadSupplierPartTable(table, url, options) {
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                visible: params['manufacturer_detail'],
 | 
			
		||||
                switchable: params['manufacturer_detail'],
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                field: 'manufacturer',
 | 
			
		||||
                title: '{% trans "Manufacturer" %}',
 | 
			
		||||
@@ -199,9 +300,18 @@ function loadSupplierPartTable(table, url, options) {
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                visible: params['manufacturer_detail'],
 | 
			
		||||
                switchable: params['manufacturer_detail'],
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                field: 'MPN',
 | 
			
		||||
                title: '{% trans "MPN" %}',
 | 
			
		||||
                formatter: function(value, row, index, field) {
 | 
			
		||||
                    if (value && row.manufacturer_part) {
 | 
			
		||||
                        return renderLink(value, `/manufacturer-part/${row.manufacturer_part.pk}/`);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return "-";
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                field: 'link',
 | 
			
		||||
 
 | 
			
		||||
@@ -164,11 +164,11 @@ function getFilterOptionList(tableKey, filterKey) {
 | 
			
		||||
        return {
 | 
			
		||||
            '1': {
 | 
			
		||||
                key: '1',
 | 
			
		||||
                value: 'true',
 | 
			
		||||
                value: '{% trans "true" %}',
 | 
			
		||||
            },
 | 
			
		||||
            '0': {
 | 
			
		||||
                key: '0',
 | 
			
		||||
                value: 'false',
 | 
			
		||||
                value: '{% trans "false" %}',
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    } else if ('options' in settings) {
 | 
			
		||||
@@ -394,8 +394,8 @@ function getFilterOptionValue(tableKey, filterKey, valueKey) {
 | 
			
		||||
 | 
			
		||||
    // Lookup for boolean options
 | 
			
		||||
    if (filter.type == 'bool') {
 | 
			
		||||
        if (value == '1') return 'true';
 | 
			
		||||
        if (value == '0') return 'false';
 | 
			
		||||
        if (value == '1') return '{% trans "true" %}';
 | 
			
		||||
        if (value == '0') return '{% trans "false" %}';
 | 
			
		||||
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -354,7 +354,7 @@ function loadStockTable(table, options) {
 | 
			
		||||
                var html = imageHoverIcon(row.part_detail.thumbnail);
 | 
			
		||||
 | 
			
		||||
                html += row.part_detail.full_name;
 | 
			
		||||
                html += ` <i>(${data.length} items)</i>`;
 | 
			
		||||
                html += ` <i>(${data.length} {% trans "items" %})</i>`;
 | 
			
		||||
 | 
			
		||||
                html += makePartIcons(row.part_detail);
 | 
			
		||||
 | 
			
		||||
@@ -446,7 +446,7 @@ function loadStockTable(table, options) {
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (batches.length > 1) {
 | 
			
		||||
                    return "" + batches.length + " batches";
 | 
			
		||||
                    return "" + batches.length + " {% trans 'batches' %}";
 | 
			
		||||
                } else if (batches.length == 1) {
 | 
			
		||||
                    if (batches[0]) {
 | 
			
		||||
                        return batches[0];
 | 
			
		||||
@@ -473,9 +473,9 @@ function loadStockTable(table, options) {
 | 
			
		||||
                    // Single location, easy!
 | 
			
		||||
                    return locations[0];
 | 
			
		||||
                } else if (locations.length > 1) {
 | 
			
		||||
                    return "In " + locations.length + " locations";
 | 
			
		||||
                    return "In " + locations.length + " {% trans 'locations' %}";
 | 
			
		||||
                } else {
 | 
			
		||||
                    return "<i>{% trans "Undefined location" %}</i>";
 | 
			
		||||
                    return "<i>{% trans 'Undefined location' %}</i>";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (field == 'notes') {
 | 
			
		||||
                var notes = [];
 | 
			
		||||
@@ -1219,7 +1219,7 @@ function loadInstalledInTable(table, options) {
 | 
			
		||||
                                // Add some buttons yo!
 | 
			
		||||
                                html += `<div class='btn-group float-right' role='group'>`;
 | 
			
		||||
                                
 | 
			
		||||
                                html += makeIconButton('fa-unlink', 'button-uninstall', pk, "{% trans "Uninstall stock item" %}");
 | 
			
		||||
                                html += makeIconButton('fa-unlink', 'button-uninstall', pk, "{% trans 'Uninstall stock item' %}");
 | 
			
		||||
 | 
			
		||||
                                html += `</div>`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,7 @@ class RuleSet(models.Model):
 | 
			
		||||
            'part_partrelated',
 | 
			
		||||
            'part_partstar',
 | 
			
		||||
            'company_supplierpart',
 | 
			
		||||
            'company_manufacturerpart',
 | 
			
		||||
        ],
 | 
			
		||||
        'stock_location': [
 | 
			
		||||
            'stock_stocklocation',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user