diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index 44e7ec383f..1bdfb79ceb 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -30,6 +30,8 @@ class InfoView(AjaxView): Use to confirm that the server is running, etc. """ + permission_classes = [permissions.AllowAny] + def get(self, request, *args, **kwargs): data = { diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index 7de41eef15..f9d856f566 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -17,3 +17,43 @@ def status_codes(request): 'BuildStatus': BuildStatus, 'StockStatus': StockStatus, } + + +def user_roles(request): + """ + Return a map of the current roles assigned to the user. + + Roles are denoted by their simple names, and then the permission type. + + Permissions can be access as follows: + + - roles.part.view + - roles.build.delete + + Each value will return a boolean True / False + """ + + user = request.user + + roles = { + } + + for group in user.groups.all(): + for rule in group.rule_sets.all(): + + # Ensure the role name is in the dict + if rule.name not in roles: + roles[rule.name] = { + 'view': user.is_superuser, + 'add': user.is_superuser, + 'change': user.is_superuser, + 'delete': user.is_superuser + } + + # Roles are additive across groups + roles[rule.name]['view'] |= rule.can_view + roles[rule.name]['add'] |= rule.can_add + roles[rule.name]['change'] |= rule.can_change + roles[rule.name]['delete'] |= rule.can_delete + + return {'roles': roles} diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 9b470902b1..13b770539c 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -15,6 +15,8 @@ from django.http import StreamingHttpResponse from django.core.exceptions import ValidationError from django.utils.translation import ugettext as _ +from django.contrib.auth.models import Permission + import InvenTree.version from .settings import MEDIA_URL, STATIC_URL @@ -441,3 +443,21 @@ def validateFilterString(value): results[k] = v return results + + +def addUserPermission(user, permission): + """ + Shortcut function for adding a certain permission to a user. + """ + + perm = Permission.objects.get(codename=permission) + user.user_permissions.add(perm) + + +def addUserPermissions(user, permissions): + """ + Shortcut function for adding multiple permissions to a user. + """ + + for permission in permissions: + addUserPermission(user, permission) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 21b8a0ead1..c6f8b40069 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -210,6 +210,7 @@ TEMPLATES = [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'InvenTree.context.status_codes', + 'InvenTree.context.user_roles', ], }, }, @@ -231,6 +232,10 @@ REST_FRAMEWORK = { 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.TokenAuthentication', ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + 'rest_framework.permissions.DjangoModelPermissions', + ), 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' } diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index d940229ebe..bb7c1e6f5d 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -22,6 +22,7 @@ from django.views.generic.base import TemplateView from part.models import Part, PartCategory from stock.models import StockLocation, StockItem from common.models import InvenTreeSetting, ColorTheme +from users.models import check_user_role, RuleSet from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm from .helpers import str2bool @@ -107,31 +108,72 @@ class TreeSerializer(views.APIView): return JsonResponse(response, safe=False) -class AjaxMixin(PermissionRequiredMixin): +class InvenTreeRoleMixin(PermissionRequiredMixin): + """ + Permission class based on user roles, not user 'permissions'. + + To specify which role is required for the mixin, + set the class attribute 'role_required' to something like the following: + + role_required = 'part.add' + role_required = [ + 'part.change', + 'build.add', + ] + """ + + # By default, no roles are required + # Roles must be specified + role_required = None + + def has_permission(self): + """ + Determine if the current user + """ + + roles_required = [] + + if type(self.role_required) is str: + roles_required.append(self.role_required) + elif type(self.role_required) in [list, tuple]: + roles_required = self.role_required + + user = self.request.user + + # Superuser can have any permissions they desire + if user.is_superuser: + return True + + for required in roles_required: + + (role, permission) = required.split('.') + + if role not in RuleSet.RULESET_NAMES: + raise ValueError(f"Role '{role}' is not a valid role") + + if permission not in RuleSet.RULESET_PERMISSIONS: + raise ValueError(f"Permission '{permission}' is not a valid permission") + + # Return False if the user does not have *any* of the required roles + if not check_user_role(user, role, permission): + return False + + # We did not fail any required checks + return True + + +class AjaxMixin(InvenTreeRoleMixin): """ AjaxMixin provides basic functionality for rendering a Django form to JSON. Handles jsonResponse rendering, and adds extra data for the modal forms to process on the client side. Any view which inherits the AjaxMixin will need - correct permissions set using the 'permission_required' attribute + correct permissions set using the 'role_required' attribute """ - # By default, allow *any* permissions - permission_required = '*' - - def has_permission(self): - """ - Override the default behaviour of has_permission from PermissionRequiredMixin. - - Basically, if permission_required attribute = '*', - no permissions are actually required! - """ - - if self.permission_required == '*': - return True - else: - return super().has_permission() + # By default, allow *any* role + role_required = None # By default, point to the modal_form template # (this can be overridden by a child class) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index c0faee6c15..d4e458c506 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters -from rest_framework import generics, permissions +from rest_framework import generics from django.conf.urls import url, include @@ -28,10 +28,6 @@ class BuildList(generics.ListCreateAPIView): queryset = Build.objects.all() serializer_class = BuildSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -99,10 +95,6 @@ class BuildDetail(generics.RetrieveUpdateAPIView): queryset = Build.objects.all() serializer_class = BuildSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - class BuildItemList(generics.ListCreateAPIView): """ API endpoint for accessing a list of BuildItem objects @@ -137,10 +129,6 @@ class BuildItemList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, ] diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index 915433b055..f076013def 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -35,25 +35,27 @@ src="{% static 'img/blank_image.png' %}"

{{ build.quantity }} x {{ build.part.full_name }} - {% if user.is_staff and perms.build.change_build %} + {% if user.is_staff and roles.build.change %} {% endif %}

- {% if build.is_active %} - - {% endif %} - {% if build.status == BuildStatus.CANCELLED %} - {% endif %} diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 9b6d51c33a..92ca034a4a 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from rest_framework.test import APITestCase from rest_framework import status @@ -30,6 +31,20 @@ class BuildTestSimple(TestCase): User.objects.create_user('testuser', 'test@testing.com', 'password') self.user = User.objects.get(username='testuser') + + g = Group.objects.create(name='builders') + self.user.groups.add(g) + + for rule in g.rule_sets.all(): + if rule.name == 'build': + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + g.save() + self.client.login(username='testuser', password='password') def test_build_objects(self): @@ -94,7 +109,20 @@ class TestBuildAPI(APITestCase): def setUp(self): # Create a user for auth User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + user = User.objects.create_user('testuser', 'test@testing.com', 'password') + + g = Group.objects.create(name='builders') + user.groups.add(g) + + for rule in g.rule_sets.all(): + if rule.name == 'build': + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + g.save() self.client.login(username='testuser', password='password') @@ -131,7 +159,20 @@ class TestBuildViews(TestCase): # Create a user User = get_user_model() - User.objects.create_user('username', 'user@email.com', 'password') + user = User.objects.create_user('username', 'user@email.com', 'password') + + g = Group.objects.create(name='builders') + user.groups.add(g) + + for rule in g.rule_sets.all(): + if rule.name == 'build': + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + g.save() self.client.login(username='username', password='password') diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 88dc66085f..b2b8b24502 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -17,16 +17,18 @@ from . import forms from stock.models import StockLocation, StockItem from InvenTree.views import AjaxUpdateView, AjaxCreateView, AjaxDeleteView +from InvenTree.views import InvenTreeRoleMixin from InvenTree.helpers import str2bool, ExtractSerialNumbers from InvenTree.status_codes import BuildStatus -class BuildIndex(ListView): +class BuildIndex(InvenTreeRoleMixin, ListView): """ View for displaying list of Builds """ model = Build template_name = 'build/index.html' context_object_name = 'builds' + role_required = 'build.view' def get_queryset(self): """ Return all Build objects (order by date, newest first) """ @@ -56,6 +58,7 @@ class BuildCancel(AjaxUpdateView): ajax_form_title = _('Cancel Build') context_object_name = 'build' form_class = forms.CancelBuildForm + role_required = 'build.change' def post(self, request, *args, **kwargs): """ Handle POST request. Mark the build status as CANCELLED """ @@ -94,6 +97,7 @@ class BuildAutoAllocate(AjaxUpdateView): context_object_name = 'build' ajax_form_title = _('Allocate Stock') ajax_template_name = 'build/auto_allocate.html' + role_required = 'build.change' def get_context_data(self, *args, **kwargs): """ Get the context data for form rendering. """ @@ -147,6 +151,7 @@ class BuildUnallocate(AjaxUpdateView): form_class = forms.ConfirmBuildForm ajax_form_title = _("Unallocate Stock") ajax_template_name = "build/unallocate.html" + form_required = 'build.change' def post(self, request, *args, **kwargs): @@ -184,6 +189,7 @@ class BuildComplete(AjaxUpdateView): context_object_name = "build" ajax_form_title = _("Complete Build") ajax_template_name = "build/complete.html" + role_required = 'build.change' def get_form(self): """ Get the form object. @@ -325,6 +331,7 @@ class BuildNotes(UpdateView): context_object_name = 'build' template_name = 'build/notes.html' model = Build + role_required = 'build.view' fields = ['notes'] @@ -342,9 +349,11 @@ class BuildNotes(UpdateView): class BuildDetail(DetailView): """ Detail view of a single Build object. """ + model = Build template_name = 'build/detail.html' context_object_name = 'build' + role_required = 'build.view' def get_context_data(self, **kwargs): @@ -363,6 +372,7 @@ class BuildAllocate(DetailView): model = Build context_object_name = 'build' template_name = 'build/allocate.html' + role_required = ['build.change'] def get_context_data(self, **kwargs): """ Provide extra context information for the Build allocation page """ @@ -392,6 +402,7 @@ class BuildCreate(AjaxCreateView): form_class = forms.EditBuildForm ajax_form_title = _('Start new Build') ajax_template_name = 'modal_form.html' + role_required = 'build.add' def get_initial(self): """ Get initial parameters for Build creation. @@ -427,6 +438,7 @@ class BuildUpdate(AjaxUpdateView): context_object_name = 'build' ajax_form_title = _('Edit Build Details') ajax_template_name = 'modal_form.html' + role_required = 'build.change' def get_data(self): return { @@ -440,6 +452,7 @@ class BuildDelete(AjaxDeleteView): model = Build ajax_template_name = 'build/delete_build.html' ajax_form_title = _('Delete Build') + role_required = 'build.delete' class BuildItemDelete(AjaxDeleteView): @@ -451,6 +464,7 @@ class BuildItemDelete(AjaxDeleteView): ajax_template_name = 'build/delete_build_item.html' ajax_form_title = _('Unallocate Stock') context_object_name = 'item' + role_required = 'build.delete' def get_data(self): return { @@ -465,6 +479,7 @@ class BuildItemCreate(AjaxCreateView): form_class = forms.EditBuildItemForm ajax_template_name = 'build/create_build_item.html' ajax_form_title = _('Allocate new Part') + role_required = 'build.add' part = None available_stock = None @@ -618,6 +633,7 @@ class BuildItemEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' form_class = forms.EditBuildItemForm ajax_form_title = _('Edit Stock Allocation') + role_required = 'build.change' def get_data(self): return { diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 0e54d7d7fb..548ac96016 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters -from rest_framework import generics, permissions +from rest_framework import generics from django.conf.urls import url, include from django.db.models import Q @@ -40,10 +40,6 @@ class CompanyList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -82,10 +78,6 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - class SupplierPartList(generics.ListCreateAPIView): """ API endpoint for list view of SupplierPart object @@ -170,10 +162,6 @@ class SupplierPartList(generics.ListCreateAPIView): serializer_class = SupplierPartSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -202,7 +190,6 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer - permission_classes = (permissions.IsAuthenticated,) read_only_fields = [ ] @@ -218,10 +205,6 @@ class SupplierPriceBreakList(generics.ListCreateAPIView): queryset = SupplierPriceBreak.objects.all() serializer_class = SupplierPriceBreakSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, ] diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index 73ebecf979..f20107277d 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -23,7 +23,7 @@ InvenTree | {% trans "Company" %} - {{ company.name }}

{{ company.name }} - {% if user.is_staff and perms.company.change_company %} + {% if user.is_staff and roles.company.change %} {% endif %}

diff --git a/InvenTree/company/test_api.py b/InvenTree/company/test_api.py index bf4cc6643e..643608542d 100644 --- a/InvenTree/company/test_api.py +++ b/InvenTree/company/test_api.py @@ -3,6 +3,8 @@ from rest_framework import status from django.urls import reverse from django.contrib.auth import get_user_model +from InvenTree.helpers import addUserPermissions + from .models import Company @@ -14,7 +16,16 @@ class CompanyTest(APITestCase): def setUp(self): # Create a user for auth User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + self.user = User.objects.create_user('testuser', 'test@testing.com', 'password') + + perms = [ + 'view_company', + 'change_company', + 'add_company', + ] + + addUserPermissions(self.user, perms) + self.client.login(username='testuser', password='password') Company.objects.create(name='ACME', description='Supplier', is_customer=False, is_supplier=True) diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 493d4a8b06..7f955ffea2 100644 --- a/InvenTree/locale/de/LC_MESSAGES/django.po +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-04 14:02+0000\n" +"POT-Creation-Date: 2020-10-06 09:31+0000\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -17,11 +17,11 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 19.12.0\n" -#: InvenTree/api.py:83 +#: InvenTree/api.py:85 msgid "No action specified" msgstr "Keine Aktion angegeben" -#: InvenTree/api.py:97 +#: InvenTree/api.py:99 msgid "No matching action found" msgstr "Keine passende Aktion gefunden" @@ -49,35 +49,35 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:337 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:339 order/models.py:187 order/models.py:261 msgid "Invalid quantity provided" msgstr "Keine gültige Menge" -#: InvenTree/helpers.py:340 +#: InvenTree/helpers.py:342 msgid "Empty serial number string" msgstr "Keine Seriennummer angegeben" -#: InvenTree/helpers.py:361 +#: InvenTree/helpers.py:363 #, python-brace-format msgid "Duplicate serial: {n}" msgstr "Doppelte Seriennummer: {n}" -#: InvenTree/helpers.py:365 InvenTree/helpers.py:368 InvenTree/helpers.py:371 +#: InvenTree/helpers.py:367 InvenTree/helpers.py:370 InvenTree/helpers.py:373 #, python-brace-format msgid "Invalid group: {g}" msgstr "Ungültige Gruppe: {g}" -#: InvenTree/helpers.py:376 +#: InvenTree/helpers.py:378 #, fuzzy, python-brace-format #| msgid "Duplicate serial: {n}" msgid "Duplicate serial: {g}" msgstr "Doppelte Seriennummer: {n}" -#: InvenTree/helpers.py:384 +#: InvenTree/helpers.py:386 msgid "No serial numbers found" msgstr "Keine Seriennummern gefunden" -#: InvenTree/helpers.py:388 +#: InvenTree/helpers.py:390 #, python-brace-format msgid "Number of unique serial number ({s}) must match quantity ({q})" msgstr "" @@ -96,7 +96,7 @@ msgstr "Datei-Kommentar" msgid "User" msgstr "Benutzer" -#: InvenTree/models.py:106 part/templates/part/params.html:20 +#: InvenTree/models.py:106 part/templates/part/params.html:22 #: templates/js/part.html:81 msgid "Name" msgstr "Name" @@ -107,19 +107,19 @@ msgstr "Name" msgid "Description (optional)" msgstr "Firmenbeschreibung" -#: InvenTree/settings.py:342 +#: InvenTree/settings.py:348 msgid "English" msgstr "Englisch" -#: InvenTree/settings.py:343 +#: InvenTree/settings.py:349 msgid "German" msgstr "Deutsch" -#: InvenTree/settings.py:344 +#: InvenTree/settings.py:350 msgid "French" msgstr "Französisch" -#: InvenTree/settings.py:345 +#: InvenTree/settings.py:351 msgid "Polish" msgstr "Polnisch" @@ -152,7 +152,7 @@ msgid "Returned" msgstr "Zurückgegeben" #: InvenTree/status_codes.py:136 -#: order/templates/order/sales_order_base.html:103 +#: order/templates/order/sales_order_base.html:105 msgid "Shipped" msgstr "Versendet" @@ -207,7 +207,7 @@ msgstr "Überschuss darf 100% nicht überschreiten" msgid "Overage must be an integer value or a percentage" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: InvenTree/views.py:661 +#: InvenTree/views.py:703 msgid "Database Statistics" msgstr "Datenbankstatistiken" @@ -281,7 +281,7 @@ msgstr "Bau-Fertigstellung bestätigen" msgid "Build quantity must be integer value for trackable parts" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: build/models.py:73 build/templates/build/build_base.html:70 +#: build/models.py:73 build/templates/build/build_base.html:72 msgid "Build Title" msgstr "Bau-Titel" @@ -289,7 +289,7 @@ msgstr "Bau-Titel" msgid "Brief description of the build" msgstr "Kurze Beschreibung des Baus" -#: build/models.py:84 build/templates/build/build_base.html:91 +#: build/models.py:84 build/templates/build/build_base.html:93 msgid "Parent Build" msgstr "Eltern-Bau" @@ -299,7 +299,7 @@ msgstr "Eltern-Bau, dem dieser Bau zugewiesen ist" #: build/models.py:90 build/templates/build/allocate.html:329 #: build/templates/build/auto_allocate.html:19 -#: build/templates/build/build_base.html:75 +#: build/templates/build/build_base.html:77 #: build/templates/build/detail.html:22 order/models.py:501 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:147 @@ -345,7 +345,7 @@ msgstr "Bau-Anzahl" msgid "Number of parts to build" msgstr "Anzahl der zu bauenden Teile" -#: build/models.py:128 part/templates/part/part_base.html:145 +#: build/models.py:128 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "Bau-Status" @@ -364,7 +364,7 @@ msgstr "Chargennummer für diese Bau-Ausgabe" #: build/models.py:155 build/templates/build/detail.html:55 #: company/templates/company/supplier_part_base.html:60 #: company/templates/company/supplier_part_detail.html:24 -#: part/templates/part/detail.html:80 part/templates/part/part_base.html:92 +#: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 #: stock/models.py:381 stock/templates/stock/item_base.html:244 msgid "External Link" msgstr "Externer Link" @@ -376,7 +376,7 @@ msgstr "Link zu einer externen URL" #: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15 #: order/templates/order/purchase_order_detail.html:202 -#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:67 +#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/models.py:1404 stock/templates/stock/tabs.html:26 #: templates/js/barcode.html:391 templates/js/bom.html:223 @@ -424,8 +424,8 @@ msgid "Stock quantity to allocate to build" msgstr "Lagerobjekt-Anzahl dem Bau zuweisen" #: build/templates/build/allocate.html:17 -#: company/templates/company/detail_part.html:18 order/views.py:779 -#: part/templates/part/category.html:112 +#: company/templates/company/detail_part.html:18 order/views.py:804 +#: part/templates/part/category.html:122 msgid "Order Parts" msgstr "Teile bestellen" @@ -441,7 +441,7 @@ msgstr "Automatisches Zuweisen" msgid "Unallocate" msgstr "Zuweisung aufheben" -#: build/templates/build/allocate.html:87 templates/stock_table.html:9 +#: build/templates/build/allocate.html:87 templates/stock_table.html:10 msgid "New Stock Item" msgstr "Neues Lagerobjekt" @@ -458,7 +458,7 @@ msgstr "Seriennummer" #: build/templates/build/allocate.html:172 #: build/templates/build/auto_allocate.html:20 -#: build/templates/build/build_base.html:80 +#: build/templates/build/build_base.html:82 #: build/templates/build/detail.html:27 #: company/templates/company/supplier_part_pricing.html:71 #: order/templates/order/order_wizard/select_parts.html:32 @@ -574,7 +574,7 @@ msgstr "Lagerobjekt wurde zugewiesen" #: build/templates/build/build_base.html:34 #: build/templates/build/complete.html:6 #: stock/templates/stock/item_base.html:223 templates/js/build.html:39 -#: templates/navbar.html:20 +#: templates/navbar.html:25 msgid "Build" msgstr "Bau" @@ -598,11 +598,29 @@ msgstr "Dieser Bau ist Kind von Bau" msgid "Admin view" msgstr "Admin" -#: build/templates/build/build_base.html:66 build/templates/build/detail.html:9 +#: build/templates/build/build_base.html:45 +#, fuzzy +#| msgid "Edited build" +msgid "Edit Build" +msgstr "Bau bearbeitet" + +#: build/templates/build/build_base.html:49 build/views.py:190 +msgid "Complete Build" +msgstr "Bau fertigstellen" + +#: build/templates/build/build_base.html:52 build/views.py:58 +msgid "Cancel Build" +msgstr "Bau abbrechen" + +#: build/templates/build/build_base.html:58 build/views.py:454 +msgid "Delete Build" +msgstr "Bau entfernt" + +#: build/templates/build/build_base.html:68 build/templates/build/detail.html:9 msgid "Build Details" msgstr "Bau-Status" -#: build/templates/build/build_base.html:85 +#: build/templates/build/build_base.html:87 #: build/templates/build/detail.html:42 #: order/templates/order/receive_parts.html:24 #: stock/templates/stock/item_base.html:276 templates/InvenTree/search.html:175 @@ -612,7 +630,7 @@ msgstr "Bau-Status" msgid "Status" msgstr "Status" -#: build/templates/build/build_base.html:98 order/models.py:499 +#: build/templates/build/build_base.html:100 order/models.py:499 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 @@ -622,15 +640,15 @@ msgstr "Status" msgid "Sales Order" msgstr "Bestellung" -#: build/templates/build/build_base.html:104 +#: build/templates/build/build_base.html:106 msgid "BOM Price" msgstr "Stücklistenpreis" -#: build/templates/build/build_base.html:109 +#: build/templates/build/build_base.html:111 msgid "BOM pricing is incomplete" msgstr "Stücklistenbepreisung ist unvollständig" -#: build/templates/build/build_base.html:112 +#: build/templates/build/build_base.html:114 msgid "No pricing information" msgstr "Keine Preisinformation" @@ -694,8 +712,8 @@ msgid "Batch" msgstr "Los" #: build/templates/build/detail.html:61 -#: order/templates/order/order_base.html:98 -#: order/templates/order/sales_order_base.html:97 templates/js/build.html:71 +#: order/templates/order/order_base.html:100 +#: order/templates/order/sales_order_base.html:99 templates/js/build.html:71 msgid "Created" msgstr "Erstellt" @@ -717,7 +735,7 @@ msgstr "Fertig" #: build/templates/build/index.html:6 build/templates/build/index.html:14 #: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9 -#: part/templates/part/tabs.html:30 +#: part/templates/part/tabs.html:31 users/models.py:30 msgid "Build Orders" msgstr "Bauaufträge" @@ -737,9 +755,9 @@ msgid "Save" msgstr "Speichern" #: build/templates/build/notes.html:33 company/templates/company/notes.html:30 -#: order/templates/order/order_notes.html:32 +#: order/templates/order/order_notes.html:33 #: order/templates/order/sales_order_notes.html:37 -#: part/templates/part/notes.html:32 stock/templates/stock/item_notes.html:33 +#: part/templates/part/notes.html:33 stock/templates/stock/item_notes.html:33 msgid "Edit notes" msgstr "Bermerkungen bearbeiten" @@ -757,100 +775,88 @@ msgid "Are you sure you wish to unallocate all stock for this build?" msgstr "" "Sind Sie sicher, dass sie alle Lagerobjekte von diesem Bau entfernen möchten?" -#: build/views.py:56 -msgid "Cancel Build" -msgstr "Bau abbrechen" - -#: build/views.py:74 +#: build/views.py:77 msgid "Confirm build cancellation" msgstr "Bauabbruch bestätigen" -#: build/views.py:79 +#: build/views.py:82 msgid "Build was cancelled" msgstr "Bau wurde abgebrochen" -#: build/views.py:95 +#: build/views.py:98 msgid "Allocate Stock" msgstr "Lagerbestand zuweisen" -#: build/views.py:108 +#: build/views.py:112 msgid "No matching build found" msgstr "Kein passender Bau gefunden" -#: build/views.py:127 +#: build/views.py:131 msgid "Confirm stock allocation" msgstr "Lagerbestandszuordnung bestätigen" -#: build/views.py:128 +#: build/views.py:132 msgid "Check the confirmation box at the bottom of the list" msgstr "Bestätigunsbox am Ende der Liste bestätigen" -#: build/views.py:148 build/views.py:452 +#: build/views.py:152 build/views.py:465 msgid "Unallocate Stock" msgstr "Zuweisung aufheben" -#: build/views.py:161 +#: build/views.py:166 msgid "Confirm unallocation of build stock" msgstr "Zuweisungsaufhebung bestätigen" -#: build/views.py:162 stock/views.py:405 +#: build/views.py:167 stock/views.py:405 msgid "Check the confirmation box" msgstr "Bestätigungsbox bestätigen" -#: build/views.py:185 -msgid "Complete Build" -msgstr "Bau fertigstellen" - -#: build/views.py:264 +#: build/views.py:270 msgid "Confirm completion of build" msgstr "Baufertigstellung bestätigen" -#: build/views.py:271 +#: build/views.py:277 msgid "Invalid location selected" msgstr "Ungültige Ortsauswahl" -#: build/views.py:296 stock/views.py:1621 +#: build/views.py:302 stock/views.py:1621 #, python-brace-format msgid "The following serial numbers already exist: ({sn})" msgstr "Die folgende Seriennummer existiert bereits: ({sn})" -#: build/views.py:317 +#: build/views.py:323 msgid "Build marked as COMPLETE" msgstr "Bau als FERTIG markiert" -#: build/views.py:393 +#: build/views.py:403 msgid "Start new Build" msgstr "Neuen Bau beginnen" -#: build/views.py:418 +#: build/views.py:429 msgid "Created new build" msgstr "Neuen Bau angelegt" -#: build/views.py:428 +#: build/views.py:439 msgid "Edit Build Details" msgstr "Baudetails bearbeiten" -#: build/views.py:433 +#: build/views.py:445 msgid "Edited build" msgstr "Bau bearbeitet" -#: build/views.py:442 -msgid "Delete Build" -msgstr "Bau entfernt" - -#: build/views.py:457 +#: build/views.py:471 msgid "Removed parts from build allocation" msgstr "Teile von Bauzuordnung entfernt" -#: build/views.py:467 +#: build/views.py:481 msgid "Allocate new Part" msgstr "Neues Teil zuordnen" -#: build/views.py:620 +#: build/views.py:635 msgid "Edit Stock Allocation" msgstr "Teilzuordnung bearbeiten" -#: build/views.py:624 +#: build/views.py:640 msgid "Updated Build Item" msgstr "Bauobjekt aktualisiert" @@ -1055,7 +1061,7 @@ msgstr "Hersteller" #: company/templates/company/detail.html:21 #: company/templates/company/supplier_part_base.html:66 #: company/templates/company/supplier_part_detail.html:21 -#: order/templates/order/order_base.html:79 +#: order/templates/order/order_base.html:81 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 #: stock/templates/stock/item_base.html:251 templates/js/company.html:48 #: templates/js/company.html:162 templates/js/order.html:146 @@ -1063,7 +1069,7 @@ msgid "Supplier" msgstr "Zulieferer" #: company/templates/company/detail.html:26 -#: order/templates/order/sales_order_base.html:78 stock/models.py:370 +#: order/templates/order/sales_order_base.html:80 stock/models.py:370 #: stock/models.py:371 stock/templates/stock/item_base.html:169 #: templates/js/company.html:40 templates/js/order.html:221 msgid "Customer" @@ -1085,13 +1091,13 @@ msgid "New Supplier Part" msgstr "Neues Zulieferer-Teil" #: company/templates/company/detail_part.html:15 -#: part/templates/part/category.html:109 part/templates/part/supplier.html:15 -#: templates/stock_table.html:11 +#: part/templates/part/category.html:117 part/templates/part/supplier.html:15 +#: templates/stock_table.html:14 msgid "Options" msgstr "Optionen" #: company/templates/company/detail_part.html:18 -#: part/templates/part/category.html:112 +#: part/templates/part/category.html:122 #, fuzzy #| msgid "Order part" msgid "Order parts" @@ -1108,7 +1114,7 @@ msgid "Delete Parts" msgstr "Teile löschen" #: company/templates/company/detail_part.html:43 -#: part/templates/part/category.html:107 templates/js/stock.html:791 +#: part/templates/part/category.html:114 templates/js/stock.html:791 msgid "New Part" msgstr "Neues Teil" @@ -1140,7 +1146,7 @@ msgstr "Zuliefererbestand" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/category.html:106 part/templates/part/category.html:113 +#: part/templates/part/category.html:112 part/templates/part/category.html:123 #: part/templates/part/stock.html:51 templates/stock_table.html:6 msgid "Export" msgstr "Exportieren" @@ -1163,18 +1169,18 @@ msgstr "" #: company/templates/company/tabs.html:17 #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 -#: part/templates/part/orders.html:9 part/templates/part/tabs.html:45 -#: templates/navbar.html:26 +#: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 +#: templates/navbar.html:33 users/models.py:31 msgid "Purchase Orders" msgstr "Bestellungen" #: company/templates/company/purchase_orders.html:14 -#: order/templates/order/purchase_orders.html:17 +#: order/templates/order/purchase_orders.html:18 msgid "Create new purchase order" msgstr "Neue Bestellung anlegen" #: company/templates/company/purchase_orders.html:14 -#: order/templates/order/purchase_orders.html:17 +#: order/templates/order/purchase_orders.html:18 msgid "New Purchase Order" msgstr "Neue Bestellung" @@ -1182,18 +1188,18 @@ msgstr "Neue Bestellung" #: company/templates/company/tabs.html:22 #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 -#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:53 -#: templates/navbar.html:33 +#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 +#: templates/navbar.html:42 users/models.py:32 msgid "Sales Orders" msgstr "Bestellungen" #: company/templates/company/sales_orders.html:14 -#: order/templates/order/sales_orders.html:17 +#: order/templates/order/sales_orders.html:18 msgid "Create new sales order" msgstr "Neuen Auftrag anlegen" #: company/templates/company/sales_orders.html:14 -#: order/templates/order/sales_orders.html:17 +#: order/templates/order/sales_orders.html:18 msgid "New Sales Order" msgstr "Neuer Auftrag" @@ -1204,7 +1210,7 @@ msgid "Supplier Part" msgstr "Zulieferer-Teil" #: company/templates/company/supplier_part_base.html:23 -#: part/templates/part/orders.html:14 +#: part/templates/part/orders.html:14 part/templates/part/part_base.html:66 msgid "Order part" msgstr "Teil bestellen" @@ -1251,7 +1257,7 @@ msgid "Pricing Information" msgstr "Preisinformationen ansehen" #: company/templates/company/supplier_part_pricing.html:15 company/views.py:399 -#: part/templates/part/sale_prices.html:13 part/views.py:2149 +#: part/templates/part/sale_prices.html:13 part/views.py:2228 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1293,7 +1299,7 @@ msgstr "Bepreisung" #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: templates/js/part.html:124 templates/js/part.html:372 -#: templates/js/stock.html:452 templates/navbar.html:19 +#: templates/js/stock.html:452 templates/navbar.html:22 users/models.py:29 msgid "Stock" msgstr "Lagerbestand" @@ -1303,22 +1309,22 @@ msgstr "Bestellungen" #: company/templates/company/tabs.html:9 #: order/templates/order/receive_parts.html:14 part/models.py:294 -#: part/templates/part/cat_link.html:7 part/templates/part/category.html:88 -#: part/templates/part/category_tabs.html:6 templates/navbar.html:18 -#: templates/stats.html:8 templates/stats.html:17 +#: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 +#: part/templates/part/category_tabs.html:6 templates/navbar.html:19 +#: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "Teile" -#: company/views.py:50 part/templates/part/tabs.html:39 -#: templates/navbar.html:24 +#: company/views.py:50 part/templates/part/tabs.html:42 +#: templates/navbar.html:31 msgid "Suppliers" msgstr "Zulieferer" -#: company/views.py:57 templates/navbar.html:25 +#: company/views.py:57 templates/navbar.html:32 msgid "Manufacturers" msgstr "Hersteller" -#: company/views.py:64 templates/navbar.html:32 +#: company/views.py:64 templates/navbar.html:41 msgid "Customers" msgstr "Kunden" @@ -1382,17 +1388,17 @@ msgstr "Neues Zuliefererteil anlegen" msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:404 part/views.py:2153 +#: company/views.py:404 part/views.py:2234 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:441 part/views.py:2198 +#: company/views.py:441 part/views.py:2279 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:456 part/views.py:2212 +#: company/views.py:456 part/views.py:2295 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -1424,20 +1430,20 @@ msgstr "" msgid "Enabled" msgstr "" -#: order/forms.py:24 +#: order/forms.py:24 order/templates/order/order_base.html:40 msgid "Place order" msgstr "Bestellung aufgeben" -#: order/forms.py:35 +#: order/forms.py:35 order/templates/order/order_base.html:47 msgid "Mark order as complete" msgstr "Bestellung als vollständig markieren" -#: order/forms.py:46 order/forms.py:57 -#: order/templates/order/sales_order_base.html:54 +#: order/forms.py:46 order/forms.py:57 order/templates/order/order_base.html:52 +#: order/templates/order/sales_order_base.html:52 msgid "Cancel order" msgstr "Bestellung stornieren" -#: order/forms.py:68 order/templates/order/sales_order_base.html:51 +#: order/forms.py:68 order/templates/order/sales_order_base.html:49 msgid "Ship order" msgstr "Bestellung versenden" @@ -1497,7 +1503,7 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:259 part/views.py:1304 +#: order/models.py:185 order/models.py:259 part/views.py:1345 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -1578,32 +1584,48 @@ msgstr "Zuordnungsanzahl eingeben" msgid "Are you sure you want to delete this attachment?" msgstr "Sind Sie sicher, dass Sie diesen Anhang löschen wollen?" -#: order/templates/order/order_base.html:64 +#: order/templates/order/order_base.html:36 +#, fuzzy +#| msgid "Edited company information" +msgid "Edit order information" +msgstr "Firmeninformation bearbeitet" + +#: order/templates/order/order_base.html:44 +#, fuzzy +#| msgid "Receive line item" +msgid "Receive items" +msgstr "Position empfangen" + +#: order/templates/order/order_base.html:57 +msgid "Export order to file" +msgstr "" + +#: order/templates/order/order_base.html:66 msgid "Purchase Order Details" msgstr "Bestelldetails" -#: order/templates/order/order_base.html:69 -#: order/templates/order/sales_order_base.html:68 +#: order/templates/order/order_base.html:71 +#: order/templates/order/sales_order_base.html:70 msgid "Order Reference" msgstr "Bestellreferenz" -#: order/templates/order/order_base.html:74 -#: order/templates/order/sales_order_base.html:73 +#: order/templates/order/order_base.html:76 +#: order/templates/order/sales_order_base.html:75 msgid "Order Status" msgstr "Bestellstatus" -#: order/templates/order/order_base.html:85 templates/js/order.html:153 +#: order/templates/order/order_base.html:87 templates/js/order.html:153 msgid "Supplier Reference" msgstr "Zuliefererreferenz" -#: order/templates/order/order_base.html:104 +#: order/templates/order/order_base.html:106 msgid "Issued" msgstr "Aufgegeben" -#: order/templates/order/order_base.html:111 +#: order/templates/order/order_base.html:113 #: order/templates/order/purchase_order_detail.html:182 #: order/templates/order/receive_parts.html:22 -#: order/templates/order/sales_order_base.html:110 +#: order/templates/order/sales_order_base.html:112 msgid "Received" msgstr "Empfangen" @@ -1667,13 +1689,13 @@ msgid "Purchase Order Attachments" msgstr "Bestellanhänge" #: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16 -#: part/templates/part/tabs.html:64 stock/templates/stock/tabs.html:32 +#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 msgid "Attachments" msgstr "Anhänge" #: order/templates/order/purchase_order_detail.html:16 -#: order/templates/order/sales_order_detail.html:17 order/views.py:1087 -#: order/views.py:1201 +#: order/templates/order/sales_order_detail.html:17 order/views.py:1117 +#: order/views.py:1232 msgid "Add Line Item" msgstr "Position hinzufügen" @@ -1683,7 +1705,7 @@ msgstr "Bestellpositionen" #: order/templates/order/purchase_order_detail.html:38 #: order/templates/order/purchase_order_detail.html:118 -#: part/templates/part/category.html:161 part/templates/part/category.html:202 +#: part/templates/part/category.html:171 part/templates/part/category.html:213 #: templates/js/stock.html:803 msgid "New Location" msgstr "Neuer Standort" @@ -1725,7 +1747,7 @@ msgid "Select parts to receive against this order" msgstr "" #: order/templates/order/receive_parts.html:21 -#: part/templates/part/part_base.html:135 templates/js/part.html:388 +#: part/templates/part/part_base.html:145 templates/js/part.html:388 msgid "On Order" msgstr "bestellt" @@ -1743,15 +1765,15 @@ msgstr "" msgid "This SalesOrder has not been fully allocated" msgstr "Dieser Auftrag ist nicht vollständig zugeordnet" -#: order/templates/order/sales_order_base.html:47 +#: order/templates/order/sales_order_base.html:57 msgid "Packing List" msgstr "Packliste" -#: order/templates/order/sales_order_base.html:63 +#: order/templates/order/sales_order_base.html:65 msgid "Sales Order Details" msgstr "Auftragsdetails" -#: order/templates/order/sales_order_base.html:84 templates/js/order.html:228 +#: order/templates/order/sales_order_base.html:86 templates/js/order.html:228 msgid "Customer Reference" msgstr "Kundenreferenz" @@ -1819,156 +1841,156 @@ msgstr "Sind Sie sicher, dass Sie diese Position löschen möchten?" msgid "Order Items" msgstr "Bestellungspositionen" -#: order/views.py:93 +#: order/views.py:99 msgid "Add Purchase Order Attachment" msgstr "Bestellanhang hinzufügen" -#: order/views.py:102 order/views.py:149 part/views.py:86 stock/views.py:167 +#: order/views.py:109 order/views.py:157 part/views.py:92 stock/views.py:167 msgid "Added attachment" msgstr "Anhang hinzugefügt" -#: order/views.py:141 +#: order/views.py:148 msgid "Add Sales Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: order/views.py:176 order/views.py:197 +#: order/views.py:184 order/views.py:206 msgid "Edit Attachment" msgstr "Anhang bearbeiten" -#: order/views.py:180 order/views.py:201 +#: order/views.py:189 order/views.py:211 msgid "Attachment updated" msgstr "Anhang aktualisiert" -#: order/views.py:216 order/views.py:230 +#: order/views.py:226 order/views.py:241 msgid "Delete Attachment" msgstr "Anhang löschen" -#: order/views.py:222 order/views.py:236 stock/views.py:223 +#: order/views.py:233 order/views.py:248 stock/views.py:223 msgid "Deleted attachment" msgstr "Anhang gelöscht" -#: order/views.py:287 +#: order/views.py:301 msgid "Create Purchase Order" msgstr "Bestellung anlegen" -#: order/views.py:318 +#: order/views.py:333 msgid "Create Sales Order" msgstr "Auftrag anlegen" -#: order/views.py:348 +#: order/views.py:364 msgid "Edit Purchase Order" msgstr "Bestellung bearbeiten" -#: order/views.py:368 +#: order/views.py:385 msgid "Edit Sales Order" msgstr "Auftrag bearbeiten" -#: order/views.py:384 +#: order/views.py:402 msgid "Cancel Order" msgstr "Bestellung stornieren" -#: order/views.py:399 order/views.py:431 +#: order/views.py:418 order/views.py:451 msgid "Confirm order cancellation" msgstr "Bestellstornierung bestätigen" -#: order/views.py:417 +#: order/views.py:436 msgid "Cancel sales order" msgstr "Auftrag stornieren" -#: order/views.py:437 +#: order/views.py:457 msgid "Could not cancel order" msgstr "Stornierung fehlgeschlagen" -#: order/views.py:451 +#: order/views.py:471 msgid "Issue Order" msgstr "Bestellung aufgeben" -#: order/views.py:466 +#: order/views.py:487 msgid "Confirm order placement" msgstr "Bestellungstätigung bestätigen" -#: order/views.py:487 +#: order/views.py:508 msgid "Complete Order" msgstr "Auftrag fertigstellen" -#: order/views.py:522 +#: order/views.py:544 msgid "Ship Order" msgstr "Versenden" -#: order/views.py:538 +#: order/views.py:561 msgid "Confirm order shipment" msgstr "Versand bestätigen" -#: order/views.py:544 +#: order/views.py:567 msgid "Could not ship order" msgstr "Versand fehlgeschlagen" -#: order/views.py:595 +#: order/views.py:619 msgid "Receive Parts" msgstr "Teile empfangen" -#: order/views.py:662 +#: order/views.py:687 msgid "Items received" msgstr "Anzahl empfangener Positionen" -#: order/views.py:676 +#: order/views.py:701 msgid "No destination set" msgstr "Kein Ziel gesetzt" -#: order/views.py:721 +#: order/views.py:746 msgid "Error converting quantity to number" msgstr "Fehler beim Konvertieren zu Zahl" -#: order/views.py:727 +#: order/views.py:752 msgid "Receive quantity less than zero" msgstr "Anzahl kleiner null empfangen" -#: order/views.py:733 +#: order/views.py:758 msgid "No lines specified" msgstr "Keine Zeilen angegeben" -#: order/views.py:1107 +#: order/views.py:1138 msgid "Invalid Purchase Order" msgstr "Ungültige Bestellung" -#: order/views.py:1115 +#: order/views.py:1146 msgid "Supplier must match for Part and Order" msgstr "Zulieferer muss zum Teil und zur Bestellung passen" -#: order/views.py:1120 +#: order/views.py:1151 msgid "Invalid SupplierPart selection" msgstr "Ungültige Wahl des Zulieferer-Teils" -#: order/views.py:1252 order/views.py:1270 +#: order/views.py:1284 order/views.py:1303 msgid "Edit Line Item" msgstr "Position bearbeiten" -#: order/views.py:1286 order/views.py:1298 +#: order/views.py:1320 order/views.py:1333 msgid "Delete Line Item" msgstr "Position löschen" -#: order/views.py:1291 order/views.py:1303 +#: order/views.py:1326 order/views.py:1339 msgid "Deleted line item" msgstr "Position gelöscht" -#: order/views.py:1312 +#: order/views.py:1348 msgid "Allocate Stock to Order" msgstr "Lagerbestand dem Auftrag zuweisen" -#: order/views.py:1381 +#: order/views.py:1418 msgid "Edit Allocation Quantity" msgstr "Zuordnung bearbeiten" -#: order/views.py:1396 +#: order/views.py:1434 msgid "Remove allocation" msgstr "Zuordnung entfernen" -#: part/bom.py:138 part/templates/part/category.html:55 +#: part/bom.py:138 part/templates/part/category.html:61 #: part/templates/part/detail.html:87 msgid "Default Location" msgstr "Standard-Lagerort" -#: part/bom.py:139 part/templates/part/part_base.html:108 +#: part/bom.py:139 part/templates/part/part_base.html:118 msgid "Available Stock" msgstr "Verfügbarer Lagerbestand" @@ -2100,7 +2122,7 @@ msgid "Part Category" msgstr "Teilkategorie" #: part/models.py:76 part/templates/part/category.html:18 -#: part/templates/part/category.html:83 templates/stats.html:12 +#: part/templates/part/category.html:89 templates/stats.html:12 msgid "Part Categories" msgstr "Teile-Kategorien" @@ -2339,7 +2361,7 @@ msgstr "Notizen zum Stücklisten-Objekt" msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1612 part/views.py:1310 part/views.py:1362 +#: part/models.py:1612 part/views.py:1351 part/views.py:1403 #: stock/models.py:231 #, fuzzy #| msgid "Overage must be an integer value or a percentage" @@ -2402,25 +2424,25 @@ msgstr "Neue Stücklistenposition" msgid "Finish Editing" msgstr "Bearbeitung beenden" -#: part/templates/part/bom.html:42 +#: part/templates/part/bom.html:43 msgid "Edit BOM" msgstr "Stückliste bearbeiten" -#: part/templates/part/bom.html:44 +#: part/templates/part/bom.html:45 msgid "Validate Bill of Materials" msgstr "Stückliste validieren" -#: part/templates/part/bom.html:46 part/views.py:1597 +#: part/templates/part/bom.html:48 part/views.py:1642 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" -#: part/templates/part/bom.html:101 +#: part/templates/part/bom.html:103 #, fuzzy #| msgid "Remove selected BOM items" msgid "Delete selected BOM items?" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: part/templates/part/bom.html:102 +#: part/templates/part/bom.html:104 #, fuzzy #| msgid "Remove selected BOM items" msgid "All selected BOM items will be deleted" @@ -2510,101 +2532,113 @@ msgstr "Neues Bild hochladen" msgid "Each part must already exist in the database" msgstr "" +#: part/templates/part/build.html:8 +#, fuzzy +#| msgid "Parent Build" +msgid "Part Builds" +msgstr "Eltern-Bau" + +#: part/templates/part/build.html:14 +#, fuzzy +#| msgid "Start new Build" +msgid "Start New Build" +msgstr "Neuen Bau beginnen" + #: part/templates/part/category.html:19 msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:23 part/views.py:1976 +#: part/templates/part/category.html:24 part/views.py:2045 msgid "Create new part category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:27 +#: part/templates/part/category.html:30 #, fuzzy #| msgid "Edit Part Category" msgid "Edit part category" msgstr "Teilkategorie bearbeiten" -#: part/templates/part/category.html:30 +#: part/templates/part/category.html:35 #, fuzzy #| msgid "Select part category" msgid "Delete part category" msgstr "Teilekategorie wählen" -#: part/templates/part/category.html:39 part/templates/part/category.html:78 +#: part/templates/part/category.html:45 part/templates/part/category.html:84 msgid "Category Details" msgstr "Kategorie-Details" -#: part/templates/part/category.html:44 +#: part/templates/part/category.html:50 msgid "Category Path" msgstr "Pfad zur Kategorie" -#: part/templates/part/category.html:49 +#: part/templates/part/category.html:55 msgid "Category Description" msgstr "Kategorie-Beschreibung" -#: part/templates/part/category.html:62 part/templates/part/detail.html:64 +#: part/templates/part/category.html:68 part/templates/part/detail.html:64 msgid "Keywords" msgstr "Schlüsselwörter" -#: part/templates/part/category.html:68 +#: part/templates/part/category.html:74 msgid "Subcategories" msgstr "Unter-Kategorien" -#: part/templates/part/category.html:73 +#: part/templates/part/category.html:79 msgid "Parts (Including subcategories)" msgstr "Teile (inklusive Unter-Kategorien)" -#: part/templates/part/category.html:106 +#: part/templates/part/category.html:112 msgid "Export Part Data" msgstr "" -#: part/templates/part/category.html:107 part/views.py:491 +#: part/templates/part/category.html:114 part/views.py:513 msgid "Create new part" msgstr "Neues Teil anlegen" -#: part/templates/part/category.html:111 +#: part/templates/part/category.html:120 #, fuzzy #| msgid "Part category" msgid "Set category" msgstr "Teile-Kategorie" -#: part/templates/part/category.html:111 +#: part/templates/part/category.html:120 #, fuzzy #| msgid "Set Part Category" msgid "Set Category" msgstr "Teilkategorie auswählen" -#: part/templates/part/category.html:113 +#: part/templates/part/category.html:123 #, fuzzy #| msgid "Export" msgid "Export Data" msgstr "Exportieren" -#: part/templates/part/category.html:162 +#: part/templates/part/category.html:172 #, fuzzy #| msgid "Create New Location" msgid "Create new location" msgstr "Neuen Standort anlegen" -#: part/templates/part/category.html:167 part/templates/part/category.html:196 +#: part/templates/part/category.html:177 part/templates/part/category.html:207 #, fuzzy #| msgid "Category" msgid "New Category" msgstr "Kategorie" -#: part/templates/part/category.html:168 +#: part/templates/part/category.html:178 #, fuzzy #| msgid "Create new part category" msgid "Create new category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:197 +#: part/templates/part/category.html:208 #, fuzzy #| msgid "Create new part category" msgid "Create new Part Category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:203 stock/views.py:1314 +#: part/templates/part/category.html:214 stock/views.py:1314 msgid "Create new Stock Location" msgstr "Neuen Lager-Standort erstellen" @@ -2618,7 +2652,7 @@ msgstr "Parameter Wert" msgid "Part Details" msgstr "Teile-Details" -#: part/templates/part/detail.html:25 part/templates/part/part_base.html:85 +#: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 #: templates/js/part.html:112 msgid "IPN" msgstr "IPN (Interne Produktnummer)" @@ -2652,7 +2686,7 @@ msgstr "Kategorie" msgid "Default Supplier" msgstr "Standard-Zulieferer" -#: part/templates/part/detail.html:102 part/templates/part/params.html:22 +#: part/templates/part/detail.html:102 part/templates/part/params.html:24 msgid "Units" msgstr "Einheiten" @@ -2785,24 +2819,25 @@ msgstr "Teil bestellen" msgid "Part Parameters" msgstr "Teilparameter" -#: part/templates/part/params.html:13 +#: part/templates/part/params.html:14 msgid "Add new parameter" msgstr "Parameter hinzufügen" -#: part/templates/part/params.html:13 templates/InvenTree/settings/part.html:12 +#: part/templates/part/params.html:14 templates/InvenTree/settings/part.html:12 msgid "New Parameter" msgstr "Neuer Parameter" -#: part/templates/part/params.html:21 stock/models.py:1391 +#: part/templates/part/params.html:23 stock/models.py:1391 #: templates/js/stock.html:112 msgid "Value" msgstr "Wert" -#: part/templates/part/params.html:33 +#: part/templates/part/params.html:36 msgid "Edit" msgstr "Bearbeiten" -#: part/templates/part/params.html:34 part/templates/part/supplier.html:17 +#: part/templates/part/params.html:39 part/templates/part/supplier.html:17 +#: users/models.py:145 msgid "Delete" msgstr "Löschen" @@ -2859,47 +2894,53 @@ msgstr "" msgid "Show pricing information" msgstr "Kosteninformationen ansehen" -#: part/templates/part/part_base.html:70 +#: part/templates/part/part_base.html:60 +#, fuzzy +#| msgid "Count stock" +msgid "Count part stock" +msgstr "Bestand zählen" + +#: part/templates/part/part_base.html:75 #, fuzzy #| msgid "Source Location" msgid "Part actions" msgstr "Quell-Standort" -#: part/templates/part/part_base.html:72 +#: part/templates/part/part_base.html:78 #, fuzzy #| msgid "Duplicate Part" msgid "Duplicate part" msgstr "Teil duplizieren" -#: part/templates/part/part_base.html:73 +#: part/templates/part/part_base.html:81 #, fuzzy #| msgid "Edit Template" msgid "Edit part" msgstr "Vorlage bearbeiten" -#: part/templates/part/part_base.html:75 +#: part/templates/part/part_base.html:84 #, fuzzy #| msgid "Delete Parts" msgid "Delete part" msgstr "Teile löschen" -#: part/templates/part/part_base.html:114 templates/js/table_filters.html:65 +#: part/templates/part/part_base.html:124 templates/js/table_filters.html:65 msgid "In Stock" msgstr "Auf Lager" -#: part/templates/part/part_base.html:121 +#: part/templates/part/part_base.html:131 msgid "Allocated to Build Orders" msgstr "Zu Bauaufträgen zugeordnet" -#: part/templates/part/part_base.html:128 +#: part/templates/part/part_base.html:138 msgid "Allocated to Sales Orders" msgstr "Zu Aufträgen zugeordnet" -#: part/templates/part/part_base.html:150 +#: part/templates/part/part_base.html:160 msgid "Can Build" msgstr "Herstellbar?" -#: part/templates/part/part_base.html:156 +#: part/templates/part/part_base.html:166 msgid "Underway" msgstr "unterwegs" @@ -2923,7 +2964,7 @@ msgstr "Aus vorhandenen Bildern auswählen" msgid "Upload new image" msgstr "Neues Bild hochladen" -#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:50 +#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:53 #, fuzzy #| msgid "Price" msgid "Sale Price" @@ -2994,11 +3035,11 @@ msgstr "Varianten" msgid "BOM" msgstr "Stückliste" -#: part/templates/part/tabs.html:34 +#: part/templates/part/tabs.html:37 msgid "Used In" msgstr "Benutzt in" -#: part/templates/part/tabs.html:58 stock/templates/stock/item_base.html:282 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:282 msgid "Tests" msgstr "" @@ -3032,184 +3073,184 @@ msgstr "Neues Teil hinzufügen" msgid "New Variant" msgstr "Varianten" -#: part/views.py:76 +#: part/views.py:80 msgid "Add part attachment" msgstr "Teilanhang hinzufügen" -#: part/views.py:125 templates/attachment_table.html:30 +#: part/views.py:131 templates/attachment_table.html:30 msgid "Edit attachment" msgstr "Anhang bearbeiten" -#: part/views.py:129 +#: part/views.py:137 msgid "Part attachment updated" msgstr "Teilanhang aktualisiert" -#: part/views.py:144 +#: part/views.py:152 msgid "Delete Part Attachment" msgstr "Teilanhang löschen" -#: part/views.py:150 +#: part/views.py:160 msgid "Deleted part attachment" msgstr "Teilanhang gelöscht" -#: part/views.py:159 +#: part/views.py:169 #, fuzzy #| msgid "Create Part Parameter Template" msgid "Create Test Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:186 +#: part/views.py:198 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Template" msgstr "Vorlage bearbeiten" -#: part/views.py:200 +#: part/views.py:214 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Template" msgstr "Vorlage löschen" -#: part/views.py:207 +#: part/views.py:223 msgid "Set Part Category" msgstr "Teilkategorie auswählen" -#: part/views.py:255 +#: part/views.py:273 #, python-brace-format msgid "Set category for {n} parts" msgstr "Kategorie für {n} Teile setzen" -#: part/views.py:290 +#: part/views.py:308 msgid "Create Variant" msgstr "Variante anlegen" -#: part/views.py:368 +#: part/views.py:388 msgid "Duplicate Part" msgstr "Teil duplizieren" -#: part/views.py:373 +#: part/views.py:395 msgid "Copied part" msgstr "Teil kopiert" -#: part/views.py:496 +#: part/views.py:520 msgid "Created new part" msgstr "Neues Teil angelegt" -#: part/views.py:707 +#: part/views.py:735 msgid "Part QR Code" msgstr "Teil-QR-Code" -#: part/views.py:724 +#: part/views.py:754 msgid "Upload Part Image" msgstr "Teilbild hochladen" -#: part/views.py:729 part/views.py:764 +#: part/views.py:762 part/views.py:799 msgid "Updated part image" msgstr "Teilbild aktualisiert" -#: part/views.py:738 +#: part/views.py:771 msgid "Select Part Image" msgstr "Teilbild auswählen" -#: part/views.py:767 +#: part/views.py:802 msgid "Part image not found" msgstr "Teilbild nicht gefunden" -#: part/views.py:778 +#: part/views.py:813 msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:800 +#: part/views.py:837 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:963 +#: part/views.py:1004 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1313 +#: part/views.py:1354 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1338 part/views.py:1341 +#: part/views.py:1379 part/views.py:1382 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1347 +#: part/views.py:1388 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1385 +#: part/views.py:1426 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1391 +#: part/views.py:1432 #, fuzzy #| msgid "Select part to be used in BOM" msgid "Selected part creates a circular BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/views.py:1395 +#: part/views.py:1436 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1645 +#: part/views.py:1692 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1652 +#: part/views.py:1701 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1661 +#: part/views.py:1710 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1783 +#: part/views.py:1836 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1791 +#: part/views.py:1846 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1798 +#: part/views.py:1855 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1806 +#: part/views.py:1865 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:1856 +#: part/views.py:1917 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:1870 +#: part/views.py:1933 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:1927 +#: part/views.py:1992 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:1962 +#: part/views.py:2029 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:1968 +#: part/views.py:2037 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2027 +#: part/views.py:2100 msgid "Create BOM item" msgstr "BOM-Position anlegen" -#: part/views.py:2093 +#: part/views.py:2168 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2141 +#: part/views.py:2218 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -3653,15 +3694,15 @@ msgid "Stock adjustment actions" msgstr "Bestands-Anpassung bestätigen" #: stock/templates/stock/item_base.html:98 -#: stock/templates/stock/location.html:38 templates/stock_table.html:15 +#: stock/templates/stock/location.html:38 templates/stock_table.html:19 msgid "Count stock" msgstr "Bestand zählen" -#: stock/templates/stock/item_base.html:99 templates/stock_table.html:13 +#: stock/templates/stock/item_base.html:99 templates/stock_table.html:17 msgid "Add stock" msgstr "Bestand hinzufügen" -#: stock/templates/stock/item_base.html:100 templates/stock_table.html:14 +#: stock/templates/stock/item_base.html:100 templates/stock_table.html:18 msgid "Remove stock" msgstr "Bestand entfernen" @@ -4191,6 +4232,14 @@ msgstr "Lagerbestands-Tracking-Eintrag bearbeiten" msgid "Add Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag hinzufügen" +#: templates/403.html:5 templates/403.html:11 +msgid "Permission Denied" +msgstr "" + +#: templates/403.html:14 +msgid "You do not have permission to view this page." +msgstr "" + #: templates/InvenTree/bom_invalid.html:7 msgid "BOM Waiting Validation" msgstr "" @@ -4201,6 +4250,10 @@ msgstr "" msgid "Pending Builds" msgstr "Eltern-Bau" +#: templates/InvenTree/index.html:4 +msgid "Index" +msgstr "" + #: templates/InvenTree/latest_parts.html:7 #, fuzzy #| msgid "Parent Part" @@ -4884,39 +4937,39 @@ msgstr "Favorit" msgid "Purchasable" msgstr "Käuflich" -#: templates/navbar.html:22 +#: templates/navbar.html:29 msgid "Buy" msgstr "Kaufen" -#: templates/navbar.html:30 +#: templates/navbar.html:39 msgid "Sell" msgstr "Verkaufen" -#: templates/navbar.html:40 +#: templates/navbar.html:50 msgid "Scan Barcode" msgstr "" -#: templates/navbar.html:49 +#: templates/navbar.html:59 users/models.py:27 msgid "Admin" msgstr "Admin" -#: templates/navbar.html:52 +#: templates/navbar.html:62 msgid "Settings" msgstr "Einstellungen" -#: templates/navbar.html:53 +#: templates/navbar.html:63 msgid "Logout" msgstr "Ausloggen" -#: templates/navbar.html:55 +#: templates/navbar.html:65 msgid "Login" msgstr "Einloggen" -#: templates/navbar.html:58 +#: templates/navbar.html:68 msgid "About InvenTree" msgstr "Über InvenBaum" -#: templates/navbar.html:59 +#: templates/navbar.html:69 msgid "Statistics" msgstr "Statistiken" @@ -4930,54 +4983,132 @@ msgstr "Suche" msgid "Export Stock Information" msgstr "Lagerobjekt-Standort bearbeiten" -#: templates/stock_table.html:13 +#: templates/stock_table.html:17 #, fuzzy #| msgid "Added stock to {n} items" msgid "Add to selected stock items" msgstr "Vorrat zu {n} Lagerobjekten hinzugefügt" -#: templates/stock_table.html:14 +#: templates/stock_table.html:18 #, fuzzy #| msgid "Remove selected BOM items" msgid "Remove from selected stock items" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: templates/stock_table.html:15 +#: templates/stock_table.html:19 #, fuzzy #| msgid "Delete Stock Item" msgid "Stocktake selected stock items" msgstr "Lagerobjekt löschen" -#: templates/stock_table.html:16 +#: templates/stock_table.html:20 #, fuzzy #| msgid "Delete Stock Item" msgid "Move selected stock items" msgstr "Lagerobjekt löschen" -#: templates/stock_table.html:16 +#: templates/stock_table.html:20 msgid "Move stock" msgstr "Bestand bewegen" -#: templates/stock_table.html:17 +#: templates/stock_table.html:21 #, fuzzy #| msgid "Remove selected BOM items" msgid "Order selected items" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: templates/stock_table.html:17 +#: templates/stock_table.html:21 msgid "Order stock" msgstr "Bestand bestellen" -#: templates/stock_table.html:18 +#: templates/stock_table.html:24 #, fuzzy #| msgid "Delete line item" msgid "Delete selected items" msgstr "Position löschen" -#: templates/stock_table.html:18 +#: templates/stock_table.html:24 msgid "Delete Stock" msgstr "Bestand löschen" +#: users/admin.py:61 +#, fuzzy +#| msgid "User" +msgid "Users" +msgstr "Benutzer" + +#: users/admin.py:62 +msgid "Select which users are assigned to this group" +msgstr "" + +#: users/admin.py:120 +#, fuzzy +#| msgid "External Link" +msgid "Personal info" +msgstr "Externer Link" + +#: users/admin.py:121 +#, fuzzy +#| msgid "Revision" +msgid "Permissions" +msgstr "Revision" + +#: users/admin.py:124 +#, fuzzy +#| msgid "Import BOM data" +msgid "Important dates" +msgstr "Stückliste importieren" + +#: users/models.py:128 +msgid "Permission set" +msgstr "" + +#: users/models.py:136 +msgid "Group" +msgstr "" + +#: users/models.py:139 +msgid "View" +msgstr "" + +#: users/models.py:139 +msgid "Permission to view items" +msgstr "" + +#: users/models.py:141 +#, fuzzy +#| msgid "Address" +msgid "Add" +msgstr "Adresse" + +#: users/models.py:141 +msgid "Permission to add items" +msgstr "" + +#: users/models.py:143 +msgid "Change" +msgstr "" + +#: users/models.py:143 +msgid "Permissions to edit items" +msgstr "" + +#: users/models.py:145 +#, fuzzy +#| msgid "Remove selected BOM items" +msgid "Permission to delete items" +msgstr "Ausgewählte Stücklistenpositionen entfernen" + +#, fuzzy +#~| msgid "Created" +#~ msgid "Create" +#~ msgstr "Erstellt" + +#, fuzzy +#~| msgid "Last Updated" +#~ msgid "Update" +#~ msgstr "Zuletzt aktualisiert" + #~ msgid "Belongs To" #~ msgstr "Gehört zu" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index 0b5425db6e..74b435c791 100644 --- a/InvenTree/locale/en/LC_MESSAGES/django.po +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-04 14:02+0000\n" +"POT-Creation-Date: 2020-10-06 09:31+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,11 +18,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: InvenTree/api.py:83 +#: InvenTree/api.py:85 msgid "No action specified" msgstr "" -#: InvenTree/api.py:97 +#: InvenTree/api.py:99 msgid "No matching action found" msgstr "" @@ -46,34 +46,34 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:337 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:339 order/models.py:187 order/models.py:261 msgid "Invalid quantity provided" msgstr "" -#: InvenTree/helpers.py:340 +#: InvenTree/helpers.py:342 msgid "Empty serial number string" msgstr "" -#: InvenTree/helpers.py:361 +#: InvenTree/helpers.py:363 #, python-brace-format msgid "Duplicate serial: {n}" msgstr "" -#: InvenTree/helpers.py:365 InvenTree/helpers.py:368 InvenTree/helpers.py:371 +#: InvenTree/helpers.py:367 InvenTree/helpers.py:370 InvenTree/helpers.py:373 #, python-brace-format msgid "Invalid group: {g}" msgstr "" -#: InvenTree/helpers.py:376 +#: InvenTree/helpers.py:378 #, python-brace-format msgid "Duplicate serial: {g}" msgstr "" -#: InvenTree/helpers.py:384 +#: InvenTree/helpers.py:386 msgid "No serial numbers found" msgstr "" -#: InvenTree/helpers.py:388 +#: InvenTree/helpers.py:390 #, python-brace-format msgid "Number of unique serial number ({s}) must match quantity ({q})" msgstr "" @@ -90,7 +90,7 @@ msgstr "" msgid "User" msgstr "" -#: InvenTree/models.py:106 part/templates/part/params.html:20 +#: InvenTree/models.py:106 part/templates/part/params.html:22 #: templates/js/part.html:81 msgid "Name" msgstr "" @@ -99,19 +99,19 @@ msgstr "" msgid "Description (optional)" msgstr "" -#: InvenTree/settings.py:342 +#: InvenTree/settings.py:348 msgid "English" msgstr "" -#: InvenTree/settings.py:343 +#: InvenTree/settings.py:349 msgid "German" msgstr "" -#: InvenTree/settings.py:344 +#: InvenTree/settings.py:350 msgid "French" msgstr "" -#: InvenTree/settings.py:345 +#: InvenTree/settings.py:351 msgid "Polish" msgstr "" @@ -144,7 +144,7 @@ msgid "Returned" msgstr "" #: InvenTree/status_codes.py:136 -#: order/templates/order/sales_order_base.html:103 +#: order/templates/order/sales_order_base.html:105 msgid "Shipped" msgstr "" @@ -199,7 +199,7 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:661 +#: InvenTree/views.py:703 msgid "Database Statistics" msgstr "" @@ -263,7 +263,7 @@ msgstr "" msgid "Build quantity must be integer value for trackable parts" msgstr "" -#: build/models.py:73 build/templates/build/build_base.html:70 +#: build/models.py:73 build/templates/build/build_base.html:72 msgid "Build Title" msgstr "" @@ -271,7 +271,7 @@ msgstr "" msgid "Brief description of the build" msgstr "" -#: build/models.py:84 build/templates/build/build_base.html:91 +#: build/models.py:84 build/templates/build/build_base.html:93 msgid "Parent Build" msgstr "" @@ -281,7 +281,7 @@ msgstr "" #: build/models.py:90 build/templates/build/allocate.html:329 #: build/templates/build/auto_allocate.html:19 -#: build/templates/build/build_base.html:75 +#: build/templates/build/build_base.html:77 #: build/templates/build/detail.html:22 order/models.py:501 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:147 @@ -325,7 +325,7 @@ msgstr "" msgid "Number of parts to build" msgstr "" -#: build/models.py:128 part/templates/part/part_base.html:145 +#: build/models.py:128 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "" @@ -344,7 +344,7 @@ msgstr "" #: build/models.py:155 build/templates/build/detail.html:55 #: company/templates/company/supplier_part_base.html:60 #: company/templates/company/supplier_part_detail.html:24 -#: part/templates/part/detail.html:80 part/templates/part/part_base.html:92 +#: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 #: stock/models.py:381 stock/templates/stock/item_base.html:244 msgid "External Link" msgstr "" @@ -356,7 +356,7 @@ msgstr "" #: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15 #: order/templates/order/purchase_order_detail.html:202 -#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:67 +#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/models.py:1404 stock/templates/stock/tabs.html:26 #: templates/js/barcode.html:391 templates/js/bom.html:223 @@ -403,8 +403,8 @@ msgid "Stock quantity to allocate to build" msgstr "" #: build/templates/build/allocate.html:17 -#: company/templates/company/detail_part.html:18 order/views.py:779 -#: part/templates/part/category.html:112 +#: company/templates/company/detail_part.html:18 order/views.py:804 +#: part/templates/part/category.html:122 msgid "Order Parts" msgstr "" @@ -420,7 +420,7 @@ msgstr "" msgid "Unallocate" msgstr "" -#: build/templates/build/allocate.html:87 templates/stock_table.html:9 +#: build/templates/build/allocate.html:87 templates/stock_table.html:10 msgid "New Stock Item" msgstr "" @@ -437,7 +437,7 @@ msgstr "" #: build/templates/build/allocate.html:172 #: build/templates/build/auto_allocate.html:20 -#: build/templates/build/build_base.html:80 +#: build/templates/build/build_base.html:82 #: build/templates/build/detail.html:27 #: company/templates/company/supplier_part_pricing.html:71 #: order/templates/order/order_wizard/select_parts.html:32 @@ -548,7 +548,7 @@ msgstr "" #: build/templates/build/build_base.html:34 #: build/templates/build/complete.html:6 #: stock/templates/stock/item_base.html:223 templates/js/build.html:39 -#: templates/navbar.html:20 +#: templates/navbar.html:25 msgid "Build" msgstr "" @@ -570,11 +570,27 @@ msgstr "" msgid "Admin view" msgstr "" -#: build/templates/build/build_base.html:66 build/templates/build/detail.html:9 +#: build/templates/build/build_base.html:45 +msgid "Edit Build" +msgstr "" + +#: build/templates/build/build_base.html:49 build/views.py:190 +msgid "Complete Build" +msgstr "" + +#: build/templates/build/build_base.html:52 build/views.py:58 +msgid "Cancel Build" +msgstr "" + +#: build/templates/build/build_base.html:58 build/views.py:454 +msgid "Delete Build" +msgstr "" + +#: build/templates/build/build_base.html:68 build/templates/build/detail.html:9 msgid "Build Details" msgstr "" -#: build/templates/build/build_base.html:85 +#: build/templates/build/build_base.html:87 #: build/templates/build/detail.html:42 #: order/templates/order/receive_parts.html:24 #: stock/templates/stock/item_base.html:276 templates/InvenTree/search.html:175 @@ -584,7 +600,7 @@ msgstr "" msgid "Status" msgstr "" -#: build/templates/build/build_base.html:98 order/models.py:499 +#: build/templates/build/build_base.html:100 order/models.py:499 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 @@ -594,15 +610,15 @@ msgstr "" msgid "Sales Order" msgstr "" -#: build/templates/build/build_base.html:104 +#: build/templates/build/build_base.html:106 msgid "BOM Price" msgstr "" -#: build/templates/build/build_base.html:109 +#: build/templates/build/build_base.html:111 msgid "BOM pricing is incomplete" msgstr "" -#: build/templates/build/build_base.html:112 +#: build/templates/build/build_base.html:114 msgid "No pricing information" msgstr "" @@ -664,8 +680,8 @@ msgid "Batch" msgstr "" #: build/templates/build/detail.html:61 -#: order/templates/order/order_base.html:98 -#: order/templates/order/sales_order_base.html:97 templates/js/build.html:71 +#: order/templates/order/order_base.html:100 +#: order/templates/order/sales_order_base.html:99 templates/js/build.html:71 msgid "Created" msgstr "" @@ -687,7 +703,7 @@ msgstr "" #: build/templates/build/index.html:6 build/templates/build/index.html:14 #: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9 -#: part/templates/part/tabs.html:30 +#: part/templates/part/tabs.html:31 users/models.py:30 msgid "Build Orders" msgstr "" @@ -707,9 +723,9 @@ msgid "Save" msgstr "" #: build/templates/build/notes.html:33 company/templates/company/notes.html:30 -#: order/templates/order/order_notes.html:32 +#: order/templates/order/order_notes.html:33 #: order/templates/order/sales_order_notes.html:37 -#: part/templates/part/notes.html:32 stock/templates/stock/item_notes.html:33 +#: part/templates/part/notes.html:33 stock/templates/stock/item_notes.html:33 msgid "Edit notes" msgstr "" @@ -726,100 +742,88 @@ msgstr "" msgid "Are you sure you wish to unallocate all stock for this build?" msgstr "" -#: build/views.py:56 -msgid "Cancel Build" -msgstr "" - -#: build/views.py:74 +#: build/views.py:77 msgid "Confirm build cancellation" msgstr "" -#: build/views.py:79 +#: build/views.py:82 msgid "Build was cancelled" msgstr "" -#: build/views.py:95 +#: build/views.py:98 msgid "Allocate Stock" msgstr "" -#: build/views.py:108 +#: build/views.py:112 msgid "No matching build found" msgstr "" -#: build/views.py:127 +#: build/views.py:131 msgid "Confirm stock allocation" msgstr "" -#: build/views.py:128 +#: build/views.py:132 msgid "Check the confirmation box at the bottom of the list" msgstr "" -#: build/views.py:148 build/views.py:452 +#: build/views.py:152 build/views.py:465 msgid "Unallocate Stock" msgstr "" -#: build/views.py:161 +#: build/views.py:166 msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:162 stock/views.py:405 +#: build/views.py:167 stock/views.py:405 msgid "Check the confirmation box" msgstr "" -#: build/views.py:185 -msgid "Complete Build" -msgstr "" - -#: build/views.py:264 +#: build/views.py:270 msgid "Confirm completion of build" msgstr "" -#: build/views.py:271 +#: build/views.py:277 msgid "Invalid location selected" msgstr "" -#: build/views.py:296 stock/views.py:1621 +#: build/views.py:302 stock/views.py:1621 #, python-brace-format msgid "The following serial numbers already exist: ({sn})" msgstr "" -#: build/views.py:317 +#: build/views.py:323 msgid "Build marked as COMPLETE" msgstr "" -#: build/views.py:393 +#: build/views.py:403 msgid "Start new Build" msgstr "" -#: build/views.py:418 +#: build/views.py:429 msgid "Created new build" msgstr "" -#: build/views.py:428 +#: build/views.py:439 msgid "Edit Build Details" msgstr "" -#: build/views.py:433 +#: build/views.py:445 msgid "Edited build" msgstr "" -#: build/views.py:442 -msgid "Delete Build" -msgstr "" - -#: build/views.py:457 +#: build/views.py:471 msgid "Removed parts from build allocation" msgstr "" -#: build/views.py:467 +#: build/views.py:481 msgid "Allocate new Part" msgstr "" -#: build/views.py:620 +#: build/views.py:635 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:624 +#: build/views.py:640 msgid "Updated Build Item" msgstr "" @@ -1014,7 +1018,7 @@ msgstr "" #: company/templates/company/detail.html:21 #: company/templates/company/supplier_part_base.html:66 #: company/templates/company/supplier_part_detail.html:21 -#: order/templates/order/order_base.html:79 +#: order/templates/order/order_base.html:81 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 #: stock/templates/stock/item_base.html:251 templates/js/company.html:48 #: templates/js/company.html:162 templates/js/order.html:146 @@ -1022,7 +1026,7 @@ msgid "Supplier" msgstr "" #: company/templates/company/detail.html:26 -#: order/templates/order/sales_order_base.html:78 stock/models.py:370 +#: order/templates/order/sales_order_base.html:80 stock/models.py:370 #: stock/models.py:371 stock/templates/stock/item_base.html:169 #: templates/js/company.html:40 templates/js/order.html:221 msgid "Customer" @@ -1044,13 +1048,13 @@ msgid "New Supplier Part" msgstr "" #: company/templates/company/detail_part.html:15 -#: part/templates/part/category.html:109 part/templates/part/supplier.html:15 -#: templates/stock_table.html:11 +#: part/templates/part/category.html:117 part/templates/part/supplier.html:15 +#: templates/stock_table.html:14 msgid "Options" msgstr "" #: company/templates/company/detail_part.html:18 -#: part/templates/part/category.html:112 +#: part/templates/part/category.html:122 msgid "Order parts" msgstr "" @@ -1063,7 +1067,7 @@ msgid "Delete Parts" msgstr "" #: company/templates/company/detail_part.html:43 -#: part/templates/part/category.html:107 templates/js/stock.html:791 +#: part/templates/part/category.html:114 templates/js/stock.html:791 msgid "New Part" msgstr "" @@ -1095,7 +1099,7 @@ msgstr "" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/category.html:106 part/templates/part/category.html:113 +#: part/templates/part/category.html:112 part/templates/part/category.html:123 #: part/templates/part/stock.html:51 templates/stock_table.html:6 msgid "Export" msgstr "" @@ -1117,18 +1121,18 @@ msgstr "" #: company/templates/company/tabs.html:17 #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 -#: part/templates/part/orders.html:9 part/templates/part/tabs.html:45 -#: templates/navbar.html:26 +#: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 +#: templates/navbar.html:33 users/models.py:31 msgid "Purchase Orders" msgstr "" #: company/templates/company/purchase_orders.html:14 -#: order/templates/order/purchase_orders.html:17 +#: order/templates/order/purchase_orders.html:18 msgid "Create new purchase order" msgstr "" #: company/templates/company/purchase_orders.html:14 -#: order/templates/order/purchase_orders.html:17 +#: order/templates/order/purchase_orders.html:18 msgid "New Purchase Order" msgstr "" @@ -1136,18 +1140,18 @@ msgstr "" #: company/templates/company/tabs.html:22 #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 -#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:53 -#: templates/navbar.html:33 +#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 +#: templates/navbar.html:42 users/models.py:32 msgid "Sales Orders" msgstr "" #: company/templates/company/sales_orders.html:14 -#: order/templates/order/sales_orders.html:17 +#: order/templates/order/sales_orders.html:18 msgid "Create new sales order" msgstr "" #: company/templates/company/sales_orders.html:14 -#: order/templates/order/sales_orders.html:17 +#: order/templates/order/sales_orders.html:18 msgid "New Sales Order" msgstr "" @@ -1158,7 +1162,7 @@ msgid "Supplier Part" msgstr "" #: company/templates/company/supplier_part_base.html:23 -#: part/templates/part/orders.html:14 +#: part/templates/part/orders.html:14 part/templates/part/part_base.html:66 msgid "Order part" msgstr "" @@ -1205,7 +1209,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:15 company/views.py:399 -#: part/templates/part/sale_prices.html:13 part/views.py:2149 +#: part/templates/part/sale_prices.html:13 part/views.py:2228 msgid "Add Price Break" msgstr "" @@ -1241,7 +1245,7 @@ msgstr "" #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: templates/js/part.html:124 templates/js/part.html:372 -#: templates/js/stock.html:452 templates/navbar.html:19 +#: templates/js/stock.html:452 templates/navbar.html:22 users/models.py:29 msgid "Stock" msgstr "" @@ -1251,22 +1255,22 @@ msgstr "" #: company/templates/company/tabs.html:9 #: order/templates/order/receive_parts.html:14 part/models.py:294 -#: part/templates/part/cat_link.html:7 part/templates/part/category.html:88 -#: part/templates/part/category_tabs.html:6 templates/navbar.html:18 -#: templates/stats.html:8 templates/stats.html:17 +#: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 +#: part/templates/part/category_tabs.html:6 templates/navbar.html:19 +#: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "" -#: company/views.py:50 part/templates/part/tabs.html:39 -#: templates/navbar.html:24 +#: company/views.py:50 part/templates/part/tabs.html:42 +#: templates/navbar.html:31 msgid "Suppliers" msgstr "" -#: company/views.py:57 templates/navbar.html:25 +#: company/views.py:57 templates/navbar.html:32 msgid "Manufacturers" msgstr "" -#: company/views.py:64 templates/navbar.html:32 +#: company/views.py:64 templates/navbar.html:41 msgid "Customers" msgstr "" @@ -1330,15 +1334,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:404 part/views.py:2153 +#: company/views.py:404 part/views.py:2234 msgid "Added new price break" msgstr "" -#: company/views.py:441 part/views.py:2198 +#: company/views.py:441 part/views.py:2279 msgid "Edit Price Break" msgstr "" -#: company/views.py:456 part/views.py:2212 +#: company/views.py:456 part/views.py:2295 msgid "Delete Price Break" msgstr "" @@ -1366,20 +1370,20 @@ msgstr "" msgid "Enabled" msgstr "" -#: order/forms.py:24 +#: order/forms.py:24 order/templates/order/order_base.html:40 msgid "Place order" msgstr "" -#: order/forms.py:35 +#: order/forms.py:35 order/templates/order/order_base.html:47 msgid "Mark order as complete" msgstr "" -#: order/forms.py:46 order/forms.py:57 -#: order/templates/order/sales_order_base.html:54 +#: order/forms.py:46 order/forms.py:57 order/templates/order/order_base.html:52 +#: order/templates/order/sales_order_base.html:52 msgid "Cancel order" msgstr "" -#: order/forms.py:68 order/templates/order/sales_order_base.html:51 +#: order/forms.py:68 order/templates/order/sales_order_base.html:49 msgid "Ship order" msgstr "" @@ -1431,7 +1435,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1304 +#: order/models.py:185 order/models.py:259 part/views.py:1345 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "" @@ -1512,32 +1516,44 @@ msgstr "" msgid "Are you sure you want to delete this attachment?" msgstr "" -#: order/templates/order/order_base.html:64 +#: order/templates/order/order_base.html:36 +msgid "Edit order information" +msgstr "" + +#: order/templates/order/order_base.html:44 +msgid "Receive items" +msgstr "" + +#: order/templates/order/order_base.html:57 +msgid "Export order to file" +msgstr "" + +#: order/templates/order/order_base.html:66 msgid "Purchase Order Details" msgstr "" -#: order/templates/order/order_base.html:69 -#: order/templates/order/sales_order_base.html:68 +#: order/templates/order/order_base.html:71 +#: order/templates/order/sales_order_base.html:70 msgid "Order Reference" msgstr "" -#: order/templates/order/order_base.html:74 -#: order/templates/order/sales_order_base.html:73 +#: order/templates/order/order_base.html:76 +#: order/templates/order/sales_order_base.html:75 msgid "Order Status" msgstr "" -#: order/templates/order/order_base.html:85 templates/js/order.html:153 +#: order/templates/order/order_base.html:87 templates/js/order.html:153 msgid "Supplier Reference" msgstr "" -#: order/templates/order/order_base.html:104 +#: order/templates/order/order_base.html:106 msgid "Issued" msgstr "" -#: order/templates/order/order_base.html:111 +#: order/templates/order/order_base.html:113 #: order/templates/order/purchase_order_detail.html:182 #: order/templates/order/receive_parts.html:22 -#: order/templates/order/sales_order_base.html:110 +#: order/templates/order/sales_order_base.html:112 msgid "Received" msgstr "" @@ -1600,13 +1616,13 @@ msgid "Purchase Order Attachments" msgstr "" #: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16 -#: part/templates/part/tabs.html:64 stock/templates/stock/tabs.html:32 +#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 msgid "Attachments" msgstr "" #: order/templates/order/purchase_order_detail.html:16 -#: order/templates/order/sales_order_detail.html:17 order/views.py:1087 -#: order/views.py:1201 +#: order/templates/order/sales_order_detail.html:17 order/views.py:1117 +#: order/views.py:1232 msgid "Add Line Item" msgstr "" @@ -1616,7 +1632,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:38 #: order/templates/order/purchase_order_detail.html:118 -#: part/templates/part/category.html:161 part/templates/part/category.html:202 +#: part/templates/part/category.html:171 part/templates/part/category.html:213 #: templates/js/stock.html:803 msgid "New Location" msgstr "" @@ -1658,7 +1674,7 @@ msgid "Select parts to receive against this order" msgstr "" #: order/templates/order/receive_parts.html:21 -#: part/templates/part/part_base.html:135 templates/js/part.html:388 +#: part/templates/part/part_base.html:145 templates/js/part.html:388 msgid "On Order" msgstr "" @@ -1674,15 +1690,15 @@ msgstr "" msgid "This SalesOrder has not been fully allocated" msgstr "" -#: order/templates/order/sales_order_base.html:47 +#: order/templates/order/sales_order_base.html:57 msgid "Packing List" msgstr "" -#: order/templates/order/sales_order_base.html:63 +#: order/templates/order/sales_order_base.html:65 msgid "Sales Order Details" msgstr "" -#: order/templates/order/sales_order_base.html:84 templates/js/order.html:228 +#: order/templates/order/sales_order_base.html:86 templates/js/order.html:228 msgid "Customer Reference" msgstr "" @@ -1746,156 +1762,156 @@ msgstr "" msgid "Order Items" msgstr "" -#: order/views.py:93 +#: order/views.py:99 msgid "Add Purchase Order Attachment" msgstr "" -#: order/views.py:102 order/views.py:149 part/views.py:86 stock/views.py:167 +#: order/views.py:109 order/views.py:157 part/views.py:92 stock/views.py:167 msgid "Added attachment" msgstr "" -#: order/views.py:141 +#: order/views.py:148 msgid "Add Sales Order Attachment" msgstr "" -#: order/views.py:176 order/views.py:197 +#: order/views.py:184 order/views.py:206 msgid "Edit Attachment" msgstr "" -#: order/views.py:180 order/views.py:201 +#: order/views.py:189 order/views.py:211 msgid "Attachment updated" msgstr "" -#: order/views.py:216 order/views.py:230 +#: order/views.py:226 order/views.py:241 msgid "Delete Attachment" msgstr "" -#: order/views.py:222 order/views.py:236 stock/views.py:223 +#: order/views.py:233 order/views.py:248 stock/views.py:223 msgid "Deleted attachment" msgstr "" -#: order/views.py:287 +#: order/views.py:301 msgid "Create Purchase Order" msgstr "" -#: order/views.py:318 +#: order/views.py:333 msgid "Create Sales Order" msgstr "" -#: order/views.py:348 +#: order/views.py:364 msgid "Edit Purchase Order" msgstr "" -#: order/views.py:368 +#: order/views.py:385 msgid "Edit Sales Order" msgstr "" -#: order/views.py:384 +#: order/views.py:402 msgid "Cancel Order" msgstr "" -#: order/views.py:399 order/views.py:431 +#: order/views.py:418 order/views.py:451 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:417 +#: order/views.py:436 msgid "Cancel sales order" msgstr "" -#: order/views.py:437 +#: order/views.py:457 msgid "Could not cancel order" msgstr "" -#: order/views.py:451 +#: order/views.py:471 msgid "Issue Order" msgstr "" -#: order/views.py:466 +#: order/views.py:487 msgid "Confirm order placement" msgstr "" -#: order/views.py:487 +#: order/views.py:508 msgid "Complete Order" msgstr "" -#: order/views.py:522 +#: order/views.py:544 msgid "Ship Order" msgstr "" -#: order/views.py:538 +#: order/views.py:561 msgid "Confirm order shipment" msgstr "" -#: order/views.py:544 +#: order/views.py:567 msgid "Could not ship order" msgstr "" -#: order/views.py:595 +#: order/views.py:619 msgid "Receive Parts" msgstr "" -#: order/views.py:662 +#: order/views.py:687 msgid "Items received" msgstr "" -#: order/views.py:676 +#: order/views.py:701 msgid "No destination set" msgstr "" -#: order/views.py:721 +#: order/views.py:746 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:727 +#: order/views.py:752 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:733 +#: order/views.py:758 msgid "No lines specified" msgstr "" -#: order/views.py:1107 +#: order/views.py:1138 msgid "Invalid Purchase Order" msgstr "" -#: order/views.py:1115 +#: order/views.py:1146 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1120 +#: order/views.py:1151 msgid "Invalid SupplierPart selection" msgstr "" -#: order/views.py:1252 order/views.py:1270 +#: order/views.py:1284 order/views.py:1303 msgid "Edit Line Item" msgstr "" -#: order/views.py:1286 order/views.py:1298 +#: order/views.py:1320 order/views.py:1333 msgid "Delete Line Item" msgstr "" -#: order/views.py:1291 order/views.py:1303 +#: order/views.py:1326 order/views.py:1339 msgid "Deleted line item" msgstr "" -#: order/views.py:1312 +#: order/views.py:1348 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1381 +#: order/views.py:1418 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1396 +#: order/views.py:1434 msgid "Remove allocation" msgstr "" -#: part/bom.py:138 part/templates/part/category.html:55 +#: part/bom.py:138 part/templates/part/category.html:61 #: part/templates/part/detail.html:87 msgid "Default Location" msgstr "" -#: part/bom.py:139 part/templates/part/part_base.html:108 +#: part/bom.py:139 part/templates/part/part_base.html:118 msgid "Available Stock" msgstr "" @@ -2013,7 +2029,7 @@ msgid "Part Category" msgstr "" #: part/models.py:76 part/templates/part/category.html:18 -#: part/templates/part/category.html:83 templates/stats.html:12 +#: part/templates/part/category.html:89 templates/stats.html:12 msgid "Part Categories" msgstr "" @@ -2226,7 +2242,7 @@ msgstr "" msgid "BOM line checksum" msgstr "" -#: part/models.py:1612 part/views.py:1310 part/views.py:1362 +#: part/models.py:1612 part/views.py:1351 part/views.py:1403 #: stock/models.py:231 msgid "Quantity must be integer value for trackable parts" msgstr "" @@ -2285,23 +2301,23 @@ msgstr "" msgid "Finish Editing" msgstr "" -#: part/templates/part/bom.html:42 +#: part/templates/part/bom.html:43 msgid "Edit BOM" msgstr "" -#: part/templates/part/bom.html:44 +#: part/templates/part/bom.html:45 msgid "Validate Bill of Materials" msgstr "" -#: part/templates/part/bom.html:46 part/views.py:1597 +#: part/templates/part/bom.html:48 part/views.py:1642 msgid "Export Bill of Materials" msgstr "" -#: part/templates/part/bom.html:101 +#: part/templates/part/bom.html:103 msgid "Delete selected BOM items?" msgstr "" -#: part/templates/part/bom.html:102 +#: part/templates/part/bom.html:104 msgid "All selected BOM items will be deleted" msgstr "" @@ -2373,83 +2389,91 @@ msgstr "" msgid "Each part must already exist in the database" msgstr "" +#: part/templates/part/build.html:8 +msgid "Part Builds" +msgstr "" + +#: part/templates/part/build.html:14 +msgid "Start New Build" +msgstr "" + #: part/templates/part/category.html:19 msgid "All parts" msgstr "" -#: part/templates/part/category.html:23 part/views.py:1976 +#: part/templates/part/category.html:24 part/views.py:2045 msgid "Create new part category" msgstr "" -#: part/templates/part/category.html:27 +#: part/templates/part/category.html:30 msgid "Edit part category" msgstr "" -#: part/templates/part/category.html:30 +#: part/templates/part/category.html:35 msgid "Delete part category" msgstr "" -#: part/templates/part/category.html:39 part/templates/part/category.html:78 +#: part/templates/part/category.html:45 part/templates/part/category.html:84 msgid "Category Details" msgstr "" -#: part/templates/part/category.html:44 +#: part/templates/part/category.html:50 msgid "Category Path" msgstr "" -#: part/templates/part/category.html:49 +#: part/templates/part/category.html:55 msgid "Category Description" msgstr "" -#: part/templates/part/category.html:62 part/templates/part/detail.html:64 +#: part/templates/part/category.html:68 part/templates/part/detail.html:64 msgid "Keywords" msgstr "" -#: part/templates/part/category.html:68 +#: part/templates/part/category.html:74 msgid "Subcategories" msgstr "" -#: part/templates/part/category.html:73 +#: part/templates/part/category.html:79 msgid "Parts (Including subcategories)" msgstr "" -#: part/templates/part/category.html:106 +#: part/templates/part/category.html:112 msgid "Export Part Data" msgstr "" -#: part/templates/part/category.html:107 part/views.py:491 +#: part/templates/part/category.html:114 part/views.py:513 msgid "Create new part" msgstr "" -#: part/templates/part/category.html:111 +#: part/templates/part/category.html:120 msgid "Set category" msgstr "" -#: part/templates/part/category.html:111 +#: part/templates/part/category.html:120 msgid "Set Category" msgstr "" -#: part/templates/part/category.html:113 +#: part/templates/part/category.html:123 msgid "Export Data" msgstr "" -#: part/templates/part/category.html:162 +#: part/templates/part/category.html:172 msgid "Create new location" msgstr "" -#: part/templates/part/category.html:167 part/templates/part/category.html:196 +#: part/templates/part/category.html:177 part/templates/part/category.html:207 msgid "New Category" msgstr "" -#: part/templates/part/category.html:168 +#: part/templates/part/category.html:178 msgid "Create new category" msgstr "" -#: part/templates/part/category.html:197 +#: part/templates/part/category.html:208 msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:203 stock/views.py:1314 +#: part/templates/part/category.html:214 stock/views.py:1314 msgid "Create new Stock Location" msgstr "" @@ -2461,7 +2485,7 @@ msgstr "" msgid "Part Details" msgstr "" -#: part/templates/part/detail.html:25 part/templates/part/part_base.html:85 +#: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 #: templates/js/part.html:112 msgid "IPN" msgstr "" @@ -2491,7 +2515,7 @@ msgstr "" msgid "Default Supplier" msgstr "" -#: part/templates/part/detail.html:102 part/templates/part/params.html:22 +#: part/templates/part/detail.html:102 part/templates/part/params.html:24 msgid "Units" msgstr "" @@ -2616,24 +2640,25 @@ msgstr "" msgid "Part Parameters" msgstr "" -#: part/templates/part/params.html:13 +#: part/templates/part/params.html:14 msgid "Add new parameter" msgstr "" -#: part/templates/part/params.html:13 templates/InvenTree/settings/part.html:12 +#: part/templates/part/params.html:14 templates/InvenTree/settings/part.html:12 msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:21 stock/models.py:1391 +#: part/templates/part/params.html:23 stock/models.py:1391 #: templates/js/stock.html:112 msgid "Value" msgstr "" -#: part/templates/part/params.html:33 +#: part/templates/part/params.html:36 msgid "Edit" msgstr "" -#: part/templates/part/params.html:34 part/templates/part/supplier.html:17 +#: part/templates/part/params.html:39 part/templates/part/supplier.html:17 +#: users/models.py:145 msgid "Delete" msgstr "" @@ -2684,39 +2709,43 @@ msgstr "" msgid "Show pricing information" msgstr "" -#: part/templates/part/part_base.html:70 -msgid "Part actions" -msgstr "" - -#: part/templates/part/part_base.html:72 -msgid "Duplicate part" -msgstr "" - -#: part/templates/part/part_base.html:73 -msgid "Edit part" +#: part/templates/part/part_base.html:60 +msgid "Count part stock" msgstr "" #: part/templates/part/part_base.html:75 +msgid "Part actions" +msgstr "" + +#: part/templates/part/part_base.html:78 +msgid "Duplicate part" +msgstr "" + +#: part/templates/part/part_base.html:81 +msgid "Edit part" +msgstr "" + +#: part/templates/part/part_base.html:84 msgid "Delete part" msgstr "" -#: part/templates/part/part_base.html:114 templates/js/table_filters.html:65 +#: part/templates/part/part_base.html:124 templates/js/table_filters.html:65 msgid "In Stock" msgstr "" -#: part/templates/part/part_base.html:121 +#: part/templates/part/part_base.html:131 msgid "Allocated to Build Orders" msgstr "" -#: part/templates/part/part_base.html:128 +#: part/templates/part/part_base.html:138 msgid "Allocated to Sales Orders" msgstr "" -#: part/templates/part/part_base.html:150 +#: part/templates/part/part_base.html:160 msgid "Can Build" msgstr "" -#: part/templates/part/part_base.html:156 +#: part/templates/part/part_base.html:166 msgid "Underway" msgstr "" @@ -2736,7 +2765,7 @@ msgstr "" msgid "Upload new image" msgstr "" -#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:50 +#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:53 msgid "Sale Price" msgstr "" @@ -2797,11 +2826,11 @@ msgstr "" msgid "BOM" msgstr "" -#: part/templates/part/tabs.html:34 +#: part/templates/part/tabs.html:37 msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:58 stock/templates/stock/item_base.html:282 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:282 msgid "Tests" msgstr "" @@ -2829,176 +2858,176 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:76 +#: part/views.py:80 msgid "Add part attachment" msgstr "" -#: part/views.py:125 templates/attachment_table.html:30 +#: part/views.py:131 templates/attachment_table.html:30 msgid "Edit attachment" msgstr "" -#: part/views.py:129 +#: part/views.py:137 msgid "Part attachment updated" msgstr "" -#: part/views.py:144 +#: part/views.py:152 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:150 +#: part/views.py:160 msgid "Deleted part attachment" msgstr "" -#: part/views.py:159 +#: part/views.py:169 msgid "Create Test Template" msgstr "" -#: part/views.py:186 +#: part/views.py:198 msgid "Edit Test Template" msgstr "" -#: part/views.py:200 +#: part/views.py:214 msgid "Delete Test Template" msgstr "" -#: part/views.py:207 +#: part/views.py:223 msgid "Set Part Category" msgstr "" -#: part/views.py:255 +#: part/views.py:273 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:290 +#: part/views.py:308 msgid "Create Variant" msgstr "" -#: part/views.py:368 +#: part/views.py:388 msgid "Duplicate Part" msgstr "" -#: part/views.py:373 +#: part/views.py:395 msgid "Copied part" msgstr "" -#: part/views.py:496 +#: part/views.py:520 msgid "Created new part" msgstr "" -#: part/views.py:707 +#: part/views.py:735 msgid "Part QR Code" msgstr "" -#: part/views.py:724 +#: part/views.py:754 msgid "Upload Part Image" msgstr "" -#: part/views.py:729 part/views.py:764 +#: part/views.py:762 part/views.py:799 msgid "Updated part image" msgstr "" -#: part/views.py:738 +#: part/views.py:771 msgid "Select Part Image" msgstr "" -#: part/views.py:767 +#: part/views.py:802 msgid "Part image not found" msgstr "" -#: part/views.py:778 +#: part/views.py:813 msgid "Edit Part Properties" msgstr "" -#: part/views.py:800 +#: part/views.py:837 msgid "Validate BOM" msgstr "" -#: part/views.py:963 +#: part/views.py:1004 msgid "No BOM file provided" msgstr "" -#: part/views.py:1313 +#: part/views.py:1354 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1338 part/views.py:1341 +#: part/views.py:1379 part/views.py:1382 msgid "Select valid part" msgstr "" -#: part/views.py:1347 +#: part/views.py:1388 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1385 +#: part/views.py:1426 msgid "Select a part" msgstr "" -#: part/views.py:1391 +#: part/views.py:1432 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1395 +#: part/views.py:1436 msgid "Specify quantity" msgstr "" -#: part/views.py:1645 +#: part/views.py:1692 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1652 +#: part/views.py:1701 msgid "Part was deleted" msgstr "" -#: part/views.py:1661 +#: part/views.py:1710 msgid "Part Pricing" msgstr "" -#: part/views.py:1783 +#: part/views.py:1836 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1791 +#: part/views.py:1846 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1798 +#: part/views.py:1855 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1806 +#: part/views.py:1865 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1856 +#: part/views.py:1917 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1870 +#: part/views.py:1933 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1927 +#: part/views.py:1992 msgid "Edit Part Category" msgstr "" -#: part/views.py:1962 +#: part/views.py:2029 msgid "Delete Part Category" msgstr "" -#: part/views.py:1968 +#: part/views.py:2037 msgid "Part category was deleted" msgstr "" -#: part/views.py:2027 +#: part/views.py:2100 msgid "Create BOM item" msgstr "" -#: part/views.py:2093 +#: part/views.py:2168 msgid "Edit BOM item" msgstr "" -#: part/views.py:2141 +#: part/views.py:2218 msgid "Confim BOM item deletion" msgstr "" @@ -3371,15 +3400,15 @@ msgid "Stock adjustment actions" msgstr "" #: stock/templates/stock/item_base.html:98 -#: stock/templates/stock/location.html:38 templates/stock_table.html:15 +#: stock/templates/stock/location.html:38 templates/stock_table.html:19 msgid "Count stock" msgstr "" -#: stock/templates/stock/item_base.html:99 templates/stock_table.html:13 +#: stock/templates/stock/item_base.html:99 templates/stock_table.html:17 msgid "Add stock" msgstr "" -#: stock/templates/stock/item_base.html:100 templates/stock_table.html:14 +#: stock/templates/stock/item_base.html:100 templates/stock_table.html:18 msgid "Remove stock" msgstr "" @@ -3819,6 +3848,14 @@ msgstr "" msgid "Add Stock Tracking Entry" msgstr "" +#: templates/403.html:5 templates/403.html:11 +msgid "Permission Denied" +msgstr "" + +#: templates/403.html:14 +msgid "You do not have permission to view this page." +msgstr "" + #: templates/InvenTree/bom_invalid.html:7 msgid "BOM Waiting Validation" msgstr "" @@ -3827,6 +3864,10 @@ msgstr "" msgid "Pending Builds" msgstr "" +#: templates/InvenTree/index.html:4 +msgid "Index" +msgstr "" + #: templates/InvenTree/latest_parts.html:7 msgid "Latest Parts" msgstr "" @@ -4392,39 +4433,39 @@ msgstr "" msgid "Purchasable" msgstr "" -#: templates/navbar.html:22 +#: templates/navbar.html:29 msgid "Buy" msgstr "" -#: templates/navbar.html:30 +#: templates/navbar.html:39 msgid "Sell" msgstr "" -#: templates/navbar.html:40 +#: templates/navbar.html:50 msgid "Scan Barcode" msgstr "" -#: templates/navbar.html:49 +#: templates/navbar.html:59 users/models.py:27 msgid "Admin" msgstr "" -#: templates/navbar.html:52 +#: templates/navbar.html:62 msgid "Settings" msgstr "" -#: templates/navbar.html:53 +#: templates/navbar.html:63 msgid "Logout" msgstr "" -#: templates/navbar.html:55 +#: templates/navbar.html:65 msgid "Login" msgstr "" -#: templates/navbar.html:58 +#: templates/navbar.html:68 msgid "About InvenTree" msgstr "" -#: templates/navbar.html:59 +#: templates/navbar.html:69 msgid "Statistics" msgstr "" @@ -4436,38 +4477,94 @@ msgstr "" msgid "Export Stock Information" msgstr "" -#: templates/stock_table.html:13 +#: templates/stock_table.html:17 msgid "Add to selected stock items" msgstr "" -#: templates/stock_table.html:14 +#: templates/stock_table.html:18 msgid "Remove from selected stock items" msgstr "" -#: templates/stock_table.html:15 +#: templates/stock_table.html:19 msgid "Stocktake selected stock items" msgstr "" -#: templates/stock_table.html:16 +#: templates/stock_table.html:20 msgid "Move selected stock items" msgstr "" -#: templates/stock_table.html:16 +#: templates/stock_table.html:20 msgid "Move stock" msgstr "" -#: templates/stock_table.html:17 +#: templates/stock_table.html:21 msgid "Order selected items" msgstr "" -#: templates/stock_table.html:17 +#: templates/stock_table.html:21 msgid "Order stock" msgstr "" -#: templates/stock_table.html:18 +#: templates/stock_table.html:24 msgid "Delete selected items" msgstr "" -#: templates/stock_table.html:18 +#: templates/stock_table.html:24 msgid "Delete Stock" msgstr "" + +#: users/admin.py:61 +msgid "Users" +msgstr "" + +#: users/admin.py:62 +msgid "Select which users are assigned to this group" +msgstr "" + +#: users/admin.py:120 +msgid "Personal info" +msgstr "" + +#: users/admin.py:121 +msgid "Permissions" +msgstr "" + +#: users/admin.py:124 +msgid "Important dates" +msgstr "" + +#: users/models.py:128 +msgid "Permission set" +msgstr "" + +#: users/models.py:136 +msgid "Group" +msgstr "" + +#: users/models.py:139 +msgid "View" +msgstr "" + +#: users/models.py:139 +msgid "Permission to view items" +msgstr "" + +#: users/models.py:141 +msgid "Add" +msgstr "" + +#: users/models.py:141 +msgid "Permission to add items" +msgstr "" + +#: users/models.py:143 +msgid "Change" +msgstr "" + +#: users/models.py:143 +msgid "Permissions to edit items" +msgstr "" + +#: users/models.py:145 +msgid "Permission to delete items" +msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index 0b5425db6e..74b435c791 100644 --- a/InvenTree/locale/es/LC_MESSAGES/django.po +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-04 14:02+0000\n" +"POT-Creation-Date: 2020-10-06 09:31+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,11 +18,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: InvenTree/api.py:83 +#: InvenTree/api.py:85 msgid "No action specified" msgstr "" -#: InvenTree/api.py:97 +#: InvenTree/api.py:99 msgid "No matching action found" msgstr "" @@ -46,34 +46,34 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:337 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:339 order/models.py:187 order/models.py:261 msgid "Invalid quantity provided" msgstr "" -#: InvenTree/helpers.py:340 +#: InvenTree/helpers.py:342 msgid "Empty serial number string" msgstr "" -#: InvenTree/helpers.py:361 +#: InvenTree/helpers.py:363 #, python-brace-format msgid "Duplicate serial: {n}" msgstr "" -#: InvenTree/helpers.py:365 InvenTree/helpers.py:368 InvenTree/helpers.py:371 +#: InvenTree/helpers.py:367 InvenTree/helpers.py:370 InvenTree/helpers.py:373 #, python-brace-format msgid "Invalid group: {g}" msgstr "" -#: InvenTree/helpers.py:376 +#: InvenTree/helpers.py:378 #, python-brace-format msgid "Duplicate serial: {g}" msgstr "" -#: InvenTree/helpers.py:384 +#: InvenTree/helpers.py:386 msgid "No serial numbers found" msgstr "" -#: InvenTree/helpers.py:388 +#: InvenTree/helpers.py:390 #, python-brace-format msgid "Number of unique serial number ({s}) must match quantity ({q})" msgstr "" @@ -90,7 +90,7 @@ msgstr "" msgid "User" msgstr "" -#: InvenTree/models.py:106 part/templates/part/params.html:20 +#: InvenTree/models.py:106 part/templates/part/params.html:22 #: templates/js/part.html:81 msgid "Name" msgstr "" @@ -99,19 +99,19 @@ msgstr "" msgid "Description (optional)" msgstr "" -#: InvenTree/settings.py:342 +#: InvenTree/settings.py:348 msgid "English" msgstr "" -#: InvenTree/settings.py:343 +#: InvenTree/settings.py:349 msgid "German" msgstr "" -#: InvenTree/settings.py:344 +#: InvenTree/settings.py:350 msgid "French" msgstr "" -#: InvenTree/settings.py:345 +#: InvenTree/settings.py:351 msgid "Polish" msgstr "" @@ -144,7 +144,7 @@ msgid "Returned" msgstr "" #: InvenTree/status_codes.py:136 -#: order/templates/order/sales_order_base.html:103 +#: order/templates/order/sales_order_base.html:105 msgid "Shipped" msgstr "" @@ -199,7 +199,7 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:661 +#: InvenTree/views.py:703 msgid "Database Statistics" msgstr "" @@ -263,7 +263,7 @@ msgstr "" msgid "Build quantity must be integer value for trackable parts" msgstr "" -#: build/models.py:73 build/templates/build/build_base.html:70 +#: build/models.py:73 build/templates/build/build_base.html:72 msgid "Build Title" msgstr "" @@ -271,7 +271,7 @@ msgstr "" msgid "Brief description of the build" msgstr "" -#: build/models.py:84 build/templates/build/build_base.html:91 +#: build/models.py:84 build/templates/build/build_base.html:93 msgid "Parent Build" msgstr "" @@ -281,7 +281,7 @@ msgstr "" #: build/models.py:90 build/templates/build/allocate.html:329 #: build/templates/build/auto_allocate.html:19 -#: build/templates/build/build_base.html:75 +#: build/templates/build/build_base.html:77 #: build/templates/build/detail.html:22 order/models.py:501 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:147 @@ -325,7 +325,7 @@ msgstr "" msgid "Number of parts to build" msgstr "" -#: build/models.py:128 part/templates/part/part_base.html:145 +#: build/models.py:128 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "" @@ -344,7 +344,7 @@ msgstr "" #: build/models.py:155 build/templates/build/detail.html:55 #: company/templates/company/supplier_part_base.html:60 #: company/templates/company/supplier_part_detail.html:24 -#: part/templates/part/detail.html:80 part/templates/part/part_base.html:92 +#: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 #: stock/models.py:381 stock/templates/stock/item_base.html:244 msgid "External Link" msgstr "" @@ -356,7 +356,7 @@ msgstr "" #: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15 #: order/templates/order/purchase_order_detail.html:202 -#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:67 +#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/models.py:1404 stock/templates/stock/tabs.html:26 #: templates/js/barcode.html:391 templates/js/bom.html:223 @@ -403,8 +403,8 @@ msgid "Stock quantity to allocate to build" msgstr "" #: build/templates/build/allocate.html:17 -#: company/templates/company/detail_part.html:18 order/views.py:779 -#: part/templates/part/category.html:112 +#: company/templates/company/detail_part.html:18 order/views.py:804 +#: part/templates/part/category.html:122 msgid "Order Parts" msgstr "" @@ -420,7 +420,7 @@ msgstr "" msgid "Unallocate" msgstr "" -#: build/templates/build/allocate.html:87 templates/stock_table.html:9 +#: build/templates/build/allocate.html:87 templates/stock_table.html:10 msgid "New Stock Item" msgstr "" @@ -437,7 +437,7 @@ msgstr "" #: build/templates/build/allocate.html:172 #: build/templates/build/auto_allocate.html:20 -#: build/templates/build/build_base.html:80 +#: build/templates/build/build_base.html:82 #: build/templates/build/detail.html:27 #: company/templates/company/supplier_part_pricing.html:71 #: order/templates/order/order_wizard/select_parts.html:32 @@ -548,7 +548,7 @@ msgstr "" #: build/templates/build/build_base.html:34 #: build/templates/build/complete.html:6 #: stock/templates/stock/item_base.html:223 templates/js/build.html:39 -#: templates/navbar.html:20 +#: templates/navbar.html:25 msgid "Build" msgstr "" @@ -570,11 +570,27 @@ msgstr "" msgid "Admin view" msgstr "" -#: build/templates/build/build_base.html:66 build/templates/build/detail.html:9 +#: build/templates/build/build_base.html:45 +msgid "Edit Build" +msgstr "" + +#: build/templates/build/build_base.html:49 build/views.py:190 +msgid "Complete Build" +msgstr "" + +#: build/templates/build/build_base.html:52 build/views.py:58 +msgid "Cancel Build" +msgstr "" + +#: build/templates/build/build_base.html:58 build/views.py:454 +msgid "Delete Build" +msgstr "" + +#: build/templates/build/build_base.html:68 build/templates/build/detail.html:9 msgid "Build Details" msgstr "" -#: build/templates/build/build_base.html:85 +#: build/templates/build/build_base.html:87 #: build/templates/build/detail.html:42 #: order/templates/order/receive_parts.html:24 #: stock/templates/stock/item_base.html:276 templates/InvenTree/search.html:175 @@ -584,7 +600,7 @@ msgstr "" msgid "Status" msgstr "" -#: build/templates/build/build_base.html:98 order/models.py:499 +#: build/templates/build/build_base.html:100 order/models.py:499 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 @@ -594,15 +610,15 @@ msgstr "" msgid "Sales Order" msgstr "" -#: build/templates/build/build_base.html:104 +#: build/templates/build/build_base.html:106 msgid "BOM Price" msgstr "" -#: build/templates/build/build_base.html:109 +#: build/templates/build/build_base.html:111 msgid "BOM pricing is incomplete" msgstr "" -#: build/templates/build/build_base.html:112 +#: build/templates/build/build_base.html:114 msgid "No pricing information" msgstr "" @@ -664,8 +680,8 @@ msgid "Batch" msgstr "" #: build/templates/build/detail.html:61 -#: order/templates/order/order_base.html:98 -#: order/templates/order/sales_order_base.html:97 templates/js/build.html:71 +#: order/templates/order/order_base.html:100 +#: order/templates/order/sales_order_base.html:99 templates/js/build.html:71 msgid "Created" msgstr "" @@ -687,7 +703,7 @@ msgstr "" #: build/templates/build/index.html:6 build/templates/build/index.html:14 #: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9 -#: part/templates/part/tabs.html:30 +#: part/templates/part/tabs.html:31 users/models.py:30 msgid "Build Orders" msgstr "" @@ -707,9 +723,9 @@ msgid "Save" msgstr "" #: build/templates/build/notes.html:33 company/templates/company/notes.html:30 -#: order/templates/order/order_notes.html:32 +#: order/templates/order/order_notes.html:33 #: order/templates/order/sales_order_notes.html:37 -#: part/templates/part/notes.html:32 stock/templates/stock/item_notes.html:33 +#: part/templates/part/notes.html:33 stock/templates/stock/item_notes.html:33 msgid "Edit notes" msgstr "" @@ -726,100 +742,88 @@ msgstr "" msgid "Are you sure you wish to unallocate all stock for this build?" msgstr "" -#: build/views.py:56 -msgid "Cancel Build" -msgstr "" - -#: build/views.py:74 +#: build/views.py:77 msgid "Confirm build cancellation" msgstr "" -#: build/views.py:79 +#: build/views.py:82 msgid "Build was cancelled" msgstr "" -#: build/views.py:95 +#: build/views.py:98 msgid "Allocate Stock" msgstr "" -#: build/views.py:108 +#: build/views.py:112 msgid "No matching build found" msgstr "" -#: build/views.py:127 +#: build/views.py:131 msgid "Confirm stock allocation" msgstr "" -#: build/views.py:128 +#: build/views.py:132 msgid "Check the confirmation box at the bottom of the list" msgstr "" -#: build/views.py:148 build/views.py:452 +#: build/views.py:152 build/views.py:465 msgid "Unallocate Stock" msgstr "" -#: build/views.py:161 +#: build/views.py:166 msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:162 stock/views.py:405 +#: build/views.py:167 stock/views.py:405 msgid "Check the confirmation box" msgstr "" -#: build/views.py:185 -msgid "Complete Build" -msgstr "" - -#: build/views.py:264 +#: build/views.py:270 msgid "Confirm completion of build" msgstr "" -#: build/views.py:271 +#: build/views.py:277 msgid "Invalid location selected" msgstr "" -#: build/views.py:296 stock/views.py:1621 +#: build/views.py:302 stock/views.py:1621 #, python-brace-format msgid "The following serial numbers already exist: ({sn})" msgstr "" -#: build/views.py:317 +#: build/views.py:323 msgid "Build marked as COMPLETE" msgstr "" -#: build/views.py:393 +#: build/views.py:403 msgid "Start new Build" msgstr "" -#: build/views.py:418 +#: build/views.py:429 msgid "Created new build" msgstr "" -#: build/views.py:428 +#: build/views.py:439 msgid "Edit Build Details" msgstr "" -#: build/views.py:433 +#: build/views.py:445 msgid "Edited build" msgstr "" -#: build/views.py:442 -msgid "Delete Build" -msgstr "" - -#: build/views.py:457 +#: build/views.py:471 msgid "Removed parts from build allocation" msgstr "" -#: build/views.py:467 +#: build/views.py:481 msgid "Allocate new Part" msgstr "" -#: build/views.py:620 +#: build/views.py:635 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:624 +#: build/views.py:640 msgid "Updated Build Item" msgstr "" @@ -1014,7 +1018,7 @@ msgstr "" #: company/templates/company/detail.html:21 #: company/templates/company/supplier_part_base.html:66 #: company/templates/company/supplier_part_detail.html:21 -#: order/templates/order/order_base.html:79 +#: order/templates/order/order_base.html:81 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 #: stock/templates/stock/item_base.html:251 templates/js/company.html:48 #: templates/js/company.html:162 templates/js/order.html:146 @@ -1022,7 +1026,7 @@ msgid "Supplier" msgstr "" #: company/templates/company/detail.html:26 -#: order/templates/order/sales_order_base.html:78 stock/models.py:370 +#: order/templates/order/sales_order_base.html:80 stock/models.py:370 #: stock/models.py:371 stock/templates/stock/item_base.html:169 #: templates/js/company.html:40 templates/js/order.html:221 msgid "Customer" @@ -1044,13 +1048,13 @@ msgid "New Supplier Part" msgstr "" #: company/templates/company/detail_part.html:15 -#: part/templates/part/category.html:109 part/templates/part/supplier.html:15 -#: templates/stock_table.html:11 +#: part/templates/part/category.html:117 part/templates/part/supplier.html:15 +#: templates/stock_table.html:14 msgid "Options" msgstr "" #: company/templates/company/detail_part.html:18 -#: part/templates/part/category.html:112 +#: part/templates/part/category.html:122 msgid "Order parts" msgstr "" @@ -1063,7 +1067,7 @@ msgid "Delete Parts" msgstr "" #: company/templates/company/detail_part.html:43 -#: part/templates/part/category.html:107 templates/js/stock.html:791 +#: part/templates/part/category.html:114 templates/js/stock.html:791 msgid "New Part" msgstr "" @@ -1095,7 +1099,7 @@ msgstr "" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/category.html:106 part/templates/part/category.html:113 +#: part/templates/part/category.html:112 part/templates/part/category.html:123 #: part/templates/part/stock.html:51 templates/stock_table.html:6 msgid "Export" msgstr "" @@ -1117,18 +1121,18 @@ msgstr "" #: company/templates/company/tabs.html:17 #: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:12 -#: part/templates/part/orders.html:9 part/templates/part/tabs.html:45 -#: templates/navbar.html:26 +#: part/templates/part/orders.html:9 part/templates/part/tabs.html:48 +#: templates/navbar.html:33 users/models.py:31 msgid "Purchase Orders" msgstr "" #: company/templates/company/purchase_orders.html:14 -#: order/templates/order/purchase_orders.html:17 +#: order/templates/order/purchase_orders.html:18 msgid "Create new purchase order" msgstr "" #: company/templates/company/purchase_orders.html:14 -#: order/templates/order/purchase_orders.html:17 +#: order/templates/order/purchase_orders.html:18 msgid "New Purchase Order" msgstr "" @@ -1136,18 +1140,18 @@ msgstr "" #: company/templates/company/tabs.html:22 #: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:12 -#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:53 -#: templates/navbar.html:33 +#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56 +#: templates/navbar.html:42 users/models.py:32 msgid "Sales Orders" msgstr "" #: company/templates/company/sales_orders.html:14 -#: order/templates/order/sales_orders.html:17 +#: order/templates/order/sales_orders.html:18 msgid "Create new sales order" msgstr "" #: company/templates/company/sales_orders.html:14 -#: order/templates/order/sales_orders.html:17 +#: order/templates/order/sales_orders.html:18 msgid "New Sales Order" msgstr "" @@ -1158,7 +1162,7 @@ msgid "Supplier Part" msgstr "" #: company/templates/company/supplier_part_base.html:23 -#: part/templates/part/orders.html:14 +#: part/templates/part/orders.html:14 part/templates/part/part_base.html:66 msgid "Order part" msgstr "" @@ -1205,7 +1209,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:15 company/views.py:399 -#: part/templates/part/sale_prices.html:13 part/views.py:2149 +#: part/templates/part/sale_prices.html:13 part/views.py:2228 msgid "Add Price Break" msgstr "" @@ -1241,7 +1245,7 @@ msgstr "" #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: templates/js/part.html:124 templates/js/part.html:372 -#: templates/js/stock.html:452 templates/navbar.html:19 +#: templates/js/stock.html:452 templates/navbar.html:22 users/models.py:29 msgid "Stock" msgstr "" @@ -1251,22 +1255,22 @@ msgstr "" #: company/templates/company/tabs.html:9 #: order/templates/order/receive_parts.html:14 part/models.py:294 -#: part/templates/part/cat_link.html:7 part/templates/part/category.html:88 -#: part/templates/part/category_tabs.html:6 templates/navbar.html:18 -#: templates/stats.html:8 templates/stats.html:17 +#: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 +#: part/templates/part/category_tabs.html:6 templates/navbar.html:19 +#: templates/stats.html:8 templates/stats.html:17 users/models.py:28 msgid "Parts" msgstr "" -#: company/views.py:50 part/templates/part/tabs.html:39 -#: templates/navbar.html:24 +#: company/views.py:50 part/templates/part/tabs.html:42 +#: templates/navbar.html:31 msgid "Suppliers" msgstr "" -#: company/views.py:57 templates/navbar.html:25 +#: company/views.py:57 templates/navbar.html:32 msgid "Manufacturers" msgstr "" -#: company/views.py:64 templates/navbar.html:32 +#: company/views.py:64 templates/navbar.html:41 msgid "Customers" msgstr "" @@ -1330,15 +1334,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:404 part/views.py:2153 +#: company/views.py:404 part/views.py:2234 msgid "Added new price break" msgstr "" -#: company/views.py:441 part/views.py:2198 +#: company/views.py:441 part/views.py:2279 msgid "Edit Price Break" msgstr "" -#: company/views.py:456 part/views.py:2212 +#: company/views.py:456 part/views.py:2295 msgid "Delete Price Break" msgstr "" @@ -1366,20 +1370,20 @@ msgstr "" msgid "Enabled" msgstr "" -#: order/forms.py:24 +#: order/forms.py:24 order/templates/order/order_base.html:40 msgid "Place order" msgstr "" -#: order/forms.py:35 +#: order/forms.py:35 order/templates/order/order_base.html:47 msgid "Mark order as complete" msgstr "" -#: order/forms.py:46 order/forms.py:57 -#: order/templates/order/sales_order_base.html:54 +#: order/forms.py:46 order/forms.py:57 order/templates/order/order_base.html:52 +#: order/templates/order/sales_order_base.html:52 msgid "Cancel order" msgstr "" -#: order/forms.py:68 order/templates/order/sales_order_base.html:51 +#: order/forms.py:68 order/templates/order/sales_order_base.html:49 msgid "Ship order" msgstr "" @@ -1431,7 +1435,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1304 +#: order/models.py:185 order/models.py:259 part/views.py:1345 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "" @@ -1512,32 +1516,44 @@ msgstr "" msgid "Are you sure you want to delete this attachment?" msgstr "" -#: order/templates/order/order_base.html:64 +#: order/templates/order/order_base.html:36 +msgid "Edit order information" +msgstr "" + +#: order/templates/order/order_base.html:44 +msgid "Receive items" +msgstr "" + +#: order/templates/order/order_base.html:57 +msgid "Export order to file" +msgstr "" + +#: order/templates/order/order_base.html:66 msgid "Purchase Order Details" msgstr "" -#: order/templates/order/order_base.html:69 -#: order/templates/order/sales_order_base.html:68 +#: order/templates/order/order_base.html:71 +#: order/templates/order/sales_order_base.html:70 msgid "Order Reference" msgstr "" -#: order/templates/order/order_base.html:74 -#: order/templates/order/sales_order_base.html:73 +#: order/templates/order/order_base.html:76 +#: order/templates/order/sales_order_base.html:75 msgid "Order Status" msgstr "" -#: order/templates/order/order_base.html:85 templates/js/order.html:153 +#: order/templates/order/order_base.html:87 templates/js/order.html:153 msgid "Supplier Reference" msgstr "" -#: order/templates/order/order_base.html:104 +#: order/templates/order/order_base.html:106 msgid "Issued" msgstr "" -#: order/templates/order/order_base.html:111 +#: order/templates/order/order_base.html:113 #: order/templates/order/purchase_order_detail.html:182 #: order/templates/order/receive_parts.html:22 -#: order/templates/order/sales_order_base.html:110 +#: order/templates/order/sales_order_base.html:112 msgid "Received" msgstr "" @@ -1600,13 +1616,13 @@ msgid "Purchase Order Attachments" msgstr "" #: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16 -#: part/templates/part/tabs.html:64 stock/templates/stock/tabs.html:32 +#: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32 msgid "Attachments" msgstr "" #: order/templates/order/purchase_order_detail.html:16 -#: order/templates/order/sales_order_detail.html:17 order/views.py:1087 -#: order/views.py:1201 +#: order/templates/order/sales_order_detail.html:17 order/views.py:1117 +#: order/views.py:1232 msgid "Add Line Item" msgstr "" @@ -1616,7 +1632,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:38 #: order/templates/order/purchase_order_detail.html:118 -#: part/templates/part/category.html:161 part/templates/part/category.html:202 +#: part/templates/part/category.html:171 part/templates/part/category.html:213 #: templates/js/stock.html:803 msgid "New Location" msgstr "" @@ -1658,7 +1674,7 @@ msgid "Select parts to receive against this order" msgstr "" #: order/templates/order/receive_parts.html:21 -#: part/templates/part/part_base.html:135 templates/js/part.html:388 +#: part/templates/part/part_base.html:145 templates/js/part.html:388 msgid "On Order" msgstr "" @@ -1674,15 +1690,15 @@ msgstr "" msgid "This SalesOrder has not been fully allocated" msgstr "" -#: order/templates/order/sales_order_base.html:47 +#: order/templates/order/sales_order_base.html:57 msgid "Packing List" msgstr "" -#: order/templates/order/sales_order_base.html:63 +#: order/templates/order/sales_order_base.html:65 msgid "Sales Order Details" msgstr "" -#: order/templates/order/sales_order_base.html:84 templates/js/order.html:228 +#: order/templates/order/sales_order_base.html:86 templates/js/order.html:228 msgid "Customer Reference" msgstr "" @@ -1746,156 +1762,156 @@ msgstr "" msgid "Order Items" msgstr "" -#: order/views.py:93 +#: order/views.py:99 msgid "Add Purchase Order Attachment" msgstr "" -#: order/views.py:102 order/views.py:149 part/views.py:86 stock/views.py:167 +#: order/views.py:109 order/views.py:157 part/views.py:92 stock/views.py:167 msgid "Added attachment" msgstr "" -#: order/views.py:141 +#: order/views.py:148 msgid "Add Sales Order Attachment" msgstr "" -#: order/views.py:176 order/views.py:197 +#: order/views.py:184 order/views.py:206 msgid "Edit Attachment" msgstr "" -#: order/views.py:180 order/views.py:201 +#: order/views.py:189 order/views.py:211 msgid "Attachment updated" msgstr "" -#: order/views.py:216 order/views.py:230 +#: order/views.py:226 order/views.py:241 msgid "Delete Attachment" msgstr "" -#: order/views.py:222 order/views.py:236 stock/views.py:223 +#: order/views.py:233 order/views.py:248 stock/views.py:223 msgid "Deleted attachment" msgstr "" -#: order/views.py:287 +#: order/views.py:301 msgid "Create Purchase Order" msgstr "" -#: order/views.py:318 +#: order/views.py:333 msgid "Create Sales Order" msgstr "" -#: order/views.py:348 +#: order/views.py:364 msgid "Edit Purchase Order" msgstr "" -#: order/views.py:368 +#: order/views.py:385 msgid "Edit Sales Order" msgstr "" -#: order/views.py:384 +#: order/views.py:402 msgid "Cancel Order" msgstr "" -#: order/views.py:399 order/views.py:431 +#: order/views.py:418 order/views.py:451 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:417 +#: order/views.py:436 msgid "Cancel sales order" msgstr "" -#: order/views.py:437 +#: order/views.py:457 msgid "Could not cancel order" msgstr "" -#: order/views.py:451 +#: order/views.py:471 msgid "Issue Order" msgstr "" -#: order/views.py:466 +#: order/views.py:487 msgid "Confirm order placement" msgstr "" -#: order/views.py:487 +#: order/views.py:508 msgid "Complete Order" msgstr "" -#: order/views.py:522 +#: order/views.py:544 msgid "Ship Order" msgstr "" -#: order/views.py:538 +#: order/views.py:561 msgid "Confirm order shipment" msgstr "" -#: order/views.py:544 +#: order/views.py:567 msgid "Could not ship order" msgstr "" -#: order/views.py:595 +#: order/views.py:619 msgid "Receive Parts" msgstr "" -#: order/views.py:662 +#: order/views.py:687 msgid "Items received" msgstr "" -#: order/views.py:676 +#: order/views.py:701 msgid "No destination set" msgstr "" -#: order/views.py:721 +#: order/views.py:746 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:727 +#: order/views.py:752 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:733 +#: order/views.py:758 msgid "No lines specified" msgstr "" -#: order/views.py:1107 +#: order/views.py:1138 msgid "Invalid Purchase Order" msgstr "" -#: order/views.py:1115 +#: order/views.py:1146 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1120 +#: order/views.py:1151 msgid "Invalid SupplierPart selection" msgstr "" -#: order/views.py:1252 order/views.py:1270 +#: order/views.py:1284 order/views.py:1303 msgid "Edit Line Item" msgstr "" -#: order/views.py:1286 order/views.py:1298 +#: order/views.py:1320 order/views.py:1333 msgid "Delete Line Item" msgstr "" -#: order/views.py:1291 order/views.py:1303 +#: order/views.py:1326 order/views.py:1339 msgid "Deleted line item" msgstr "" -#: order/views.py:1312 +#: order/views.py:1348 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1381 +#: order/views.py:1418 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1396 +#: order/views.py:1434 msgid "Remove allocation" msgstr "" -#: part/bom.py:138 part/templates/part/category.html:55 +#: part/bom.py:138 part/templates/part/category.html:61 #: part/templates/part/detail.html:87 msgid "Default Location" msgstr "" -#: part/bom.py:139 part/templates/part/part_base.html:108 +#: part/bom.py:139 part/templates/part/part_base.html:118 msgid "Available Stock" msgstr "" @@ -2013,7 +2029,7 @@ msgid "Part Category" msgstr "" #: part/models.py:76 part/templates/part/category.html:18 -#: part/templates/part/category.html:83 templates/stats.html:12 +#: part/templates/part/category.html:89 templates/stats.html:12 msgid "Part Categories" msgstr "" @@ -2226,7 +2242,7 @@ msgstr "" msgid "BOM line checksum" msgstr "" -#: part/models.py:1612 part/views.py:1310 part/views.py:1362 +#: part/models.py:1612 part/views.py:1351 part/views.py:1403 #: stock/models.py:231 msgid "Quantity must be integer value for trackable parts" msgstr "" @@ -2285,23 +2301,23 @@ msgstr "" msgid "Finish Editing" msgstr "" -#: part/templates/part/bom.html:42 +#: part/templates/part/bom.html:43 msgid "Edit BOM" msgstr "" -#: part/templates/part/bom.html:44 +#: part/templates/part/bom.html:45 msgid "Validate Bill of Materials" msgstr "" -#: part/templates/part/bom.html:46 part/views.py:1597 +#: part/templates/part/bom.html:48 part/views.py:1642 msgid "Export Bill of Materials" msgstr "" -#: part/templates/part/bom.html:101 +#: part/templates/part/bom.html:103 msgid "Delete selected BOM items?" msgstr "" -#: part/templates/part/bom.html:102 +#: part/templates/part/bom.html:104 msgid "All selected BOM items will be deleted" msgstr "" @@ -2373,83 +2389,91 @@ msgstr "" msgid "Each part must already exist in the database" msgstr "" +#: part/templates/part/build.html:8 +msgid "Part Builds" +msgstr "" + +#: part/templates/part/build.html:14 +msgid "Start New Build" +msgstr "" + #: part/templates/part/category.html:19 msgid "All parts" msgstr "" -#: part/templates/part/category.html:23 part/views.py:1976 +#: part/templates/part/category.html:24 part/views.py:2045 msgid "Create new part category" msgstr "" -#: part/templates/part/category.html:27 +#: part/templates/part/category.html:30 msgid "Edit part category" msgstr "" -#: part/templates/part/category.html:30 +#: part/templates/part/category.html:35 msgid "Delete part category" msgstr "" -#: part/templates/part/category.html:39 part/templates/part/category.html:78 +#: part/templates/part/category.html:45 part/templates/part/category.html:84 msgid "Category Details" msgstr "" -#: part/templates/part/category.html:44 +#: part/templates/part/category.html:50 msgid "Category Path" msgstr "" -#: part/templates/part/category.html:49 +#: part/templates/part/category.html:55 msgid "Category Description" msgstr "" -#: part/templates/part/category.html:62 part/templates/part/detail.html:64 +#: part/templates/part/category.html:68 part/templates/part/detail.html:64 msgid "Keywords" msgstr "" -#: part/templates/part/category.html:68 +#: part/templates/part/category.html:74 msgid "Subcategories" msgstr "" -#: part/templates/part/category.html:73 +#: part/templates/part/category.html:79 msgid "Parts (Including subcategories)" msgstr "" -#: part/templates/part/category.html:106 +#: part/templates/part/category.html:112 msgid "Export Part Data" msgstr "" -#: part/templates/part/category.html:107 part/views.py:491 +#: part/templates/part/category.html:114 part/views.py:513 msgid "Create new part" msgstr "" -#: part/templates/part/category.html:111 +#: part/templates/part/category.html:120 msgid "Set category" msgstr "" -#: part/templates/part/category.html:111 +#: part/templates/part/category.html:120 msgid "Set Category" msgstr "" -#: part/templates/part/category.html:113 +#: part/templates/part/category.html:123 msgid "Export Data" msgstr "" -#: part/templates/part/category.html:162 +#: part/templates/part/category.html:172 msgid "Create new location" msgstr "" -#: part/templates/part/category.html:167 part/templates/part/category.html:196 +#: part/templates/part/category.html:177 part/templates/part/category.html:207 msgid "New Category" msgstr "" -#: part/templates/part/category.html:168 +#: part/templates/part/category.html:178 msgid "Create new category" msgstr "" -#: part/templates/part/category.html:197 +#: part/templates/part/category.html:208 msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:203 stock/views.py:1314 +#: part/templates/part/category.html:214 stock/views.py:1314 msgid "Create new Stock Location" msgstr "" @@ -2461,7 +2485,7 @@ msgstr "" msgid "Part Details" msgstr "" -#: part/templates/part/detail.html:25 part/templates/part/part_base.html:85 +#: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 #: templates/js/part.html:112 msgid "IPN" msgstr "" @@ -2491,7 +2515,7 @@ msgstr "" msgid "Default Supplier" msgstr "" -#: part/templates/part/detail.html:102 part/templates/part/params.html:22 +#: part/templates/part/detail.html:102 part/templates/part/params.html:24 msgid "Units" msgstr "" @@ -2616,24 +2640,25 @@ msgstr "" msgid "Part Parameters" msgstr "" -#: part/templates/part/params.html:13 +#: part/templates/part/params.html:14 msgid "Add new parameter" msgstr "" -#: part/templates/part/params.html:13 templates/InvenTree/settings/part.html:12 +#: part/templates/part/params.html:14 templates/InvenTree/settings/part.html:12 msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:21 stock/models.py:1391 +#: part/templates/part/params.html:23 stock/models.py:1391 #: templates/js/stock.html:112 msgid "Value" msgstr "" -#: part/templates/part/params.html:33 +#: part/templates/part/params.html:36 msgid "Edit" msgstr "" -#: part/templates/part/params.html:34 part/templates/part/supplier.html:17 +#: part/templates/part/params.html:39 part/templates/part/supplier.html:17 +#: users/models.py:145 msgid "Delete" msgstr "" @@ -2684,39 +2709,43 @@ msgstr "" msgid "Show pricing information" msgstr "" -#: part/templates/part/part_base.html:70 -msgid "Part actions" -msgstr "" - -#: part/templates/part/part_base.html:72 -msgid "Duplicate part" -msgstr "" - -#: part/templates/part/part_base.html:73 -msgid "Edit part" +#: part/templates/part/part_base.html:60 +msgid "Count part stock" msgstr "" #: part/templates/part/part_base.html:75 +msgid "Part actions" +msgstr "" + +#: part/templates/part/part_base.html:78 +msgid "Duplicate part" +msgstr "" + +#: part/templates/part/part_base.html:81 +msgid "Edit part" +msgstr "" + +#: part/templates/part/part_base.html:84 msgid "Delete part" msgstr "" -#: part/templates/part/part_base.html:114 templates/js/table_filters.html:65 +#: part/templates/part/part_base.html:124 templates/js/table_filters.html:65 msgid "In Stock" msgstr "" -#: part/templates/part/part_base.html:121 +#: part/templates/part/part_base.html:131 msgid "Allocated to Build Orders" msgstr "" -#: part/templates/part/part_base.html:128 +#: part/templates/part/part_base.html:138 msgid "Allocated to Sales Orders" msgstr "" -#: part/templates/part/part_base.html:150 +#: part/templates/part/part_base.html:160 msgid "Can Build" msgstr "" -#: part/templates/part/part_base.html:156 +#: part/templates/part/part_base.html:166 msgid "Underway" msgstr "" @@ -2736,7 +2765,7 @@ msgstr "" msgid "Upload new image" msgstr "" -#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:50 +#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:53 msgid "Sale Price" msgstr "" @@ -2797,11 +2826,11 @@ msgstr "" msgid "BOM" msgstr "" -#: part/templates/part/tabs.html:34 +#: part/templates/part/tabs.html:37 msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:58 stock/templates/stock/item_base.html:282 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:282 msgid "Tests" msgstr "" @@ -2829,176 +2858,176 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:76 +#: part/views.py:80 msgid "Add part attachment" msgstr "" -#: part/views.py:125 templates/attachment_table.html:30 +#: part/views.py:131 templates/attachment_table.html:30 msgid "Edit attachment" msgstr "" -#: part/views.py:129 +#: part/views.py:137 msgid "Part attachment updated" msgstr "" -#: part/views.py:144 +#: part/views.py:152 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:150 +#: part/views.py:160 msgid "Deleted part attachment" msgstr "" -#: part/views.py:159 +#: part/views.py:169 msgid "Create Test Template" msgstr "" -#: part/views.py:186 +#: part/views.py:198 msgid "Edit Test Template" msgstr "" -#: part/views.py:200 +#: part/views.py:214 msgid "Delete Test Template" msgstr "" -#: part/views.py:207 +#: part/views.py:223 msgid "Set Part Category" msgstr "" -#: part/views.py:255 +#: part/views.py:273 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:290 +#: part/views.py:308 msgid "Create Variant" msgstr "" -#: part/views.py:368 +#: part/views.py:388 msgid "Duplicate Part" msgstr "" -#: part/views.py:373 +#: part/views.py:395 msgid "Copied part" msgstr "" -#: part/views.py:496 +#: part/views.py:520 msgid "Created new part" msgstr "" -#: part/views.py:707 +#: part/views.py:735 msgid "Part QR Code" msgstr "" -#: part/views.py:724 +#: part/views.py:754 msgid "Upload Part Image" msgstr "" -#: part/views.py:729 part/views.py:764 +#: part/views.py:762 part/views.py:799 msgid "Updated part image" msgstr "" -#: part/views.py:738 +#: part/views.py:771 msgid "Select Part Image" msgstr "" -#: part/views.py:767 +#: part/views.py:802 msgid "Part image not found" msgstr "" -#: part/views.py:778 +#: part/views.py:813 msgid "Edit Part Properties" msgstr "" -#: part/views.py:800 +#: part/views.py:837 msgid "Validate BOM" msgstr "" -#: part/views.py:963 +#: part/views.py:1004 msgid "No BOM file provided" msgstr "" -#: part/views.py:1313 +#: part/views.py:1354 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1338 part/views.py:1341 +#: part/views.py:1379 part/views.py:1382 msgid "Select valid part" msgstr "" -#: part/views.py:1347 +#: part/views.py:1388 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1385 +#: part/views.py:1426 msgid "Select a part" msgstr "" -#: part/views.py:1391 +#: part/views.py:1432 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1395 +#: part/views.py:1436 msgid "Specify quantity" msgstr "" -#: part/views.py:1645 +#: part/views.py:1692 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1652 +#: part/views.py:1701 msgid "Part was deleted" msgstr "" -#: part/views.py:1661 +#: part/views.py:1710 msgid "Part Pricing" msgstr "" -#: part/views.py:1783 +#: part/views.py:1836 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1791 +#: part/views.py:1846 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1798 +#: part/views.py:1855 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1806 +#: part/views.py:1865 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1856 +#: part/views.py:1917 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1870 +#: part/views.py:1933 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1927 +#: part/views.py:1992 msgid "Edit Part Category" msgstr "" -#: part/views.py:1962 +#: part/views.py:2029 msgid "Delete Part Category" msgstr "" -#: part/views.py:1968 +#: part/views.py:2037 msgid "Part category was deleted" msgstr "" -#: part/views.py:2027 +#: part/views.py:2100 msgid "Create BOM item" msgstr "" -#: part/views.py:2093 +#: part/views.py:2168 msgid "Edit BOM item" msgstr "" -#: part/views.py:2141 +#: part/views.py:2218 msgid "Confim BOM item deletion" msgstr "" @@ -3371,15 +3400,15 @@ msgid "Stock adjustment actions" msgstr "" #: stock/templates/stock/item_base.html:98 -#: stock/templates/stock/location.html:38 templates/stock_table.html:15 +#: stock/templates/stock/location.html:38 templates/stock_table.html:19 msgid "Count stock" msgstr "" -#: stock/templates/stock/item_base.html:99 templates/stock_table.html:13 +#: stock/templates/stock/item_base.html:99 templates/stock_table.html:17 msgid "Add stock" msgstr "" -#: stock/templates/stock/item_base.html:100 templates/stock_table.html:14 +#: stock/templates/stock/item_base.html:100 templates/stock_table.html:18 msgid "Remove stock" msgstr "" @@ -3819,6 +3848,14 @@ msgstr "" msgid "Add Stock Tracking Entry" msgstr "" +#: templates/403.html:5 templates/403.html:11 +msgid "Permission Denied" +msgstr "" + +#: templates/403.html:14 +msgid "You do not have permission to view this page." +msgstr "" + #: templates/InvenTree/bom_invalid.html:7 msgid "BOM Waiting Validation" msgstr "" @@ -3827,6 +3864,10 @@ msgstr "" msgid "Pending Builds" msgstr "" +#: templates/InvenTree/index.html:4 +msgid "Index" +msgstr "" + #: templates/InvenTree/latest_parts.html:7 msgid "Latest Parts" msgstr "" @@ -4392,39 +4433,39 @@ msgstr "" msgid "Purchasable" msgstr "" -#: templates/navbar.html:22 +#: templates/navbar.html:29 msgid "Buy" msgstr "" -#: templates/navbar.html:30 +#: templates/navbar.html:39 msgid "Sell" msgstr "" -#: templates/navbar.html:40 +#: templates/navbar.html:50 msgid "Scan Barcode" msgstr "" -#: templates/navbar.html:49 +#: templates/navbar.html:59 users/models.py:27 msgid "Admin" msgstr "" -#: templates/navbar.html:52 +#: templates/navbar.html:62 msgid "Settings" msgstr "" -#: templates/navbar.html:53 +#: templates/navbar.html:63 msgid "Logout" msgstr "" -#: templates/navbar.html:55 +#: templates/navbar.html:65 msgid "Login" msgstr "" -#: templates/navbar.html:58 +#: templates/navbar.html:68 msgid "About InvenTree" msgstr "" -#: templates/navbar.html:59 +#: templates/navbar.html:69 msgid "Statistics" msgstr "" @@ -4436,38 +4477,94 @@ msgstr "" msgid "Export Stock Information" msgstr "" -#: templates/stock_table.html:13 +#: templates/stock_table.html:17 msgid "Add to selected stock items" msgstr "" -#: templates/stock_table.html:14 +#: templates/stock_table.html:18 msgid "Remove from selected stock items" msgstr "" -#: templates/stock_table.html:15 +#: templates/stock_table.html:19 msgid "Stocktake selected stock items" msgstr "" -#: templates/stock_table.html:16 +#: templates/stock_table.html:20 msgid "Move selected stock items" msgstr "" -#: templates/stock_table.html:16 +#: templates/stock_table.html:20 msgid "Move stock" msgstr "" -#: templates/stock_table.html:17 +#: templates/stock_table.html:21 msgid "Order selected items" msgstr "" -#: templates/stock_table.html:17 +#: templates/stock_table.html:21 msgid "Order stock" msgstr "" -#: templates/stock_table.html:18 +#: templates/stock_table.html:24 msgid "Delete selected items" msgstr "" -#: templates/stock_table.html:18 +#: templates/stock_table.html:24 msgid "Delete Stock" msgstr "" + +#: users/admin.py:61 +msgid "Users" +msgstr "" + +#: users/admin.py:62 +msgid "Select which users are assigned to this group" +msgstr "" + +#: users/admin.py:120 +msgid "Personal info" +msgstr "" + +#: users/admin.py:121 +msgid "Permissions" +msgstr "" + +#: users/admin.py:124 +msgid "Important dates" +msgstr "" + +#: users/models.py:128 +msgid "Permission set" +msgstr "" + +#: users/models.py:136 +msgid "Group" +msgstr "" + +#: users/models.py:139 +msgid "View" +msgstr "" + +#: users/models.py:139 +msgid "Permission to view items" +msgstr "" + +#: users/models.py:141 +msgid "Add" +msgstr "" + +#: users/models.py:141 +msgid "Permission to add items" +msgstr "" + +#: users/models.py:143 +msgid "Change" +msgstr "" + +#: users/models.py:143 +msgid "Permissions to edit items" +msgstr "" + +#: users/models.py:145 +msgid "Permission to delete items" +msgstr "" diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index a7915878c5..4a9dbfa2ac 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -6,7 +6,7 @@ JSON API for the Order app from __future__ import unicode_literals from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import generics, permissions +from rest_framework import generics from rest_framework import filters from django.conf.urls import url, include @@ -109,10 +109,6 @@ class POList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -162,10 +158,6 @@ class PODetail(generics.RetrieveUpdateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated - ] - class POLineItemList(generics.ListCreateAPIView): """ API endpoint for accessing a list of POLineItem objects @@ -188,10 +180,6 @@ class POLineItemList(generics.ListCreateAPIView): return self.serializer_class(*args, **kwargs) - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, ] @@ -208,10 +196,6 @@ class POLineItemDetail(generics.RetrieveUpdateAPIView): queryset = PurchaseOrderLineItem serializer_class = POLineItemSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ @@ -300,10 +284,6 @@ class SOList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -351,8 +331,6 @@ class SODetail(generics.RetrieveUpdateAPIView): return queryset - permission_classes = [permissions.IsAuthenticated] - class SOLineItemList(generics.ListCreateAPIView): """ @@ -398,8 +376,6 @@ class SOLineItemList(generics.ListCreateAPIView): return queryset - permission_classes = [permissions.IsAuthenticated] - filter_backends = [DjangoFilterBackend] filter_fields = [ @@ -414,8 +390,6 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView): queryset = SalesOrderLineItem.objects.all() serializer_class = SOLineItemSerializer - permission_classes = [permissions.IsAuthenticated] - class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html index 036021b12d..3b199c1840 100644 --- a/InvenTree/order/templates/order/order_base.html +++ b/InvenTree/order/templates/order/order_base.html @@ -24,7 +24,7 @@ src="{% static 'img/blank_image.png' %}"

{{ order }} - {% if user.is_staff and perms.order.change_purchaseorder %} + {% if user.is_staff and roles.purchase_order.change %} {% endif %}

@@ -32,29 +32,31 @@ src="{% static 'img/blank_image.png' %}"

- - {% if order.status == PurchaseOrderStatus.PENDING and order.lines.count > 0 %} - {% elif order.status == PurchaseOrderStatus.PLACED %} - - {% endif %} {% if order.status == PurchaseOrderStatus.PENDING or order.status == PurchaseOrderStatus.PLACED %} - {% endif %} + {% endif %} +

diff --git a/InvenTree/order/templates/order/order_notes.html b/InvenTree/order/templates/order/order_notes.html index 237098e10d..0b45a5da4b 100644 --- a/InvenTree/order/templates/order/order_notes.html +++ b/InvenTree/order/templates/order/order_notes.html @@ -28,9 +28,11 @@

{% trans "Order Notes" %}

+ {% if roles.purchase_order.change %}
+ {% endif %}

diff --git a/InvenTree/order/templates/order/po_attachments.html b/InvenTree/order/templates/order/po_attachments.html index f5421e8760..d60d605771 100644 --- a/InvenTree/order/templates/order/po_attachments.html +++ b/InvenTree/order/templates/order/po_attachments.html @@ -14,7 +14,6 @@ {% include "attachment_table.html" with attachments=order.attachments.all %} - {% endblock %} {% block js_ready %} diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index 9b9eddb887..642ff96d4a 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -12,7 +12,7 @@
- {% if order.status == PurchaseOrderStatus.PENDING %} + {% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %} {% endif %}
@@ -209,12 +209,12 @@ $("#po-table").inventreeTable({ var pk = row.pk; - {% if order.status == PurchaseOrderStatus.PENDING %} + {% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.delete %} html += makeIconButton('fa-edit icon-blue', 'button-line-edit', pk, '{% trans "Edit line item" %}'); html += makeIconButton('fa-trash-alt icon-red', 'button-line-delete', pk, '{% trans "Delete line item" %}'); {% endif %} - {% if order.status == PurchaseOrderStatus.PLACED %} + {% if order.status == PurchaseOrderStatus.PLACED and roles.purchase_order.change %} if (row.received < row.quantity) { html += makeIconButton('fa-clipboard-check', 'button-line-receive', pk, '{% trans "Receive line item" %}'); } diff --git a/InvenTree/order/templates/order/purchase_orders.html b/InvenTree/order/templates/order/purchase_orders.html index 1019092151..d02af36ff5 100644 --- a/InvenTree/order/templates/order/purchase_orders.html +++ b/InvenTree/order/templates/order/purchase_orders.html @@ -14,7 +14,9 @@ InvenTree | {% trans "Purchase Orders" %}
+ {% if roles.purchase_order.add %} + {% endif %}
diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html index 70505ddccc..2eb13eaa64 100644 --- a/InvenTree/order/templates/order/sales_order_base.html +++ b/InvenTree/order/templates/order/sales_order_base.html @@ -34,19 +34,17 @@ src="{% static 'img/blank_image.png' %}"

{{ order }} - {% if user.is_staff and perms.order.change_salesorder %} + {% if user.is_staff and roles.sales_order.change %} {% endif %}

{{ order.description }}

+ {% if roles.sales_order.change %} - {% if order.status == SalesOrderStatus.PENDING %} {% endif %} + {% endif %} +
{% endblock %} diff --git a/InvenTree/order/templates/order/sales_orders.html b/InvenTree/order/templates/order/sales_orders.html index 4e29156773..dfe09d5d0d 100644 --- a/InvenTree/order/templates/order/sales_orders.html +++ b/InvenTree/order/templates/order/sales_orders.html @@ -14,7 +14,9 @@ InvenTree | {% trans "Sales Orders" %}
+ {% if roles.sales_order.add %} + {% endif %}
diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index 932cac9060..246cc2dd48 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from InvenTree.status_codes import PurchaseOrderStatus @@ -32,7 +33,21 @@ class OrderViewTestCase(TestCase): # Create a user User = get_user_model() - User.objects.create_user('username', 'user@email.com', 'password') + user = User.objects.create_user('username', 'user@email.com', 'password') + + # Ensure that the user has the correct permissions! + g = Group.objects.create(name='orders') + user.groups.add(g) + + for rule in g.rule_sets.all(): + if rule.name in ['purchase_order', 'sales_order']: + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + g.save() self.client.login(username='username', password='password') diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 5d140fb77c..d2d02bb2c9 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -28,19 +28,22 @@ from . import forms as order_forms from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.helpers import DownloadFile, str2bool +from InvenTree.views import InvenTreeRoleMixin from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus logger = logging.getLogger(__name__) -class PurchaseOrderIndex(ListView): +class PurchaseOrderIndex(InvenTreeRoleMixin, ListView): """ List view for all purchase orders """ model = PurchaseOrder template_name = 'order/purchase_orders.html' context_object_name = 'orders' + role_required = 'purchase_order.view' + def get_queryset(self): """ Retrieve the list of purchase orders, ensure that the most recent ones are returned first. """ @@ -55,19 +58,21 @@ class PurchaseOrderIndex(ListView): return ctx -class SalesOrderIndex(ListView): +class SalesOrderIndex(InvenTreeRoleMixin, ListView): model = SalesOrder template_name = 'order/sales_orders.html' context_object_name = 'orders' + role_required = 'sales_order.view' -class PurchaseOrderDetail(DetailView): +class PurchaseOrderDetail(InvenTreeRoleMixin, DetailView): """ Detail view for a PurchaseOrder object """ context_object_name = 'order' queryset = PurchaseOrder.objects.all().prefetch_related('lines') template_name = 'order/purchase_order_detail.html' + role_required = 'purchase_order.view' def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) @@ -75,12 +80,13 @@ class PurchaseOrderDetail(DetailView): return ctx -class SalesOrderDetail(DetailView): +class SalesOrderDetail(InvenTreeRoleMixin, DetailView): """ Detail view for a SalesOrder object """ context_object_name = 'order' queryset = SalesOrder.objects.all().prefetch_related('lines') template_name = 'order/sales_order_detail.html' + role_required = 'sales_order.view' class PurchaseOrderAttachmentCreate(AjaxCreateView): @@ -92,6 +98,7 @@ class PurchaseOrderAttachmentCreate(AjaxCreateView): form_class = order_forms.EditPurchaseOrderAttachmentForm ajax_form_title = _("Add Purchase Order Attachment") ajax_template_name = "modal_form.html" + role_required = 'purchase_order.add' def post_save(self, **kwargs): self.object.user = self.request.user @@ -139,6 +146,7 @@ class SalesOrderAttachmentCreate(AjaxCreateView): model = SalesOrderAttachment form_class = order_forms.EditSalesOrderAttachmentForm ajax_form_title = _('Add Sales Order Attachment') + role_required = 'sales_order.add' def post_save(self, **kwargs): self.object.user = self.request.user @@ -174,6 +182,7 @@ class PurchaseOrderAttachmentEdit(AjaxUpdateView): model = PurchaseOrderAttachment form_class = order_forms.EditPurchaseOrderAttachmentForm ajax_form_title = _("Edit Attachment") + role_required = 'purchase_order.change' def get_data(self): return { @@ -195,6 +204,7 @@ class SalesOrderAttachmentEdit(AjaxUpdateView): model = SalesOrderAttachment form_class = order_forms.EditSalesOrderAttachmentForm ajax_form_title = _("Edit Attachment") + role_required = 'sales_order.change' def get_data(self): return { @@ -216,6 +226,7 @@ class PurchaseOrderAttachmentDelete(AjaxDeleteView): ajax_form_title = _("Delete Attachment") ajax_template_name = "order/delete_attachment.html" context_object_name = "attachment" + role_required = 'purchase_order.delete' def get_data(self): return { @@ -230,6 +241,7 @@ class SalesOrderAttachmentDelete(AjaxDeleteView): ajax_form_title = _("Delete Attachment") ajax_template_name = "order/delete_attachment.html" context_object_name = "attachment" + role_required = 'sales_order.delete' def get_data(self): return { @@ -237,12 +249,13 @@ class SalesOrderAttachmentDelete(AjaxDeleteView): } -class PurchaseOrderNotes(UpdateView): +class PurchaseOrderNotes(InvenTreeRoleMixin, UpdateView): """ View for updating the 'notes' field of a PurchaseOrder """ context_object_name = 'order' template_name = 'order/order_notes.html' model = PurchaseOrder + role_required = 'purchase_order.view' fields = ['notes'] @@ -259,12 +272,13 @@ class PurchaseOrderNotes(UpdateView): return ctx -class SalesOrderNotes(UpdateView): +class SalesOrderNotes(InvenTreeRoleMixin, UpdateView): """ View for editing the 'notes' field of a SalesORder """ context_object_name = 'order' template_name = 'order/sales_order_notes.html' model = SalesOrder + role_required = 'sales_order.view' fields = ['notes'] @@ -286,6 +300,7 @@ class PurchaseOrderCreate(AjaxCreateView): model = PurchaseOrder ajax_form_title = _("Create Purchase Order") form_class = order_forms.EditPurchaseOrderForm + role_required = 'purchase_order.add' def get_initial(self): initials = super().get_initial().copy() @@ -317,6 +332,7 @@ class SalesOrderCreate(AjaxCreateView): model = SalesOrder ajax_form_title = _("Create Sales Order") form_class = order_forms.EditSalesOrderForm + role_required = 'sales_order.add' def get_initial(self): initials = super().get_initial().copy() @@ -347,6 +363,7 @@ class PurchaseOrderEdit(AjaxUpdateView): model = PurchaseOrder ajax_form_title = _('Edit Purchase Order') form_class = order_forms.EditPurchaseOrderForm + role_required = 'purchase_order.change' def get_form(self): @@ -367,6 +384,7 @@ class SalesOrderEdit(AjaxUpdateView): model = SalesOrder ajax_form_title = _('Edit Sales Order') form_class = order_forms.EditSalesOrderForm + role_required = 'sales_order.change' def get_form(self): form = super().get_form() @@ -384,6 +402,7 @@ class PurchaseOrderCancel(AjaxUpdateView): ajax_form_title = _('Cancel Order') ajax_template_name = 'order/order_cancel.html' form_class = order_forms.CancelPurchaseOrderForm + role_required = 'purchase_order.change' def post(self, request, *args, **kwargs): """ Mark the PO as 'CANCELLED' """ @@ -417,6 +436,7 @@ class SalesOrderCancel(AjaxUpdateView): ajax_form_title = _("Cancel sales order") ajax_template_name = "order/sales_order_cancel.html" form_class = order_forms.CancelSalesOrderForm + role_required = 'sales_order.change' def post(self, request, *args, **kwargs): @@ -451,6 +471,7 @@ class PurchaseOrderIssue(AjaxUpdateView): ajax_form_title = _('Issue Order') ajax_template_name = "order/order_issue.html" form_class = order_forms.IssuePurchaseOrderForm + role_required = 'purchase_order.change' def post(self, request, *args, **kwargs): """ Mark the purchase order as 'PLACED' """ @@ -486,6 +507,7 @@ class PurchaseOrderComplete(AjaxUpdateView): ajax_template_name = "order/order_complete.html" ajax_form_title = _("Complete Order") context_object_name = 'order' + role_required = 'purchase_order.change' def get_context_data(self): @@ -520,6 +542,7 @@ class SalesOrderShip(AjaxUpdateView): context_object_name = 'order' ajax_template_name = 'order/sales_order_ship.html' ajax_form_title = _('Ship Order') + role_required = 'sales_order.change' def post(self, request, *args, **kwargs): @@ -563,6 +586,7 @@ class PurchaseOrderExport(AjaxView): """ model = PurchaseOrder + role_required = 'purchase_order.view' def get(self, request, *args, **kwargs): @@ -594,6 +618,7 @@ class PurchaseOrderReceive(AjaxUpdateView): form_class = order_forms.ReceivePurchaseOrderForm ajax_form_title = _("Receive Parts") ajax_template_name = "order/receive_parts.html" + role_required = 'purchase_order.change' # Where the parts will be going (selected in POST request) destination = None @@ -779,6 +804,11 @@ class OrderParts(AjaxView): ajax_form_title = _("Order Parts") ajax_template_name = 'order/order_wizard/select_parts.html' + role_required = [ + 'part.view', + 'purchase_order.change', + ] + # List of Parts we wish to order parts = [] suppliers = [] @@ -1085,6 +1115,7 @@ class POLineItemCreate(AjaxCreateView): context_object_name = 'line' form_class = order_forms.EditPurchaseOrderLineItemForm ajax_form_title = _('Add Line Item') + role_required = 'purchase_order.add' def post(self, request, *arg, **kwargs): @@ -1199,6 +1230,7 @@ class SOLineItemCreate(AjaxCreateView): context_order_name = 'line' form_class = order_forms.EditSalesOrderLineItemForm ajax_form_title = _('Add Line Item') + role_required = 'sales_order.add' def get_form(self, *args, **kwargs): @@ -1250,6 +1282,7 @@ class SOLineItemEdit(AjaxUpdateView): model = SalesOrderLineItem form_class = order_forms.EditSalesOrderLineItemForm ajax_form_title = _('Edit Line Item') + role_required = 'sales_order.change' def get_form(self): form = super().get_form() @@ -1268,6 +1301,7 @@ class POLineItemEdit(AjaxUpdateView): form_class = order_forms.EditPurchaseOrderLineItemForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Line Item') + role_required = 'purchase_order.change' def get_form(self): form = super().get_form() @@ -1285,7 +1319,8 @@ class POLineItemDelete(AjaxDeleteView): model = PurchaseOrderLineItem ajax_form_title = _('Delete Line Item') ajax_template_name = 'order/po_lineitem_delete.html' - + role_required = 'purchase_order.delete' + def get_data(self): return { 'danger': _('Deleted line item'), @@ -1297,6 +1332,7 @@ class SOLineItemDelete(AjaxDeleteView): model = SalesOrderLineItem ajax_form_title = _("Delete Line Item") ajax_template_name = "order/so_lineitem_delete.html" + role_required = 'sales_order.delete' def get_data(self): return { @@ -1310,6 +1346,7 @@ class SalesOrderAllocationCreate(AjaxCreateView): model = SalesOrderAllocation form_class = order_forms.EditSalesOrderAllocationForm ajax_form_title = _('Allocate Stock to Order') + role_required = 'sales_order.add' def get_initial(self): initials = super().get_initial().copy() @@ -1379,6 +1416,7 @@ class SalesOrderAllocationEdit(AjaxUpdateView): model = SalesOrderAllocation form_class = order_forms.EditSalesOrderAllocationForm ajax_form_title = _('Edit Allocation Quantity') + role_required = 'sales_order.change' def get_form(self): form = super().get_form() @@ -1396,3 +1434,4 @@ class SalesOrderAllocationDelete(AjaxDeleteView): ajax_form_title = _("Remove allocation") context_object_name = 'allocation' ajax_template_name = "order/so_allocation_delete.html" + role_required = 'sales_order.delete' diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 6834503466..d643b8671a 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -44,6 +44,10 @@ class PartCategoryTree(TreeSerializer): def get_items(self): return PartCategory.objects.all().prefetch_related('parts', 'children') + permission_classes = [ + permissions.IsAuthenticated, + ] + class CategoryList(generics.ListCreateAPIView): """ API endpoint for accessing a list of PartCategory objects. @@ -55,10 +59,6 @@ class CategoryList(generics.ListCreateAPIView): queryset = PartCategory.objects.all() serializer_class = part_serializers.CategorySerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - def get_queryset(self): """ Custom filtering: @@ -119,10 +119,6 @@ class PartSalePriceList(generics.ListCreateAPIView): queryset = PartSellPriceBreak.objects.all() serializer_class = part_serializers.PartSalePriceSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend ] @@ -182,8 +178,6 @@ class PartTestTemplateList(generics.ListCreateAPIView): return queryset - permission_classes = [permissions.IsAuthenticated] - filter_backends = [ DjangoFilterBackend, filters.OrderingFilter, @@ -221,10 +215,6 @@ class PartThumbsUpdate(generics.RetrieveUpdateAPIView): queryset = Part.objects.all() serializer_class = part_serializers.PartThumbSerializerUpdate - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend ] @@ -246,10 +236,6 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - def get_serializer(self, *args, **kwargs): try: @@ -580,10 +566,6 @@ class PartList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -676,10 +658,6 @@ class PartParameterTemplateList(generics.ListCreateAPIView): queryset = PartParameterTemplate.objects.all() serializer_class = part_serializers.PartParameterTemplateSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ filters.OrderingFilter, ] @@ -699,10 +677,6 @@ class PartParameterList(generics.ListCreateAPIView): queryset = PartParameter.objects.all() serializer_class = part_serializers.PartParameterSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend ] @@ -796,10 +770,6 @@ class BomList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -816,10 +786,6 @@ class BomDetail(generics.RetrieveUpdateDestroyAPIView): queryset = BomItem.objects.all() serializer_class = part_serializers.BomItemSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - class BomItemValidate(generics.UpdateAPIView): """ API endpoint for validating a BomItem """ diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index 2653a42575..c996edc2db 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -39,10 +39,12 @@ {% elif part.active %} + {% if roles.part.change %} {% if part.is_bom_valid == False %} {% endif %} + {% endif %} {% endif %}
diff --git a/InvenTree/part/templates/part/build.html b/InvenTree/part/templates/part/build.html index 442693ddeb..bfd72a2f70 100644 --- a/InvenTree/part/templates/part/build.html +++ b/InvenTree/part/templates/part/build.html @@ -1,15 +1,18 @@ {% extends "part/part_base.html" %} {% load static %} +{% load i18n %} {% block details %} {% include 'part/tabs.html' with tab='build' %} -

Part Builds

+

{% trans "Part Builds" %}

{% if part.active %} - + {% if roles.build.add %} + + {% endif %} {% endif %}
diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 9a6fb9d7e5..d73aba0291 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -9,7 +9,7 @@ {% if category %}

{{ category.name }} - {% if user.is_staff and perms.part.change_partcategory %} + {% if user.is_staff and roles.part.change %} {% endif %}

@@ -20,17 +20,23 @@ {% endif %}

+ {% if roles.part.add %} + {% endif %} {% if category %} + {% if roles.part.change %} + {% endif %} + {% if roles.part.delete %} {% endif %} + {% endif %}

@@ -104,11 +110,15 @@
+ {% if roles.part.add %} + {% endif %}
@@ -180,6 +190,7 @@ location.href = url; }); + {% if roles.part.add %} $("#part-create").click(function() { launchModalForm( "{% url 'part-create' %}", @@ -207,6 +218,7 @@ } ); }); + {% endif %} {% if category %} $("#cat-edit").click(function () { diff --git a/InvenTree/part/templates/part/notes.html b/InvenTree/part/templates/part/notes.html index afa215600d..3f833325cd 100644 --- a/InvenTree/part/templates/part/notes.html +++ b/InvenTree/part/templates/part/notes.html @@ -29,7 +29,9 @@

{% trans "Part Notes" %}

+ {% if roles.part.change %} + {% endif %}

diff --git a/InvenTree/part/templates/part/params.html b/InvenTree/part/templates/part/params.html index 8ad4f61252..ba8aa0566d 100644 --- a/InvenTree/part/templates/part/params.html +++ b/InvenTree/part/templates/part/params.html @@ -10,7 +10,9 @@
+ {% if roles.part.add %} + {% endif %}
@@ -30,8 +32,12 @@ {{ param.template.units }}
+ {% if roles.part.change %} + {% endif %} + {% if roles.part.delete %} + {% endif %}
@@ -48,6 +54,7 @@ $('#param-table').inventreeTable({ }); + {% if roles.part.add %} $('#param-create').click(function() { launchModalForm("{% url 'part-param-create' %}?part={{ part.id }}", { reload: true, @@ -59,6 +66,7 @@ }], }); }); + {% endif %} $('.param-edit').click(function() { var button = $(this); diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 1de4976740..d7604deae4 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -28,7 +28,7 @@

{{ part.full_name }} - {% if user.is_staff and perms.part.change_part %} + {% if user.is_staff and roles.part.change %} {% endif %} {% if not part.active %} @@ -56,26 +56,36 @@ - + {% endif %} {% if part.purchaseable %} - {% endif %} {% endif %} + {% endif %} + {% if roles.part.add or roles.part.change or roles.part.delete %}
+ {% endif %}

@@ -274,6 +284,7 @@ }); }); + {% if roles.part.change %} $("#part-edit").click(function() { launchModalForm( "{% url 'part-edit' part.id %}", @@ -282,6 +293,7 @@ } ); }); + {% endif %} $("#part-order").click(function() { launchModalForm("{% url 'order-parts' %}", { @@ -292,6 +304,7 @@ }); }); + {% if roles.part.add %} $("#part-duplicate").click(function() { launchModalForm( "{% url 'part-duplicate' part.id %}", @@ -300,8 +313,9 @@ } ); }); + {% endif %} - {% if not part.active %} + {% if not part.active and roles.part.delete %} $("#part-delete").click(function() { launchModalForm( "{% url 'part-delete' part.id %}", diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index 1eab299ed5..8322a225bc 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -26,14 +26,17 @@ {% if part.assembly %} {% trans "BOM" %}{{ part.bom_count }} + {% if roles.build.view %} - {% trans "Build Orders" %}{{ part.builds.count }} + {% trans "Build Orders" %}{{ part.builds.count }} + + {% endif %} {% endif %} {% if part.component or part.used_in_count > 0 %} {% trans "Used In" %} {% if part.used_in_count > 0 %}{{ part.used_in_count }}{% endif %} {% endif %} - {% if part.purchaseable %} + {% if part.purchaseable and roles.purchase_order.view %} {% if part.is_template == False %} {% trans "Suppliers" %} @@ -45,7 +48,7 @@ {% trans "Purchase Orders" %} {{ part.purchase_orders|length }} {% endif %} - {% if part.salable %} + {% if part.salable and roles.sales_order.view %}
  • {% trans "Sale Price" %}
  • diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 3b116fa445..328ad3ef9e 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -3,6 +3,7 @@ from rest_framework import status from django.urls import reverse from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from part.models import Part from stock.models import StockItem @@ -29,7 +30,26 @@ class PartAPITest(APITestCase): def setUp(self): # Create a user for auth User = get_user_model() - User.objects.create_user('testuser', 'test@testing.com', 'password') + self.user = User.objects.create_user( + username='testuser', + email='test@testing.com', + password='password' + ) + + # Put the user into a group with the correct permissions + group = Group.objects.create(name='mygroup') + self.user.groups.add(group) + + # Give the group *all* the permissions! + for rule in group.rule_sets.all(): + rule.can_view = True + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + group.save() self.client.login(username='testuser', password='password') diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index bc09784a47..d8c345d243 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -3,6 +3,7 @@ from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from .models import Part @@ -23,7 +24,24 @@ class PartViewTestCase(TestCase): # Create a user User = get_user_model() - User.objects.create_user('username', 'user@email.com', 'password') + self.user = User.objects.create_user( + username='username', + email='user@email.com', + password='password' + ) + + # Put the user into a group with the correct permissions + group = Group.objects.create(name='mygroup') + self.user.groups.add(group) + + # Give the group *all* the permissions! + for rule in group.rule_sets.all(): + rule.can_view = True + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() self.client.login(username='username', password='password') @@ -140,12 +158,14 @@ class PartTests(PartViewTestCase): """ Tests for Part forms """ def test_part_edit(self): + response = self.client.get(reverse('part-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) keys = response.context.keys() data = str(response.content) + self.assertEqual(response.status_code, 200) + self.assertIn('part', keys) self.assertIn('csrf_token', keys) @@ -189,6 +209,8 @@ class PartAttachmentTests(PartViewTestCase): response = self.client.get(reverse('part-attachment-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) + # TODO - Create a new attachment using this view + def test_invalid_create(self): """ test creation of an attachment for an invalid part """ diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index dc8d07f5cd..6498774285 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -38,17 +38,21 @@ from .admin import PartResource from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import QRCodeView +from InvenTree.views import InvenTreeRoleMixin from InvenTree.helpers import DownloadFile, str2bool -class PartIndex(ListView): +class PartIndex(InvenTreeRoleMixin, ListView): """ View for displaying list of Part objects """ + model = Part template_name = 'part/category.html' context_object_name = 'parts' + role_required = 'part.view' + def get_queryset(self): return Part.objects.all().select_related('category') @@ -76,6 +80,8 @@ class PartAttachmentCreate(AjaxCreateView): ajax_form_title = _("Add part attachment") ajax_template_name = "modal_form.html" + role_required = 'part.add' + def post_save(self): """ Record the user that uploaded the attachment """ self.object.user = self.request.user @@ -123,6 +129,8 @@ class PartAttachmentEdit(AjaxUpdateView): form_class = part_forms.EditPartAttachmentForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit attachment') + + role_required = 'part.change' def get_data(self): return { @@ -145,6 +153,8 @@ class PartAttachmentDelete(AjaxDeleteView): ajax_template_name = "attachment_delete.html" context_object_name = "attachment" + role_required = 'part.delete' + def get_data(self): return { 'danger': _('Deleted part attachment') @@ -157,6 +167,8 @@ class PartTestTemplateCreate(AjaxCreateView): model = PartTestTemplate form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Create Test Template") + + role_required = 'part.add' def get_initial(self): @@ -185,6 +197,8 @@ class PartTestTemplateEdit(AjaxUpdateView): form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Edit Test Template") + role_required = 'part.change' + def get_form(self): form = super().get_form() @@ -199,6 +213,8 @@ class PartTestTemplateDelete(AjaxDeleteView): model = PartTestTemplate ajax_form_title = _("Delete Test Template") + role_required = 'part.delete' + class PartSetCategory(AjaxUpdateView): """ View for settings the part category for multiple parts at once """ @@ -207,6 +223,8 @@ class PartSetCategory(AjaxUpdateView): ajax_form_title = _('Set Part Category') form_class = part_forms.SetPartCategoryForm + role_required = 'part.change' + category = None parts = [] @@ -290,6 +308,8 @@ class MakePartVariant(AjaxCreateView): ajax_form_title = _('Create Variant') ajax_template_name = 'part/variant_part.html' + role_required = 'part.add' + def get_part_template(self): return get_object_or_404(Part, id=self.kwargs['pk']) @@ -368,6 +388,8 @@ class PartDuplicate(AjaxCreateView): ajax_form_title = _("Duplicate Part") ajax_template_name = "part/copy_part.html" + role_required = 'part.add' + def get_data(self): return { 'success': _('Copied part') @@ -491,6 +513,8 @@ class PartCreate(AjaxCreateView): ajax_form_title = _('Create new part') ajax_template_name = 'part/create_part.html' + role_required = 'part.add' + def get_data(self): return { 'success': _("Created new part"), @@ -613,6 +637,8 @@ class PartNotes(UpdateView): template_name = 'part/notes.html' model = Part + role_required = 'part.change' + fields = ['notes'] def get_success_url(self): @@ -634,7 +660,7 @@ class PartNotes(UpdateView): return ctx -class PartDetail(DetailView): +class PartDetail(InvenTreeRoleMixin, DetailView): """ Detail view for Part object """ @@ -642,6 +668,8 @@ class PartDetail(DetailView): queryset = Part.objects.all().select_related('category') template_name = 'part/detail.html' + role_required = 'part.view' + # Add in some extra context information based on query params def get_context_data(self, **kwargs): """ Provide extra context data to template @@ -706,6 +734,8 @@ class PartQRCode(QRCodeView): ajax_form_title = _("Part QR Code") + role_required = 'part.view' + def get_qr_data(self): """ Generate QR code data for the Part """ @@ -722,8 +752,11 @@ class PartImageUpload(AjaxUpdateView): model = Part ajax_template_name = 'modal_form.html' ajax_form_title = _('Upload Part Image') + form_class = part_forms.PartImageForm + role_required = 'part.change' + def get_data(self): return { 'success': _('Updated part image'), @@ -737,6 +770,8 @@ class PartImageSelect(AjaxUpdateView): ajax_template_name = 'part/select_image.html' ajax_form_title = _('Select Part Image') + role_required = 'part.change' + fields = [ 'image', ] @@ -778,6 +813,8 @@ class PartEdit(AjaxUpdateView): ajax_form_title = _('Edit Part Properties') context_object_name = 'part' + role_required = 'part.change' + def get_form(self): """ Create form for Part editing. Overrides default get_form() method to limit the choices @@ -802,6 +839,8 @@ class BomValidate(AjaxUpdateView): context_object_name = 'part' form_class = part_forms.BomValidateForm + role_required = 'part.change' + def get_context(self): return { 'part': self.get_object(), @@ -832,7 +871,7 @@ class BomValidate(AjaxUpdateView): return self.renderJsonResponse(request, form, data, context=self.get_context()) -class BomUpload(FormView): +class BomUpload(InvenTreeRoleMixin, FormView): """ View for uploading a BOM file, and handling BOM data importing. The BOM upload process is as follows: @@ -868,6 +907,8 @@ class BomUpload(FormView): missing_columns = [] allowed_parts = [] + role_required = ('part.change', 'part.add') + def get_success_url(self): part = self.get_object() return reverse('upload-bom', kwargs={'pk': part.id}) @@ -1466,6 +1507,8 @@ class BomUpload(FormView): class PartExport(AjaxView): """ Export a CSV file containing information on multiple parts """ + role_required = 'part.view' + def get_parts(self, request): """ Extract part list from the POST parameters. Parts can be supplied as: @@ -1543,6 +1586,8 @@ class BomDownload(AjaxView): - File format should be passed as a query param e.g. ?format=csv """ + role_required = 'part.view' + model = Part def get(self, request, *args, **kwargs): @@ -1596,6 +1641,8 @@ class BomExport(AjaxView): form_class = part_forms.BomExportForm ajax_form_title = _("Export Bill of Materials") + role_required = 'part.view' + def get(self, request, *args, **kwargs): return self.renderJsonResponse(request, self.form_class()) @@ -1645,6 +1692,8 @@ class PartDelete(AjaxDeleteView): ajax_form_title = _('Confirm Part Deletion') context_object_name = 'part' + role_required = 'part.delete' + success_url = '/part/' def get_data(self): @@ -1661,6 +1710,8 @@ class PartPricing(AjaxView): ajax_form_title = _("Part Pricing") form_class = part_forms.PartPriceForm + role_required = ['sales_order.view', 'part.view'] + def get_part(self): try: return Part.objects.get(id=self.kwargs['pk']) @@ -1778,6 +1829,8 @@ class PartPricing(AjaxView): class PartParameterTemplateCreate(AjaxCreateView): """ View for creating a new PartParameterTemplate """ + role_required = 'part.add' + model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm ajax_form_title = _('Create Part Parameter Template') @@ -1786,6 +1839,8 @@ class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateEdit(AjaxUpdateView): """ View for editing a PartParameterTemplate """ + role_required = 'part.change' + model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm ajax_form_title = _('Edit Part Parameter Template') @@ -1794,6 +1849,8 @@ class PartParameterTemplateEdit(AjaxUpdateView): class PartParameterTemplateDelete(AjaxDeleteView): """ View for deleting an existing PartParameterTemplate """ + role_required = 'part.delete' + model = PartParameterTemplate ajax_form_title = _("Delete Part Parameter Template") @@ -1801,6 +1858,8 @@ class PartParameterTemplateDelete(AjaxDeleteView): class PartParameterCreate(AjaxCreateView): """ View for creating a new PartParameter """ + role_required = 'part.add' + model = PartParameter form_class = part_forms.EditPartParameterForm ajax_form_title = _('Create Part Parameter') @@ -1851,6 +1910,8 @@ class PartParameterCreate(AjaxCreateView): class PartParameterEdit(AjaxUpdateView): """ View for editing a PartParameter """ + role_required = 'part.change' + model = PartParameter form_class = part_forms.EditPartParameterForm ajax_form_title = _('Edit Part Parameter') @@ -1865,12 +1926,14 @@ class PartParameterEdit(AjaxUpdateView): class PartParameterDelete(AjaxDeleteView): """ View for deleting a PartParameter """ + role_required = 'part.delete' + model = PartParameter ajax_template_name = 'part/param_delete.html' ajax_form_title = _('Delete Part Parameter') -class CategoryDetail(DetailView): +class CategoryDetail(InvenTreeRoleMixin, DetailView): """ Detail view for PartCategory """ model = PartCategory @@ -1878,6 +1941,8 @@ class CategoryDetail(DetailView): queryset = PartCategory.objects.all().prefetch_related('children') template_name = 'part/category_partlist.html' + role_required = 'part.view' + def get_context_data(self, **kwargs): context = super(CategoryDetail, self).get_context_data(**kwargs).copy() @@ -1926,6 +1991,8 @@ class CategoryEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Part Category') + role_required = 'part.change' + def get_context_data(self, **kwargs): context = super(CategoryEdit, self).get_context_data(**kwargs).copy() @@ -1963,6 +2030,8 @@ class CategoryDelete(AjaxDeleteView): context_object_name = 'category' success_url = '/part/' + role_required = 'part.delete' + def get_data(self): return { 'danger': _('Part category was deleted'), @@ -1977,6 +2046,8 @@ class CategoryCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' form_class = part_forms.EditCategoryForm + role_required = 'part.add' + def get_context_data(self, **kwargs): """ Add extra context data to template. @@ -2012,12 +2083,14 @@ class CategoryCreate(AjaxCreateView): return initials -class BomItemDetail(DetailView): +class BomItemDetail(InvenTreeRoleMixin, DetailView): """ Detail view for BomItem """ context_object_name = 'item' queryset = BomItem.objects.all() template_name = 'part/bom-detail.html' + role_required = 'part.view' + class BomItemCreate(AjaxCreateView): """ Create view for making a new BomItem object """ @@ -2026,6 +2099,8 @@ class BomItemCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Create BOM item') + role_required = 'part.add' + def get_form(self): """ Override get_form() method to reduce Part selection options. @@ -2092,6 +2167,8 @@ class BomItemEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit BOM item') + role_required = 'part.change' + def get_form(self): """ Override get_form() method to filter part selection options @@ -2140,6 +2217,8 @@ class BomItemDelete(AjaxDeleteView): context_object_name = 'item' ajax_form_title = _('Confim BOM item deletion') + role_required = 'part.delete' + class PartSalePriceBreakCreate(AjaxCreateView): """ View for creating a sale price break for a part """ @@ -2147,6 +2226,8 @@ class PartSalePriceBreakCreate(AjaxCreateView): model = PartSellPriceBreak form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Add Price Break') + + role_required = 'part.add' def get_data(self): return { @@ -2197,6 +2278,8 @@ class PartSalePriceBreakEdit(AjaxUpdateView): form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Edit Price Break') + role_required = 'part.change' + def get_form(self): form = super().get_form() @@ -2211,3 +2294,5 @@ class PartSalePriceBreakDelete(AjaxDeleteView): model = PartSellPriceBreak ajax_form_title = _("Delete Price Break") ajax_template_name = "modal_delete_form.html" + + role_required = 'part.delete' diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 55bc62a44e..ba802b75d9 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -52,6 +52,10 @@ class StockCategoryTree(TreeSerializer): def get_items(self): return StockLocation.objects.all().prefetch_related('stock_items', 'children') + permission_classes = [ + permissions.IsAuthenticated, + ] + class StockDetail(generics.RetrieveUpdateDestroyAPIView): """ API detail endpoint for Stock object @@ -68,7 +72,6 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StockItem.objects.all() serializer_class = StockItemSerializer - permission_classes = (permissions.IsAuthenticated,) def get_queryset(self, *args, **kwargs): @@ -289,10 +292,6 @@ class StockLocationList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -695,10 +694,6 @@ class StockList(generics.ListCreateAPIView): return queryset - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -744,10 +739,6 @@ class StockItemTestResultList(generics.ListCreateAPIView): queryset = StockItemTestResult.objects.all() serializer_class = StockItemTestResultSerializer - permission_classes = [ - permissions.IsAuthenticated, - ] - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -799,7 +790,6 @@ class StockTrackingList(generics.ListCreateAPIView): queryset = StockItemTracking.objects.all() serializer_class = StockTrackingSerializer - permission_classes = [permissions.IsAuthenticated] def get_serializer(self, *args, **kwargs): try: @@ -871,7 +861,6 @@ class LocationDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StockLocation.objects.all() serializer_class = LocationSerializer - permission_classes = (permissions.IsAuthenticated,) stock_endpoints = [ diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index b3fb9af743..928aa6b7a1 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -65,7 +65,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% else %} {{ item.part.full_name }} × {% decimal item.quantity %} {% endif %} -{% if user.is_staff and perms.stock.change_stockitem %} +{% if user.is_staff and roles.stock.change %} {% endif %} diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index 2f319f4925..d411891078 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -8,7 +8,7 @@ {% if location %}

    {{ location.name }} - {% if user.is_staff and perms.stock.change_stocklocation %} + {% if user.is_staff and roles.stock.change %} {% endif %}

    diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index a522bc5415..8348a3e331 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -3,6 +3,8 @@ from rest_framework import status from django.urls import reverse from django.contrib.auth import get_user_model +from InvenTree.helpers import addUserPermissions + from .models import StockLocation @@ -22,6 +24,20 @@ class StockAPITestCase(APITestCase): # Create a user for auth User = get_user_model() self.user = User.objects.create_user('testuser', 'test@testing.com', 'password') + + # Add the necessary permissions to the user + perms = [ + 'view_stockitemtestresult', + 'change_stockitemtestresult', + 'add_stockitemtestresult', + 'add_stocklocation', + 'change_stocklocation', + 'add_stockitem', + 'change_stockitem', + ] + + addUserPermissions(self.user, perms) + self.client.login(username='testuser', password='password') def doPost(self, url, data={}): diff --git a/InvenTree/templates/403.html b/InvenTree/templates/403.html new file mode 100644 index 0000000000..372bd9fe27 --- /dev/null +++ b/InvenTree/templates/403.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block page_title %} +InvenTree | {% trans "Permission Denied" %} +{% endblock %} + +{% block content %} + +
    +

    {% trans "Permission Denied" %}

    + +
    + {% trans "You do not have permission to view this page." %} +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index a9e4ae92ea..8e59d51d2b 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -1,7 +1,7 @@ {% extends "base.html" %} - +{% load i18n %} {% block page_title %} -InvenTree | Index +InvenTree | {% trans "Index" %} {% endblock %} {% block content %} @@ -9,24 +9,24 @@ InvenTree | Index
    - {% if perms.part.view_part %} + {% if roles.part.view %} {% include "InvenTree/latest_parts.html" with collapse_id="latest_parts" %} {% include "InvenTree/bom_invalid.html" with collapse_id="bom_invalid" %} {% include "InvenTree/starred_parts.html" with collapse_id="starred" %} {% endif %} - {% if perms.build.view_build %} + {% if roles.build.view %} {% include "InvenTree/build_pending.html" with collapse_id="build_pending" %} {% endif %}
    - {% if perms.stock.view_stockitem %} + {% if roles.stock.view %} {% include "InvenTree/low_stock.html" with collapse_id="order" %} {% include "InvenTree/required_stock_build.html" with collapse_id="stock_to_build" %} {% endif %} - {% if perms.order.view_purchaseorder %} + {% if roles.purchase_order.view %} {% include "InvenTree/po_outstanding.html" with collapse_id="po_outstanding" %} {% endif %} - {% if perms.order.view_salesorder %} + {% if roles.sales_order.view %} {% include "InvenTree/so_outstanding.html" with collapse_id="so_outstanding" %} {% endif %}
    diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index 2224d85bc4..148a96c583 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -15,16 +15,16 @@
    diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index df94ffab56..f8454acb02 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -119,15 +119,12 @@ class RoleGroupAdmin(admin.ModelAdmin): # Save inlines before model # https://stackoverflow.com/a/14860703/12794913 def save_model(self, request, obj, form, change): - if obj is not None: - # Save model immediately only if in 'Add role' view - super().save_model(request, obj, form, change) - else: - pass # don't actually save the parent instance + pass # don't actually save the parent instance def save_formset(self, request, form, formset, change): formset.save() # this will save the children - form.instance.save() # form.instance is the parent + # update_fields is required to trigger permissions update + form.instance.save(update_fields=['name']) # form.instance is the parent class InvenTreeUserAdmin(UserAdmin): diff --git a/InvenTree/users/migrations/0003_auto_20201005_2227.py b/InvenTree/users/migrations/0003_auto_20201005_2227.py new file mode 100644 index 0000000000..92d7e341fa --- /dev/null +++ b/InvenTree/users/migrations/0003_auto_20201005_2227.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.7 on 2020-10-05 22:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_auto_20201004_0158'), + ] + + operations = [ + migrations.AlterField( + model_name='ruleset', + name='can_add', + field=models.BooleanField(default=False, help_text='Permission to add items', verbose_name='Add'), + ), + migrations.AlterField( + model_name='ruleset', + name='can_change', + field=models.BooleanField(default=False, help_text='Permissions to edit items', verbose_name='Change'), + ), + ] diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 5fe86e15fa..d3c713d07d 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -36,6 +36,10 @@ class RuleSet(models.Model): choice[0] for choice in RULESET_CHOICES ] + RULESET_PERMISSIONS = [ + 'view', 'add', 'change', 'delete', + ] + RULESET_MODELS = { 'admin': [ 'auth_group', @@ -134,9 +138,9 @@ class RuleSet(models.Model): can_view = models.BooleanField(verbose_name=_('View'), default=True, help_text=_('Permission to view items')) - can_add = models.BooleanField(verbose_name=_('Create'), default=False, help_text=_('Permission to add items')) + can_add = models.BooleanField(verbose_name=_('Add'), default=False, help_text=_('Permission to add items')) - can_change = models.BooleanField(verbose_name=_('Update'), default=False, help_text=_('Permissions to edit items')) + can_change = models.BooleanField(verbose_name=_('Change'), default=False, help_text=_('Permissions to edit items')) can_delete = models.BooleanField(verbose_name=_('Delete'), default=False, help_text=_('Permission to delete items')) @@ -155,8 +159,15 @@ class RuleSet(models.Model): model=model ) - def __str__(self): - return self.name + def __str__(self, debug=False): + """ Ruleset string representation """ + if debug: + # Makes debugging easier + return f'{str(self.group).ljust(15)}: {self.name.title().ljust(15)} | ' \ + f'v: {str(self.can_view).ljust(5)} | a: {str(self.can_add).ljust(5)} | ' \ + f'c: {str(self.can_change).ljust(5)} | d: {str(self.can_delete).ljust(5)}' + else: + return self.name def save(self, *args, **kwargs): @@ -171,6 +182,10 @@ class RuleSet(models.Model): super().save(*args, **kwargs) + if self.group: + # Update the group too! + self.group.save() + def get_models(self): """ Return the database tables / models that this ruleset covers. @@ -329,3 +344,35 @@ def create_missing_rule_sets(sender, instance, **kwargs): """ update_group_roles(instance) + + +def check_user_role(user, role, permission): + """ + Check if a user has a particular role:permission combination. + + If the user is a superuser, this will return True + """ + + if user.is_superuser: + return True + + for group in user.groups.all(): + + for rule in group.rule_sets.all(): + + if rule.name == role: + + if permission == 'add' and rule.can_add: + return True + + if permission == 'change' and rule.can_change: + return True + + if permission == 'view' and rule.can_view: + return True + + if permission == 'delete' and rule.can_delete: + return True + + # No matching permissions found + return False diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py index d14ffc4950..e277422f71 100644 --- a/InvenTree/users/tests.py +++ b/InvenTree/users/tests.py @@ -137,7 +137,8 @@ class RuleSetModelTest(TestCase): rule.save() - group.save() + # update_fields is required to trigger permissions update + group.save(update_fields=['name']) # There should now be three permissions for each rule set self.assertEqual(group.permissions.count(), 3 * len(permission_set)) @@ -151,7 +152,8 @@ class RuleSetModelTest(TestCase): rule.save() - group.save() + # update_fields is required to trigger permissions update + group.save(update_fields=['name']) # There should now not be any permissions assigned to this group self.assertEqual(group.permissions.count(), 0)