Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2020-06-12 18:16:23 +10:00
commit 82433db1ea
32 changed files with 1968 additions and 717 deletions

View File

@ -20,9 +20,6 @@ from .version import inventreeVersion, inventreeInstanceName
from plugins import plugins as inventree_plugins
# Load barcode plugins
print("Loading barcode plugins")
barcode_plugins = inventree_plugins.load_barcode_plugins()
print("Loading action plugins")
action_plugins = inventree_plugins.load_action_plugins()
@ -100,66 +97,3 @@ class ActionPluginView(APIView):
'error': _("No matching action found"),
"action": action,
})
class BarcodePluginView(APIView):
"""
Endpoint for handling barcode scan requests.
Barcode data are decoded by the client application,
and sent to this endpoint (as a JSON object) for validation.
A barcode could follow the internal InvenTree barcode format,
or it could match to a third-party barcode format (e.g. Digikey).
"""
permission_classes = [
permissions.IsAuthenticated,
]
def post(self, request, *args, **kwargs):
response = {}
barcode_data = request.data.get('barcode', None)
print("Barcode data:")
print(barcode_data)
if barcode_data is None:
response['error'] = _('No barcode data provided')
else:
# Look for a barcode plugin that knows how to handle the data
for plugin_class in barcode_plugins:
# Instantiate the plugin with the provided plugin data
plugin = plugin_class(barcode_data)
if plugin.validate():
# Plugin should return a dict response
response = plugin.decode()
if type(response) is dict:
if 'success' not in response.keys() and 'error' not in response.keys():
response['success'] = _('Barcode successfully decoded')
else:
response = {
'error': _('Barcode plugin returned incorrect response')
}
response['plugin'] = plugin.plugin_name()
response['hash'] = plugin.hash()
break
if 'error' not in response and 'success' not in response:
response = {
'error': _('Unknown barcode format'),
}
# Include the original barcode data
response['barcode_data'] = barcode_data
return Response(response)

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import inspect
import importlib
import pkgutil
def iter_namespace(pkg):
return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
def get_modules(pkg):
# Return all modules in a given package
return [importlib.import_module(name) for finder, name, ispkg in iter_namespace(pkg)]
def get_classes(module):
# Return all classes in a given module
return inspect.getmembers(module, inspect.isclass)
def get_plugins(pkg, baseclass):
"""
Return a list of all modules under a given package.
- Modules must be a subclass of the provided 'baseclass'
- Modules must have a non-empty PLUGIN_NAME parameter
"""
plugins = []
modules = get_modules(pkg)
# Iterate through each module in the package
for mod in modules:
# Iterate through each class in the module
for item in get_classes(mod):
plugin = item[1]
if issubclass(plugin, baseclass) and plugin.PLUGIN_NAME:
plugins.append(plugin)
return plugins

View File

@ -82,12 +82,25 @@
float: left;
}
.navbar-barcode-li {
border-left: none;
border-right: none;
}
.navbar-nav > li {
border-left: 1px solid;
border-right: 1px solid;
border-color: #eee;
}
.navbar-form {
padding-right: 3px;
}
#barcode-scan {
margin-top: 8px;
}
.icon-header {
margin-right: 10px;
}
@ -259,6 +272,16 @@
margin-left: 1px;
}
.dropdown-buttons {
display: inline-block
}
.dropdown-menu .open{
z-index: 1000;
position: relative;
overflow: visible;
}
/* Styles for table buttons and filtering */
.button-toolbar .btn {
margin-left: 1px;
@ -455,6 +478,7 @@
.media-body {
padding-top: 10px;
overflow: visible;
}
.navigation {

View File

@ -166,14 +166,28 @@ function modalSetContent(modal, content='') {
}
function modalSetSubmitText(modal, text) {
if (text) {
$(modal).find('#modal-form-submit').html(text);
}
}
function modalSetCloseText(modal, text) {
if (text) {
$(modal).find('#modal-form-close').html(text);
}
}
function modalSetButtonText(modal, submit_text, close_text) {
/* Set the button text for a modal form
*
* submit_text - text for the form submit button
* close_text - text for the form dismiss button
*/
$(modal).find("#modal-form-submit").html(submit_text);
$(modal).find("#modal-form-close").html(close_text);
modalSetSubmitText(modal, submit_text);
modalSetCloseText(modal, close_text);
}
@ -442,7 +456,8 @@ function attachSecondaryModal(modal, options) {
*/
var select = '#id_' + options.field;
var option = new Option(response.text, response.pk, true, true)
var option = new Option(response.text, response.pk, true, true);
$(modal).find(select).append(option).trigger('change');
}

View File

@ -79,30 +79,3 @@ class APITests(APITestCase):
self.assertIn('instance', data)
self.assertEquals('InvenTree', data['server'])
def test_barcode_fail(self):
# Test barcode endpoint without auth
response = self.client.post(reverse('api-barcode-plugin'), format='json')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_barcode(self):
""" Test the barcode endpoint """
self.tokenAuth()
url = reverse('api-barcode-plugin')
data = {
'barcode': {
'asdlaksdfalsdkfsa;fdlkasd;'
},
}
response = self.client.post(url, format='json', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('error', response.data)
self.assertIn('barcode_data', response.data)
self.assertEqual(response.data['error'], 'Unknown barcode format')

View File

@ -20,6 +20,7 @@ from stock.urls import stock_urls
from build.urls import build_urls
from order.urls import order_urls
from barcode.api import barcode_api_urls
from common.api import common_api_urls
from part.api import part_api_urls, bom_api_urls
from company.api import company_api_urls
@ -37,13 +38,15 @@ from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView
from .views import DynamicJsView
from .api import InfoView, BarcodePluginView, ActionPluginView
from .api import InfoView
from .api import ActionPluginView
from users.urls import user_urls
admin.site.site_header = "InvenTree Admin"
apipatterns = [
url(r'^barcode/', include(barcode_api_urls)),
url(r'^common/', include(common_api_urls)),
url(r'^part/', include(part_api_urls)),
url(r'^bom/', include(bom_api_urls)),
@ -56,7 +59,6 @@ apipatterns = [
url(r'^user/', include(user_urls)),
# Plugin endpoints
url(r'^barcode/', BarcodePluginView.as_view(), name='api-barcode-plugin'),
url(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'),
# InvenTree information endpoint
@ -74,7 +76,9 @@ settings_urls = [
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),
]
# Some javascript files are served 'dynamically', allowing them to pass through the Django translation layer
dynamic_javascript_urls = [
url(r'^barcode.js', DynamicJsView.as_view(template_name='js/barcode.html'), name='barcode.js'),
url(r'^part.js', DynamicJsView.as_view(template_name='js/part.html'), name='part.js'),
url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.html'), name='stock.js'),
url(r'^build.js', DynamicJsView.as_view(template_name='js/build.html'), name='build.js'),

240
InvenTree/barcode/api.py Normal file
View File

@ -0,0 +1,240 @@
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.conf.urls import url
from django.utils.translation import ugettext as _
from rest_framework.exceptions import ValidationError
from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from stock.models import StockItem
from stock.serializers import StockItemSerializer
from barcode.barcode import load_barcode_plugins, hash_barcode
class BarcodeScan(APIView):
"""
Endpoint for handling generic barcode scan requests.
Barcode data are decoded by the client application,
and sent to this endpoint (as a JSON object) for validation.
A barcode could follow the internal InvenTree barcode format,
or it could match to a third-party barcode format (e.g. Digikey).
When a barcode is sent to the server, the following parameters must be provided:
- barcode: The raw barcode data
plugins:
Third-party barcode formats may be supported using 'plugins'
(more information to follow)
hashing:
Barcode hashes are calculated using MD5
"""
permission_classes = [
permissions.IsAuthenticated,
]
def post(self, request, *args, **kwargs):
"""
Respond to a barcode POST request
"""
data = request.data
if 'barcode' not in data:
raise ValidationError({'barcode': _('Must provide barcode_data parameter')})
plugins = load_barcode_plugins()
barcode_data = data.get('barcode')
# Look for a barcode plugin which knows how to deal with this barcode
plugin = None
for plugin_class in plugins:
plugin_instance = plugin_class(barcode_data)
if plugin_instance.validate():
plugin = plugin_instance
break
match_found = False
response = {}
response['barcode_data'] = barcode_data
# A plugin has been found!
if plugin is not None:
# Try to associate with a stock item
item = plugin.getStockItem()
if item is None:
item = plugin.getStockItemByHash()
if item is not None:
response['stockitem'] = plugin.renderStockItem(item)
response['url'] = reverse('stock-item-detail', kwargs={'pk': item.id})
match_found = True
# Try to associate with a stock location
loc = plugin.getStockLocation()
if loc is not None:
response['stocklocation'] = plugin.renderStockLocation(loc)
response['url'] = reverse('location-detail', kwargs={'pk': loc.id})
match_found = True
# Try to associate with a part
part = plugin.getPart()
if part is not None:
response['part'] = plugin.renderPart(part)
response['url'] = reverse('part-detail', kwargs={'pk': part.id})
match_found = True
response['hash'] = plugin.hash()
response['plugin'] = plugin.name
# No plugin is found!
# However, the hash of the barcode may still be associated with a StockItem!
else:
hash = hash_barcode(barcode_data)
response['hash'] = hash
response['plugin'] = None
# Try to look for a matching StockItem
try:
item = StockItem.objects.get(uid=hash)
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
response['stockitem'] = serializer.data
response['url'] = reverse('stock-item-detail', kwargs={'pk': item.id})
match_found = True
except StockItem.DoesNotExist:
pass
if not match_found:
response['error'] = _('No match found for barcode data')
else:
response['success'] = _('Match found for barcode data')
return Response(response)
class BarcodeAssign(APIView):
"""
Endpoint for assigning a barcode to a stock item.
- This only works if the barcode is not already associated with an object in the database
- If the barcode does not match an object, then the barcode hash is assigned to the StockItem
"""
permission_classes = [
permissions.IsAuthenticated
]
def post(self, request, *args, **kwargs):
data = request.data
if 'barcode' not in data:
raise ValidationError({'barcode': _('Must provide barcode_data parameter')})
if 'stockitem' not in data:
raise ValidationError({'stockitem': _('Must provide stockitem parameter')})
barcode_data = data['barcode']
try:
item = StockItem.objects.get(pk=data['stockitem'])
except (ValueError, StockItem.DoesNotExist):
raise ValidationError({'stockitem': _('No matching stock item found')})
plugins = load_barcode_plugins()
plugin = None
for plugin_class in plugins:
plugin_instance = plugin_class(barcode_data)
if plugin_instance.validate():
plugin = plugin_instance
break
match_found = False
response = {}
response['barcode_data'] = barcode_data
# Matching plugin was found
if plugin is not None:
hash = plugin.hash()
response['hash'] = hash
response['plugin'] = plugin.name
# Ensure that the barcode does not already match a database entry
if plugin.getStockItem() is not None:
match_found = True
response['error'] = _('Barcode already matches StockItem object')
if plugin.getStockLocation() is not None:
match_found = True
response['error'] = _('Barcode already matches StockLocation object')
if plugin.getPart() is not None:
match_found = True
response['error'] = _('Barcode already matches Part object')
if not match_found:
item = plugin.getStockItemByHash()
if item is not None:
response['error'] = _('Barcode hash already matches StockItem object')
match_found = True
else:
hash = hash_barcode(barcode_data)
response['hash'] = hash
response['plugin'] = None
# Lookup stock item by hash
try:
item = StockItem.objects.get(uid=hash)
response['error'] = _('Barcode hash already matches StockItem object')
match_found = True
except StockItem.DoesNotExist:
pass
if not match_found:
response['success'] = _('Barcode associated with StockItem')
# Save the barcode hash
item.uid = response['hash']
item.save()
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
response['stockitem'] = serializer.data
return Response(response)
barcode_api_urls = [
url(r'^link/$', BarcodeAssign.as_view(), name='api-barcode-link'),
# Catch-all performs barcode 'scan'
url(r'^.*$', BarcodeScan.as_view(), name='api-barcode-scan'),
]

View File

@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
import hashlib
from InvenTree import plugins as InvenTreePlugins
from barcode import plugins as BarcodePlugins
from stock.models import StockItem
from stock.serializers import StockItemSerializer, LocationSerializer
from part.serializers import PartSerializer
def hash_barcode(barcode_data):
"""
Calculate an MD5 hash of barcode data
"""
hash = hashlib.md5(str(barcode_data).encode())
return str(hash.hexdigest())
class BarcodePlugin:
"""
Base class for barcode handling.
Custom barcode plugins should extend this class as necessary.
"""
# Override the barcode plugin name for each sub-class
PLUGIN_NAME = ""
@property
def name(self):
return self.PLUGIN_NAME
def __init__(self, barcode_data):
"""
Initialize the BarcodePlugin instance
Args:
barcode_data - The raw barcode data
"""
self.data = barcode_data
def getStockItem(self):
"""
Attempt to retrieve a StockItem associated with this barcode.
Default implementation returns None
"""
return None
def getStockItemByHash(self):
"""
Attempt to retrieve a StockItem associated with this barcode,
based on the barcode hash.
"""
try:
item = StockItem.objects.get(uid=self.hash())
return item
except StockItem.DoesNotExist:
return None
def renderStockItem(self, item):
"""
Render a stock item to JSON response
"""
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
return serializer.data
def getStockLocation(self):
"""
Attempt to retrieve a StockLocation associated with this barcode.
Default implementation returns None
"""
return None
def renderStockLocation(self, loc):
"""
Render a stock location to a JSON response
"""
serializer = LocationSerializer(loc)
return serializer.data
def getPart(self):
"""
Attempt to retrieve a Part associated with this barcode.
Default implementation returns None
"""
return None
def renderPart(self, part):
"""
Render a part to JSON response
"""
serializer = PartSerializer(part)
return serializer.data
def hash(self):
"""
Calculate a hash for the barcode data.
This is supposed to uniquely identify the barcode contents,
at least within the bardcode sub-type.
The default implementation simply returns an MD5 hash of the barcode data,
encoded to a string.
This may be sufficient for most applications, but can obviously be overridden
by a subclass.
"""
return hash_barcode(self.data)
def validate(self):
"""
Default implementation returns False
"""
return False
def load_barcode_plugins(debug=False):
"""
Function to load all barcode plugins
"""
if debug:
print("Loading barcode plugins")
plugins = InvenTreePlugins.get_plugins(BarcodePlugins, BarcodePlugin)
if debug:
if len(plugins) > 0:
print("Discovered {n} plugins:".format(n=len(plugins)))
for p in plugins:
print(" - {p}".format(p=p.PLUGIN_NAME))
else:
print("No barcode plugins found")
return plugins

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
"""
DigiKey barcode decoding
"""
from barcode.barcode import BarcodePlugin
class DigikeyBarcodePlugin(BarcodePlugin):
PLUGIN_NAME = "DigikeyBarcode"
def validate(self):
"""
TODO: Validation of Digikey barcodes.
"""
return False

View File

@ -0,0 +1,108 @@
"""
The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself.
It can be used as a template for developing third-party barcode plugins.
The data format is very simple, and maps directly to database objects,
via the "id" parameter.
Parsing an InvenTree barcode simply involves validating that the
references model objects actually exist in the database.
"""
# -*- coding: utf-8 -*-
import json
from barcode.barcode import BarcodePlugin
from stock.models import StockItem, StockLocation
from part.models import Part
from rest_framework.exceptions import ValidationError
class InvenTreeBarcodePlugin(BarcodePlugin):
PLUGIN_NAME = "InvenTreeBarcode"
def validate(self):
"""
An "InvenTree" barcode must be a jsonnable-dict with the following tags:
{
'tool': 'InvenTree',
'version': <anything>
}
"""
# The data must either be dict or be able to dictified
if type(self.data) is dict:
pass
elif type(self.data) is str:
try:
self.data = json.loads(self.data)
except json.JSONDecodeError:
return False
else:
return False
for key in ['tool', 'version']:
if key not in self.data.keys():
return False
if not self.data['tool'] == 'InvenTree':
return False
return True
def getStockItem(self):
for k in self.data.keys():
if k.lower() == 'stockitem':
try:
pk = self.data[k]['id']
except (AttributeError, KeyError):
raise ValidationError({k: "id parameter not supplied"})
try:
item = StockItem.objects.get(pk=pk)
return item
except (ValueError, StockItem.DoesNotExist):
raise ValidationError({k, "Stock item does not exist"})
return None
def getStockLocation(self):
for k in self.data.keys():
if k.lower() == 'stocklocation':
try:
pk = self.data[k]['id']
except (AttributeError, KeyError):
raise ValidationError({k: "id parameter not supplied"})
try:
loc = StockLocation.objects.get(pk=pk)
return loc
except (ValueError, StockLocation.DoesNotExist):
raise ValidationError({k, "Stock location does not exist"})
return None
def getPart(self):
for k in self.data.keys():
if k.lower() == 'part':
try:
pk = self.data[k]['id']
except (AttributeError, KeyError):
raise ValidationError({k, 'id parameter not supplied'})
try:
part = Part.objects.get(pk=pk)
return part
except (ValueError, Part.DoesNotExist):
raise ValidationError({k, 'Part does not exist'})
return None

118
InvenTree/barcode/tests.py Normal file
View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
"""
Unit tests for Barcode endpoints
"""
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from stock.models import StockItem
class BarcodeAPITest(APITestCase):
fixtures = [
'category',
'part',
'location',
'stock'
]
def setUp(self):
# Create a user for auth
User = get_user_model()
User.objects.create_user('testuser', 'test@testing.com', 'password')
self.client.login(username='testuser', password='password')
self.scan_url = reverse('api-barcode-scan')
self.assign_url = reverse('api-barcode-link')
def postBarcode(self, url, barcode):
return self.client.post(url, format='json', data={'barcode': str(barcode)})
def test_invalid(self):
response = self.client.post(self.scan_url, format='json', data={})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_empty(self):
response = self.postBarcode(self.scan_url, '')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.data
self.assertIn('error', data)
self.assertIn('barcode_data', data)
self.assertIn('hash', data)
self.assertIn('plugin', data)
self.assertIsNone(data['plugin'])
def test_barcode_generation(self):
item = StockItem.objects.get(pk=522)
response = self.postBarcode(self.scan_url, item.format_barcode())
data = response.data
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('stockitem', data)
pk = data['stockitem']['pk']
self.assertEqual(pk, item.pk)
def test_association(self):
"""
Test that a barcode can be associated with a StockItem
"""
item = StockItem.objects.get(pk=522)
self.assertEqual(len(item.uid), 0)
barcode_data = 'A-TEST-BARCODE-STRING'
response = self.client.post(
self.assign_url, format='json',
data={
'barcode': barcode_data,
'stockitem': item.pk
}
)
data = response.data
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('success', data)
hash = data['hash']
# Read the item out from the database again
item = StockItem.objects.get(pk=522)
self.assertEqual(hash, item.uid)
# Ensure that the same UID cannot be assigned to a different stock item!
response = self.client.post(
self.assign_url, format='json',
data={
'barcode': barcode_data,
'stockitem': 521
}
)
data = response.data
self.assertIn('error', data)
self.assertNotIn('success', data)

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-06-09 06:17+0000\n"
"POT-Creation-Date: 2020-06-12 00:43+0000\n"
"PO-Revision-Date: 2020-05-03 11:32+0200\n"
"Last-Translator: Christian Schlüter <chschlue@gmail.com>\n"
"Language-Team: C <kde-i18n-doc@kde.org>\n"
@ -17,30 +17,14 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 19.12.0\n"
#: InvenTree/api.py:86
#: InvenTree/api.py:83
msgid "No action specified"
msgstr "Keine Aktion angegeben"
#: InvenTree/api.py:100
#: InvenTree/api.py:97
msgid "No matching action found"
msgstr "Keine passende Aktion gefunden"
#: InvenTree/api.py:131
msgid "No barcode data provided"
msgstr "Keine Strichcodedaten bereitgestellt"
#: InvenTree/api.py:146
msgid "Barcode successfully decoded"
msgstr "Strichcode erfolgreich dekodiert"
#: InvenTree/api.py:149
msgid "Barcode plugin returned incorrect response"
msgstr "Ungültige Antwort vom Strichcode-Plugin"
#: InvenTree/api.py:159
msgid "Unknown barcode format"
msgstr "Unbekanntes Strichcode-Format"
#: InvenTree/forms.py:101 build/forms.py:37
msgid "Confirm"
msgstr "Bestätigen"
@ -225,6 +209,50 @@ msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein"
msgid "Database Statistics"
msgstr "Datenbankstatistiken"
#: barcode/api.py:53 barcode/api.py:150
msgid "Must provide barcode_data parameter"
msgstr ""
#: barcode/api.py:126
msgid "No match found for barcode data"
msgstr ""
#: barcode/api.py:128
msgid "Match found for barcode data"
msgstr ""
#: barcode/api.py:153
msgid "Must provide stockitem parameter"
msgstr ""
#: barcode/api.py:160
#, fuzzy
#| msgid "No matching action found"
msgid "No matching stock item found"
msgstr "Keine passende Aktion gefunden"
#: barcode/api.py:190
msgid "Barcode already matches StockItem object"
msgstr ""
#: barcode/api.py:194
msgid "Barcode already matches StockLocation object"
msgstr ""
#: barcode/api.py:198
msgid "Barcode already matches Part object"
msgstr ""
#: barcode/api.py:204 barcode/api.py:216
msgid "Barcode hash already matches StockItem object"
msgstr ""
#: barcode/api.py:222
#, fuzzy
#| msgid "Create new Stock Item"
msgid "Barcode associated with StockItem"
msgstr "Neues Lagerobjekt hinzufügen"
#: build/forms.py:58
#, fuzzy
#| msgid "Location Details"
@ -333,7 +361,7 @@ msgstr "Chargennummer für diese Bau-Ausgabe"
#: company/templates/company/supplier_part_base.html:60
#: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:74 part/templates/part/part_base.html:88
#: stock/models.py:368 stock/templates/stock/item_base.html:221
#: stock/models.py:368 stock/templates/stock/item_base.html:230
msgid "External Link"
msgstr "Externer Link"
@ -414,7 +442,7 @@ msgstr "Neues Lagerobjekt"
#: build/templates/build/allocate.html:161
#: order/templates/order/sales_order_detail.html:68
#: order/templates/order/sales_order_detail.html:150 stock/models.py:362
#: stock/templates/stock/item_base.html:180
#: stock/templates/stock/item_base.html:189
msgid "Serial Number"
msgstr "Seriennummer"
@ -431,7 +459,7 @@ msgstr "Seriennummer"
#: part/templates/part/allocation.html:49
#: stock/templates/stock/item_base.html:26
#: stock/templates/stock/item_base.html:32
#: stock/templates/stock/item_base.html:186
#: stock/templates/stock/item_base.html:195
#: stock/templates/stock/stock_adjust.html:18 templates/js/bom.html:172
#: templates/js/build.html:52 templates/js/stock.html:653
msgid "Quantity"
@ -439,7 +467,7 @@ msgstr "Anzahl"
#: build/templates/build/allocate.html:177
#: build/templates/build/auto_allocate.html:20
#: stock/templates/stock/item_base.html:162
#: stock/templates/stock/item_base.html:171
#: stock/templates/stock/stock_adjust.html:17 templates/js/stock.html:493
msgid "Location"
msgstr "Standort"
@ -521,7 +549,7 @@ msgstr "Keine Lagerobjekt gefunden, die diesem Bau zugewiesen werden können"
#: build/templates/build/build_base.html:8
#: build/templates/build/build_base.html:34
#: build/templates/build/complete.html:6
#: stock/templates/stock/item_base.html:200 templates/js/build.html:33
#: stock/templates/stock/item_base.html:209 templates/js/build.html:33
#: templates/navbar.html:12
msgid "Build"
msgstr "Bau"
@ -541,7 +569,7 @@ msgstr "Bau-Status"
#: build/templates/build/build_base.html:80
#: build/templates/build/detail.html:42
#: order/templates/order/receive_parts.html:24
#: stock/templates/stock/item_base.html:253 templates/js/build.html:57
#: stock/templates/stock/item_base.html:262 templates/js/build.html:57
#: templates/js/order.html:162 templates/js/order.html:235
#: templates/js/stock.html:480
msgid "Status"
@ -553,7 +581,7 @@ msgstr "Status"
#: order/templates/order/sales_order_notes.html:10
#: order/templates/order/sales_order_ship.html:25
#: part/templates/part/allocation.html:27
#: stock/templates/stock/item_base.html:150 templates/js/order.html:209
#: stock/templates/stock/item_base.html:159 templates/js/order.html:209
msgid "Sales Order"
msgstr "Bestellung"
@ -622,7 +650,7 @@ msgid "Stock can be taken from any available location."
msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden."
#: build/templates/build/detail.html:48
#: stock/templates/stock/item_base.html:193 templates/js/stock.html:488
#: stock/templates/stock/item_base.html:202 templates/js/stock.html:488
msgid "Batch"
msgstr "Los"
@ -976,14 +1004,14 @@ msgstr "Hersteller"
#: company/templates/company/supplier_part_detail.html:21 order/models.py:148
#: order/templates/order/order_base.html:74
#: order/templates/order/order_wizard/select_pos.html:30
#: stock/templates/stock/item_base.html:228 templates/js/company.html:52
#: stock/templates/stock/item_base.html:237 templates/js/company.html:52
#: templates/js/company.html:134 templates/js/order.html:144
msgid "Supplier"
msgstr "Zulieferer"
#: company/templates/company/detail.html:26 order/models.py:314
#: order/templates/order/sales_order_base.html:73 stock/models.py:357
#: stock/models.py:358 stock/templates/stock/item_base.html:137
#: stock/models.py:358 stock/templates/stock/item_base.html:146
#: templates/js/company.html:44 templates/js/order.html:217
msgid "Customer"
msgstr "Kunde"
@ -1098,7 +1126,7 @@ msgstr "Neuer Auftrag"
#: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:19 stock/models.py:331
#: stock/templates/stock/item_base.html:233 templates/js/company.html:150
#: stock/templates/stock/item_base.html:242 templates/js/company.html:150
msgid "Supplier Part"
msgstr "Zulieferer-Teil"
@ -1385,7 +1413,7 @@ msgstr "Position - Notizen"
#: order/models.py:466 order/templates/order/order_base.html:9
#: order/templates/order/order_base.html:23
#: stock/templates/stock/item_base.html:207 templates/js/order.html:136
#: stock/templates/stock/item_base.html:216 templates/js/order.html:136
msgid "Purchase Order"
msgstr "Kaufvertrag"
@ -2114,14 +2142,14 @@ msgstr "Bestellung"
#: part/templates/part/allocation.html:45
#: stock/templates/stock/item_base.html:8
#: stock/templates/stock/item_base.html:58
#: stock/templates/stock/item_base.html:215
#: stock/templates/stock/item_base.html:224
#: stock/templates/stock/stock_adjust.html:16 templates/js/build.html:106
#: templates/js/stock.html:623
msgid "Stock Item"
msgstr "Lagerobjekt"
#: part/templates/part/allocation.html:20
#: stock/templates/stock/item_base.html:156
#: stock/templates/stock/item_base.html:165
msgid "Build Order"
msgstr "Bauauftrag"
@ -2539,7 +2567,7 @@ msgstr "Benutzt in"
msgid "Tracking"
msgstr "Tracking"
#: part/templates/part/tabs.html:64 stock/templates/stock/item_base.html:259
#: part/templates/part/tabs.html:64 stock/templates/stock/item_base.html:268
msgid "Tests"
msgstr ""
@ -2738,22 +2766,6 @@ msgstr "BOM-Position beaarbeiten"
msgid "Confim BOM item deletion"
msgstr "Löschung von BOM-Position bestätigen"
#: plugins/barcode/inventree.py:70
msgid "Part does not exist"
msgstr "Teil existiert nicht"
#: plugins/barcode/inventree.py:79
msgid "StockLocation does not exist"
msgstr "Lagerort existiert nicht"
#: plugins/barcode/inventree.py:89
msgid "StockItem does not exist"
msgstr "Lagerobjekt existiert nicht"
#: plugins/barcode/inventree.py:92
msgid "No matching data"
msgstr "Keine passenden Daten"
#: report/models.py:167
#, fuzzy
#| msgid "Template part"
@ -2834,7 +2846,7 @@ msgstr "Teil kann nicht zu sich selbst gehören"
msgid "Parent Stock Item"
msgstr "Eltern-Lagerobjekt"
#: stock/models.py:322 stock/templates/stock/item_base.html:129
#: stock/models.py:322 stock/templates/stock/item_base.html:138
msgid "Base Part"
msgstr "Basisteil"
@ -3066,99 +3078,132 @@ msgstr ""
"Dieses Lagerobjekt wird automatisch gelöscht wenn der Lagerbestand "
"aufgebraucht ist."
#: stock/templates/stock/item_base.html:74
#: stock/templates/stock/item_base.html:78
#, fuzzy
#| msgid "Add stock"
msgid "Add to stock"
msgstr "Bestand hinzufügen"
#| msgid "Source Location"
msgid "Barcode actions"
msgstr "Quell-Standort"
#: stock/templates/stock/item_base.html:77
#: stock/templates/stock/item_base.html:80
#, fuzzy
#| msgid "Remove From Stock"
msgid "Take from stock"
msgstr "Aus Lagerbestand entfernen"
#| msgid "Part QR Code"
msgid "Show QR Code"
msgstr "Teil-QR-Code"
#: stock/templates/stock/item_base.html:80 templates/stock_table.html:14
#: stock/templates/stock/item_base.html:81
msgid "Print Label"
msgstr ""
#: stock/templates/stock/item_base.html:83 templates/js/barcode.html:263
#: templates/js/barcode.html:268
msgid "Unlink Barcode"
msgstr ""
#: stock/templates/stock/item_base.html:85
msgid "Link Barcode"
msgstr ""
#: stock/templates/stock/item_base.html:92
#, fuzzy
#| msgid "Confirm stock adjustment"
msgid "Stock adjustment actions"
msgstr "Bestands-Anpassung bestätigen"
#: stock/templates/stock/item_base.html:95 templates/stock_table.html:14
msgid "Count stock"
msgstr "Bestand zählen"
#: stock/templates/stock/item_base.html:84
#, fuzzy
#| msgid "Serialize Stock"
msgid "Serialize stock"
msgstr "Lagerbestand erfassen"
#: stock/templates/stock/item_base.html:96 templates/stock_table.html:12
msgid "Add stock"
msgstr "Bestand hinzufügen"
#: stock/templates/stock/item_base.html:90 stock/views.py:232
#, fuzzy
#| msgid "Item assigned to customer?"
msgid "Assign to Customer"
msgstr "Ist dieses Objekt einem Kunden zugeteilt?"
#: stock/templates/stock/item_base.html:97 templates/stock_table.html:13
msgid "Remove stock"
msgstr "Bestand entfernen"
#: stock/templates/stock/item_base.html:94
#: stock/templates/stock/item_base.html:99
#, fuzzy
#| msgid "Order stock"
msgid "Transfer stock"
msgstr "Bestand bestellen"
#: stock/templates/stock/item_base.html:97
#: stock/templates/stock/item_base.html:105
#, fuzzy
#| msgid "Stock Locations"
msgid "Stock actions"
msgstr "Lagerobjekt-Standorte"
#: stock/templates/stock/item_base.html:108
#, fuzzy
#| msgid "Serialize Stock"
msgid "Serialize stock"
msgstr "Lagerbestand erfassen"
#: stock/templates/stock/item_base.html:111
#, fuzzy
#| msgid "Item assigned to customer?"
msgid "Assign to customer"
msgstr "Ist dieses Objekt einem Kunden zugeteilt?"
#: stock/templates/stock/item_base.html:114
#, fuzzy
#| msgid "Count stock items"
msgid "Convert to variant"
msgstr "Lagerobjekte zählen"
#: stock/templates/stock/item_base.html:116
#, fuzzy
#| msgid "Count stock items"
msgid "Duplicate stock item"
msgstr "Lagerobjekte zählen"
#: stock/templates/stock/item_base.html:102
#, fuzzy
#| msgid "Count stock items"
msgid "Convert stock to variant"
msgstr "Lagerobjekte zählen"
#: stock/templates/stock/item_base.html:107
msgid "Generate test report"
msgstr ""
#: stock/templates/stock/item_base.html:111
#: stock/templates/stock/item_base.html:117
#, fuzzy
#| msgid "Edit Stock Item"
msgid "Edit stock item"
msgstr "Lagerobjekt bearbeiten"
#: stock/templates/stock/item_base.html:115
#: stock/templates/stock/item_base.html:119
#, fuzzy
#| msgid "Delete Stock Item"
msgid "Delete stock item"
msgstr "Lagerobjekt löschen"
#: stock/templates/stock/item_base.html:124
msgid "Generate test report"
msgstr ""
#: stock/templates/stock/item_base.html:133
msgid "Stock Item Details"
msgstr "Lagerbestands-Details"
#: stock/templates/stock/item_base.html:144
#: stock/templates/stock/item_base.html:153
msgid "Belongs To"
msgstr "Gehört zu"
#: stock/templates/stock/item_base.html:166
#: stock/templates/stock/item_base.html:175
#, fuzzy
#| msgid "No stock location set"
msgid "No location set"
msgstr "Kein Lagerort gesetzt"
#: stock/templates/stock/item_base.html:173
#: stock/templates/stock/item_base.html:182
msgid "Unique Identifier"
msgstr "Eindeutiger Bezeichner"
#: stock/templates/stock/item_base.html:214
#: stock/templates/stock/item_base.html:223
msgid "Parent Item"
msgstr "Elternposition"
#: stock/templates/stock/item_base.html:239
#: stock/templates/stock/item_base.html:248
msgid "Last Updated"
msgstr "Zuletzt aktualisiert"
#: stock/templates/stock/item_base.html:244
#: stock/templates/stock/item_base.html:253
msgid "Last Stocktake"
msgstr "Letzte Inventur"
#: stock/templates/stock/item_base.html:248
#: stock/templates/stock/item_base.html:257
msgid "No stocktake performed"
msgstr "Keine Inventur ausgeführt"
@ -3289,6 +3334,12 @@ msgstr "Lagerobjekt bearbeiten"
msgid "Delete Stock Item Attachment"
msgstr "Teilanhang löschen"
#: stock/views.py:232
#, fuzzy
#| msgid "Item assigned to customer?"
msgid "Assign to Customer"
msgstr "Ist dieses Objekt einem Kunden zugeteilt?"
#: stock/views.py:270
#, fuzzy
#| msgid "Delete Template"
@ -3424,8 +3475,10 @@ msgid "Create new Stock Item"
msgstr "Neues Lagerobjekt hinzufügen"
#: stock/views.py:1167
msgid "Copy Stock Item"
msgstr "Lagerobjekt kopieren"
#, fuzzy
#| msgid "Count stock items"
msgid "Duplicate Stock Item"
msgstr "Lagerobjekte zählen"
#: stock/views.py:1240
msgid "Invalid quantity"
@ -3550,6 +3603,49 @@ msgstr ""
msgid "Delete attachment"
msgstr "Anhang löschen"
#: templates/js/barcode.html:28
#, fuzzy
#| msgid "No barcode data provided"
msgid "Scan barcode data here using wedge scanner"
msgstr "Keine Strichcodedaten bereitgestellt"
#: templates/js/barcode.html:34
#, fuzzy
#| msgid "Source Location"
msgid "Barcode"
msgstr "Quell-Standort"
#: templates/js/barcode.html:42
#, fuzzy
#| msgid "No barcode data provided"
msgid "Enter barcode data"
msgstr "Keine Strichcodedaten bereitgestellt"
#: templates/js/barcode.html:140
#, fuzzy
#| msgid "No barcode data provided"
msgid "Scan barcode data below"
msgstr "Keine Strichcodedaten bereitgestellt"
#: templates/js/barcode.html:195 templates/js/barcode.html:243
#, fuzzy
#| msgid "Unknown barcode format"
msgid "Unknown response from server"
msgstr "Unbekanntes Strichcode-Format"
#: templates/js/barcode.html:198 templates/js/barcode.html:247
msgid "Invalid server response"
msgstr ""
#: templates/js/barcode.html:265
msgid ""
"This will remove the association between this stock item and the barcode"
msgstr ""
#: templates/js/barcode.html:271
msgid "Unlink"
msgstr ""
#: templates/js/bom.html:143
msgid "Open subassembly"
msgstr "Unterbaugruppe öffnen"
@ -3693,8 +3789,10 @@ msgid "NO RESULT"
msgstr ""
#: templates/js/stock.html:58
#, fuzzy
#| msgid "Edit Sales Order"
msgid "Add test result"
msgstr ""
msgstr "Auftrag bearbeiten"
#: templates/js/stock.html:76
#, fuzzy
@ -3832,42 +3930,38 @@ msgstr "Kaufen"
msgid "Sell"
msgstr "Verkaufen"
#: templates/navbar.html:36
#: templates/navbar.html:32
msgid "Scan Barcode"
msgstr ""
#: templates/navbar.html:41
msgid "Admin"
msgstr "Admin"
#: templates/navbar.html:39
#: templates/navbar.html:44
msgid "Settings"
msgstr "Einstellungen"
#: templates/navbar.html:40
#: templates/navbar.html:45
msgid "Logout"
msgstr "Ausloggen"
#: templates/navbar.html:42
#: templates/navbar.html:47
msgid "Login"
msgstr "Einloggen"
#: templates/navbar.html:45
#: templates/navbar.html:50
msgid "About InvenTree"
msgstr "Über InvenBaum"
#: templates/navbar.html:46
#: templates/navbar.html:51
msgid "Statistics"
msgstr "Statistiken"
#: templates/search_form.html:6
#: templates/search_form.html:6 templates/search_form.html:8
msgid "Search"
msgstr "Suche"
#: templates/stock_table.html:12
msgid "Add stock"
msgstr "Bestand hinzufügen"
#: templates/stock_table.html:13
msgid "Remove stock"
msgstr "Bestand entfernen"
#: templates/stock_table.html:15
msgid "Move stock"
msgstr "Bestand bewegen"
@ -3880,6 +3974,37 @@ msgstr "Bestand bestellen"
msgid "Delete Stock"
msgstr "Bestand löschen"
#~ msgid "Barcode successfully decoded"
#~ msgstr "Strichcode erfolgreich dekodiert"
#~ msgid "Barcode plugin returned incorrect response"
#~ msgstr "Ungültige Antwort vom Strichcode-Plugin"
#~ msgid "Part does not exist"
#~ msgstr "Teil existiert nicht"
#~ msgid "StockLocation does not exist"
#~ msgstr "Lagerort existiert nicht"
#~ msgid "StockItem does not exist"
#~ msgstr "Lagerobjekt existiert nicht"
#~ msgid "No matching data"
#~ msgstr "Keine passenden Daten"
#, fuzzy
#~| msgid "Add stock"
#~ msgid "Add to stock"
#~ msgstr "Bestand hinzufügen"
#, fuzzy
#~| msgid "Remove From Stock"
#~ msgid "Take from stock"
#~ msgstr "Aus Lagerbestand entfernen"
#~ msgid "Copy Stock Item"
#~ msgstr "Lagerobjekt kopieren"
#~ msgid "Part cannot be a variant of another part if it is already a template"
#~ msgstr ""
#~ "Teil kann keine Variante eines anderen Teils sein wenn es bereits eine "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-06-09 06:17+0000\n"
"POT-Creation-Date: 2020-06-12 00:43+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"
@ -18,30 +18,14 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: InvenTree/api.py:86
#: InvenTree/api.py:83
msgid "No action specified"
msgstr ""
#: InvenTree/api.py:100
#: InvenTree/api.py:97
msgid "No matching action found"
msgstr ""
#: InvenTree/api.py:131
msgid "No barcode data provided"
msgstr ""
#: InvenTree/api.py:146
msgid "Barcode successfully decoded"
msgstr ""
#: InvenTree/api.py:149
msgid "Barcode plugin returned incorrect response"
msgstr ""
#: InvenTree/api.py:159
msgid "Unknown barcode format"
msgstr ""
#: InvenTree/forms.py:101 build/forms.py:37
msgid "Confirm"
msgstr ""
@ -218,6 +202,46 @@ msgstr ""
msgid "Database Statistics"
msgstr ""
#: barcode/api.py:53 barcode/api.py:150
msgid "Must provide barcode_data parameter"
msgstr ""
#: barcode/api.py:126
msgid "No match found for barcode data"
msgstr ""
#: barcode/api.py:128
msgid "Match found for barcode data"
msgstr ""
#: barcode/api.py:153
msgid "Must provide stockitem parameter"
msgstr ""
#: barcode/api.py:160
msgid "No matching stock item found"
msgstr ""
#: barcode/api.py:190
msgid "Barcode already matches StockItem object"
msgstr ""
#: barcode/api.py:194
msgid "Barcode already matches StockLocation object"
msgstr ""
#: barcode/api.py:198
msgid "Barcode already matches Part object"
msgstr ""
#: barcode/api.py:204 barcode/api.py:216
msgid "Barcode hash already matches StockItem object"
msgstr ""
#: barcode/api.py:222
msgid "Barcode associated with StockItem"
msgstr ""
#: build/forms.py:58
msgid "Location of completed parts"
msgstr ""
@ -318,7 +342,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:60
#: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:74 part/templates/part/part_base.html:88
#: stock/models.py:368 stock/templates/stock/item_base.html:221
#: stock/models.py:368 stock/templates/stock/item_base.html:230
msgid "External Link"
msgstr ""
@ -398,7 +422,7 @@ msgstr ""
#: build/templates/build/allocate.html:161
#: order/templates/order/sales_order_detail.html:68
#: order/templates/order/sales_order_detail.html:150 stock/models.py:362
#: stock/templates/stock/item_base.html:180
#: stock/templates/stock/item_base.html:189
msgid "Serial Number"
msgstr ""
@ -415,7 +439,7 @@ msgstr ""
#: part/templates/part/allocation.html:49
#: stock/templates/stock/item_base.html:26
#: stock/templates/stock/item_base.html:32
#: stock/templates/stock/item_base.html:186
#: stock/templates/stock/item_base.html:195
#: stock/templates/stock/stock_adjust.html:18 templates/js/bom.html:172
#: templates/js/build.html:52 templates/js/stock.html:653
msgid "Quantity"
@ -423,7 +447,7 @@ msgstr ""
#: build/templates/build/allocate.html:177
#: build/templates/build/auto_allocate.html:20
#: stock/templates/stock/item_base.html:162
#: stock/templates/stock/item_base.html:171
#: stock/templates/stock/stock_adjust.html:17 templates/js/stock.html:493
msgid "Location"
msgstr ""
@ -504,7 +528,7 @@ msgstr ""
#: build/templates/build/build_base.html:8
#: build/templates/build/build_base.html:34
#: build/templates/build/complete.html:6
#: stock/templates/stock/item_base.html:200 templates/js/build.html:33
#: stock/templates/stock/item_base.html:209 templates/js/build.html:33
#: templates/navbar.html:12
msgid "Build"
msgstr ""
@ -524,7 +548,7 @@ msgstr ""
#: build/templates/build/build_base.html:80
#: build/templates/build/detail.html:42
#: order/templates/order/receive_parts.html:24
#: stock/templates/stock/item_base.html:253 templates/js/build.html:57
#: stock/templates/stock/item_base.html:262 templates/js/build.html:57
#: templates/js/order.html:162 templates/js/order.html:235
#: templates/js/stock.html:480
msgid "Status"
@ -536,7 +560,7 @@ msgstr ""
#: order/templates/order/sales_order_notes.html:10
#: order/templates/order/sales_order_ship.html:25
#: part/templates/part/allocation.html:27
#: stock/templates/stock/item_base.html:150 templates/js/order.html:209
#: stock/templates/stock/item_base.html:159 templates/js/order.html:209
msgid "Sales Order"
msgstr ""
@ -603,7 +627,7 @@ msgid "Stock can be taken from any available location."
msgstr ""
#: build/templates/build/detail.html:48
#: stock/templates/stock/item_base.html:193 templates/js/stock.html:488
#: stock/templates/stock/item_base.html:202 templates/js/stock.html:488
msgid "Batch"
msgstr ""
@ -950,14 +974,14 @@ msgstr ""
#: company/templates/company/supplier_part_detail.html:21 order/models.py:148
#: order/templates/order/order_base.html:74
#: order/templates/order/order_wizard/select_pos.html:30
#: stock/templates/stock/item_base.html:228 templates/js/company.html:52
#: stock/templates/stock/item_base.html:237 templates/js/company.html:52
#: templates/js/company.html:134 templates/js/order.html:144
msgid "Supplier"
msgstr ""
#: company/templates/company/detail.html:26 order/models.py:314
#: order/templates/order/sales_order_base.html:73 stock/models.py:357
#: stock/models.py:358 stock/templates/stock/item_base.html:137
#: stock/models.py:358 stock/templates/stock/item_base.html:146
#: templates/js/company.html:44 templates/js/order.html:217
msgid "Customer"
msgstr ""
@ -1071,7 +1095,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:19 stock/models.py:331
#: stock/templates/stock/item_base.html:233 templates/js/company.html:150
#: stock/templates/stock/item_base.html:242 templates/js/company.html:150
msgid "Supplier Part"
msgstr ""
@ -1354,7 +1378,7 @@ msgstr ""
#: order/models.py:466 order/templates/order/order_base.html:9
#: order/templates/order/order_base.html:23
#: stock/templates/stock/item_base.html:207 templates/js/order.html:136
#: stock/templates/stock/item_base.html:216 templates/js/order.html:136
msgid "Purchase Order"
msgstr ""
@ -2052,14 +2076,14 @@ msgstr ""
#: part/templates/part/allocation.html:45
#: stock/templates/stock/item_base.html:8
#: stock/templates/stock/item_base.html:58
#: stock/templates/stock/item_base.html:215
#: stock/templates/stock/item_base.html:224
#: stock/templates/stock/stock_adjust.html:16 templates/js/build.html:106
#: templates/js/stock.html:623
msgid "Stock Item"
msgstr ""
#: part/templates/part/allocation.html:20
#: stock/templates/stock/item_base.html:156
#: stock/templates/stock/item_base.html:165
msgid "Build Order"
msgstr ""
@ -2453,7 +2477,7 @@ msgstr ""
msgid "Tracking"
msgstr ""
#: part/templates/part/tabs.html:64 stock/templates/stock/item_base.html:259
#: part/templates/part/tabs.html:64 stock/templates/stock/item_base.html:268
msgid "Tests"
msgstr ""
@ -2646,22 +2670,6 @@ msgstr ""
msgid "Confim BOM item deletion"
msgstr ""
#: plugins/barcode/inventree.py:70
msgid "Part does not exist"
msgstr ""
#: plugins/barcode/inventree.py:79
msgid "StockLocation does not exist"
msgstr ""
#: plugins/barcode/inventree.py:89
msgid "StockItem does not exist"
msgstr ""
#: plugins/barcode/inventree.py:92
msgid "No matching data"
msgstr ""
#: report/models.py:167
msgid "Template name"
msgstr ""
@ -2731,7 +2739,7 @@ msgstr ""
msgid "Parent Stock Item"
msgstr ""
#: stock/models.py:322 stock/templates/stock/item_base.html:129
#: stock/models.py:322 stock/templates/stock/item_base.html:138
msgid "Base Part"
msgstr ""
@ -2937,79 +2945,108 @@ msgid ""
"This stock item will be automatically deleted when all stock is depleted."
msgstr ""
#: stock/templates/stock/item_base.html:74
msgid "Add to stock"
#: stock/templates/stock/item_base.html:78
msgid "Barcode actions"
msgstr ""
#: stock/templates/stock/item_base.html:77
msgid "Take from stock"
#: stock/templates/stock/item_base.html:80
msgid "Show QR Code"
msgstr ""
#: stock/templates/stock/item_base.html:80 templates/stock_table.html:14
#: stock/templates/stock/item_base.html:81
msgid "Print Label"
msgstr ""
#: stock/templates/stock/item_base.html:83 templates/js/barcode.html:263
#: templates/js/barcode.html:268
msgid "Unlink Barcode"
msgstr ""
#: stock/templates/stock/item_base.html:85
msgid "Link Barcode"
msgstr ""
#: stock/templates/stock/item_base.html:92
msgid "Stock adjustment actions"
msgstr ""
#: stock/templates/stock/item_base.html:95 templates/stock_table.html:14
msgid "Count stock"
msgstr ""
#: stock/templates/stock/item_base.html:84
msgid "Serialize stock"
#: stock/templates/stock/item_base.html:96 templates/stock_table.html:12
msgid "Add stock"
msgstr ""
#: stock/templates/stock/item_base.html:90 stock/views.py:232
msgid "Assign to Customer"
#: stock/templates/stock/item_base.html:97 templates/stock_table.html:13
msgid "Remove stock"
msgstr ""
#: stock/templates/stock/item_base.html:94
#: stock/templates/stock/item_base.html:99
msgid "Transfer stock"
msgstr ""
#: stock/templates/stock/item_base.html:97
msgid "Duplicate stock item"
#: stock/templates/stock/item_base.html:105
msgid "Stock actions"
msgstr ""
#: stock/templates/stock/item_base.html:102
msgid "Convert stock to variant"
msgstr ""
#: stock/templates/stock/item_base.html:107
msgid "Generate test report"
#: stock/templates/stock/item_base.html:108
msgid "Serialize stock"
msgstr ""
#: stock/templates/stock/item_base.html:111
msgid "Assign to customer"
msgstr ""
#: stock/templates/stock/item_base.html:114
msgid "Convert to variant"
msgstr ""
#: stock/templates/stock/item_base.html:116
msgid "Duplicate stock item"
msgstr ""
#: stock/templates/stock/item_base.html:117
msgid "Edit stock item"
msgstr ""
#: stock/templates/stock/item_base.html:115
#: stock/templates/stock/item_base.html:119
msgid "Delete stock item"
msgstr ""
#: stock/templates/stock/item_base.html:124
msgid "Generate test report"
msgstr ""
#: stock/templates/stock/item_base.html:133
msgid "Stock Item Details"
msgstr ""
#: stock/templates/stock/item_base.html:144
#: stock/templates/stock/item_base.html:153
msgid "Belongs To"
msgstr ""
#: stock/templates/stock/item_base.html:166
#: stock/templates/stock/item_base.html:175
msgid "No location set"
msgstr ""
#: stock/templates/stock/item_base.html:173
#: stock/templates/stock/item_base.html:182
msgid "Unique Identifier"
msgstr ""
#: stock/templates/stock/item_base.html:214
#: stock/templates/stock/item_base.html:223
msgid "Parent Item"
msgstr ""
#: stock/templates/stock/item_base.html:239
#: stock/templates/stock/item_base.html:248
msgid "Last Updated"
msgstr ""
#: stock/templates/stock/item_base.html:244
#: stock/templates/stock/item_base.html:253
msgid "Last Stocktake"
msgstr ""
#: stock/templates/stock/item_base.html:248
#: stock/templates/stock/item_base.html:257
msgid "No stocktake performed"
msgstr ""
@ -3128,6 +3165,10 @@ msgstr ""
msgid "Delete Stock Item Attachment"
msgstr ""
#: stock/views.py:232
msgid "Assign to Customer"
msgstr ""
#: stock/views.py:270
msgid "Delete All Test Data"
msgstr ""
@ -3251,7 +3292,7 @@ msgid "Create new Stock Item"
msgstr ""
#: stock/views.py:1167
msgid "Copy Stock Item"
msgid "Duplicate Stock Item"
msgstr ""
#: stock/views.py:1240
@ -3375,6 +3416,39 @@ msgstr ""
msgid "Delete attachment"
msgstr ""
#: templates/js/barcode.html:28
msgid "Scan barcode data here using wedge scanner"
msgstr ""
#: templates/js/barcode.html:34
msgid "Barcode"
msgstr ""
#: templates/js/barcode.html:42
msgid "Enter barcode data"
msgstr ""
#: templates/js/barcode.html:140
msgid "Scan barcode data below"
msgstr ""
#: templates/js/barcode.html:195 templates/js/barcode.html:243
msgid "Unknown response from server"
msgstr ""
#: templates/js/barcode.html:198 templates/js/barcode.html:247
msgid "Invalid server response"
msgstr ""
#: templates/js/barcode.html:265
msgid ""
"This will remove the association between this stock item and the barcode"
msgstr ""
#: templates/js/barcode.html:271
msgid "Unlink"
msgstr ""
#: templates/js/bom.html:143
msgid "Open subassembly"
msgstr ""
@ -3635,42 +3709,38 @@ msgstr ""
msgid "Sell"
msgstr ""
#: templates/navbar.html:36
#: templates/navbar.html:32
msgid "Scan Barcode"
msgstr ""
#: templates/navbar.html:41
msgid "Admin"
msgstr ""
#: templates/navbar.html:39
#: templates/navbar.html:44
msgid "Settings"
msgstr ""
#: templates/navbar.html:40
#: templates/navbar.html:45
msgid "Logout"
msgstr ""
#: templates/navbar.html:42
#: templates/navbar.html:47
msgid "Login"
msgstr ""
#: templates/navbar.html:45
#: templates/navbar.html:50
msgid "About InvenTree"
msgstr ""
#: templates/navbar.html:46
#: templates/navbar.html:51
msgid "Statistics"
msgstr ""
#: templates/search_form.html:6
#: templates/search_form.html:6 templates/search_form.html:8
msgid "Search"
msgstr ""
#: templates/stock_table.html:12
msgid "Add stock"
msgstr ""
#: templates/stock_table.html:13
msgid "Remove stock"
msgstr ""
#: templates/stock_table.html:15
msgid "Move stock"
msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-06-09 06:17+0000\n"
"POT-Creation-Date: 2020-06-12 00:43+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"
@ -18,30 +18,14 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: InvenTree/api.py:86
#: InvenTree/api.py:83
msgid "No action specified"
msgstr ""
#: InvenTree/api.py:100
#: InvenTree/api.py:97
msgid "No matching action found"
msgstr ""
#: InvenTree/api.py:131
msgid "No barcode data provided"
msgstr ""
#: InvenTree/api.py:146
msgid "Barcode successfully decoded"
msgstr ""
#: InvenTree/api.py:149
msgid "Barcode plugin returned incorrect response"
msgstr ""
#: InvenTree/api.py:159
msgid "Unknown barcode format"
msgstr ""
#: InvenTree/forms.py:101 build/forms.py:37
msgid "Confirm"
msgstr ""
@ -218,6 +202,46 @@ msgstr ""
msgid "Database Statistics"
msgstr ""
#: barcode/api.py:53 barcode/api.py:150
msgid "Must provide barcode_data parameter"
msgstr ""
#: barcode/api.py:126
msgid "No match found for barcode data"
msgstr ""
#: barcode/api.py:128
msgid "Match found for barcode data"
msgstr ""
#: barcode/api.py:153
msgid "Must provide stockitem parameter"
msgstr ""
#: barcode/api.py:160
msgid "No matching stock item found"
msgstr ""
#: barcode/api.py:190
msgid "Barcode already matches StockItem object"
msgstr ""
#: barcode/api.py:194
msgid "Barcode already matches StockLocation object"
msgstr ""
#: barcode/api.py:198
msgid "Barcode already matches Part object"
msgstr ""
#: barcode/api.py:204 barcode/api.py:216
msgid "Barcode hash already matches StockItem object"
msgstr ""
#: barcode/api.py:222
msgid "Barcode associated with StockItem"
msgstr ""
#: build/forms.py:58
msgid "Location of completed parts"
msgstr ""
@ -318,7 +342,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:60
#: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:74 part/templates/part/part_base.html:88
#: stock/models.py:368 stock/templates/stock/item_base.html:221
#: stock/models.py:368 stock/templates/stock/item_base.html:230
msgid "External Link"
msgstr ""
@ -398,7 +422,7 @@ msgstr ""
#: build/templates/build/allocate.html:161
#: order/templates/order/sales_order_detail.html:68
#: order/templates/order/sales_order_detail.html:150 stock/models.py:362
#: stock/templates/stock/item_base.html:180
#: stock/templates/stock/item_base.html:189
msgid "Serial Number"
msgstr ""
@ -415,7 +439,7 @@ msgstr ""
#: part/templates/part/allocation.html:49
#: stock/templates/stock/item_base.html:26
#: stock/templates/stock/item_base.html:32
#: stock/templates/stock/item_base.html:186
#: stock/templates/stock/item_base.html:195
#: stock/templates/stock/stock_adjust.html:18 templates/js/bom.html:172
#: templates/js/build.html:52 templates/js/stock.html:653
msgid "Quantity"
@ -423,7 +447,7 @@ msgstr ""
#: build/templates/build/allocate.html:177
#: build/templates/build/auto_allocate.html:20
#: stock/templates/stock/item_base.html:162
#: stock/templates/stock/item_base.html:171
#: stock/templates/stock/stock_adjust.html:17 templates/js/stock.html:493
msgid "Location"
msgstr ""
@ -504,7 +528,7 @@ msgstr ""
#: build/templates/build/build_base.html:8
#: build/templates/build/build_base.html:34
#: build/templates/build/complete.html:6
#: stock/templates/stock/item_base.html:200 templates/js/build.html:33
#: stock/templates/stock/item_base.html:209 templates/js/build.html:33
#: templates/navbar.html:12
msgid "Build"
msgstr ""
@ -524,7 +548,7 @@ msgstr ""
#: build/templates/build/build_base.html:80
#: build/templates/build/detail.html:42
#: order/templates/order/receive_parts.html:24
#: stock/templates/stock/item_base.html:253 templates/js/build.html:57
#: stock/templates/stock/item_base.html:262 templates/js/build.html:57
#: templates/js/order.html:162 templates/js/order.html:235
#: templates/js/stock.html:480
msgid "Status"
@ -536,7 +560,7 @@ msgstr ""
#: order/templates/order/sales_order_notes.html:10
#: order/templates/order/sales_order_ship.html:25
#: part/templates/part/allocation.html:27
#: stock/templates/stock/item_base.html:150 templates/js/order.html:209
#: stock/templates/stock/item_base.html:159 templates/js/order.html:209
msgid "Sales Order"
msgstr ""
@ -603,7 +627,7 @@ msgid "Stock can be taken from any available location."
msgstr ""
#: build/templates/build/detail.html:48
#: stock/templates/stock/item_base.html:193 templates/js/stock.html:488
#: stock/templates/stock/item_base.html:202 templates/js/stock.html:488
msgid "Batch"
msgstr ""
@ -950,14 +974,14 @@ msgstr ""
#: company/templates/company/supplier_part_detail.html:21 order/models.py:148
#: order/templates/order/order_base.html:74
#: order/templates/order/order_wizard/select_pos.html:30
#: stock/templates/stock/item_base.html:228 templates/js/company.html:52
#: stock/templates/stock/item_base.html:237 templates/js/company.html:52
#: templates/js/company.html:134 templates/js/order.html:144
msgid "Supplier"
msgstr ""
#: company/templates/company/detail.html:26 order/models.py:314
#: order/templates/order/sales_order_base.html:73 stock/models.py:357
#: stock/models.py:358 stock/templates/stock/item_base.html:137
#: stock/models.py:358 stock/templates/stock/item_base.html:146
#: templates/js/company.html:44 templates/js/order.html:217
msgid "Customer"
msgstr ""
@ -1071,7 +1095,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:19 stock/models.py:331
#: stock/templates/stock/item_base.html:233 templates/js/company.html:150
#: stock/templates/stock/item_base.html:242 templates/js/company.html:150
msgid "Supplier Part"
msgstr ""
@ -1354,7 +1378,7 @@ msgstr ""
#: order/models.py:466 order/templates/order/order_base.html:9
#: order/templates/order/order_base.html:23
#: stock/templates/stock/item_base.html:207 templates/js/order.html:136
#: stock/templates/stock/item_base.html:216 templates/js/order.html:136
msgid "Purchase Order"
msgstr ""
@ -2052,14 +2076,14 @@ msgstr ""
#: part/templates/part/allocation.html:45
#: stock/templates/stock/item_base.html:8
#: stock/templates/stock/item_base.html:58
#: stock/templates/stock/item_base.html:215
#: stock/templates/stock/item_base.html:224
#: stock/templates/stock/stock_adjust.html:16 templates/js/build.html:106
#: templates/js/stock.html:623
msgid "Stock Item"
msgstr ""
#: part/templates/part/allocation.html:20
#: stock/templates/stock/item_base.html:156
#: stock/templates/stock/item_base.html:165
msgid "Build Order"
msgstr ""
@ -2453,7 +2477,7 @@ msgstr ""
msgid "Tracking"
msgstr ""
#: part/templates/part/tabs.html:64 stock/templates/stock/item_base.html:259
#: part/templates/part/tabs.html:64 stock/templates/stock/item_base.html:268
msgid "Tests"
msgstr ""
@ -2646,22 +2670,6 @@ msgstr ""
msgid "Confim BOM item deletion"
msgstr ""
#: plugins/barcode/inventree.py:70
msgid "Part does not exist"
msgstr ""
#: plugins/barcode/inventree.py:79
msgid "StockLocation does not exist"
msgstr ""
#: plugins/barcode/inventree.py:89
msgid "StockItem does not exist"
msgstr ""
#: plugins/barcode/inventree.py:92
msgid "No matching data"
msgstr ""
#: report/models.py:167
msgid "Template name"
msgstr ""
@ -2731,7 +2739,7 @@ msgstr ""
msgid "Parent Stock Item"
msgstr ""
#: stock/models.py:322 stock/templates/stock/item_base.html:129
#: stock/models.py:322 stock/templates/stock/item_base.html:138
msgid "Base Part"
msgstr ""
@ -2937,79 +2945,108 @@ msgid ""
"This stock item will be automatically deleted when all stock is depleted."
msgstr ""
#: stock/templates/stock/item_base.html:74
msgid "Add to stock"
#: stock/templates/stock/item_base.html:78
msgid "Barcode actions"
msgstr ""
#: stock/templates/stock/item_base.html:77
msgid "Take from stock"
#: stock/templates/stock/item_base.html:80
msgid "Show QR Code"
msgstr ""
#: stock/templates/stock/item_base.html:80 templates/stock_table.html:14
#: stock/templates/stock/item_base.html:81
msgid "Print Label"
msgstr ""
#: stock/templates/stock/item_base.html:83 templates/js/barcode.html:263
#: templates/js/barcode.html:268
msgid "Unlink Barcode"
msgstr ""
#: stock/templates/stock/item_base.html:85
msgid "Link Barcode"
msgstr ""
#: stock/templates/stock/item_base.html:92
msgid "Stock adjustment actions"
msgstr ""
#: stock/templates/stock/item_base.html:95 templates/stock_table.html:14
msgid "Count stock"
msgstr ""
#: stock/templates/stock/item_base.html:84
msgid "Serialize stock"
#: stock/templates/stock/item_base.html:96 templates/stock_table.html:12
msgid "Add stock"
msgstr ""
#: stock/templates/stock/item_base.html:90 stock/views.py:232
msgid "Assign to Customer"
#: stock/templates/stock/item_base.html:97 templates/stock_table.html:13
msgid "Remove stock"
msgstr ""
#: stock/templates/stock/item_base.html:94
#: stock/templates/stock/item_base.html:99
msgid "Transfer stock"
msgstr ""
#: stock/templates/stock/item_base.html:97
msgid "Duplicate stock item"
#: stock/templates/stock/item_base.html:105
msgid "Stock actions"
msgstr ""
#: stock/templates/stock/item_base.html:102
msgid "Convert stock to variant"
msgstr ""
#: stock/templates/stock/item_base.html:107
msgid "Generate test report"
#: stock/templates/stock/item_base.html:108
msgid "Serialize stock"
msgstr ""
#: stock/templates/stock/item_base.html:111
msgid "Assign to customer"
msgstr ""
#: stock/templates/stock/item_base.html:114
msgid "Convert to variant"
msgstr ""
#: stock/templates/stock/item_base.html:116
msgid "Duplicate stock item"
msgstr ""
#: stock/templates/stock/item_base.html:117
msgid "Edit stock item"
msgstr ""
#: stock/templates/stock/item_base.html:115
#: stock/templates/stock/item_base.html:119
msgid "Delete stock item"
msgstr ""
#: stock/templates/stock/item_base.html:124
msgid "Generate test report"
msgstr ""
#: stock/templates/stock/item_base.html:133
msgid "Stock Item Details"
msgstr ""
#: stock/templates/stock/item_base.html:144
#: stock/templates/stock/item_base.html:153
msgid "Belongs To"
msgstr ""
#: stock/templates/stock/item_base.html:166
#: stock/templates/stock/item_base.html:175
msgid "No location set"
msgstr ""
#: stock/templates/stock/item_base.html:173
#: stock/templates/stock/item_base.html:182
msgid "Unique Identifier"
msgstr ""
#: stock/templates/stock/item_base.html:214
#: stock/templates/stock/item_base.html:223
msgid "Parent Item"
msgstr ""
#: stock/templates/stock/item_base.html:239
#: stock/templates/stock/item_base.html:248
msgid "Last Updated"
msgstr ""
#: stock/templates/stock/item_base.html:244
#: stock/templates/stock/item_base.html:253
msgid "Last Stocktake"
msgstr ""
#: stock/templates/stock/item_base.html:248
#: stock/templates/stock/item_base.html:257
msgid "No stocktake performed"
msgstr ""
@ -3128,6 +3165,10 @@ msgstr ""
msgid "Delete Stock Item Attachment"
msgstr ""
#: stock/views.py:232
msgid "Assign to Customer"
msgstr ""
#: stock/views.py:270
msgid "Delete All Test Data"
msgstr ""
@ -3251,7 +3292,7 @@ msgid "Create new Stock Item"
msgstr ""
#: stock/views.py:1167
msgid "Copy Stock Item"
msgid "Duplicate Stock Item"
msgstr ""
#: stock/views.py:1240
@ -3375,6 +3416,39 @@ msgstr ""
msgid "Delete attachment"
msgstr ""
#: templates/js/barcode.html:28
msgid "Scan barcode data here using wedge scanner"
msgstr ""
#: templates/js/barcode.html:34
msgid "Barcode"
msgstr ""
#: templates/js/barcode.html:42
msgid "Enter barcode data"
msgstr ""
#: templates/js/barcode.html:140
msgid "Scan barcode data below"
msgstr ""
#: templates/js/barcode.html:195 templates/js/barcode.html:243
msgid "Unknown response from server"
msgstr ""
#: templates/js/barcode.html:198 templates/js/barcode.html:247
msgid "Invalid server response"
msgstr ""
#: templates/js/barcode.html:265
msgid ""
"This will remove the association between this stock item and the barcode"
msgstr ""
#: templates/js/barcode.html:271
msgid "Unlink"
msgstr ""
#: templates/js/bom.html:143
msgid "Open subassembly"
msgstr ""
@ -3635,42 +3709,38 @@ msgstr ""
msgid "Sell"
msgstr ""
#: templates/navbar.html:36
#: templates/navbar.html:32
msgid "Scan Barcode"
msgstr ""
#: templates/navbar.html:41
msgid "Admin"
msgstr ""
#: templates/navbar.html:39
#: templates/navbar.html:44
msgid "Settings"
msgstr ""
#: templates/navbar.html:40
#: templates/navbar.html:45
msgid "Logout"
msgstr ""
#: templates/navbar.html:42
#: templates/navbar.html:47
msgid "Login"
msgstr ""
#: templates/navbar.html:45
#: templates/navbar.html:50
msgid "About InvenTree"
msgstr ""
#: templates/navbar.html:46
#: templates/navbar.html:51
msgid "Statistics"
msgstr ""
#: templates/search_form.html:6
#: templates/search_form.html:6 templates/search_form.html:8
msgid "Search"
msgstr ""
#: templates/stock_table.html:12
msgid "Add stock"
msgstr ""
#: templates/stock_table.html:13
msgid "Remove stock"
msgstr ""
#: templates/stock_table.html:15
msgid "Move stock"
msgstr ""

View File

@ -35,44 +35,42 @@
{% endif %}
</h3>
<p><i>{{ part.description }}</i></p>
<p>
<div class='btn-row'>
<div class='btn-group action-buttons'>
<div class='btn-group action-buttons' role='group'>
<button type='button' class='btn btn-default' id='toggle-starred' title='{% trans "Star this part" %}'>
<span id='part-star-icon' class='fas fa-star {% if starred %}icon-yellow{% endif %}'/>
</button>
{% if part.is_template == False %}
{% include "qr_button.html" %}
<!-- Barcode actions menu -->
<div class='dropdown dropdown-buttons'>
<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>
<ul class='dropdown-menu' role='menu'>
<li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li>
<li class='disabled'><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
</ul>
</div>
{% 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'/>
</button>
{% if not part.virtual %}
<button type='button' class='btn btn-default' id='part-count' title='Count part stock'>
<span class='fas fa-clipboard-list'/>
</button>
{% endif %}
{% if part.purchaseable %}
<button type='button' class='btn btn-default' id='part-order' title='Order part'>
<span id='part-order-icon' class='fas fa-shopping-cart'/>
</button>
{% endif %}
{% endif %}
{% endif %}
<button type='button' class='btn btn-default' id='part-duplicate' title='Duplicate part'>
<span id='part-duplicate-icon' class='fas fa-copy'/>
</button>
<button type='button' class='btn btn-default' id='part-edit' title='Edit part'>
<span id='part-edit-icon' class='fas fa-edit icon-blue'/>
</button>
<div class='dropdown dropdown-buttons'>
<button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button>
<ul class='dropdown-menu' role='menu'>
<li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li>
<li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li>
{% if not part.active %}
<button type='button' class='btn btn-default' id='part-delete' title='Delete part'>
<span id='part-delete-icon' class='fas fa-trash-alt icon-red'/>
</button>
<li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li>
{% endif %}
</ul>
</div>
</div>
</p>
<table class='table table-condensed'>
<col width='25'>
{% if part.IPN %}

View File

@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
import hashlib
from stock.serializers import StockItemSerializer, LocationSerializer
from part.serializers import PartSerializer
import plugins.plugin as plugin
class BarcodePlugin(plugin.InvenTreePlugin):
"""
The BarcodePlugin class is the base class for any barcode plugin.
"""
def __init__(self, barcode_data):
plugin.InvenTreePlugin.__init__(self)
self.data = barcode_data
def hash(self):
"""
Calculate a hash for the barcode data.
This is supposed to uniquely identify the barcode contents,
at least within the bardcode sub-type.
The default implementation simply returns an MD5 hash of the barcode data,
encoded to a string.
This may be sufficient for most applications, but can obviously be overridden
by a subclass.
"""
hash = hashlib.md5(str(self.data).encode())
return str(hash.hexdigest())
def validate(self):
"""
Default implementation returns False
"""
return False
def decode(self):
"""
Decode the barcode, and craft a response
"""
return None
def render_part(self, part):
"""
Render a Part object to JSON
Use the existing serializer to do this.
"""
serializer = PartSerializer(part)
return serializer.data
def render_stock_location(self, loc):
"""
Render a StockLocation object to JSON
Use the existing serializer to do this.
"""
serializer = LocationSerializer(loc)
return serializer.data
def render_stock_item(self, item):
"""
Render a StockItem object to JSON.
Use the existing serializer to do this
"""
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
return serializer.data

View File

@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
from . import barcode
class DigikeyBarcodePlugin(barcode.BarcodePlugin):
PLUGIN_NAME = "DigikeyBarcodePlugin"

View File

@ -1,94 +0,0 @@
"""
The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself.
It can be used as a template for developing third-party barcode plugins.
The data format is very simple, and maps directly to database objects,
via the "id" parameter.
Parsing an InvenTree barcode simply involves validating that the
references model objects actually exist in the database.
"""
# -*- coding: utf-8 -*-
import json
from . import barcode
from stock.models import StockItem, StockLocation
from part.models import Part
from django.utils.translation import ugettext as _
class InvenTreeBarcodePlugin(barcode.BarcodePlugin):
PLUGIN_NAME = "InvenTreeBarcodePlugin"
def validate(self):
"""
An "InvenTree" barcode must include the following tags:
{
'tool': 'InvenTree',
'version': <anything>
}
"""
# The data must either be dict or be able to dictified
if type(self.data) is dict:
pass
elif type(self.data) is str:
try:
self.data = json.loads(self.data)
except json.JSONDecodeError:
return False
else:
return False
for key in ['tool', 'version']:
if key not in self.data.keys():
return False
if not self.data['tool'] == 'InvenTree':
return False
return True
def decode(self):
response = {}
if 'part' in self.data.keys():
id = self.data['part'].get('id', None)
try:
part = Part.objects.get(id=id)
response['part'] = self.render_part(part)
except (ValueError, Part.DoesNotExist):
response['error'] = _('Part does not exist')
elif 'stocklocation' in self.data.keys():
id = self.data['stocklocation'].get('id', None)
try:
loc = StockLocation.objects.get(id=id)
response['stocklocation'] = self.render_stock_location(loc)
except (ValueError, StockLocation.DoesNotExist):
response['error'] = _('StockLocation does not exist')
elif 'stockitem' in self.data.keys():
id = self.data['stockitem'].get('id', None)
try:
item = StockItem.objects.get(id=id)
response['stockitem'] = self.render_stock_item(item)
except (ValueError, StockItem.DoesNotExist):
response['error'] = _('StockItem does not exist')
else:
response['error'] = _('No matching data')
return response

View File

@ -4,10 +4,6 @@ import inspect
import importlib
import pkgutil
# Barcode plugins
import plugins.barcode as barcode
from plugins.barcode.barcode import BarcodePlugin
# Action plugins
import plugins.action as action
from plugins.action.action import ActionPlugin
@ -51,24 +47,6 @@ def get_plugins(pkg, baseclass):
return plugins
def load_barcode_plugins():
"""
Return a list of all registered barcode plugins
"""
print("Loading barcode plugins")
plugins = get_plugins(barcode, BarcodePlugin)
if len(plugins) > 0:
print("Discovered {n} barcode plugins:".format(n=len(plugins)))
for bp in plugins:
print(" - {bp}".format(bp=bp.PLUGIN_NAME))
return plugins
def load_action_plugins():
"""
Return a list of all registered action plugins

View File

@ -67,55 +67,64 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
{% endif %}
</h4>
<div class='btn-group action-buttons'>
{% include "qr_button.html" %}
<div class='btn-group' role='group'>
</div>
<div class='btn-group action-buttons' role='group'>
<!-- Barcode actions menu -->
<div class='dropdown dropdown-buttons'>
<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>
<ul class='dropdown-menu' role='menu'>
<li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li>
<li class='disabled'><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
{% 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 %}
</ul>
</div>
{% if item.in_stock %}
<!-- Stock adjustment menu -->
<div class='dropdown dropdown-buttons'>
<button id='stock-options' title='{% trans "Stock adjustment 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'>
{% if not item.serialized %}
<button type='button' class='btn btn-default' id='stock-add' title='{% trans "Add to stock" %}'>
<span class='fas fa-plus-circle icon-green'/>
</button>
<button type='button' class='btn btn-default' id='stock-remove' title='{% trans "Take from stock" %}'>
<span class='fas fa-minus-circle icon-red''/>
</button>
<button type='button' class='btn btn-default' id='stock-count' title='{% trans "Count stock" %}'>
<span class='fas fa-clipboard-list'/>
</button>
{% if item.part.trackable %}
<button type='button' class='btn btn-default' id='stock-serialize' title='{% trans "Serialize stock" %}'>
<span class='fas fa-hashtag'/>
</button>
<li><a href='#' id='stock-count' title='{% trans "Count stock" %}'><span class='fas fa-clipboard-list'></span> {% trans "Count stock" %}</a></li>
<li><a href='#' id='stock-add' title='{% trans "Add stock" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li>
<li><a href='#' id='stock-remove' title='{% trans "Remove stock" %}'><span class='fas fa-minus-circle icon-red'></span> {% trans "Remove stock" %}</a></li>
{% endif %}
<li><a href='#' id='stock-move' title='{% trans "Transfer stock" %}'><span class='fas fa-exchange-alt icon-blue'></span> {% trans "Transfer stock" %}</a></li>
</ul>
</div>
{% endif %}
{% if item.part.salable %}
<button type='button' class='btn btn-default' id='stock-assign-to-customer' title='{% trans "Assign to Customer" %}'>
<span class='fas fa-user-tie'/>
</button>
<!-- Edit stock item -->
<div class='dropdown dropdown-buttons'>
<button id='stock-edit-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-tools'></span> <span class='caret'></span></button>
<ul class='dropdown-menu' role='menu'>
{% if item.part.trackable and not item.serialized %}
<li><a href='#' id='stock-serialize' title='{% trans "Serialize stock" %}'><span class='fas fa-hashtag'></span> {% trans "Serialize stock" %}</a> </li>
{% endif %}
<button type='button' class='btn btn-default' id='stock-move' title='{% trans "Transfer stock" %}'>
<span class='fas fa-exchange-alt icon-blue'/>
</button>
<button type='button' class='btn btn-default' id='stock-duplicate' title='{% trans "Duplicate stock item" %}'>
<span class='fas fa-copy'/>
</button>
{% if item.part.salable and not item.customer %}
<li><a href='#' id='stock-assign-to-customer' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li>
{% endif %}
{% if item.part.has_variants %}
<button type='button' class='btn btn-default' id='stock-convert' title='{% trans "Convert stock to variant" %}'>
<span class='fas fa-screwdriver'/>
</button>
<li><a href='#' id='stock-convert' title='{% trans "Convert to variant" %}'><span class='fas fa-screwdriver'></span> {% trans "Convert to variant" %}</a></li>
{% endif %}
<li><a href='#' id='stock-duplicate' title='{% trans "Duplicate stock item" %}'><span class='fas fa-copy'></span> {% trans "Duplicate stock item" %}</a></li>
<li><a href='#' id='stock-edit' title='{% trans "Edit stock item" %}'><span class='fas fa-edit icon-blue'></span> {% trans "Edit stock item" %}</a></li>
{% if item.can_delete %}
<li><a href='#' id='stock-delete' title='{% trans "Delete stock item" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete stock item" %}</a></li>
{% endif %}
</ul>
</div>
{% if item.part.has_test_report_templates %}
<button type='button' class='btn btn-default' id='stock-test-report' title='{% trans "Generate test report" %}'>
<span class='fas fa-tasks'/>
</button>
{% endif %}
<button type='button' class='btn btn-default' id='stock-edit' title='{% trans "Edit stock item" %}'>
<span class='fas fa-edit icon-blue'/>
</button>
{% if item.can_delete %}
<button type='button' class='btn btn-default' id='stock-delete' title='{% trans "Delete stock item" %}'>
<span class='fas fa-trash-alt icon-red'/>
</button>
{% endif %}
</div>
{% endblock %}
@ -131,6 +140,19 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
<a href="{% url 'part-stock' item.part.id %}">{{ item.part.full_name }}
</td>
</tr>
{% if item.serialized %}
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Serial Number" %}</td>
<td>{{ item.serial }}</td>
</tr>
{% else %}
<tr>
<td></td>
<td>{% trans "Quantity" %}</td>
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
</tr>
{% endif %}
{% if item.customer %}
<tr>
<td><span class='fas fa-user-tie'></span></td>
@ -174,19 +196,6 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
<td>{{ item.uid }}</td>
</tr>
{% endif %}
{% if item.serialized %}
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Serial Number" %}</td>
<td>{{ item.serial }}</td>
</tr>
{% else %}
<tr>
<td></td>
<td>{% trans "Quantity" %}</td>
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
</tr>
{% endif %}
{% if item.batch %}
<tr>
<td></td>
@ -330,6 +339,14 @@ $("#show-qr-code").click(function() {
});
});
$("#link-barcode").click(function() {
linkBarcodeDialog({{ item.id }});
});
$("#unlink-barcode").click(function() {
unlinkBarcode({{ item.id }});
});
{% if item.in_stock %}
{% if item.part.salable %}
@ -388,7 +405,8 @@ $("#stock-delete").click(function () {
"{% url 'stock-item-delete' item.id %}",
{
redirect: "{% url 'part-stock' item.part.id %}"
});
}
);
});
{% endblock %}

View File

@ -12,25 +12,36 @@
<h3>{% trans "Stock" %}</h3>
<p>{% trans "All stock items" %}</p>
{% endif %}
<p>
<div class='btn-group action-buttons'>
<div class='btn-group action-buttons' role='group'>
<button class='btn btn-default' id='location-create' title='{% trans "Create new stock location" %}'>
<span class='fas fa-plus-circle icon-green'/>
</button>
<!-- Barcode actions menu -->
{% if location %}
{% include "qr_button.html" %}
<button class='btn btn-default' id='location-count' title='{% trans "Count stock items" %}'>
<span class='fas fa-clipboard-list'/>
</button>
<button class='btn btn-default btn-glyph' id='location-edit' title='{% trans "Edit stock location" %}'>
<span class='fas fa-edit icon-blue'/>
</button>
<button class='btn btn-default btn-glyph' id='location-delete' title='{% trans "Delete stock location" %}'>
<span class='fas fa-trash-alt icon-red'/>
</button>
<div class='dropdown dropdown-buttons'>
<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>
<ul class='dropdown-menu' role='menu'>
<li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li>
<li class='disabled'><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li>
<li><a href='#' id='barcode-check-in'><span class='fas fa-arrow-right'></span> {% trans "Check-in Items" %}</a></li>
</ul>
</div>
<div class='dropdown dropdown-buttons'>
<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>
<div class='dropdown dropdown-buttons'>
<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>
<li><a href='#' id='location-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete location" %}</a></li>
</ul>
</div>
{% endif %}
</div>
</p>
</div>
<div class='col-sm-6'>
{% if location %}
@ -109,6 +120,12 @@
inventreeDel('show-part-locs');
});
{% if location %}
$("#barcode-check-in").click(function() {
barcodeCheckIn({{ location.id }});
});
{% endif %}
$("#stock-export").click(function() {
launchModalForm("{% url 'stock-export-options' %}", {
submit_text: "Export",

View File

@ -1,7 +1,10 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block pre_form_content %}
Are you sure you want to delete stock location '{{ location.name }}'?
{% trans "Are you sure you want to delete this stock location?" %}
<br>
@ -33,7 +36,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
<ul class='list-group'>
{% for item in location.stock_items.all %}
<li class='list-group-item'><b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i><span class='badge'>{{ item.quantity }}</span></li>
<li class='list-group-item'><b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i><span class='badge'>{% decimal item.quantity %}</span></li>
{% endfor %}
</ul>
{% endif %}

View File

@ -1164,7 +1164,7 @@ class StockItemCreate(AjaxCreateView):
try:
original = StockItem.objects.get(pk=item_to_copy)
initials = model_to_dict(original)
self.ajax_form_title = _("Copy Stock Item")
self.ajax_form_title = _("Duplicate Stock Item")
except StockItem.DoesNotExist:
initials = super(StockItemCreate, self).get_initial().copy()

View File

@ -108,6 +108,7 @@ InvenTree
<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="{% url 'barcode.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 'part.js' %}"></script>
@ -132,6 +133,11 @@ $(document).ready(function () {
inventreeDocReady();
showCachedAlerts();
$('#barcode-scan').click(function() {
barcodeScanDialog();
});
});
</script>

View File

@ -0,0 +1,514 @@
{% load i18n %}
function makeBarcodeInput(placeholderText='') {
/*
* Generate HTML for a barcode input
*/
placeholderText = placeholderText || '{% trans "Scan barcode data here using wedge scanner" %}';
var html = `
<div class='form-group'>
<label class='control-label' for='barcode'>{% trans "Barcode" %}</label>
<div class='controls'>
<div class='input-group'>
<span class='input-group-addon'>
<span class='fas fa-qrcode'></span>
</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>
</div>
`;
return html;
}
function showBarcodeMessage(modal, message, style='danger') {
var html = `<div class='alert alert-block alert-${style}'>`;
html += message;
html += "</div>";
$(modal + ' #barcode-error-message').html(html);
}
function showInvalidResponseError(modal, response, status) {
showBarcodeMessage(modal, `{% trans "Invalid server response" %}<br>{% trans "Status" %}: '${status}'`);
}
function clearBarcodeError(modal, message) {
$(modal + ' #barcode-error-message').html('');
}
function enableBarcodeInput(modal, enabled=true) {
var barcode = $(modal + ' #barcode');
barcode.prop('disabled', !enabled);
modalEnable(modal, enabled);
barcode.focus();
}
function getBarcodeData(modal) {
modal = modal || '#modal-form';
var el = $(modal + ' #barcode');
var barcode = el.val();
el.val('');
el.focus();
return barcode.trim();
}
function barcodeDialog(title, options={}) {
/*
* Handle a barcode display dialog.
*/
var modal = '#modal-form';
function sendBarcode() {
var barcode = getBarcodeData(modal);
if (barcode && barcode.length > 0) {
if (options.onScan) {
options.onScan(barcode);
}
}
}
$(modal).on('shown.bs.modal', function() {
$(modal + ' .modal-form-content').scrollTop(0);
var barcode = $(modal + ' #barcode');
// Handle 'enter' key on barcode
barcode.keyup(function(event) {
event.preventDefault();
if (event.which == 10 || event.which == 13) {
sendBarcode();
}
});
// Ensure the barcode field has focus
barcode.focus();
var form = $(modal).find('.js-modal-form');
// Override form submission
form.submit(function() {
return false;
});
// Callback for when the "submit" button is pressed on the modal
modalSubmit(modal, function() {
if (options.onSubmit) {
options.onSubmit();
}
});
if (options.onShow) {
options.onShow();
}
});
modalSetTitle(modal, title);
if (options.onSubmit) {
modalShowSubmitButton(modal, true);
} else {
modalShowSubmitButton(modal, false);
}
var content = '';
content += `<div class='alert alert-info alert-block'>{% trans "Scan barcode data below" %}</div>`;
content += `<div id='barcode-error-message'></div>`;
content += `<form class='js-modal-form' method='post'>`;
// Optional content before barcode input
content += `<div class='container' id='barcode-header'>`;
content += options.headerContent || '';
content += `</div>`;
content += makeBarcodeInput();
if (options.extraFields) {
content += options.extraFields;
}
content += `</form>`;
// Optional content after barcode input
content += `<div class='container' id='barcode-footer'>`;
content += options.footerContent || '';
content += '</div>';
modalSetContent(modal, content);
$(modal).modal({
backdrop: 'static',
keyboard: false,
});
if (options.preShow) {
options.preShow();
}
$(modal).modal('show');
}
function barcodeScanDialog() {
/*
* Perform a barcode scan,
* and (potentially) redirect the browser
*/
var modal = '#modal-form';
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);
}
},
},
);
},
},
);
}
/*
* Dialog for linking a particular barcode to a stock item.
*/
function linkBarcodeDialog(stockitem, options={}) {
var modal = '#modal-form';
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) {
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);
}
},
},
);
}
}
);
}
/*
* Remove barcode association from a device.
*/
function unlinkBarcode(stockitem) {
var html = `<b>{% trans "Unlink Barcode" %}</b><br>`;
html += "{% trans 'This will remove the association between this stock item and the barcode' %}";
showQuestionDialog(
"{% trans 'Unlink Barcode' %}",
html,
{
accept_text: "{% trans 'Unlink' %}",
accept: function() {
inventreePut(
`/api/stock/${stockitem}/`,
{
// Clear the UID field
uid: '',
},
{
method: 'PATCH',
success: function(response, status) {
location.reload();
},
},
);
},
}
);
}
/*
* Display dialog to check multiple stock items in to a stock location.
*/
function barcodeCheckIn(location_id, options={}) {
var modal = '#modal-form';
// List of items we are going to checkin
var items = [];
function reloadTable() {
modalEnable(modal, false);
// Remove click listeners
$(modal + ' .button-item-remove').unbind('click');
var table = $(modal + ' #items-table-div');
var html = `
<table class='table table-condensed table-striped' id='items-table'>
<thead>
<tr>
<th>{% trans "Part" %}</th>
<th>{% trans "Location" %}</th>
<th>{% trans "Quantity" %}</th>
<th></th>
</tr>
</thead>
<tbody>`;
items.forEach(function(item) {
html += `
<tr pk='${item.pk}'>
<td>${imageHoverIcon(item.part_detail.thumbnail)} ${item.part_detail.name}</td>
<td>${item.location_detail.name}</td>
<td>${item.quantity}</td>
<td>${makeIconButton('fa-times-circle icon-red', 'button-item-remove', item.pk, '{% trans "Remove stock item" %}')}</td>
</tr>`;
});
html += `
</tbody>
</table>`;
table.html(html);
modalEnable(modal, items.length > 0);
$(modal + ' #barcode').focus();
$(modal + ' .button-item-remove').unbind('click').on('mouseup', function() {
var pk = $(this).attr('pk');
var match = false;
for (var ii = 0; ii < items.length; ii++) {
if (pk.toString() == items[ii].pk.toString()) {
items.splice(ii, 1);
match = true;
break;
}
}
if (match) {
reloadTable();
}
return false;
});
}
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>`;
barcodeDialog(
"{% trans "Check Stock Items into Location" %}",
{
headerContent: table,
preShow: function() {
modalSetSubmitText(modal, '{% trans "Check In" %}');
modalEnable(modal, false);
reloadTable();
},
onShow: function() {
},
extraFields: extra,
onSubmit: function() {
// Called when the 'check-in' button is pressed
var data = {location: location_id};
// Extract 'notes' field
data.notes = $(modal + ' #notes').val();
var entries = [];
items.forEach(function(item) {
entries.push({
pk: item.pk,
quantity: item.quantity,
});
});
data.items = entries;
inventreePut(
'{% url 'api-stock-transfer' %}',
data,
{
method: 'POST',
success: function(response, status) {
// Hide the modal
$(modal).modal('hide');
if (status == 'success' && 'success' in response) {
showAlertOrCache('alert-success', response.success, true);
location.reload();
} else {
showAlertOrCache('alert-success', '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) {
enableBarcodeInput(modal, true);
if (status == 'success') {
if ('stockitem' in response) {
stockitem = response.stockitem;
var duplicate = false;
items.forEach(function(item) {
if (item.pk == stockitem.pk) {
duplicate = true;
}
});
if (duplicate) {
showBarcodeMessage(modal, "{% trans "Stock Item already scanned" %}", "warning");
} else {
if (stockitem.location == location_id) {
showBarcodeMessage(modal, "{% trans "Stock Item already in this location" %}");
return;
}
// Add this stock item to the list
items.push(stockitem);
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);
}
},
},
);
},
}
);
}

View File

@ -28,6 +28,11 @@
</ul>
<ul class="nav navbar-nav navbar-right">
{% include "search_form.html" %}
<li class ='navbar-barcode-li nav navbar-nav'>
<button id='barcode-scan' class='btn btn-default' title='{% trans "Scan Barcode" %}'>
<span class='fas fa-qrcode'></span>
</button>
</li>
<li class='dropdown'>
<a class='dropdown-toggle' data-toggle='dropdown' href="#"><span class="fas fa-user"></span> <b>{{ user.get_username }}</b></a>
<ul class='dropdown-menu'>

View File

@ -3,7 +3,7 @@
<div class='container' style='width: 80%;'>
{% if qr_data %}
<div class='qr-container'>
<img class='qr-code' src="{% qr_url_from_text qr_data size='m' image_format='png' error_correction='q' %}" alt="QR Code">
<img class='qr-code' src="{% qr_url_from_text qr_data size='m' image_format='png' error_correction='m' %}" alt="QR Code">
</div>
{% else %}
<b>Error:</b><br>

View File

@ -5,5 +5,7 @@
<div class="form-group">
<input type="text" name='search' class="form-control" placeholder="{% trans 'Search' %}"{% if query_text %} value="{{ query }}"{% endif %}>
</div>
<button type="submit" id='search-submit' class="btn btn-default"><span class='fas fa-search'></span></button>
<button type="submit" id='search-submit' class="btn btn-default" title='{% trans "Search" %}'>
<span class='fas fa-search'></span>
</button>
</form>

View File

@ -51,12 +51,12 @@ style:
# Run unit tests
test:
cd InvenTree && python3 manage.py check
cd InvenTree && python3 manage.py test build common company order part report stock InvenTree
cd InvenTree && python3 manage.py test barcode build common company order part report stock InvenTree
# Run code coverage
coverage:
cd InvenTree && python3 manage.py check
coverage run InvenTree/manage.py test build common company order part report stock InvenTree
coverage run InvenTree/manage.py test barcode build common company order part report stock InvenTree
coverage html
# Install packages required to generate code docs

View File

@ -2,6 +2,7 @@
ignore =
# - W293 - blank lines contain whitespace
W293,
W605,
# - E501 - line too long (82 characters)
E501, E722,
# - C901 - function is too complex