mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
82433db1ea
@ -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)
|
||||
|
43
InvenTree/InvenTree/plugins.py
Normal file
43
InvenTree/InvenTree/plugins.py
Normal 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
|
@ -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 {
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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')
|
||||
|
@ -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
240
InvenTree/barcode/api.py
Normal 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'),
|
||||
]
|
147
InvenTree/barcode/barcode.py
Normal file
147
InvenTree/barcode/barcode.py
Normal 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
|
19
InvenTree/barcode/plugins/digikey_barcode.py
Normal file
19
InvenTree/barcode/plugins/digikey_barcode.py
Normal 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
|
108
InvenTree/barcode/plugins/inventree_barcode.py
Normal file
108
InvenTree/barcode/plugins/inventree_barcode.py
Normal 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
118
InvenTree/barcode/tests.py
Normal 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)
|
Binary file not shown.
@ -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 "
|
||||
|
@ -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 ""
|
||||
|
@ -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 ""
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import barcode
|
||||
|
||||
|
||||
class DigikeyBarcodePlugin(barcode.BarcodePlugin):
|
||||
|
||||
PLUGIN_NAME = "DigikeyBarcodePlugin"
|
@ -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
|
@ -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
|
||||
|
@ -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 %}
|
||||
|
@ -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",
|
||||
|
@ -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 %}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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>
|
||||
|
514
InvenTree/templates/js/barcode.html
Normal file
514
InvenTree/templates/js/barcode.html
Normal 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);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
@ -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'>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
4
Makefile
4
Makefile
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user