Add endpoint for printing labels

This commit is contained in:
Oliver Walters 2021-01-09 20:43:48 +11:00
parent 44e60a705e
commit bdc7367e29
7 changed files with 184 additions and 41 deletions

View File

@ -92,6 +92,7 @@ settings_urls = [
# Some javascript files are served 'dynamically', allowing them to pass through the Django translation layer # Some javascript files are served 'dynamically', allowing them to pass through the Django translation layer
dynamic_javascript_urls = [ dynamic_javascript_urls = [
url(r'^modals.js', DynamicJsView.as_view(template_name='js/modals.js'), name='modals.js'),
url(r'^barcode.js', DynamicJsView.as_view(template_name='js/barcode.js'), name='barcode.js'), url(r'^barcode.js', DynamicJsView.as_view(template_name='js/barcode.js'), name='barcode.js'),
url(r'^bom.js', DynamicJsView.as_view(template_name='js/bom.js'), name='bom.js'), url(r'^bom.js', DynamicJsView.as_view(template_name='js/bom.js'), name='bom.js'),
url(r'^build.js', DynamicJsView.as_view(template_name='js/build.js'), name='build.js'), url(r'^build.js', DynamicJsView.as_view(template_name='js/build.js'), name='build.js'),
@ -99,6 +100,7 @@ dynamic_javascript_urls = [
url(r'^company.js', DynamicJsView.as_view(template_name='js/company.js'), name='company.js'), url(r'^company.js', DynamicJsView.as_view(template_name='js/company.js'), name='company.js'),
url(r'^order.js', DynamicJsView.as_view(template_name='js/order.js'), name='order.js'), url(r'^order.js', DynamicJsView.as_view(template_name='js/order.js'), name='order.js'),
url(r'^part.js', DynamicJsView.as_view(template_name='js/part.js'), name='part.js'), url(r'^part.js', DynamicJsView.as_view(template_name='js/part.js'), name='part.js'),
url(r'^label.js', DynamicJsView.as_view(template_name='js/label.js'), name='label.js'),
url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'), url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'),
url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.js'), url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.js'),
] ]

View File

@ -1,11 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from django.utils.translation import ugettext as _
from django.conf.urls import url, include from django.conf.urls import url, include
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, filters from rest_framework import generics, filters
from rest_framework.response import Response
import InvenTree.helpers import InvenTree.helpers
@ -35,21 +39,11 @@ class LabelListView(generics.ListAPIView):
] ]
class StockItemLabelList(LabelListView): class StockItemLabelMixin:
""" """
API endpoint for viewing list of StockItemLabel objects. Mixin for extracting stock items from query params
Filterable by:
- enabled: Filter by enabled / disabled status
- item: Filter by single stock item
- items: Filter by list of stock items
""" """
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
def get_items(self): def get_items(self):
""" """
Return a list of requested stock items Return a list of requested stock items
@ -59,8 +53,8 @@ class StockItemLabelList(LabelListView):
params = self.request.query_params params = self.request.query_params
if 'items' in params: if 'items[]' in params:
items = params.getlist('items', []) items = params.getlist('items[]', [])
elif 'item' in params: elif 'item' in params:
items = [params.get('item', None)] items = [params.get('item', None)]
@ -80,6 +74,22 @@ class StockItemLabelList(LabelListView):
return valid_items return valid_items
class StockItemLabelList(LabelListView, StockItemLabelMixin):
"""
API endpoint for viewing list of StockItemLabel objects.
Filterable by:
- enabled: Filter by enabled / disabled status
- item: Filter by single stock item
- items: Filter by list of stock items
"""
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
""" """
Filter the StockItem label queryset. Filter the StockItem label queryset.
@ -140,6 +150,47 @@ class StockItemLabelDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = StockItemLabelSerializer serializer_class = StockItemLabelSerializer
class StockItemLabelPrint(generics.RetrieveAPIView, StockItemLabelMixin):
"""
API endpoint for printing a StockItemLabel object
"""
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
def get(self, request, *args, **kwargs):
"""
Check if valid stock item(s) have been provided.
"""
items = self.get_items()
if len(items) == 0:
# No valid items provided, return an error message
data = {
'error': _('Must provide valid StockItem(s)'),
}
return Response(data, status=400)
label = self.get_object()
try:
pdf = label.render(items)
except:
e = sys.exc_info()[1]
data = {
'error': _('Error during label printing'),
'message': str(e),
}
return Response(data, status=400)
return InvenTree.helpers.DownloadFile(pdf.getbuffer(), 'stock_item_labels.pdf', content_type='application/pdf')
class StockLocationLabelList(LabelListView): class StockLocationLabelList(LabelListView):
""" """
API endpoint for viewiing list of StockLocationLabel objects. API endpoint for viewiing list of StockLocationLabel objects.
@ -163,7 +214,7 @@ class StockLocationLabelList(LabelListView):
params = self.request.query_params params = self.request.query_params
if 'locations' in params: if 'locations[]' in params:
locations = params.getlist('locations', []) locations = params.getlist('locations', [])
elif 'location' in params: elif 'location' in params:
locations = [params.get('location', None)] locations = [params.get('location', None)]
@ -175,7 +226,7 @@ class StockLocationLabelList(LabelListView):
for loc in locations: for loc in locations:
try: try:
valid_ids.append(int(item)) valid_ids.append(int(loc))
except (ValueError): except (ValueError):
pass pass
@ -249,6 +300,7 @@ label_api_urls = [
url(r'stock/', include([ url(r'stock/', include([
# Detail views # Detail views
url(r'^(?P<pk>\d+)/', include([ url(r'^(?P<pk>\d+)/', include([
url(r'print/?', StockItemLabelPrint.as_view(), name='api-stockitem-label-print'),
url(r'^.*$', StockItemLabelDetail.as_view(), name='api-stockitem-label-detail'), url(r'^.*$', StockItemLabelDetail.as_view(), name='api-stockitem-label-detail'),
])), ])),

View File

@ -112,7 +112,6 @@ InvenTree
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/filters.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/filters.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/modals.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script> <script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
@ -120,6 +119,8 @@ InvenTree
<script type='text/javascript' src="{% url 'bom.js' %}"></script> <script type='text/javascript' src="{% url 'bom.js' %}"></script>
<script type='text/javascript' src="{% url 'company.js' %}"></script> <script type='text/javascript' src="{% url 'company.js' %}"></script>
<script type='text/javascript' src="{% url 'part.js' %}"></script> <script type='text/javascript' src="{% url 'part.js' %}"></script>
<script type='text/javascript' src="{% url 'modals.js' %}"></script>
<script type='text/javascript' src="{% url 'label.js' %}"></script>
<script type='text/javascript' src="{% url 'stock.js' %}"></script> <script type='text/javascript' src="{% url 'stock.js' %}"></script>
<script type='text/javascript' src="{% url 'build.js' %}"></script> <script type='text/javascript' src="{% url 'build.js' %}"></script>
<script type='text/javascript' src="{% url 'order.js' %}"></script> <script type='text/javascript' src="{% url 'order.js' %}"></script>

View File

@ -0,0 +1,52 @@
{% load i18n %}
function selectLabel(labels, options={}) {
/**
* Present the user with the available labels,
* and allow them to select which label to print.
*
* The intent is that the available labels have been requested
* (via AJAX) from the server.
*/
var modal = options.modal || '#modal-form';
var label_list = makeOptionsList(
labels,
function(item) {
var text = item.name;
if (item.description) {
text += ` - ${item.description}`;
}
return text;
},
function(item) {
return item.pk;
}
);
// Construct form
var html = `
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
<div class='form-group'>
<label class='control-label requiredField' for='id_label'>
{% trans "Select Label" %}
</label>
<div class='controls'>
<select id='id_label' class='select form-control name='label'>
${label_list}
</select>
</div>
</div>
</form>`;
openModal({
modal: modal,
});
modalEnable(modal, true);
modalSetTitle(modal, '{% trans "Select Label Template" %}');
modalSetContent(modal, html);
}

View File

@ -1,3 +1,5 @@
{% load i18n %}
function makeOption(text, value, title) { function makeOption(text, value, title) {
/* Format an option for a select element /* Format an option for a select element
*/ */
@ -392,7 +394,7 @@ function renderErrorMessage(xhr) {
<div class='panel panel-default'> <div class='panel panel-default'>
<div class='panel panel-heading'> <div class='panel panel-heading'>
<div class='panel-title'> <div class='panel-title'>
<a data-toggle='collapse' href="#collapse-error-info">Show Error Information</a> <a data-toggle='collapse' href="#collapse-error-info">{% trans "Show Error Information" %}</a>
</div> </div>
</div> </div>
<div class='panel-collapse collapse' id='collapse-error-info'> <div class='panel-collapse collapse' id='collapse-error-info'>
@ -459,8 +461,8 @@ function showQuestionDialog(title, content, options={}) {
modalSetTitle(modal, title); modalSetTitle(modal, title);
modalSetContent(modal, content); modalSetContent(modal, content);
var accept_text = options.accept_text || 'Accept'; var accept_text = options.accept_text || '{% trans "Accept" %}';
var cancel_text = options.cancel_text || 'Cancel'; var cancel_text = options.cancel_text || '{% trans "Cancel" %}';
$(modal).find('#modal-form-cancel').html(cancel_text); $(modal).find('#modal-form-cancel').html(cancel_text);
$(modal).find('#modal-form-accept').html(accept_text); $(modal).find('#modal-form-accept').html(accept_text);
@ -524,7 +526,7 @@ function openModal(options) {
if (options.title) { if (options.title) {
modalSetTitle(modal, options.title); modalSetTitle(modal, options.title);
} else { } else {
modalSetTitle(modal, 'Loading Data...'); modalSetTitle(modal, '{% trans "Loading Data" %}...');
} }
// Unless the content is explicitly set, display loading message // Unless the content is explicitly set, display loading message
@ -535,8 +537,8 @@ function openModal(options) {
} }
// Default labels for 'Submit' and 'Close' buttons in the form // Default labels for 'Submit' and 'Close' buttons in the form
var submit_text = options.submit_text || 'Submit'; var submit_text = options.submit_text || '{% trans "Submit" %}';
var close_text = options.close_text || 'Close'; var close_text = options.close_text || '{% trans "Close" %}';
modalSetButtonText(modal, submit_text, close_text); modalSetButtonText(modal, submit_text, close_text);
@ -745,7 +747,7 @@ function handleModalForm(url, options) {
} }
else { else {
$(modal).modal('hide'); $(modal).modal('hide');
showAlertDialog('Invalid response from server', 'Form data missing from server response'); showAlertDialog('{% trans "Invalid response from server" %}', '{% trans "Form data missing from server response" %}');
} }
} }
} }
@ -758,7 +760,7 @@ function handleModalForm(url, options) {
// There was an error submitting form data via POST // There was an error submitting form data via POST
$(modal).modal('hide'); $(modal).modal('hide');
showAlertDialog('Error posting form data', renderErrorMessage(xhr)); showAlertDialog('{% trans "Error posting form data" %}', renderErrorMessage(xhr));
}, },
complete: function(xhr) { complete: function(xhr) {
//TODO //TODO
@ -793,8 +795,8 @@ function launchModalForm(url, options = {}) {
var modal = options.modal || '#modal-form'; var modal = options.modal || '#modal-form';
// Default labels for 'Submit' and 'Close' buttons in the form // Default labels for 'Submit' and 'Close' buttons in the form
var submit_text = options.submit_text || 'Submit'; var submit_text = options.submit_text || '{% trans "Submit" %}';
var close_text = options.close_text || 'Close'; var close_text = options.close_text || '{% trans "Close" %}';
// Form the ajax request to retrieve the django form data // Form the ajax request to retrieve the django form data
ajax_data = { ajax_data = {
@ -842,7 +844,7 @@ function launchModalForm(url, options = {}) {
} else { } else {
$(modal).modal('hide'); $(modal).modal('hide');
showAlertDialog('Invalid server response', 'JSON response missing form data'); showAlertDialog('{% trans "Invalid server response" %}', '{% trans "JSON response missing form data" %}');
} }
}, },
error: function (xhr, ajaxOptions, thrownError) { error: function (xhr, ajaxOptions, thrownError) {
@ -852,36 +854,36 @@ function launchModalForm(url, options = {}) {
if (xhr.status == 0) { if (xhr.status == 0) {
// No response from the server // No response from the server
showAlertDialog( showAlertDialog(
"No Response", '{% trans "No Response" %}',
"No response from the InvenTree server", '{% trans "No response from the InvenTree server" %}',
); );
} else if (xhr.status == 400) { } else if (xhr.status == 400) {
showAlertDialog( showAlertDialog(
"Error 400: Bad Request", '{% trans "Error 400: Bad Request" %}',
"Server returned error code 400" '{% trans "Server returned error code 400" %}',
); );
} else if (xhr.status == 401) { } else if (xhr.status == 401) {
showAlertDialog( showAlertDialog(
"Error 401: Not Authenticated", '{% trans "Error 401: Not Authenticated" %}',
"Authentication credentials not supplied" '{% trans "Authentication credentials not supplied" %}',
); );
} else if (xhr.status == 403) { } else if (xhr.status == 403) {
showAlertDialog( showAlertDialog(
"Error 403: Permission Denied", '{% trans "Error 403: Permission Denied" %}',
"You do not have the required permissions to access this function" '{% trans "You do not have the required permissions to access this function" %}',
); );
} else if (xhr.status == 404) { } else if (xhr.status == 404) {
showAlertDialog( showAlertDialog(
"Error 404: Resource Not Found", '{% trans "Error 404: Resource Not Found" %}',
"The requested resource could not be located on the server" '{% trans "The requested resource could not be located on the server" %}',
); );
} else if (xhr.status == 408) { } else if (xhr.status == 408) {
showAlertDialog( showAlertDialog(
"Error 408: Timeout", '{% trans "Error 408: Timeout" %}',
"Connection timeout while requesting data from server" '{% trans "Connection timeout while requesting data from server" %}',
); );
} else { } else {
showAlertDialog('Error requesting form data', renderErrorMessage(xhr)); showAlertDialog('{% trans "Error requesting form data" %}', renderErrorMessage(xhr));
} }
console.log("Modal form error: " + xhr.status); console.log("Modal form error: " + xhr.status);

View File

@ -645,6 +645,39 @@ function loadStockTable(table, options) {
} }
// Automatically link button callbacks // Automatically link button callbacks
$('#multi-item-print-label').click(function() {
var selections = $('#stock-table').bootstrapTable('getSelections');
var items = [];
selections.forEach(function(item) {
items.push(item.pk);
});
// Request available labels from the server
inventreeGet(
'{% url "api-stockitem-label-list" %}',
{
enabled: true,
items: items,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Labels Found" %}',
'{% trans "No labels found which match selected stock item(s)" %}',
);
return;
}
var label = selectLabel(response);
}
}
);
});
$('#multi-item-stocktake').click(function() { $('#multi-item-stocktake').click(function() {
stockAdjustment('count'); stockAdjustment('count');
}); });

View File

@ -17,6 +17,7 @@
<div class="btn-group"> <div class="btn-group">
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button> <button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href='#' id='multi-item-print-label' title='{% trans "Print labels" %}'><span class='fas fa-tags'></span> {% trans "Print labels" %}</a></li>
{% if roles.stock.change %} {% if roles.stock.change %}
<li><a href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'><span class='fas fa-plus-circle'></span> {% trans "Add stock" %}</a></li> <li><a href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'><span class='fas fa-plus-circle'></span> {% trans "Add stock" %}</a></li>
<li><a href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'><span class='fas fa-minus-circle'></span> {% trans "Remove stock" %}</a></li> <li><a href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'><span class='fas fa-minus-circle'></span> {% trans "Remove stock" %}</a></li>