mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 05:26:45 +00:00
commit
522432f4aa
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import StockItemLabel, StockLocationLabel
|
from .models import StockItemLabel, StockLocationLabel, PartLabel
|
||||||
|
|
||||||
|
|
||||||
class LabelAdmin(admin.ModelAdmin):
|
class LabelAdmin(admin.ModelAdmin):
|
||||||
@ -13,3 +13,4 @@ class LabelAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
admin.site.register(StockItemLabel, LabelAdmin)
|
admin.site.register(StockItemLabel, LabelAdmin)
|
||||||
admin.site.register(StockLocationLabel, LabelAdmin)
|
admin.site.register(StockLocationLabel, LabelAdmin)
|
||||||
|
admin.site.register(PartLabel, LabelAdmin)
|
||||||
|
@ -15,9 +15,10 @@ import InvenTree.helpers
|
|||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
|
from part.models import Part
|
||||||
|
|
||||||
from .models import StockItemLabel, StockLocationLabel
|
from .models import StockItemLabel, StockLocationLabel, PartLabel
|
||||||
from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer
|
from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer, PartLabelSerializer
|
||||||
|
|
||||||
|
|
||||||
class LabelListView(generics.ListAPIView):
|
class LabelListView(generics.ListAPIView):
|
||||||
@ -132,6 +133,7 @@ class StockItemLabelMixin:
|
|||||||
for key in ['item', 'item[]', 'items', 'items[]']:
|
for key in ['item', 'item[]', 'items', 'items[]']:
|
||||||
if key in params:
|
if key in params:
|
||||||
items = params.getlist(key, [])
|
items = params.getlist(key, [])
|
||||||
|
break
|
||||||
|
|
||||||
valid_ids = []
|
valid_ids = []
|
||||||
|
|
||||||
@ -376,6 +378,112 @@ class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin,
|
|||||||
return self.print(request, locations)
|
return self.print(request, locations)
|
||||||
|
|
||||||
|
|
||||||
|
class PartLabelMixin:
|
||||||
|
"""
|
||||||
|
Mixin for extracting Part objects from query parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_parts(self):
|
||||||
|
"""
|
||||||
|
Return a list of requested Part objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
params = self.request.query_params
|
||||||
|
|
||||||
|
for key in ['part', 'part[]', 'parts', 'parts[]']:
|
||||||
|
if key in params:
|
||||||
|
parts = params.getlist(key, [])
|
||||||
|
break
|
||||||
|
|
||||||
|
valid_ids = []
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
try:
|
||||||
|
valid_ids.append(int(part))
|
||||||
|
except (ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# List of Part objects which match provided values
|
||||||
|
return Part.objects.filter(pk__in=valid_ids)
|
||||||
|
|
||||||
|
|
||||||
|
class PartLabelList(LabelListView, PartLabelMixin):
|
||||||
|
"""
|
||||||
|
API endpoint for viewing list of PartLabel objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = PartLabel.objects.all()
|
||||||
|
serializer_class = PartLabelSerializer
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
parts = self.get_parts()
|
||||||
|
|
||||||
|
if len(parts) > 0:
|
||||||
|
|
||||||
|
valid_label_ids = set()
|
||||||
|
|
||||||
|
for label in queryset.all():
|
||||||
|
|
||||||
|
matches = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
filters = InvenTree.helpers.validateFilterString(label.filters)
|
||||||
|
except ValidationError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
|
||||||
|
part_query = Part.objects.filter(pk=part.pk)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not part_query.filter(**filters).exists():
|
||||||
|
matches = False
|
||||||
|
break
|
||||||
|
except FieldError:
|
||||||
|
matches = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
valid_label_ids.add(label.pk)
|
||||||
|
|
||||||
|
# Reduce queryset to only valid matches
|
||||||
|
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class PartLabelDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for a single PartLabel object
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = PartLabel.objects.all()
|
||||||
|
serializer_class = PartLabelSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PartLabelPrint(generics.RetrieveAPIView, PartLabelMixin, LabelPrintMixin):
|
||||||
|
"""
|
||||||
|
API endpoint for printing a PartLabel object
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = PartLabel.objects.all()
|
||||||
|
serializer_class = PartLabelSerializer
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Check if valid part(s) have been provided
|
||||||
|
"""
|
||||||
|
|
||||||
|
parts = self.get_parts()
|
||||||
|
|
||||||
|
return self.print(request, parts)
|
||||||
|
|
||||||
|
|
||||||
label_api_urls = [
|
label_api_urls = [
|
||||||
|
|
||||||
# Stock item labels
|
# Stock item labels
|
||||||
@ -401,4 +509,16 @@ label_api_urls = [
|
|||||||
# List view
|
# List view
|
||||||
url(r'^.*$', StockLocationLabelList.as_view(), name='api-stocklocation-label-list'),
|
url(r'^.*$', StockLocationLabelList.as_view(), name='api-stocklocation-label-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
# Part labels
|
||||||
|
url(r'^part/', include([
|
||||||
|
# Detail views
|
||||||
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
|
url(r'^print/', PartLabelPrint.as_view(), name='api-part-label-print'),
|
||||||
|
url(r'^.*$', PartLabelDetail.as_view(), name='api-part-label-detail'),
|
||||||
|
])),
|
||||||
|
|
||||||
|
# List view
|
||||||
|
url(r'^.*$', PartLabelList.as_view(), name='api-part-label-list'),
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
@ -37,6 +37,7 @@ class LabelConfig(AppConfig):
|
|||||||
if canAppAccessDatabase():
|
if canAppAccessDatabase():
|
||||||
self.create_stock_item_labels()
|
self.create_stock_item_labels()
|
||||||
self.create_stock_location_labels()
|
self.create_stock_location_labels()
|
||||||
|
self.create_part_labels()
|
||||||
|
|
||||||
def create_stock_item_labels(self):
|
def create_stock_item_labels(self):
|
||||||
"""
|
"""
|
||||||
@ -65,7 +66,7 @@ class LabelConfig(AppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(dst_dir):
|
if not os.path.exists(dst_dir):
|
||||||
logger.info(f"Creating missing directory: '{dst_dir}'")
|
logger.info(f"Creating required directory: '{dst_dir}'")
|
||||||
os.makedirs(dst_dir, exist_ok=True)
|
os.makedirs(dst_dir, exist_ok=True)
|
||||||
|
|
||||||
labels = [
|
labels = [
|
||||||
@ -109,7 +110,6 @@ class LabelConfig(AppConfig):
|
|||||||
logger.info(f"Copying label template '{dst_file}'")
|
logger.info(f"Copying label template '{dst_file}'")
|
||||||
shutil.copyfile(src_file, dst_file)
|
shutil.copyfile(src_file, dst_file)
|
||||||
|
|
||||||
try:
|
|
||||||
# Check if a label matching the template already exists
|
# Check if a label matching the template already exists
|
||||||
if StockItemLabel.objects.filter(label=filename).exists():
|
if StockItemLabel.objects.filter(label=filename).exists():
|
||||||
continue
|
continue
|
||||||
@ -125,8 +125,6 @@ class LabelConfig(AppConfig):
|
|||||||
width=label['width'],
|
width=label['width'],
|
||||||
height=label['height'],
|
height=label['height'],
|
||||||
)
|
)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_stock_location_labels(self):
|
def create_stock_location_labels(self):
|
||||||
"""
|
"""
|
||||||
@ -155,7 +153,7 @@ class LabelConfig(AppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(dst_dir):
|
if not os.path.exists(dst_dir):
|
||||||
logger.info(f"Creating missing directory: '{dst_dir}'")
|
logger.info(f"Creating required directory: '{dst_dir}'")
|
||||||
os.makedirs(dst_dir, exist_ok=True)
|
os.makedirs(dst_dir, exist_ok=True)
|
||||||
|
|
||||||
labels = [
|
labels = [
|
||||||
@ -206,7 +204,6 @@ class LabelConfig(AppConfig):
|
|||||||
logger.info(f"Copying label template '{dst_file}'")
|
logger.info(f"Copying label template '{dst_file}'")
|
||||||
shutil.copyfile(src_file, dst_file)
|
shutil.copyfile(src_file, dst_file)
|
||||||
|
|
||||||
try:
|
|
||||||
# Check if a label matching the template already exists
|
# Check if a label matching the template already exists
|
||||||
if StockLocationLabel.objects.filter(label=filename).exists():
|
if StockLocationLabel.objects.filter(label=filename).exists():
|
||||||
continue
|
continue
|
||||||
@ -222,5 +219,88 @@ class LabelConfig(AppConfig):
|
|||||||
width=label['width'],
|
width=label['width'],
|
||||||
height=label['height'],
|
height=label['height'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def create_part_labels(self):
|
||||||
|
"""
|
||||||
|
Create database entries for the default PartLabel templates,
|
||||||
|
if they do not already exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .models import PartLabel
|
||||||
except:
|
except:
|
||||||
pass
|
# Database might not yet be ready
|
||||||
|
return
|
||||||
|
|
||||||
|
src_dir = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)),
|
||||||
|
'templates',
|
||||||
|
'label',
|
||||||
|
'part',
|
||||||
|
)
|
||||||
|
|
||||||
|
dst_dir = os.path.join(
|
||||||
|
settings.MEDIA_ROOT,
|
||||||
|
'label',
|
||||||
|
'inventree',
|
||||||
|
'part',
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(dst_dir):
|
||||||
|
logger.info(f"Creating required directory: '{dst_dir}'")
|
||||||
|
os.makedirs(dst_dir, exist_ok=True)
|
||||||
|
|
||||||
|
labels = [
|
||||||
|
{
|
||||||
|
'file': 'part_label.html',
|
||||||
|
'name': 'Part Label',
|
||||||
|
'description': 'Simple part label',
|
||||||
|
'width': 70,
|
||||||
|
'height': 24,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for label in labels:
|
||||||
|
|
||||||
|
filename = os.path.join(
|
||||||
|
'label',
|
||||||
|
'inventree',
|
||||||
|
'part',
|
||||||
|
label['file']
|
||||||
|
)
|
||||||
|
|
||||||
|
src_file = os.path.join(src_dir, label['file'])
|
||||||
|
dst_file = os.path.join(settings.MEDIA_ROOT, filename)
|
||||||
|
|
||||||
|
to_copy = False
|
||||||
|
|
||||||
|
if os.path.exists(dst_file):
|
||||||
|
# File already exists - let's see if it is the "same"
|
||||||
|
|
||||||
|
if not hashFile(dst_file) == hashFile(src_file):
|
||||||
|
logger.info(f"Hash differs for '{filename}'")
|
||||||
|
to_copy = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.info(f"Label template '{filename}' is not present")
|
||||||
|
to_copy = True
|
||||||
|
|
||||||
|
if to_copy:
|
||||||
|
logger.info(f"Copying label template '{dst_file}'")
|
||||||
|
shutil.copyfile(src_file, dst_file)
|
||||||
|
|
||||||
|
# Check if a label matching the template already exists
|
||||||
|
if PartLabel.objects.filter(label=filename).exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Creating entry for PartLabel '{label['name']}'")
|
||||||
|
|
||||||
|
PartLabel.objects.create(
|
||||||
|
name=label['name'],
|
||||||
|
description=label['description'],
|
||||||
|
label=filename,
|
||||||
|
filters='',
|
||||||
|
enabled=True,
|
||||||
|
width=label['width'],
|
||||||
|
height=label['height'],
|
||||||
|
)
|
||||||
|
37
InvenTree/label/migrations/0008_auto_20210708_2106.py
Normal file
37
InvenTree/label/migrations/0008_auto_20210708_2106.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 3.2.4 on 2021-07-08 11:06
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import label.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('label', '0007_auto_20210513_1327'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PartLabel',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Label name', max_length=100, verbose_name='Name')),
|
||||||
|
('description', models.CharField(blank=True, help_text='Label description', max_length=250, null=True, verbose_name='Description')),
|
||||||
|
('label', models.FileField(help_text='Label template file', unique=True, upload_to=label.models.rename_label, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html'])], verbose_name='Label')),
|
||||||
|
('enabled', models.BooleanField(default=True, help_text='Label template is enabled', verbose_name='Enabled')),
|
||||||
|
('width', models.FloatField(default=50, help_text='Label width, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Width [mm]')),
|
||||||
|
('height', models.FloatField(default=20, help_text='Label height, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Height [mm]')),
|
||||||
|
('filename_pattern', models.CharField(default='label.pdf', help_text='Pattern for generating label filenames', max_length=100, verbose_name='Filename Pattern')),
|
||||||
|
('filters', models.CharField(blank=True, help_text='Part query filters (comma-separated value of key=value pairs)', max_length=250, validators=[label.models.validate_part_filters], verbose_name='Filters')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stockitemlabel',
|
||||||
|
name='filters',
|
||||||
|
field=models.CharField(blank=True, help_text='Query filters (comma-separated list of key=value pairs),', max_length=250, validators=[label.models.validate_stock_item_filters], verbose_name='Filters'),
|
||||||
|
),
|
||||||
|
]
|
@ -25,6 +25,8 @@ from InvenTree.helpers import validateFilterString, normalize
|
|||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
import stock.models
|
import stock.models
|
||||||
|
import part.models
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django_weasyprint import WeasyTemplateResponseMixin
|
from django_weasyprint import WeasyTemplateResponseMixin
|
||||||
@ -59,6 +61,13 @@ def validate_stock_location_filters(filters):
|
|||||||
return filters
|
return filters
|
||||||
|
|
||||||
|
|
||||||
|
def validate_part_filters(filters):
|
||||||
|
|
||||||
|
filters = validateFilterString(filters, model=part.models.Part)
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class WeasyprintLabelMixin(WeasyTemplateResponseMixin):
|
class WeasyprintLabelMixin(WeasyTemplateResponseMixin):
|
||||||
"""
|
"""
|
||||||
Class for rendering a label to a PDF
|
Class for rendering a label to a PDF
|
||||||
@ -246,10 +255,11 @@ class StockItemLabel(LabelTemplate):
|
|||||||
|
|
||||||
filters = models.CharField(
|
filters = models.CharField(
|
||||||
blank=True, max_length=250,
|
blank=True, max_length=250,
|
||||||
help_text=_('Query filters (comma-separated list of key=value pairs'),
|
help_text=_('Query filters (comma-separated list of key=value pairs),'),
|
||||||
verbose_name=_('Filters'),
|
verbose_name=_('Filters'),
|
||||||
validators=[
|
validators=[
|
||||||
validate_stock_item_filters]
|
validate_stock_item_filters
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def matches_stock_item(self, item):
|
def matches_stock_item(self, item):
|
||||||
@ -335,3 +345,57 @@ class StockLocationLabel(LabelTemplate):
|
|||||||
'location': location,
|
'location': location,
|
||||||
'qr_data': location.format_barcode(brief=True),
|
'qr_data': location.format_barcode(brief=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PartLabel(LabelTemplate):
|
||||||
|
"""
|
||||||
|
Template for printing Part labels
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_api_url():
|
||||||
|
return reverse('api-part-label-list')
|
||||||
|
|
||||||
|
SUBDIR = 'part'
|
||||||
|
|
||||||
|
filters = models.CharField(
|
||||||
|
blank=True, max_length=250,
|
||||||
|
help_text=_('Part query filters (comma-separated value of key=value pairs)'),
|
||||||
|
verbose_name=_('Filters'),
|
||||||
|
validators=[
|
||||||
|
validate_part_filters
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def matches_part(self, part):
|
||||||
|
"""
|
||||||
|
Test if this label template matches a given Part object
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
filters = validateFilterString(self.filters)
|
||||||
|
parts = part.models.Part.objects.filter(**filters)
|
||||||
|
except (ValidationError, FieldError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
parts = parts.filter(pk=part.pk)
|
||||||
|
|
||||||
|
return parts.exists()
|
||||||
|
|
||||||
|
def get_context_data(self, request):
|
||||||
|
"""
|
||||||
|
Generate context data for each provided Part object
|
||||||
|
"""
|
||||||
|
|
||||||
|
part = self.object_to_print
|
||||||
|
|
||||||
|
return {
|
||||||
|
'part': part,
|
||||||
|
'category': part.category,
|
||||||
|
'name': part.name,
|
||||||
|
'description': part.description,
|
||||||
|
'IPN': part.IPN,
|
||||||
|
'revision': part.revision,
|
||||||
|
'qr_data': part.format_barcode(brief=True),
|
||||||
|
'qr_url': part.format_barcode(url=True, request=request),
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
|||||||
from InvenTree.serializers import InvenTreeModelSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer
|
||||||
from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
||||||
|
|
||||||
from .models import StockItemLabel, StockLocationLabel
|
from .models import StockItemLabel, StockLocationLabel, PartLabel
|
||||||
|
|
||||||
|
|
||||||
class StockItemLabelSerializer(InvenTreeModelSerializer):
|
class StockItemLabelSerializer(InvenTreeModelSerializer):
|
||||||
@ -43,3 +43,22 @@ class StockLocationLabelSerializer(InvenTreeModelSerializer):
|
|||||||
'filters',
|
'filters',
|
||||||
'enabled',
|
'enabled',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PartLabelSerializer(InvenTreeModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializes a PartLabel object
|
||||||
|
"""
|
||||||
|
|
||||||
|
label = InvenTreeAttachmentSerializerField(required=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PartLabel
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'label',
|
||||||
|
'filters',
|
||||||
|
'enabled',
|
||||||
|
]
|
||||||
|
33
InvenTree/label/templates/label/part/part_label.html
Normal file
33
InvenTree/label/templates/label/part/part_label.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{% extends "label/label_base.html" %}
|
||||||
|
|
||||||
|
{% load barcode %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
|
||||||
|
.qr {
|
||||||
|
position: fixed;
|
||||||
|
left: 0mm;
|
||||||
|
top: 0mm;
|
||||||
|
height: {{ height }}mm;
|
||||||
|
width: {{ height }}mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.part {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
display: inline;
|
||||||
|
position: absolute;
|
||||||
|
left: {{ height }}mm;
|
||||||
|
top: 2mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<img class='qr' src='{% qrcode qr_data %}'>
|
||||||
|
|
||||||
|
<div class='part'>
|
||||||
|
{{ part.full_name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -268,6 +268,10 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#print-label').click(function() {
|
||||||
|
printPartLabels([{{ part.pk }}]);
|
||||||
|
});
|
||||||
|
|
||||||
$("#part-count").click(function() {
|
$("#part-count").click(function() {
|
||||||
launchModalForm("/stock/adjust/", {
|
launchModalForm("/stock/adjust/", {
|
||||||
data: {
|
data: {
|
||||||
|
@ -105,6 +105,61 @@ function printStockLocationLabels(locations, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function printPartLabels(parts, options={}) {
|
||||||
|
/**
|
||||||
|
* Print labels for the provided parts
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (parts.length == 0) {
|
||||||
|
showAlertDialog(
|
||||||
|
'{% trans "Select Parts" %}',
|
||||||
|
'{% trans "Part(s) must be selected before printing labels" %}',
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request available labels from the server
|
||||||
|
inventreeGet(
|
||||||
|
'{% url "api-part-label-list" %}',
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
parts: parts,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
success: function(response) {
|
||||||
|
|
||||||
|
if (response.length == 0) {
|
||||||
|
showAlertDialog(
|
||||||
|
'{% trans "No Labels Found" %}',
|
||||||
|
'{% trans "No labels found which match the selected part(s)" %}',
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select label to print
|
||||||
|
selectLabel(
|
||||||
|
response,
|
||||||
|
parts,
|
||||||
|
{
|
||||||
|
success: function(pk) {
|
||||||
|
var url = `/api/label/part/${pk}/print/?`;
|
||||||
|
|
||||||
|
parts.forEach(function(part) {
|
||||||
|
url += `parts[]=${part}&`;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function selectLabel(labels, items, options={}) {
|
function selectLabel(labels, items, options={}) {
|
||||||
/**
|
/**
|
||||||
* Present the user with the available labels,
|
* Present the user with the available labels,
|
||||||
|
@ -87,6 +87,7 @@ class RuleSet(models.Model):
|
|||||||
'company_supplierpart',
|
'company_supplierpart',
|
||||||
'company_manufacturerpart',
|
'company_manufacturerpart',
|
||||||
'company_manufacturerpartparameter',
|
'company_manufacturerpartparameter',
|
||||||
|
'label_partlabel',
|
||||||
],
|
],
|
||||||
'stock_location': [
|
'stock_location': [
|
||||||
'stock_stocklocation',
|
'stock_stocklocation',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user