mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master' into simple-qr-codes
This commit is contained in:
commit
7d38507785
@ -196,23 +196,23 @@ INSTALLED_APPS = [
|
||||
'users.apps.UsersConfig',
|
||||
|
||||
# Third part add-ons
|
||||
'django_filters', # Extended filter functionality
|
||||
'dbbackup', # Database backup / restore
|
||||
'rest_framework', # DRF (Django Rest Framework)
|
||||
'rest_framework.authtoken', # Token authentication for API
|
||||
'corsheaders', # Cross-origin Resource Sharing for DRF
|
||||
'crispy_forms', # Improved form rendering
|
||||
'import_export', # Import / export tables to file
|
||||
'django_cleanup', # Automatically delete orphaned MEDIA files
|
||||
'qr_code', # Generate QR codes
|
||||
'mptt', # Modified Preorder Tree Traversal
|
||||
'markdownx', # Markdown editing
|
||||
'markdownify', # Markdown template rendering
|
||||
'django_tex', # LaTeX output
|
||||
'django_admin_shell', # Python shell for the admin interface
|
||||
'djmoney', # django-money integration
|
||||
'djmoney.contrib.exchange', # django-money exchange rates
|
||||
'error_report', # Error reporting in the admin interface
|
||||
'django_filters', # Extended filter functionality
|
||||
'dbbackup', # Database backup / restore
|
||||
'rest_framework', # DRF (Django Rest Framework)
|
||||
'rest_framework.authtoken', # Token authentication for API
|
||||
'corsheaders', # Cross-origin Resource Sharing for DRF
|
||||
'crispy_forms', # Improved form rendering
|
||||
'import_export', # Import / export tables to file
|
||||
'django_cleanup.apps.CleanupConfig', # Automatically delete orphaned MEDIA files
|
||||
'qr_code', # Generate QR codes
|
||||
'mptt', # Modified Preorder Tree Traversal
|
||||
'markdownx', # Markdown editing
|
||||
'markdownify', # Markdown template rendering
|
||||
'django_tex', # LaTeX output
|
||||
'django_admin_shell', # Python shell for the admin interface
|
||||
'djmoney', # django-money integration
|
||||
'djmoney.contrib.exchange', # django-money exchange rates
|
||||
'error_report', # Error reporting in the admin interface
|
||||
]
|
||||
|
||||
MIDDLEWARE = CONFIG.get('middleware', [
|
||||
|
@ -105,6 +105,7 @@ dynamic_javascript_urls = [
|
||||
url(r'^label.js', DynamicJsView.as_view(template_name='js/label.js'), name='label.js'),
|
||||
url(r'^report.js', DynamicJsView.as_view(template_name='js/report.js'), name='report.js'),
|
||||
url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'),
|
||||
url(r'^tables.js', DynamicJsView.as_view(template_name='js/tables.js'), name='tables.js'),
|
||||
url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.js'),
|
||||
]
|
||||
|
||||
|
@ -90,7 +90,7 @@ class BarcodeScan(APIView):
|
||||
|
||||
if loc is not None:
|
||||
response['stocklocation'] = plugin.renderStockLocation(loc)
|
||||
response['url'] = reverse('location-detail', kwargs={'pk': loc.id})
|
||||
response['url'] = reverse('stock-location-detail', kwargs={'pk': loc.id})
|
||||
match_found = True
|
||||
|
||||
# Try to associate with a part
|
||||
|
@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import string
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
@ -16,9 +17,18 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def hash_barcode(barcode_data):
|
||||
"""
|
||||
Calculate an MD5 hash of barcode data
|
||||
Calculate an MD5 hash of barcode data.
|
||||
|
||||
HACK: Remove any 'non printable' characters from the hash,
|
||||
as it seems browers will remove special control characters...
|
||||
|
||||
TODO: Work out a way around this!
|
||||
"""
|
||||
|
||||
printable_chars = filter(lambda x: x in string.printable, barcode_data)
|
||||
|
||||
barcode_data = ''.join(list(printable_chars))
|
||||
|
||||
hash = hashlib.md5(str(barcode_data).encode())
|
||||
return str(hash.hexdigest())
|
||||
|
||||
|
@ -71,6 +71,13 @@ class InvenTreeSetting(models.Model):
|
||||
'choices': djmoney.settings.CURRENCY_CHOICES,
|
||||
},
|
||||
|
||||
'BARCODE_ENABLE': {
|
||||
'name': _('Barcode Support'),
|
||||
'description': _('Enable barcode scanner support'),
|
||||
'default': True,
|
||||
'validator': bool,
|
||||
},
|
||||
|
||||
'PART_IPN_REGEX': {
|
||||
'name': _('IPN Regex'),
|
||||
'description': _('Regular expression pattern for matching Part IPN')
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -343,11 +343,11 @@ class PurchaseOrder(Order):
|
||||
|
||||
stock.save()
|
||||
|
||||
text = _("Received items")
|
||||
note = f"{_('Received')} {quantity} {_('items against order')} {str(self)}"
|
||||
|
||||
# Add a new transaction note to the newly created stock item
|
||||
stock.addTransactionNote("Received items", user, "Received {q} items against order '{po}'".format(
|
||||
q=quantity,
|
||||
po=str(self))
|
||||
)
|
||||
stock.addTransactionNote(text, user, note)
|
||||
|
||||
# Update the number of parts received against the particular line item
|
||||
line.received += quantity
|
||||
|
@ -6,6 +6,7 @@ Part database model definitions
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -51,6 +52,9 @@ import common.models
|
||||
import part.settings as part_settings
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PartCategory(InvenTreeTree):
|
||||
""" PartCategory provides hierarchical organization of Part objects.
|
||||
|
||||
@ -335,11 +339,14 @@ class Part(MPTTModel):
|
||||
if self.pk:
|
||||
previous = Part.objects.get(pk=self.pk)
|
||||
|
||||
if previous.image and not self.image == previous.image:
|
||||
# Image has been changed
|
||||
if previous.image is not None and not self.image == previous.image:
|
||||
|
||||
# Are there any (other) parts which reference the image?
|
||||
n_refs = Part.objects.filter(image=previous.image).exclude(pk=self.pk).count()
|
||||
|
||||
if n_refs == 0:
|
||||
logger.info(f"Deleting unused image file '{previous.image}'")
|
||||
previous.image.delete(save=False)
|
||||
|
||||
self.clean()
|
||||
@ -710,7 +717,7 @@ class Part(MPTTModel):
|
||||
null=True,
|
||||
blank=True,
|
||||
variations={'thumbnail': (128, 128)},
|
||||
delete_orphans=True,
|
||||
delete_orphans=False,
|
||||
)
|
||||
|
||||
default_location = TreeForeignKey(
|
||||
|
@ -44,6 +44,8 @@
|
||||
<span id='part-star-icon' class='fas fa-star {% if starred %}icon-yellow{% endif %}'/>
|
||||
</button>
|
||||
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
{% if barcodes %}
|
||||
<!-- Barcode actions menu -->
|
||||
<div class='btn-group'>
|
||||
<button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-qrcode'></span> <span class='caret'></span></button>
|
||||
@ -52,6 +54,7 @@
|
||||
<li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if part.active %}
|
||||
<button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'>
|
||||
<span id='part-price-icon' class='fas fa-dollar-sign'/>
|
||||
|
@ -121,12 +121,17 @@ class StockAdjust(APIView):
|
||||
- StockAdd: add stock items
|
||||
- StockRemove: remove stock items
|
||||
- StockTransfer: transfer stock items
|
||||
|
||||
# TODO - This needs serious refactoring!!!
|
||||
|
||||
"""
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
]
|
||||
|
||||
allow_missing_quantity = False
|
||||
|
||||
def get_items(self, request):
|
||||
"""
|
||||
Return a list of items posted to the endpoint.
|
||||
@ -157,10 +162,13 @@ class StockAdjust(APIView):
|
||||
except (ValueError, StockItem.DoesNotExist):
|
||||
raise ValidationError({'pk': 'Each entry must contain a valid pk field'})
|
||||
|
||||
if self.allow_missing_quantity and 'quantity' not in entry:
|
||||
entry['quantity'] = item.quantity
|
||||
|
||||
try:
|
||||
quantity = Decimal(str(entry.get('quantity', None)))
|
||||
except (ValueError, TypeError, InvalidOperation):
|
||||
raise ValidationError({'quantity': 'Each entry must contain a valid quantity field'})
|
||||
raise ValidationError({'quantity': "Each entry must contain a valid quantity value"})
|
||||
|
||||
if quantity < 0:
|
||||
raise ValidationError({'quantity': 'Quantity field must not be less than zero'})
|
||||
@ -234,6 +242,8 @@ class StockTransfer(StockAdjust):
|
||||
API endpoint for performing stock movements
|
||||
"""
|
||||
|
||||
allow_missing_quantity = True
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
self.get_items(request)
|
||||
|
@ -195,11 +195,14 @@ class StockItem(MPTTModel):
|
||||
super(StockItem, self).save(*args, **kwargs)
|
||||
|
||||
if add_note:
|
||||
|
||||
note = f"{_('Created new stock item for')} {str(self.part)}"
|
||||
|
||||
# This StockItem is being saved for the first time
|
||||
self.addTransactionNote(
|
||||
_('Created stock item'),
|
||||
user,
|
||||
notes="Created new stock item for part '{p}'".format(p=str(self.part)),
|
||||
note,
|
||||
system=True
|
||||
)
|
||||
|
||||
@ -611,9 +614,9 @@ class StockItem(MPTTModel):
|
||||
"""
|
||||
|
||||
self.addTransactionNote(
|
||||
_("Returned from customer") + " " + self.customer.name,
|
||||
_("Returned from customer") + f" {self.customer.name}",
|
||||
user,
|
||||
notes=_("Returned to location") + " " + location.name,
|
||||
notes=_("Returned to location") + f" {location.name}",
|
||||
system=True
|
||||
)
|
||||
|
||||
@ -1000,12 +1003,17 @@ class StockItem(MPTTModel):
|
||||
|
||||
# Add a new tracking item for the new stock item
|
||||
new_stock.addTransactionNote(
|
||||
"Split from existing stock",
|
||||
_("Split from existing stock"),
|
||||
user,
|
||||
"Split {n} from existing stock item".format(n=quantity))
|
||||
f"{_('Split')} {helpers.normalize(quantity)} {_('items')}"
|
||||
)
|
||||
|
||||
# Remove the specified quantity from THIS stock item
|
||||
self.take_stock(quantity, user, 'Split {n} items into new stock item'.format(n=quantity))
|
||||
self.take_stock(
|
||||
quantity,
|
||||
user,
|
||||
f"{_('Split')} {quantity} {_('items into new stock item')}"
|
||||
)
|
||||
|
||||
# Return a copy of the "new" stock item
|
||||
return new_stock
|
||||
@ -1054,10 +1062,10 @@ class StockItem(MPTTModel):
|
||||
|
||||
return True
|
||||
|
||||
msg = "Moved to {loc}".format(loc=str(location))
|
||||
|
||||
msg = f"{_('Moved to')} {str(location)}"
|
||||
|
||||
if self.location:
|
||||
msg += " (from {loc})".format(loc=str(self.location))
|
||||
msg += f" ({_('from')} {str(self.location)})"
|
||||
|
||||
self.location = location
|
||||
|
||||
@ -1125,10 +1133,16 @@ class StockItem(MPTTModel):
|
||||
|
||||
if self.updateQuantity(count):
|
||||
|
||||
self.addTransactionNote('Stocktake - counted {n} items'.format(n=helpers.normalize(count)),
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
n = helpers.normalize(count)
|
||||
|
||||
text = f"{_('Counted')} {n} {_('items')}"
|
||||
|
||||
self.addTransactionNote(
|
||||
text,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -1154,10 +1168,15 @@ class StockItem(MPTTModel):
|
||||
|
||||
if self.updateQuantity(self.quantity + quantity):
|
||||
|
||||
self.addTransactionNote('Added {n} items to stock'.format(n=helpers.normalize(quantity)),
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
n = helpers.normalize(quantity)
|
||||
text = f"{_('Added')} {n} {_('items')}"
|
||||
|
||||
self.addTransactionNote(
|
||||
text,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -1180,7 +1199,10 @@ class StockItem(MPTTModel):
|
||||
|
||||
if self.updateQuantity(self.quantity - quantity):
|
||||
|
||||
self.addTransactionNote('Removed {n} items from stock'.format(n=helpers.normalize(quantity)),
|
||||
q = helpers.normalize(quantity)
|
||||
text = f"{_('Removed')} {q} {_('items')}"
|
||||
|
||||
self.addTransactionNote(text,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
|
@ -120,6 +120,8 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
||||
</div>
|
||||
|
||||
<div class='btn-group action-buttons' role='group'>
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
{% if barcodes %}
|
||||
<!-- Barcode actions menu -->
|
||||
<div class='btn-group'>
|
||||
<button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-qrcode'></span> <span class='caret'></span></button>
|
||||
@ -127,24 +129,26 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
||||
<li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li>
|
||||
{% if roles.stock.change %}
|
||||
{% if item.uid %}
|
||||
<li><a href='#' id='unlink-barcode'><span class='fas fa-unlink'></span> {% trans "Unlink Barcode" %}</a></li>
|
||||
{% else %}
|
||||
<li><a href='#' id='link-barcode'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Document / label menu -->
|
||||
{% if item.has_labels or item.has_test_reports %}
|
||||
<div class='btn-group'>
|
||||
<button id='document-options' title='{% trans "Printing actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-print'></span> <span class='caret'></span></button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
{% if item.has_labels %}
|
||||
<li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
|
||||
{% endif %}
|
||||
{% if item.has_test_reports %}
|
||||
<li><a href='#' id='stock-test-report'><span class='fas fa-file-pdf'></span> {% trans "Test Report" %}</a></li>
|
||||
{% endif %}
|
||||
<li><a href='#' id='barcode-unlink'><span class='fas fa-unlink'></span> {% trans "Unlink Barcode" %}</a></li>
|
||||
{% else %}
|
||||
<li><a href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li>
|
||||
{% endif %}
|
||||
<li><a href='#' id='barcode-scan-into-location'><span class='fas fa-sitemap'></span> {% trans "Scan to Location" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Document / label menu -->
|
||||
{% if item.has_labels or item.has_test_reports %}
|
||||
<div class='btn-group'>
|
||||
<button id='document-options' title='{% trans "Printing actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-print'></span> <span class='caret'></span></button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
{% if item.has_labels %}
|
||||
<li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
|
||||
{% endif %}
|
||||
{% if item.has_test_reports %}
|
||||
<li><a href='#' id='stock-test-report'><span class='fas fa-file-pdf'></span> {% trans "Test Report" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -447,14 +451,18 @@ $("#show-qr-code").click(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("#link-barcode").click(function() {
|
||||
$("#barcode-link").click(function() {
|
||||
linkBarcodeDialog({{ item.id }});
|
||||
});
|
||||
|
||||
$("#unlink-barcode").click(function() {
|
||||
$("#barcode-unlink").click(function() {
|
||||
unlinkBarcode({{ item.id }});
|
||||
});
|
||||
|
||||
$("#barcode-scan-into-location").click(function() {
|
||||
scanItemsIntoLocation([{{ item.id }}]);
|
||||
});
|
||||
|
||||
{% if item.in_stock %}
|
||||
|
||||
$("#stock-assign-to-customer").click(function() {
|
||||
|
@ -37,6 +37,8 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
{% if barcodes %}
|
||||
<!-- Barcode actions menu -->
|
||||
{% if location %}
|
||||
<div class='btn-group'>
|
||||
@ -47,29 +49,30 @@
|
||||
<li><a href='#' id='barcode-check-in'><span class='fas fa-arrow-right'></span> {% trans "Check-in Items" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Check permissions and owner -->
|
||||
{% if owner_control.value == "False" or owner_control.value == "True" and user in owners or user.is_superuser %}
|
||||
{% if roles.stock.change %}
|
||||
<div class='btn-group'>
|
||||
<button id='stock-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-boxes'></span> <span class='caret'></span></button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
<li><a href='#' id='location-count'><span class='fas fa-clipboard-list'></span>
|
||||
{% trans "Count stock" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if roles.stock_location.change %}
|
||||
<div class='btn-group'>
|
||||
<button id='location-actions' title='{% trans "Location actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle="dropdown"><span class='fas fa-sitemap'></span> <span class='caret'></span></button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
<li><a href='#' id='location-edit'><span class='fas fa-edit icon-green'></span> {% trans "Edit location" %}</a></li>
|
||||
{% if roles.stock.delete %}
|
||||
<li><a href='#' id='location-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete location" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<!-- Check permissions and owner -->
|
||||
{% if owner_control.value == "False" or owner_control.value == "True" and user in owners or user.is_superuser %}
|
||||
{% if roles.stock.change %}
|
||||
<div class='btn-group'>
|
||||
<button id='stock-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-boxes'></span> <span class='caret'></span></button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
<li><a href='#' id='location-count'><span class='fas fa-clipboard-list'></span>
|
||||
{% trans "Count stock" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if roles.stock_location.change %}
|
||||
<div class='btn-group'>
|
||||
<button id='location-actions' title='{% trans "Location actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle="dropdown"><span class='fas fa-sitemap'></span> <span class='caret'></span></button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
<li><a href='#' id='location-edit'><span class='fas fa-edit icon-green'></span> {% trans "Edit location" %}</a></li>
|
||||
{% if roles.stock.delete %}
|
||||
<li><a href='#' id='location-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete location" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -284,7 +284,8 @@ class StockTest(TestCase):
|
||||
# Check that a tracking item was added
|
||||
track = StockItemTracking.objects.filter(item=it).latest('id')
|
||||
|
||||
self.assertIn('Stocktake', track.title)
|
||||
self.assertIn('Counted', track.title)
|
||||
self.assertIn('items', track.title)
|
||||
self.assertIn('Counted items', track.notes)
|
||||
|
||||
n = it.tracking_info.count()
|
||||
|
@ -1114,7 +1114,7 @@ class StockAdjust(AjaxView, FormMixin):
|
||||
return self.do_delete()
|
||||
|
||||
else:
|
||||
return 'No action performed'
|
||||
return _('No action performed')
|
||||
|
||||
def do_add(self):
|
||||
|
||||
@ -1129,7 +1129,7 @@ class StockAdjust(AjaxView, FormMixin):
|
||||
|
||||
count += 1
|
||||
|
||||
return _("Added stock to {n} items".format(n=count))
|
||||
return f"{_('Added stock to ')} {count} {_('items')}"
|
||||
|
||||
def do_take(self):
|
||||
|
||||
@ -1144,7 +1144,7 @@ class StockAdjust(AjaxView, FormMixin):
|
||||
|
||||
count += 1
|
||||
|
||||
return _("Removed stock from {n} items".format(n=count))
|
||||
return f"{_('Removed stock from ')} {count} {_('items')}"
|
||||
|
||||
def do_count(self):
|
||||
|
||||
|
@ -21,4 +21,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>{% trans "Barcode Settings" %}</h4>
|
||||
<table class='table table-striped table-condensed'>
|
||||
{% include "InvenTree/settings/header.html" %}
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="BARCODE_ENABLE" icon="fa-qrcode" %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
@ -2,6 +2,8 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@ -111,7 +113,6 @@ InvenTree
|
||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.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/tables.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
|
||||
|
||||
@ -126,6 +127,7 @@ InvenTree
|
||||
<script type='text/javascript' src="{% url 'build.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'order.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'calendar.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'tables.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'table_filters.js' %}"></script>
|
||||
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
|
||||
@ -145,9 +147,11 @@ $(document).ready(function () {
|
||||
|
||||
showCachedAlerts();
|
||||
|
||||
{% if barcodes %}
|
||||
$('#barcode-scan').click(function() {
|
||||
barcodeScanDialog();
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
});
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
{% load i18n %}
|
||||
|
||||
function makeBarcodeInput(placeholderText='') {
|
||||
function makeBarcodeInput(placeholderText='', hintText='') {
|
||||
/*
|
||||
* Generate HTML for a barcode input
|
||||
*/
|
||||
|
||||
placeholderText = placeholderText || '{% trans "Scan barcode data here using wedge scanner" %}';
|
||||
|
||||
hintText = hintText || '{% trans "Enter barcode data" %}';
|
||||
|
||||
var html = `
|
||||
<div class='form-group'>
|
||||
<label class='control-label' for='barcode'>{% trans "Barcode" %}</label>
|
||||
@ -17,7 +19,7 @@ function makeBarcodeInput(placeholderText='') {
|
||||
</span>
|
||||
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
|
||||
</div>
|
||||
<div id='hint_barcode_data' class='help-block'>{% trans "Enter barcode data" %}</div>
|
||||
<div id='hint_barcode_data' class='help-block'>${hintText}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -25,6 +27,81 @@ function makeBarcodeInput(placeholderText='') {
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeNotesField(options={}) {
|
||||
|
||||
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
|
||||
var placeholder = options.placeholder || '{% trans "Enter notes" %}';
|
||||
|
||||
return `
|
||||
<div class='form-group'>
|
||||
<label class='control-label' for='notes'>{% trans "Notes" %}</label>
|
||||
<div class='controls'>
|
||||
<div class='input-group'>
|
||||
<span class='input-group-addon'>
|
||||
<span class='fas fa-sticky-note'></span>
|
||||
</span>
|
||||
<input id='notes' class='textinput textInput form-control' type='text' name='notes' placeholder='${placeholder}'>
|
||||
</div>
|
||||
<div id='hint_notes' class='help_block'>${tooltip}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* POST data to the server, and handle standard responses.
|
||||
*/
|
||||
function postBarcodeData(barcode_data, options={}) {
|
||||
|
||||
var modal = options.modal || '#modal-form';
|
||||
|
||||
var url = options.url || '/api/barcode/';
|
||||
|
||||
var data = options.data || {};
|
||||
|
||||
data.barcode = barcode_data;
|
||||
|
||||
inventreePut(
|
||||
url,
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
error: function() {
|
||||
enableBarcodeInput(modal, true);
|
||||
showBarcodeMessage(modal, '{% trans "Server error" %}');
|
||||
},
|
||||
success: function(response, status) {
|
||||
modalEnable(modal, false);
|
||||
enableBarcodeInput(modal, true);
|
||||
|
||||
if (status == 'success') {
|
||||
|
||||
if ('success' in response) {
|
||||
if (options.onScan) {
|
||||
options.onScan(response);
|
||||
}
|
||||
} else if ('error' in response) {
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
response.error,
|
||||
'warning'
|
||||
);
|
||||
} else {
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
'{% trans "Unknown response from server" %}',
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Invalid response returned from server
|
||||
showInvalidResponseError(modal, response, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function showBarcodeMessage(modal, message, style='danger') {
|
||||
|
||||
@ -43,12 +120,6 @@ function showInvalidResponseError(modal, response, status) {
|
||||
}
|
||||
|
||||
|
||||
function clearBarcodeError(modal, message) {
|
||||
|
||||
$(modal + ' #barcode-error-message').html('');
|
||||
}
|
||||
|
||||
|
||||
function enableBarcodeInput(modal, enabled=true) {
|
||||
|
||||
var barcode = $(modal + ' #barcode');
|
||||
@ -87,9 +158,7 @@ function barcodeDialog(title, options={}) {
|
||||
|
||||
if (barcode && barcode.length > 0) {
|
||||
|
||||
if (options.onScan) {
|
||||
options.onScan(barcode);
|
||||
}
|
||||
postBarcodeData(barcode, options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,40 +258,20 @@ function barcodeScanDialog() {
|
||||
barcodeDialog(
|
||||
"Scan Barcode",
|
||||
{
|
||||
onScan: function(barcode) {
|
||||
enableBarcodeInput(modal, false);
|
||||
inventreePut(
|
||||
'/api/barcode/',
|
||||
{
|
||||
barcode: barcode,
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
success: function(response, status) {
|
||||
|
||||
enableBarcodeInput(modal, true);
|
||||
|
||||
if (status == 'success') {
|
||||
|
||||
if ('success' in response) {
|
||||
if ('url' in response) {
|
||||
// Redirect to the URL!
|
||||
$(modal).modal('hide');
|
||||
window.location.href = response.url;
|
||||
}
|
||||
|
||||
} else if ('error' in response) {
|
||||
showBarcodeMessage(modal, response.error, 'warning');
|
||||
} else {
|
||||
showBarcodeMessage(modal, "{% trans 'Unknown response from server' %}", 'warning');
|
||||
}
|
||||
} else {
|
||||
showInvalidResponseError(modal, response, status);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
onScan: function(response) {
|
||||
if ('url' in response) {
|
||||
$(modal).modal('hide');
|
||||
|
||||
// Redirect to the URL!
|
||||
window.location.href = response.url;
|
||||
} else {
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
'{% trans "No URL in response" %}',
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -238,37 +287,14 @@ function linkBarcodeDialog(stockitem, options={}) {
|
||||
barcodeDialog(
|
||||
"{% trans 'Link Barcode to Stock Item' %}",
|
||||
{
|
||||
onScan: function(barcode) {
|
||||
enableBarcodeInput(modal, false);
|
||||
inventreePut(
|
||||
'/api/barcode/link/',
|
||||
{
|
||||
barcode: barcode,
|
||||
stockitem: stockitem,
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
success: function(response, status) {
|
||||
url: '/api/barcode/link/',
|
||||
data: {
|
||||
stockitem: stockitem,
|
||||
},
|
||||
onScan: function(response) {
|
||||
|
||||
enableBarcodeInput(modal, true);
|
||||
|
||||
if (status == 'success') {
|
||||
|
||||
if ('success' in response) {
|
||||
$(modal).modal('hide');
|
||||
location.reload();
|
||||
} else if ('error' in response) {
|
||||
showBarcodeMessage(modal, response.error, 'warning');
|
||||
} else {
|
||||
showBarcodeMessage(modal, "{% trans 'Unknown response from server' %}", warning);
|
||||
}
|
||||
|
||||
} else {
|
||||
showInvalidResponseError(modal, response, status);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
$(modal).modal('hide');
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -386,22 +412,10 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
var table = `<div class='container' id='items-table-div' style='width: 80%; float: left;'></div>`;
|
||||
|
||||
// Extra form fields
|
||||
var extra = `
|
||||
<div class='form-group'>
|
||||
<label class='control-label' for='notes'>{% trans "Notes" %}</label>
|
||||
<div class='controls'>
|
||||
<div class='input-group'>
|
||||
<span class='input-group-addon'>
|
||||
<span class='fas fa-sticky-note'></span>
|
||||
</span>
|
||||
<input id='notes' class='textinput textInput form-control' type='text' name='notes' placeholder='{% trans "Enter notes" %}'>
|
||||
</div>
|
||||
<div id='hint_notes' class='help_block'>{% trans "Enter optional notes for stock transfer" %}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
var extra = makeNotesField();
|
||||
|
||||
barcodeDialog(
|
||||
"{% trans "Check Stock Items into Location" %}",
|
||||
'{% trans "Check Stock Items into Location" %}',
|
||||
{
|
||||
headerContent: table,
|
||||
preShow: function() {
|
||||
@ -414,7 +428,6 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
extraFields: extra,
|
||||
onSubmit: function() {
|
||||
|
||||
|
||||
// Called when the 'check-in' button is pressed
|
||||
|
||||
var data = {location: location_id};
|
||||
@ -434,7 +447,7 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
data.items = entries;
|
||||
|
||||
inventreePut(
|
||||
'{% url 'api-stock-transfer' %}',
|
||||
"{% url 'api-stock-transfer' %}",
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
@ -446,69 +459,154 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
showAlertOrCache('alert-success', response.success, true);
|
||||
location.reload();
|
||||
} else {
|
||||
showAlertOrCache('alert-success', 'Error transferring stock', false);
|
||||
showAlertOrCache('alert-success', '{% trans "Error transferring stock" %}', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
onScan: function(barcode) {
|
||||
enableBarcodeInput(modal, false);
|
||||
inventreePut(
|
||||
'/api/barcode/',
|
||||
{
|
||||
barcode: barcode,
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
error: function() {
|
||||
enableBarcodeInput(modal, true);
|
||||
showBarcodeMessage(modal, '{% trans "Server error" %}');
|
||||
},
|
||||
success: function(response, status) {
|
||||
onScan: function(response) {
|
||||
if ('stockitem' in response) {
|
||||
stockitem = response.stockitem;
|
||||
|
||||
enableBarcodeInput(modal, true);
|
||||
var duplicate = false;
|
||||
|
||||
if (status == 'success') {
|
||||
if ('stockitem' in response) {
|
||||
stockitem = response.stockitem;
|
||||
items.forEach(function(item) {
|
||||
if (item.pk == stockitem.pk) {
|
||||
duplicate = true;
|
||||
}
|
||||
});
|
||||
|
||||
var duplicate = false;
|
||||
if (duplicate) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', "warning");
|
||||
} else {
|
||||
|
||||
items.forEach(function(item) {
|
||||
if (item.pk == stockitem.pk) {
|
||||
duplicate = true;
|
||||
}
|
||||
});
|
||||
if (stockitem.location == location_id) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already in this location" %}');
|
||||
return;
|
||||
}
|
||||
|
||||
if (duplicate) {
|
||||
showBarcodeMessage(modal, "{% trans "Stock Item already scanned" %}", "warning");
|
||||
} else {
|
||||
// Add this stock item to the list
|
||||
items.push(stockitem);
|
||||
|
||||
if (stockitem.location == location_id) {
|
||||
showBarcodeMessage(modal, "{% trans "Stock Item already in this location" %}");
|
||||
return;
|
||||
}
|
||||
showBarcodeMessage(modal, '{% trans "Added stock item" %}', "success");
|
||||
|
||||
// Add this stock item to the list
|
||||
items.push(stockitem);
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
showBarcodeMessage(modal, "{% trans "Added stock item" %}", "success");
|
||||
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Barcode does not match a stock item
|
||||
showBarcodeMessage(modal, "{% trans "Barcode does not match Stock Item" %}", "warning");
|
||||
}
|
||||
} else {
|
||||
showInvalidResponseError(modal, response, status);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Barcode does not match a stock item
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', "warning");
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Display dialog to check a single stock item into a stock location
|
||||
*/
|
||||
function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
|
||||
var modal = options.modal || '#modal-form';
|
||||
|
||||
var stock_location = null;
|
||||
|
||||
// Extra form fields
|
||||
var extra = makeNotesField();
|
||||
|
||||
// Header contentfor
|
||||
var header = `
|
||||
<div id='header-div'>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function updateLocationInfo(location) {
|
||||
var div = $(modal + ' #header-div');
|
||||
|
||||
if (stock_location && stock_location.pk) {
|
||||
div.html(`
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Location" %}</b></br>
|
||||
${stock_location.name}<br>
|
||||
<i>${stock_location.description}</i>
|
||||
</div>
|
||||
`);
|
||||
} else {
|
||||
div.html('');
|
||||
}
|
||||
}
|
||||
|
||||
barcodeDialog(
|
||||
'{% trans "Check Into Location" %}',
|
||||
{
|
||||
headerContent: header,
|
||||
extraFields: extra,
|
||||
preShow: function() {
|
||||
modalSetSubmitText(modal, '{% trans "Check In" %}');
|
||||
modalEnable(modal, false);
|
||||
},
|
||||
onShow: function() {
|
||||
},
|
||||
onSubmit: function() {
|
||||
// Called when the 'check-in' button is pressed
|
||||
if (!stock_location) {
|
||||
return;
|
||||
}
|
||||
|
||||
var items = [];
|
||||
|
||||
item_id_list.forEach(function(pk) {
|
||||
items.push({
|
||||
pk: pk,
|
||||
});
|
||||
})
|
||||
|
||||
var data = {
|
||||
location: stock_location.pk,
|
||||
notes: $(modal + ' #notes').val(),
|
||||
items: items,
|
||||
};
|
||||
|
||||
// Send API request
|
||||
inventreePut(
|
||||
'{% url "api-stock-transfer" %}',
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
success: function(response, status) {
|
||||
// First hide the modal
|
||||
$(modal).modal('hide');
|
||||
|
||||
if (status == 'success' && 'success' in response) {
|
||||
showAlertOrCache('alert-success', response.success, true);
|
||||
location.reload();
|
||||
} else {
|
||||
showAlertOrCache('alert-danger', '{% trans "Error transferring stock" %}', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
onScan: function(response) {
|
||||
updateLocationInfo(null);
|
||||
if ('stocklocation' in response) {
|
||||
// Barcode corresponds to a StockLocation
|
||||
stock_location = response.stocklocation;
|
||||
|
||||
updateLocationInfo(stock_location);
|
||||
modalEnable(modal, true);
|
||||
|
||||
} else {
|
||||
// Barcode does *NOT* correspond to a StockLocation
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
'{% trans "Barcode does not match a valid location" %}',
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
* Requires api.js to be loaded first
|
||||
*/
|
||||
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
|
||||
function stockStatusCodes() {
|
||||
return [
|
||||
@ -635,6 +636,9 @@ function loadStockTable(table, options) {
|
||||
table,
|
||||
[
|
||||
'#stock-print-options',
|
||||
{% if barcodes %}
|
||||
'#stock-barcode-options',
|
||||
{% endif %}
|
||||
'#stock-options',
|
||||
]
|
||||
);
|
||||
@ -700,6 +704,20 @@ function loadStockTable(table, options) {
|
||||
printTestReports(items);
|
||||
})
|
||||
|
||||
{% if barcodes %}
|
||||
$('#multi-item-barcode-scan-into-location').click(function() {
|
||||
var selections = $('#stock-table').bootstrapTable('getSelections');
|
||||
|
||||
var items = [];
|
||||
|
||||
selections.forEach(function(item) {
|
||||
items.push(item.pk);
|
||||
})
|
||||
|
||||
scanItemsIntoLocation(items);
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$('#multi-item-stocktake').click(function() {
|
||||
stockAdjustment('count');
|
||||
});
|
||||
|
@ -1,3 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
function editButton(url, text='Edit') {
|
||||
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
|
||||
}
|
||||
@ -263,4 +265,45 @@ function customGroupSorter(sortName, sortOrder, sortData) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Expose default bootstrap table string literals to translation layer
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
$.fn.bootstrapTable.locales['en-US-custom'] = {
|
||||
formatLoadingMessage: function () {
|
||||
return '{% trans "Loading data" %}';
|
||||
},
|
||||
formatRecordsPerPage: function (pageNumber) {
|
||||
return `${pageNumber} {% trans "rows per page" %}`;
|
||||
},
|
||||
formatShowingRows: function (pageFrom, pageTo, totalRows) {
|
||||
return `{% trans "Showing" %} ${pageFrom} {% trans "to" %} ${pageTo} {% trans "of" %} ${totalRows} {% trans "rows" %}`;
|
||||
},
|
||||
formatSearch: function () {
|
||||
return '{% trans "Search" %}';
|
||||
},
|
||||
formatNoMatches: function () {
|
||||
return '{% trans "No matching results" %}';
|
||||
},
|
||||
formatPaginationSwitch: function () {
|
||||
return '{% trans "Hide/Show pagination" %}';
|
||||
},
|
||||
formatRefresh: function () {
|
||||
return '{% trans "Refresh" %}';
|
||||
},
|
||||
formatToggle: function () {
|
||||
return '{% trans "Toggle" %}';
|
||||
},
|
||||
formatColumns: function () {
|
||||
return '{% trans "Columns" %}';
|
||||
},
|
||||
formatAllRows: function () {
|
||||
return '{% trans "All" %}';
|
||||
}
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']);
|
||||
|
||||
})(jQuery);
|
@ -1,5 +1,9 @@
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
|
||||
<nav class="navbar navbar-xs navbar-default navbar-fixed-top ">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header clearfix content-heading">
|
||||
@ -46,11 +50,13 @@
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% include "search_form.html" %}
|
||||
{% if barcodes %}
|
||||
<li id='navbar-barcode-li'>
|
||||
<button id='barcode-scan' class='btn btn-default' title='{% trans "Scan Barcode" %}'>
|
||||
<span class='fas fa-qrcode'></span>
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class='dropdown'>
|
||||
<a class='dropdown-toggle' data-toggle='dropdown' href="#">
|
||||
{% if not system_healthy %}
|
||||
@ -61,14 +67,13 @@
|
||||
{% if user.is_authenticated %}
|
||||
{% if user.is_staff %}
|
||||
<li><a href="/admin/"><span class="fas fa-user"></span> {% trans "Admin" %}</a></li>
|
||||
<hr>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'settings' %}"><span class="fas fa-cog"></span> {% trans "Settings" %}</a></li>
|
||||
<li><a href="{% url 'logout' %}"><span class="fas fa-sign-out-alt"></span> {% trans "Logout" %}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'login' %}"><span class="fas fa-sign-in-alt"></span> {% trans "Login" %}</a></li>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<hr>
|
||||
<li><a href="{% url 'settings' %}"><span class="fas fa-cog"></span> {% trans "Settings" %}</a></li>
|
||||
<li id='launch-stats'><a href='#'>
|
||||
{% if system_healthy %}
|
||||
<span class='fas fa-server'>
|
||||
@ -83,4 +88,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
@ -1,6 +1,8 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
||||
|
||||
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
|
||||
{% if owner_control.value == "True" %}
|
||||
{% authorized_owners location.owner as owners %}
|
||||
@ -19,6 +21,17 @@
|
||||
<span class='fas fa-plus-circle'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if barcodes %}
|
||||
<!-- Barcode actions menu -->
|
||||
<div class='btn-group'>
|
||||
<button id='stock-barcode-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle='dropdown' title='{% trans "Barcode Actions" %}'>
|
||||
<span class='fas fa-qrcode'></span> <span class='caret'></span>
|
||||
</button>
|
||||
<ul class='dropdown-menu'>
|
||||
<li><a href='#' id='multi-item-barcode-scan-into-location' title='{% trans "Scan to Location" %}'><span class='fas fa-sitemap'></span> {% trans "Scan to Location" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class='btn-group'>
|
||||
<button id='stock-print-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle="dropdown" title='{% trans "Printing Actions" %}'>
|
||||
<span class='fas fa-print'></span> <span class='caret'></span>
|
||||
|
@ -15,7 +15,7 @@ pygments==2.2.0 # Syntax highlighting
|
||||
tablib==0.13.0 # Import / export data files
|
||||
django-crispy-forms==1.8.1 # Form helpers
|
||||
django-import-export==2.0.0 # Data import / export for admin interface
|
||||
django-cleanup==4.0.0 # Manage deletion of old / unused uploaded files
|
||||
django-cleanup==5.1.0 # Manage deletion of old / unused uploaded files
|
||||
django-qr-code==1.2.0 # Generate QR codes
|
||||
flake8==3.8.3 # PEP checking
|
||||
pep8-naming==0.11.1 # PEP naming convention extension
|
||||
|
Loading…
Reference in New Issue
Block a user