From d535e4fa12f4274396f9dce7071c4cae9e40ba31 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 15 Apr 2022 22:38:31 +1000 Subject: [PATCH 01/38] Add 'available_variant_stock' to BomItem serializer - Note: This is definitely *not* the optimum solution here --- InvenTree/part/models.py | 55 +++++++++++++++++++++++++++++++++++ InvenTree/part/serializers.py | 7 +++++ 2 files changed, 62 insertions(+) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index b7269f3e5e..3635f22587 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2703,6 +2703,61 @@ class BomItem(models.Model, DataImportMixin): def get_api_url(): return reverse('api-bom-list') + def available_variant_stock(self): + """ + Returns the total quantity of variant stock available for this BomItem. + + Notes: + - If "allow_variants" is False, this will return zero + - This is used for the API serializer, and is very inefficient + - This logic needs to be converted to a queryset annotation + """ + + # Variant stock is not allowed for this BOM item + if not self.allow_variants: + return 0 + + # Extract a flattened list of part variants + variants = self.sub_part.get_descendants(include_self=False) + + # Calculate 'in_stock' quantity - this is the total current stock count + query = StockModels.StockItem.objects.filter(StockModels.StockItem.IN_STOCK_FILTER) + + query = query.filter( + part__in=variants, + ) + + query = query.aggregate( + in_stock=Coalesce(Sum('quantity'), Decimal(0)) + ) + + in_stock = query['in_stock'] or 0 + + # Calculate the quantity allocated to sales orders + query = OrderModels.SalesOrderAllocation.objects.filter( + line__order__status__in=SalesOrderStatus.OPEN, + shipment__shipment_date=None, + item__part__in=variants, + ).aggregate( + allocated=Coalesce(Sum('quantity'), Decimal(0)), + ) + + sales_order_allocations = query['allocated'] or 0 + + # Calculate the quantity allocated to build orders + query = BuildModels.BuildItem.objects.filter( + build__status__in=BuildStatus.ACTIVE_CODES, + stock_item__part__in=variants, + ).aggregate( + allocated=Coalesce(Sum('quantity'), Decimal(0)), + ) + + build_order_allocations = query['allocated'] or 0 + + available = in_stock - sales_order_allocations - build_order_allocations + + return max(available, 0) + def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True): """ Return a list of valid parts which can be allocated against this BomItem: diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 8bf3d77501..0daac6e630 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -581,6 +581,12 @@ class BomItemSerializer(InvenTreeModelSerializer): available_stock = serializers.FloatField(read_only=True) available_substitute_stock = serializers.FloatField(read_only=True) + # Note: 2022-04-15 + # The 'available_variant_stock' field is calculated per-object, + # which means it is very inefficient! + # TODO: This needs to be converted into a query annotation, if possible! + available_variant_stock = serializers.FloatField(read_only=True) + def __init__(self, *args, **kwargs): # part_detail and sub_part_detail serializers are only included if requested. # This saves a bunch of database requests @@ -790,6 +796,7 @@ class BomItemSerializer(InvenTreeModelSerializer): # Annotated fields describing available quantity 'available_stock', 'available_substitute_stock', + 'available_variant_stock', ] From 7f07c526896bd6329dcc0e8253b48cfc33d798e9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 15 Apr 2022 22:40:24 +1000 Subject: [PATCH 02/38] Incremement APi version --- InvenTree/InvenTree/version.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 32f359f5f2..160716bba3 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -12,11 +12,14 @@ import common.models INVENTREE_SW_VERSION = "0.7.0 dev" # InvenTree API version -INVENTREE_API_VERSION = 38 +INVENTREE_API_VERSION = 39 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v39 -> 2022-04-15 : https://github.com/inventree/InvenTree/pull/2833 + - Adds 'available_variant_stock' information to the BomItem API + v38 -> 2022-04-14 : https://github.com/inventree/InvenTree/pull/2828 - Adds the ability to include stock test results for "installed items" From 6f5b560fb61d89ff6071da7057be052a3c8fa561 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 15 Apr 2022 22:45:22 +1000 Subject: [PATCH 03/38] Update BOM table to include variant stock information --- InvenTree/templates/js/translated/bom.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 6591068e9d..7ae922788f 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -807,15 +807,27 @@ function loadBomTable(table, options={}) { var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`; // Calculate total "available" (unallocated) quantity - var total = row.available_stock + row.available_substitute_stock; + var total = row.available_stock; + + total += (row.available_substitute_stock || 0); + total += (row.available_variant_stock || 0); var text = `${total}`; if (total <= 0) { text = `{% trans "No Stock Available" %}`; } else { - if (row.available_substitute_stock > 0) { - text += ``; + var extra = ''; + if (row.available_substitute_stock && row.available_variant_stock) { + extra = '{% trans "Includes variant and substitute stock" %}'; + } else if (row.available_variant_stock) { + extra = '{% trans "Includes variant stock" %}'; + } else if (row.available_substitute_stock) { + extra = '{% trans "Includes substitute stock" %}'; + } + + if (extra) { + text += ``; } } @@ -910,7 +922,7 @@ function loadBomTable(table, options={}) { formatter: function(value, row) { var can_build = 0; - var available = row.available_stock + row.available_substitute_stock; + var available = row.available_stock + (row.available_substitute_stock || 0) + (row.available_variant_stock || 0); if (row.quantity > 0) { can_build = available / row.quantity; From 43135036491991c1226da03af5b53ff15b13904f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 15 Apr 2022 22:47:32 +1000 Subject: [PATCH 04/38] PEP fix --- InvenTree/part/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 3635f22587..01ea8c661b 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2716,7 +2716,7 @@ class BomItem(models.Model, DataImportMixin): # Variant stock is not allowed for this BOM item if not self.allow_variants: return 0 - + # Extract a flattened list of part variants variants = self.sub_part.get_descendants(include_self=False) From 1d73f7c066615a1cf0a7fc03271114a995d61903 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 21 Apr 2022 20:14:46 +1000 Subject: [PATCH 05/38] Update API version info --- InvenTree/InvenTree/api_version.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 7851efd8dd..3f86d8a669 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -4,11 +4,14 @@ InvenTree API version information # InvenTree API version -INVENTREE_API_VERSION = 40 +INVENTREE_API_VERSION = 41 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v41 -> 2022-04-21 : https://github.com/inventree/InvenTree/pull/2833 + - Adds variant stock information to the Part and BomItem serializers + v40 -> 2022-04-19 - Adds ability to filter StockItem list by "tracked" parameter - This checks the serial number or batch code fields From fa2510c42f284e0c0caa51bbe9890c61b22a01bb Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 21 Apr 2022 23:51:29 +1000 Subject: [PATCH 06/38] Use a proper queryset annotation to calculate the "available_variant_stock" --- InvenTree/part/models.py | 54 ------------------------------ InvenTree/part/serializers.py | 62 ++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 62 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 01ea8c661b..d942c68c85 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2703,60 +2703,6 @@ class BomItem(models.Model, DataImportMixin): def get_api_url(): return reverse('api-bom-list') - def available_variant_stock(self): - """ - Returns the total quantity of variant stock available for this BomItem. - - Notes: - - If "allow_variants" is False, this will return zero - - This is used for the API serializer, and is very inefficient - - This logic needs to be converted to a queryset annotation - """ - - # Variant stock is not allowed for this BOM item - if not self.allow_variants: - return 0 - - # Extract a flattened list of part variants - variants = self.sub_part.get_descendants(include_self=False) - - # Calculate 'in_stock' quantity - this is the total current stock count - query = StockModels.StockItem.objects.filter(StockModels.StockItem.IN_STOCK_FILTER) - - query = query.filter( - part__in=variants, - ) - - query = query.aggregate( - in_stock=Coalesce(Sum('quantity'), Decimal(0)) - ) - - in_stock = query['in_stock'] or 0 - - # Calculate the quantity allocated to sales orders - query = OrderModels.SalesOrderAllocation.objects.filter( - line__order__status__in=SalesOrderStatus.OPEN, - shipment__shipment_date=None, - item__part__in=variants, - ).aggregate( - allocated=Coalesce(Sum('quantity'), Decimal(0)), - ) - - sales_order_allocations = query['allocated'] or 0 - - # Calculate the quantity allocated to build orders - query = BuildModels.BuildItem.objects.filter( - build__status__in=BuildStatus.ACTIVE_CODES, - stock_item__part__in=variants, - ).aggregate( - allocated=Coalesce(Sum('quantity'), Decimal(0)), - ) - - build_order_allocations = query['allocated'] or 0 - - available = in_stock - sales_order_allocations - build_order_allocations - - return max(available, 0) def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True): """ diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 0daac6e630..6dc502ae85 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -7,7 +7,9 @@ from decimal import Decimal from django.urls import reverse_lazy from django.db import models, transaction -from django.db.models import ExpressionWrapper, F, Q +from django.db.models import ExpressionWrapper, F, Q, Func +from django.db.models import Subquery, OuterRef, IntegerField, FloatField + from django.db.models.functions import Coalesce from django.utils.translation import ugettext_lazy as _ @@ -577,14 +579,9 @@ class BomItemSerializer(InvenTreeModelSerializer): purchase_price_range = serializers.SerializerMethodField() - # Annotated fields + # Annotated fields for available stock available_stock = serializers.FloatField(read_only=True) available_substitute_stock = serializers.FloatField(read_only=True) - - # Note: 2022-04-15 - # The 'available_variant_stock' field is calculated per-object, - # which means it is very inefficient! - # TODO: This needs to be converted into a query annotation, if possible! available_variant_stock = serializers.FloatField(read_only=True) def __init__(self, *args, **kwargs): @@ -619,11 +616,18 @@ class BomItemSerializer(InvenTreeModelSerializer): queryset = queryset.prefetch_related('sub_part') queryset = queryset.prefetch_related('sub_part__category') + queryset = queryset.prefetch_related( 'sub_part__stock_items', 'sub_part__stock_items__allocations', 'sub_part__stock_items__sales_order_allocations', ) + + queryset = queryset.prefetch_related( + 'substitutes', + 'substitutes__part__stock_items', + ) + queryset = queryset.prefetch_related('sub_part__supplier_parts__pricebreaks') return queryset @@ -713,7 +717,7 @@ class BomItemSerializer(InvenTreeModelSerializer): ), ) - # Calculate 'available_variant_stock' field + # Calculate 'available_substitute_stock' field queryset = queryset.annotate( available_substitute_stock=ExpressionWrapper( F('substitute_stock') - F('substitute_build_allocations') - F('substitute_sales_allocations'), @@ -721,6 +725,47 @@ class BomItemSerializer(InvenTreeModelSerializer): ) ) + # Annotate the queryset with 'available variant stock' information + variant_stock_query = StockItem.objects.filter( + part__tree_id=OuterRef('sub_part__tree_id'), + part__lft__gt=OuterRef('sub_part__lft'), + part__rght__lt=OuterRef('sub_part__rght'), + ) + + queryset = queryset.alias( + variant_stock_total=Coalesce( + Subquery( + variant_stock_query.annotate( + total=Func(F('quantity'), function='SUM', output_field=FloatField()) + ).values('total')), + 0, + output_field=FloatField() + ), + variant_stock_build_order_allocations=Coalesce( + Subquery( + variant_stock_query.annotate( + total=Func(F('sales_order_allocations__quantity'), function='SUM', output_field=FloatField()), + ).values('total')), + 0, + output_field=FloatField(), + ), + variant_stock_sales_order_allocations=Coalesce( + Subquery( + variant_stock_query.annotate( + total=Func(F('allocations__quantity'), function='SUM', output_field=FloatField()), + ).values('total')), + 0, + output_field=FloatField(), + ) + ) + + queryset = queryset.annotate( + available_variant_stock=ExpressionWrapper( + F('variant_stock_total') - F('variant_stock_build_order_allocations') - F('variant_stock_sales_order_allocations'), + output_field=FloatField(), + ) + ) + return queryset def get_purchase_price_range(self, obj): @@ -797,6 +842,7 @@ class BomItemSerializer(InvenTreeModelSerializer): 'available_stock', 'available_substitute_stock', 'available_variant_stock', + ] From ec34d23bfc36dbe60a46e6d05435a4a19fc7b5ea Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 21 Apr 2022 23:58:12 +1000 Subject: [PATCH 07/38] Update javascript rendering in BOM table --- InvenTree/templates/js/translated/bom.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 7ae922788f..0d27a5e028 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -807,22 +807,23 @@ function loadBomTable(table, options={}) { var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`; // Calculate total "available" (unallocated) quantity - var total = row.available_stock; + var base_stock = row.available_stock; + var substitute_stock = row.available_substitute_stock || 0; + var variant_stock = row.allow_variants ? (row.available_variant_stock || 0) : 0; + + var available_stock = base_stock + substitute_stock + variant_stock; - total += (row.available_substitute_stock || 0); - total += (row.available_variant_stock || 0); + var text = `${available_stock}`; - var text = `${total}`; - - if (total <= 0) { + if (available_stock <= 0) { text = `{% trans "No Stock Available" %}`; } else { var extra = ''; - if (row.available_substitute_stock && row.available_variant_stock) { + if ((substitute_stock > 0) && (variant_stock > 0)) { extra = '{% trans "Includes variant and substitute stock" %}'; - } else if (row.available_variant_stock) { + } else if (variant_stock > 0) { extra = '{% trans "Includes variant stock" %}'; - } else if (row.available_substitute_stock) { + } else if (substitute_stock > 0) { extra = '{% trans "Includes substitute stock" %}'; } From aa20d84cc1f89beabe04b3385be4dfd2a3fdb5e3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 Apr 2022 00:00:50 +1000 Subject: [PATCH 08/38] Update javascript rendering in build table --- InvenTree/templates/js/translated/build.js | 31 +++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index ff1f475d5d..eb955d7ff0 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -1425,19 +1425,36 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { title: '{% trans "Available" %}', sortable: true, formatter: function(value, row) { - var total = row.available_stock + row.available_substitute_stock; - var text = `${total}`; + var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`; - if (total <= 0) { + // Calculate total "available" (unallocated) quantity + var base_stock = row.available_stock; + var substitute_stock = row.available_substitute_stock || 0; + var variant_stock = row.allow_variants ? (row.available_variant_stock || 0) : 0; + + var available_stock = base_stock + substitute_stock + variant_stock; + + var text = `${available_stock}`; + + if (available_stock <= 0) { text = `{% trans "No Stock Available" %}`; } else { - if (row.available_substitute_stock > 0) { - text += ``; + var extra = ''; + if ((substitute_stock > 0) && (variant_stock > 0)) { + extra = '{% trans "Includes variant and substitute stock" %}'; + } else if (variant_stock > 0) { + extra = '{% trans "Includes variant stock" %}'; + } else if (substitute_stock > 0) { + extra = '{% trans "Includes substitute stock" %}'; + } + + if (extra) { + text += ``; } } - - return text; + + return renderLink(text, url); } }, { From 8b9aa86a0a57889ac8ada9a883364a2e692068de Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 Apr 2022 00:14:37 +1000 Subject: [PATCH 09/38] Add 'variant_stock' to Part API serializer --- InvenTree/part/serializers.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 6dc502ae85..387d3df1d5 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -8,7 +8,7 @@ from decimal import Decimal from django.urls import reverse_lazy from django.db import models, transaction from django.db.models import ExpressionWrapper, F, Q, Func -from django.db.models import Subquery, OuterRef, IntegerField, FloatField +from django.db.models import Subquery, OuterRef, FloatField from django.db.models.functions import Coalesce from django.utils.translation import ugettext_lazy as _ @@ -310,9 +310,6 @@ class PartSerializer(InvenTreeModelSerializer): to reduce database trips. """ - # TODO: Update the "in_stock" annotation to include stock for variants of the part - # Ref: https://github.com/inventree/InvenTree/issues/2240 - # Annotate with the total 'in stock' quantity queryset = queryset.annotate( in_stock=Coalesce( @@ -327,6 +324,24 @@ class PartSerializer(InvenTreeModelSerializer): stock_item_count=SubqueryCount('stock_items') ) + # Annotate with the total variant stock quantity + variant_query = StockItem.objects.filter( + part__tree_id=OuterRef('tree_id'), + part__lft__gt=OuterRef('lft'), + part__rght__lt=OuterRef('rght'), + ).filter(StockItem.IN_STOCK_FILTER) + + queryset = queryset.annotate( + variant_stock=Coalesce( + Subquery( + variant_query.annotate( + total=Func(F('quantity'), function='SUM', output_field=FloatField()) + ).values('total')), + 0, + output_field=FloatField(), + ) + ) + # Filter to limit builds to "active" build_filter = Q( status__in=BuildStatus.ACTIVE_CODES @@ -431,6 +446,7 @@ class PartSerializer(InvenTreeModelSerializer): unallocated_stock = serializers.FloatField(read_only=True) building = serializers.FloatField(read_only=True) in_stock = serializers.FloatField(read_only=True) + variant_stock = serializers.FloatField(read_only=True) ordering = serializers.FloatField(read_only=True) stock_item_count = serializers.IntegerField(read_only=True) suppliers = serializers.IntegerField(read_only=True) @@ -465,6 +481,7 @@ class PartSerializer(InvenTreeModelSerializer): 'full_name', 'image', 'in_stock', + 'variant_stock', 'ordering', 'building', 'IPN', @@ -730,7 +747,7 @@ class BomItemSerializer(InvenTreeModelSerializer): part__tree_id=OuterRef('sub_part__tree_id'), part__lft__gt=OuterRef('sub_part__lft'), part__rght__lt=OuterRef('sub_part__rght'), - ) + ).filter(StockItem.IN_STOCK_FILTER) queryset = queryset.alias( variant_stock_total=Coalesce( From f504dfe7972183ebc3e59f5b2b18b2f861dd6b6b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 Apr 2022 00:20:32 +1000 Subject: [PATCH 10/38] part variant table now displays information about variant stock quantities --- InvenTree/templates/js/translated/part.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 1a8e728040..35d5d0d5a6 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -672,7 +672,20 @@ function loadPartVariantTable(table, partId, options={}) { field: 'in_stock', title: '{% trans "Stock" %}', formatter: function(value, row) { - return renderLink(value, `/part/${row.pk}/?display=part-stock`); + + var base_stock = row.in_stock; + var variant_stock = row.variant_stock || 0; + + var total = base_stock + variant_stock; + + var text = `${total}`; + + if (variant_stock > 0) { + text = `${text}`; + text += ``; + } + + return renderLink(text, `/part/${row.pk}/?display=part-stock`); } } ]; From 6dec8ba1132b0ddafb5c234de5be6f630c9788cd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 Apr 2022 00:24:25 +1000 Subject: [PATCH 11/38] PEP fix --- InvenTree/part/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index d942c68c85..b7269f3e5e 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2703,7 +2703,6 @@ class BomItem(models.Model, DataImportMixin): def get_api_url(): return reverse('api-bom-list') - def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True): """ Return a list of valid parts which can be allocated against this BomItem: From b4068c5e0f7a203f57089280f3c85c4b9e50439e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 00:25:12 +0200 Subject: [PATCH 12/38] remove dead middleware --- InvenTree/InvenTree/middleware.py | 61 ------------------------------- 1 file changed, 61 deletions(-) diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index a9c43c71b6..1e9ada3cfa 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -92,67 +92,6 @@ class AuthRequiredMiddleware(object): return response -class QueryCountMiddleware(object): - """ - This middleware will log the number of queries run - and the total time taken for each request (with a - status code of 200). It does not currently support - multi-db setups. - - To enable this middleware, set 'log_queries: True' in the local InvenTree config file. - - Reference: https://www.dabapps.com/blog/logging-sql-queries-django-13/ - - Note: 2020-08-15 - This is no longer used, instead we now rely on the django-debug-toolbar addon - """ - - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - - t_start = time.time() - response = self.get_response(request) - t_stop = time.time() - - if response.status_code == 200: - total_time = 0 - - if len(connection.queries) > 0: - - queries = {} - - for query in connection.queries: - query_time = query.get('time') - - sql = query.get('sql').split('.')[0] - - if sql in queries: - queries[sql] += 1 - else: - queries[sql] = 1 - - if query_time is None: - # django-debug-toolbar monkeypatches the connection - # cursor wrapper and adds extra information in each - # item in connection.queries. The query time is stored - # under the key "duration" rather than "time" and is - # in milliseconds, not seconds. - query_time = float(query.get('duration', 0)) - - total_time += float(query_time) - - logger.debug('{n} queries run, {a:.3f}s / {b:.3f}s'.format( - n=len(connection.queries), - a=total_time, - b=(t_stop - t_start))) - - for x in sorted(queries.items(), key=operator.itemgetter(1), reverse=True): - print(x[0], ':', x[1]) - - return response - - url_matcher = url('', include(frontendpatterns)) From b72650d1622bc1eef72ca1fcbaef16b8b75898f8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 00:25:31 +0200 Subject: [PATCH 13/38] remove translation script --- InvenTree/script/translate.py | 116 ---------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 InvenTree/script/translate.py diff --git a/InvenTree/script/translate.py b/InvenTree/script/translate.py deleted file mode 100644 index b41bb96788..0000000000 --- a/InvenTree/script/translate.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -This script is used to simplify the translation process. - -Django provides a framework for working out which strings are "translatable", -and these strings are then dumped in a file under InvenTree/locale//LC_MESSAGES/django.po - -This script presents the translator with a list of strings which have not yet been translated, -allowing for a simpler and quicker translation process. - -If a string translation needs to be updated, this will still need to be done manually, -by editing the appropriate .po file. - -""" - -import argparse -import os -import sys - - -def manually_translate_file(filename, save=False): - """ - Manually translate a .po file. - Present any missing translation strings to the translator, - and write their responses back to the file. - """ - - print("Add manual translations to '{f}'".format(f=filename)) - print("For each missing translation:") - print("a) Directly enter a new tranlation in the target language") - print("b) Leave empty to skip") - print("c) Press Ctrl+C to exit") - - print("-------------------------") - input("Press to start") - print("") - - with open(filename, 'r') as f: - lines = f.readlines() - - out = [] - - # Context data - source_line = '' - msgid = '' - - for num, line in enumerate(lines): - # Keep track of context data BEFORE an empty msgstr object - line = line.strip() - - if line.startswith("#: "): - source_line = line.replace("#: ", "") - - elif line.startswith("msgid "): - msgid = line.replace("msgid ", "") - - if line.strip() == 'msgstr ""': - # We have found an empty translation! - - if msgid and len(msgid) > 0 and not msgid == '""': - print("Source:", source_line) - print("Enter translation for {t}".format(t=msgid)) - - try: - translation = str(input(">")) - except KeyboardInterrupt: - break - - if translation and len(translation) > 0: - # Update the line with the new translation - line = 'msgstr "{msg}"'.format(msg=translation) - - out.append(line + "\r\n") - - if save: - with open(filename, 'w') as output_file: - output_file.writelines(out) - - print("Translation done: written to", filename) - print("Run 'invoke translate' to rebuild translation data") - - -if __name__ == '__main__': - - MY_DIR = os.path.dirname(os.path.realpath(__file__)) - LOCALE_DIR = os.path.join(MY_DIR, '..', 'locale') - - if not os.path.exists(LOCALE_DIR): - print("Error: {d} does not exist!".format(d=LOCALE_DIR)) - sys.exit(1) - - parser = argparse.ArgumentParser(description="InvenTree Translation Helper") - - parser.add_argument('language', help='Language code', action='store') - - parser.add_argument('--fake', help="Do not save updated translations", action='store_true') - - args = parser.parse_args() - - language = args.language - - LANGUAGE_DIR = os.path.abspath(os.path.join(LOCALE_DIR, language)) - - # Check that a locale directory exists for the given language! - if not os.path.exists(LANGUAGE_DIR): - print("Error: Locale directory for language '{l}' does not exist".format(l=language)) - sys.exit(1) - - # Check that a .po file exists for the given language! - PO_FILE = os.path.join(LANGUAGE_DIR, 'LC_MESSAGES', 'django.po') - - if not os.path.exists(PO_FILE): - print("Error: File '{f}' does not exist".format(f=PO_FILE)) - sys.exit(1) - - # Ok, now we run the user through the translation file - manually_translate_file(PO_FILE, save=args.fake is not True) From 65357df648ea4c49af6664c2f2d14c07c41c7222 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 00:27:41 +0200 Subject: [PATCH 14/38] PEP fix --- InvenTree/InvenTree/middleware.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 1e9ada3cfa..b679713b32 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -1,11 +1,8 @@ from django.shortcuts import HttpResponseRedirect from django.urls import reverse_lazy, Resolver404 -from django.db import connection from django.shortcuts import redirect from django.conf.urls import include, url import logging -import time -import operator from rest_framework.authtoken.models import Token from allauth_2fa.middleware import BaseRequire2FAMiddleware, AllauthTwoFactorMiddleware From e7357726ca9f7860809a4f235a328fa7d05830f9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 11:19:30 +0200 Subject: [PATCH 15/38] Add oauth for zapier --- InvenTree/InvenTree/settings.py | 2 ++ InvenTree/InvenTree/urls.py | 3 +++ requirements.txt | 1 + 3 files changed, 6 insertions(+) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e1c584362f..3f1f41d24c 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -269,6 +269,8 @@ INSTALLED_APPS = [ 'django_q', 'formtools', # Form wizard tools + 'oauth2_provider', # Oauth provider + 'allauth', # Base app for SSO 'allauth.account', # Extend user with accounts 'allauth.socialaccount', # Use 'social' providers diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index ec8b891f93..101130045e 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -194,6 +194,9 @@ frontendpatterns = [ url(r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"), url(r'^accounts/', include('allauth_2fa.urls')), # MFA support url(r'^accounts/', include('allauth.urls')), # included urlpatterns + + # Oauth provider urls + url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')), ] # Append custom plugin URLs (if plugin support is enabled) diff --git a/requirements.txt b/requirements.txt index 9aef6607d7..3777c24f12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,6 +22,7 @@ django-markdownify==0.8.0 # Markdown rendering django-markdownx==3.0.1 # Markdown form fields django-money==1.1 # Django app for currency management django-mptt==0.11.0 # Modified Preorder Tree Traversal +django-oauth-toolkit==1.7.1 # Oauth endpoints django-redis>=5.0.0 # Redis integration django-q==1.3.4 # Background task scheduling django-sql-utils==0.5.0 # Advanced query annotation / aggregation From 1603bf4e60eb253776b2636a0e461e9f7595125c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Apr 2022 01:32:31 +0200 Subject: [PATCH 16/38] fix api call --- InvenTree/plugin/builtin/integration/mixins.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 118f0b775b..18c1afe64a 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -504,10 +504,10 @@ class APICallMixin: @property def api_headers(self): - return { - self.API_TOKEN: self.get_setting(self.API_TOKEN_SETTING), - 'Content-Type': 'application/json' - } + headers = {'Content-Type': 'application/json'} + if getattr(self, 'API_TOKEN_SETTING'): + headers[self.API_TOKEN] = self.get_setting(self.API_TOKEN_SETTING) + return headers def api_build_url_args(self, arguments): groups = [] @@ -515,16 +515,21 @@ class APICallMixin: groups.append(f'{key}={",".join([str(a) for a in val])}') return f'?{"&".join(groups)}' - def api_call(self, endpoint, method: str = 'GET', url_args=None, data=None, headers=None, simple_response: bool = True): + def api_call(self, endpoint, method: str = 'GET', url_args=None, data=None, headers=None, simple_response: bool = True, endpoint_is_url: bool = False): if url_args: endpoint += self.api_build_url_args(url_args) if headers is None: headers = self.api_headers + if endpoint_is_url: + url = endpoint + else: + url = f'{self.api_url}/{endpoint}' + # build kwargs for call kwargs = { - 'url': f'{self.api_url}/{endpoint}', + 'url': url, 'headers': headers, } if data: From e1e04329637d5d1b410911108dcc3389c267ea61 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 10:57:43 +1000 Subject: [PATCH 17/38] Adds 'variant_of' filter back into Part API --- InvenTree/InvenTree/api_version.py | 5 ++++- InvenTree/part/api.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 7851efd8dd..f767148199 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -4,11 +4,14 @@ InvenTree API version information # InvenTree API version -INVENTREE_API_VERSION = 40 +INVENTREE_API_VERSION = 41 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v41 -> 2022-04-26 + - Fixes 'variant_of' filter for Part list endpoint + v40 -> 2022-04-19 - Adds ability to filter StockItem list by "tracked" parameter - This checks the serial number or batch code fields diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index de6cd4a974..e394efae7e 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1175,6 +1175,18 @@ class PartList(generics.ListCreateAPIView): except (ValueError, Part.DoesNotExist): pass + # Filter by 'variant_of' + # Note that this is subtly different from 'ancestor' filter (above) + variant_of = params.get('variant_of', None) + + if variant_of is not None: + try: + template = Part.objects.get(pk=variant_of) + variants = template.get_children() + queryset = queryset.filter(pk__in=[v.pk for v in variants]) + except (ValueError, Part.DoesNotExist): + pass + # Filter only parts which are in the "BOM" for a given part in_bom_for = params.get('in_bom_for', None) From 693e47ab891328357d355e1932b58f7b2d9d93ea Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 11:02:22 +1000 Subject: [PATCH 18/38] Remove outdated filter_fields variable --- InvenTree/part/api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index e394efae7e..b025791a7f 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1351,10 +1351,6 @@ class PartList(generics.ListCreateAPIView): filters.OrderingFilter, ] - filter_fields = [ - 'variant_of', - ] - ordering_fields = [ 'name', 'creation_date', From b5551a1ceba31ed586aa2d2494f319d2a3ccd541 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 11:05:40 +1000 Subject: [PATCH 19/38] Adds pt_br (brazillian) --- InvenTree/InvenTree/settings.py | 1 + InvenTree/locale/pt_br/LC_MESSAGES/django.po | 7859 ++++++++++++++++++ 2 files changed, 7860 insertions(+) create mode 100644 InvenTree/locale/pt_br/LC_MESSAGES/django.po diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e1c584362f..2514d2a07f 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -688,6 +688,7 @@ LANGUAGES = [ ('no', _('Norwegian')), ('pl', _('Polish')), ('pt', _('Portugese')), + ('pt-br', _('Portugese (Brazilian)')), ('ru', _('Russian')), ('sv', _('Swedish')), ('th', _('Thai')), diff --git a/InvenTree/locale/pt_br/LC_MESSAGES/django.po b/InvenTree/locale/pt_br/LC_MESSAGES/django.po new file mode 100644 index 0000000000..f4ee57bc48 --- /dev/null +++ b/InvenTree/locale/pt_br/LC_MESSAGES/django.po @@ -0,0 +1,7859 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-04-26 01:04+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: InvenTree/api.py:57 +msgid "API endpoint not found" +msgstr "" + +#: InvenTree/api.py:103 +msgid "No action specified" +msgstr "" + +#: InvenTree/api.py:118 +msgid "No matching action found" +msgstr "" + +#: InvenTree/fields.py:100 +msgid "Enter date" +msgstr "" + +#: InvenTree/forms.py:126 order/forms.py:24 order/forms.py:35 order/forms.py:46 +#: order/forms.py:57 templates/account/email_confirm.html:20 +msgid "Confirm" +msgstr "" + +#: InvenTree/forms.py:142 +msgid "Confirm delete" +msgstr "" + +#: InvenTree/forms.py:143 +msgid "Confirm item deletion" +msgstr "" + +#: InvenTree/forms.py:174 +msgid "Enter password" +msgstr "" + +#: InvenTree/forms.py:175 +msgid "Enter new password" +msgstr "" + +#: InvenTree/forms.py:182 +msgid "Confirm password" +msgstr "" + +#: InvenTree/forms.py:183 +msgid "Confirm new password" +msgstr "" + +#: InvenTree/forms.py:215 +msgid "Select Category" +msgstr "" + +#: InvenTree/forms.py:236 +msgid "Email (again)" +msgstr "" + +#: InvenTree/forms.py:240 +msgid "Email address confirmation" +msgstr "" + +#: InvenTree/forms.py:260 +msgid "You must type the same email each time." +msgstr "" + +#: InvenTree/helpers.py:442 +#, python-brace-format +msgid "Duplicate serial: {sn}" +msgstr "" + +#: InvenTree/helpers.py:449 order/models.py:282 order/models.py:435 +#: stock/views.py:993 +msgid "Invalid quantity provided" +msgstr "" + +#: InvenTree/helpers.py:452 +msgid "Empty serial number string" +msgstr "" + +#: InvenTree/helpers.py:474 InvenTree/helpers.py:477 InvenTree/helpers.py:480 +#: InvenTree/helpers.py:504 +#, python-brace-format +msgid "Invalid group: {g}" +msgstr "" + +#: InvenTree/helpers.py:518 +#, python-brace-format +msgid "Invalid/no group {group}" +msgstr "" + +#: InvenTree/helpers.py:524 +msgid "No serial numbers found" +msgstr "" + +#: InvenTree/helpers.py:528 +#, python-brace-format +msgid "Number of unique serial number ({s}) must match quantity ({q})" +msgstr "" + +#: InvenTree/models.py:185 +msgid "Missing file" +msgstr "" + +#: InvenTree/models.py:186 +msgid "Missing external link" +msgstr "" + +#: InvenTree/models.py:197 stock/models.py:2173 +msgid "Attachment" +msgstr "" + +#: InvenTree/models.py:198 +msgid "Select file to attach" +msgstr "" + +#: InvenTree/models.py:204 company/models.py:131 company/models.py:348 +#: company/models.py:564 order/models.py:127 part/models.py:873 +#: report/templates/report/inventree_build_order_base.html:165 +msgid "Link" +msgstr "" + +#: InvenTree/models.py:205 build/models.py:332 part/models.py:874 +#: stock/models.py:641 +msgid "Link to external URL" +msgstr "" + +#: InvenTree/models.py:208 +msgid "Comment" +msgstr "" + +#: InvenTree/models.py:208 +msgid "File comment" +msgstr "" + +#: InvenTree/models.py:214 InvenTree/models.py:215 common/models.py:1396 +#: common/models.py:1397 common/models.py:1618 common/models.py:1619 +#: common/models.py:1848 common/models.py:1849 part/models.py:2374 +#: part/models.py:2394 +#: report/templates/report/inventree_test_report_base.html:96 +msgid "User" +msgstr "" + +#: InvenTree/models.py:218 +msgid "upload date" +msgstr "" + +#: InvenTree/models.py:241 +msgid "Filename must not be empty" +msgstr "" + +#: InvenTree/models.py:264 +msgid "Invalid attachment directory" +msgstr "" + +#: InvenTree/models.py:274 +#, python-brace-format +msgid "Filename contains illegal character '{c}'" +msgstr "" + +#: InvenTree/models.py:277 +msgid "Filename missing extension" +msgstr "" + +#: InvenTree/models.py:284 +msgid "Attachment with this filename already exists" +msgstr "" + +#: InvenTree/models.py:291 +msgid "Error renaming file" +msgstr "" + +#: InvenTree/models.py:326 +msgid "Invalid choice" +msgstr "" + +#: InvenTree/models.py:342 InvenTree/models.py:343 common/models.py:1604 +#: company/models.py:415 label/models.py:112 part/models.py:817 +#: part/models.py:2558 plugin/models.py:40 report/models.py:177 +#: templates/InvenTree/notifications/notifications.html:84 +#: templates/InvenTree/settings/mixins/urls.html:13 +#: templates/InvenTree/settings/plugin.html:49 +#: templates/InvenTree/settings/plugin.html:132 +#: templates/InvenTree/settings/plugin_settings.html:23 +#: templates/InvenTree/settings/settings.html:320 +msgid "Name" +msgstr "" + +#: InvenTree/models.py:349 build/models.py:209 +#: build/templates/build/detail.html:24 company/models.py:354 +#: company/models.py:570 company/templates/company/company_base.html:68 +#: company/templates/company/manufacturer_part.html:76 +#: company/templates/company/supplier_part.html:73 label/models.py:119 +#: order/models.py:125 part/models.py:840 part/templates/part/category.html:74 +#: part/templates/part/part_base.html:167 +#: part/templates/part/set_category.html:14 report/models.py:190 +#: report/models.py:553 report/models.py:592 +#: report/templates/report/inventree_build_order_base.html:118 +#: stock/templates/stock/location.html:94 +#: templates/InvenTree/settings/plugin_settings.html:33 +msgid "Description" +msgstr "" + +#: InvenTree/models.py:350 +msgid "Description (optional)" +msgstr "" + +#: InvenTree/models.py:358 +msgid "parent" +msgstr "" + +#: InvenTree/serializers.py:65 part/models.py:2877 +msgid "Must be a valid number" +msgstr "" + +#: InvenTree/serializers.py:299 +msgid "Filename" +msgstr "" + +#: InvenTree/serializers.py:334 +msgid "Invalid value" +msgstr "" + +#: InvenTree/serializers.py:355 +msgid "Data File" +msgstr "" + +#: InvenTree/serializers.py:356 +msgid "Select data file for upload" +msgstr "" + +#: InvenTree/serializers.py:380 +msgid "Unsupported file type" +msgstr "" + +#: InvenTree/serializers.py:386 +msgid "File is too large" +msgstr "" + +#: InvenTree/serializers.py:407 +msgid "No columns found in file" +msgstr "" + +#: InvenTree/serializers.py:410 +msgid "No data rows found in file" +msgstr "" + +#: InvenTree/serializers.py:533 +msgid "No data rows provided" +msgstr "" + +#: InvenTree/serializers.py:536 +msgid "No data columns supplied" +msgstr "" + +#: InvenTree/serializers.py:623 +#, python-brace-format +msgid "Missing required column: '{name}'" +msgstr "" + +#: InvenTree/serializers.py:632 +#, python-brace-format +msgid "Duplicate column: '{col}'" +msgstr "" + +#: InvenTree/settings.py:674 +msgid "Czech" +msgstr "" + +#: InvenTree/settings.py:675 +msgid "German" +msgstr "" + +#: InvenTree/settings.py:676 +msgid "Greek" +msgstr "" + +#: InvenTree/settings.py:677 +msgid "English" +msgstr "" + +#: InvenTree/settings.py:678 +msgid "Spanish" +msgstr "" + +#: InvenTree/settings.py:679 +msgid "Spanish (Mexican)" +msgstr "" + +#: InvenTree/settings.py:680 +msgid "Farsi / Persian" +msgstr "" + +#: InvenTree/settings.py:681 +msgid "French" +msgstr "" + +#: InvenTree/settings.py:682 +msgid "Hebrew" +msgstr "" + +#: InvenTree/settings.py:683 +msgid "Hungarian" +msgstr "" + +#: InvenTree/settings.py:684 +msgid "Italian" +msgstr "" + +#: InvenTree/settings.py:685 +msgid "Japanese" +msgstr "" + +#: InvenTree/settings.py:686 +msgid "Korean" +msgstr "" + +#: InvenTree/settings.py:687 +msgid "Dutch" +msgstr "" + +#: InvenTree/settings.py:688 +msgid "Norwegian" +msgstr "" + +#: InvenTree/settings.py:689 +msgid "Polish" +msgstr "" + +#: InvenTree/settings.py:690 +msgid "Portugese" +msgstr "" + +#: InvenTree/settings.py:691 +msgid "Portugese (Brazilian)" +msgstr "" + +#: InvenTree/settings.py:692 +msgid "Russian" +msgstr "" + +#: InvenTree/settings.py:693 +msgid "Swedish" +msgstr "" + +#: InvenTree/settings.py:694 +msgid "Thai" +msgstr "" + +#: InvenTree/settings.py:695 +msgid "Turkish" +msgstr "" + +#: InvenTree/settings.py:696 +msgid "Vietnamese" +msgstr "" + +#: InvenTree/settings.py:697 +msgid "Chinese" +msgstr "" + +#: InvenTree/status.py:110 +msgid "Background worker check failed" +msgstr "" + +#: InvenTree/status.py:114 +msgid "Email backend not configured" +msgstr "" + +#: InvenTree/status.py:117 +msgid "InvenTree system health checks failed" +msgstr "" + +#: InvenTree/status_codes.py:101 InvenTree/status_codes.py:142 +#: InvenTree/status_codes.py:323 +msgid "Pending" +msgstr "" + +#: InvenTree/status_codes.py:102 +msgid "Placed" +msgstr "" + +#: InvenTree/status_codes.py:103 InvenTree/status_codes.py:326 +#: order/templates/order/order_base.html:128 +#: order/templates/order/sales_order_base.html:132 +msgid "Complete" +msgstr "" + +#: InvenTree/status_codes.py:104 InvenTree/status_codes.py:144 +#: InvenTree/status_codes.py:325 +msgid "Cancelled" +msgstr "" + +#: InvenTree/status_codes.py:105 InvenTree/status_codes.py:145 +#: InvenTree/status_codes.py:187 +msgid "Lost" +msgstr "" + +#: InvenTree/status_codes.py:106 InvenTree/status_codes.py:146 +#: InvenTree/status_codes.py:189 +msgid "Returned" +msgstr "" + +#: InvenTree/status_codes.py:143 order/models.py:997 +msgid "Shipped" +msgstr "" + +#: InvenTree/status_codes.py:183 +msgid "OK" +msgstr "" + +#: InvenTree/status_codes.py:184 +msgid "Attention needed" +msgstr "" + +#: InvenTree/status_codes.py:185 +msgid "Damaged" +msgstr "" + +#: InvenTree/status_codes.py:186 +msgid "Destroyed" +msgstr "" + +#: InvenTree/status_codes.py:188 +msgid "Rejected" +msgstr "" + +#: InvenTree/status_codes.py:276 +msgid "Legacy stock tracking entry" +msgstr "" + +#: InvenTree/status_codes.py:278 +msgid "Stock item created" +msgstr "" + +#: InvenTree/status_codes.py:280 +msgid "Edited stock item" +msgstr "" + +#: InvenTree/status_codes.py:281 +msgid "Assigned serial number" +msgstr "" + +#: InvenTree/status_codes.py:283 +msgid "Stock counted" +msgstr "" + +#: InvenTree/status_codes.py:284 +msgid "Stock manually added" +msgstr "" + +#: InvenTree/status_codes.py:285 +msgid "Stock manually removed" +msgstr "" + +#: InvenTree/status_codes.py:287 +msgid "Location changed" +msgstr "" + +#: InvenTree/status_codes.py:289 +msgid "Installed into assembly" +msgstr "" + +#: InvenTree/status_codes.py:290 +msgid "Removed from assembly" +msgstr "" + +#: InvenTree/status_codes.py:292 +msgid "Installed component item" +msgstr "" + +#: InvenTree/status_codes.py:293 +msgid "Removed component item" +msgstr "" + +#: InvenTree/status_codes.py:295 +msgid "Split from parent item" +msgstr "" + +#: InvenTree/status_codes.py:296 +msgid "Split child item" +msgstr "" + +#: InvenTree/status_codes.py:298 +msgid "Merged stock items" +msgstr "" + +#: InvenTree/status_codes.py:300 +msgid "Converted to variant" +msgstr "" + +#: InvenTree/status_codes.py:302 +msgid "Sent to customer" +msgstr "" + +#: InvenTree/status_codes.py:303 +msgid "Returned from customer" +msgstr "" + +#: InvenTree/status_codes.py:305 +msgid "Build order output created" +msgstr "" + +#: InvenTree/status_codes.py:306 +msgid "Build order output completed" +msgstr "" + +#: InvenTree/status_codes.py:307 +msgid "Consumed by build order" +msgstr "" + +#: InvenTree/status_codes.py:309 +msgid "Received against purchase order" +msgstr "" + +#: InvenTree/status_codes.py:324 +msgid "Production" +msgstr "" + +#: InvenTree/validators.py:25 +msgid "Not a valid currency code" +msgstr "" + +#: InvenTree/validators.py:53 +msgid "Invalid character in part name" +msgstr "" + +#: InvenTree/validators.py:66 +#, python-brace-format +msgid "IPN must match regex pattern {pat}" +msgstr "" + +#: InvenTree/validators.py:80 InvenTree/validators.py:94 +#: InvenTree/validators.py:108 +#, python-brace-format +msgid "Reference must match pattern {pattern}" +msgstr "" + +#: InvenTree/validators.py:116 +#, python-brace-format +msgid "Illegal character in name ({x})" +msgstr "" + +#: InvenTree/validators.py:137 InvenTree/validators.py:153 +msgid "Overage value must not be negative" +msgstr "" + +#: InvenTree/validators.py:155 +msgid "Overage must not exceed 100%" +msgstr "" + +#: InvenTree/validators.py:162 +msgid "Invalid value for overage" +msgstr "" + +#: InvenTree/views.py:538 +msgid "Delete Item" +msgstr "" + +#: InvenTree/views.py:587 +msgid "Check box to confirm item deletion" +msgstr "" + +#: InvenTree/views.py:602 templates/InvenTree/settings/user.html:21 +msgid "Edit User Information" +msgstr "" + +#: InvenTree/views.py:613 templates/InvenTree/settings/user.html:19 +msgid "Set Password" +msgstr "" + +#: InvenTree/views.py:632 +msgid "Password fields must match" +msgstr "" + +#: InvenTree/views.py:883 templates/navbar.html:144 +msgid "System Information" +msgstr "" + +#: barcodes/api.py:55 barcodes/api.py:156 +msgid "Must provide barcode_data parameter" +msgstr "" + +#: barcodes/api.py:132 +msgid "No match found for barcode data" +msgstr "" + +#: barcodes/api.py:134 +msgid "Match found for barcode data" +msgstr "" + +#: barcodes/api.py:159 +msgid "Must provide stockitem parameter" +msgstr "" + +#: barcodes/api.py:166 +msgid "No matching stock item found" +msgstr "" + +#: barcodes/api.py:197 +msgid "Barcode already matches Stock Item" +msgstr "" + +#: barcodes/api.py:201 +msgid "Barcode already matches Stock Location" +msgstr "" + +#: barcodes/api.py:205 +msgid "Barcode already matches Part" +msgstr "" + +#: barcodes/api.py:211 barcodes/api.py:223 +msgid "Barcode hash already matches Stock Item" +msgstr "" + +#: barcodes/api.py:229 +msgid "Barcode associated with Stock Item" +msgstr "" + +#: build/forms.py:20 +msgid "Confirm cancel" +msgstr "" + +#: build/forms.py:20 build/views.py:62 +msgid "Confirm build cancellation" +msgstr "" + +#: build/models.py:135 +msgid "Invalid choice for parent build" +msgstr "" + +#: build/models.py:139 build/templates/build/build_base.html:9 +#: build/templates/build/build_base.html:27 +#: report/templates/report/inventree_build_order_base.html:106 +msgid "Build Order" +msgstr "" + +#: build/models.py:140 build/templates/build/build_base.html:13 +#: build/templates/build/index.html:8 build/templates/build/index.html:12 +#: order/templates/order/sales_order_detail.html:91 +#: order/templates/order/so_sidebar.html:13 +#: part/templates/part/part_sidebar.html:23 templates/InvenTree/index.html:221 +#: templates/InvenTree/search.html:139 +#: templates/InvenTree/settings/sidebar.html:45 users/models.py:44 +msgid "Build Orders" +msgstr "" + +#: build/models.py:200 +msgid "Build Order Reference" +msgstr "" + +#: build/models.py:201 order/models.py:213 order/models.py:563 +#: order/models.py:843 part/models.py:2788 +#: part/templates/part/upload_bom.html:54 +#: report/templates/report/inventree_po_report.html:91 +#: report/templates/report/inventree_so_report.html:92 +msgid "Reference" +msgstr "" + +#: build/models.py:212 +msgid "Brief description of the build" +msgstr "" + +#: build/models.py:221 build/templates/build/build_base.html:169 +#: build/templates/build/detail.html:87 +msgid "Parent Build" +msgstr "" + +#: build/models.py:222 +msgid "BuildOrder to which this build is allocated" +msgstr "" + +#: build/models.py:227 build/templates/build/build_base.html:77 +#: build/templates/build/detail.html:29 company/models.py:706 +#: order/models.py:912 order/models.py:986 +#: order/templates/order/order_wizard/select_parts.html:32 part/models.py:367 +#: part/models.py:2320 part/models.py:2336 part/models.py:2355 +#: part/models.py:2372 part/models.py:2474 part/models.py:2596 +#: part/models.py:2686 part/models.py:2763 part/models.py:3053 +#: part/serializers.py:834 part/templates/part/part_app_base.html:8 +#: part/templates/part/part_pricing.html:12 +#: part/templates/part/set_category.html:13 +#: part/templates/part/upload_bom.html:52 +#: report/templates/report/inventree_build_order_base.html:110 +#: report/templates/report/inventree_po_report.html:89 +#: report/templates/report/inventree_so_report.html:90 +#: templates/InvenTree/search.html:80 +#: templates/email/build_order_required_stock.html:17 +#: templates/email/low_stock_notification.html:16 +msgid "Part" +msgstr "" + +#: build/models.py:235 +msgid "Select part to build" +msgstr "" + +#: build/models.py:240 +msgid "Sales Order Reference" +msgstr "" + +#: build/models.py:244 +msgid "SalesOrder to which this build is allocated" +msgstr "" + +#: build/models.py:249 build/serializers.py:730 +msgid "Source Location" +msgstr "" + +#: build/models.py:253 +msgid "" +"Select location to take stock from for this build (leave blank to take from " +"any stock location)" +msgstr "" + +#: build/models.py:258 +msgid "Destination Location" +msgstr "" + +#: build/models.py:262 +msgid "Select location where the completed items will be stored" +msgstr "" + +#: build/models.py:266 +msgid "Build Quantity" +msgstr "" + +#: build/models.py:269 +msgid "Number of stock items to build" +msgstr "" + +#: build/models.py:273 +msgid "Completed items" +msgstr "" + +#: build/models.py:275 +msgid "Number of stock items which have been completed" +msgstr "" + +#: build/models.py:279 +msgid "Build Status" +msgstr "" + +#: build/models.py:283 +msgid "Build status code" +msgstr "" + +#: build/models.py:287 build/serializers.py:218 order/serializers.py:272 +#: stock/models.py:645 +msgid "Batch Code" +msgstr "" + +#: build/models.py:291 build/serializers.py:219 +msgid "Batch code for this build output" +msgstr "" + +#: build/models.py:294 order/models.py:129 part/models.py:1012 +#: part/templates/part/part_base.html:331 +msgid "Creation Date" +msgstr "" + +#: build/models.py:298 order/models.py:585 +msgid "Target completion date" +msgstr "" + +#: build/models.py:299 +msgid "" +"Target date for build completion. Build will be overdue after this date." +msgstr "" + +#: build/models.py:302 order/models.py:255 +msgid "Completion Date" +msgstr "" + +#: build/models.py:308 +msgid "completed by" +msgstr "" + +#: build/models.py:316 +msgid "Issued by" +msgstr "" + +#: build/models.py:317 +msgid "User who issued this build order" +msgstr "" + +#: build/models.py:325 build/templates/build/build_base.html:190 +#: build/templates/build/detail.html:115 order/models.py:143 +#: order/templates/order/order_base.html:170 +#: order/templates/order/sales_order_base.html:182 part/models.py:1016 +#: report/templates/report/inventree_build_order_base.html:159 +msgid "Responsible" +msgstr "" + +#: build/models.py:326 +msgid "User responsible for this build order" +msgstr "" + +#: build/models.py:331 build/templates/build/detail.html:101 +#: company/templates/company/manufacturer_part.html:102 +#: company/templates/company/supplier_part.html:126 +#: part/templates/part/part_base.html:372 stock/models.py:639 +#: stock/templates/stock/item_base.html:357 +msgid "External Link" +msgstr "" + +#: build/models.py:336 build/serializers.py:381 +#: build/templates/build/sidebar.html:21 company/models.py:142 +#: company/models.py:577 company/templates/company/sidebar.html:25 +#: order/models.py:147 order/models.py:845 order/models.py:1107 +#: order/templates/order/po_sidebar.html:11 +#: order/templates/order/so_sidebar.html:17 part/models.py:1001 +#: part/templates/part/part_sidebar.html:60 +#: report/templates/report/inventree_build_order_base.html:173 +#: stock/forms.py:137 stock/forms.py:171 stock/models.py:711 +#: stock/models.py:2073 stock/models.py:2179 stock/serializers.py:332 +#: stock/serializers.py:697 stock/serializers.py:795 stock/serializers.py:927 +#: stock/templates/stock/stock_sidebar.html:25 +msgid "Notes" +msgstr "" + +#: build/models.py:337 +msgid "Extra build notes" +msgstr "" + +#: build/models.py:750 +msgid "No build output specified" +msgstr "" + +#: build/models.py:753 +msgid "Build output is already completed" +msgstr "" + +#: build/models.py:756 +msgid "Build output does not match Build Order" +msgstr "" + +#: build/models.py:1168 +msgid "" +"Build item must specify a build output, as master part is marked as trackable" +msgstr "" + +#: build/models.py:1177 +#, python-brace-format +msgid "Allocated quantity ({q}) must not execed available stock quantity ({a})" +msgstr "" + +#: build/models.py:1187 +msgid "Stock item is over-allocated" +msgstr "" + +#: build/models.py:1193 order/models.py:1225 +msgid "Allocation quantity must be greater than zero" +msgstr "" + +#: build/models.py:1199 +msgid "Quantity must be 1 for serialized stock" +msgstr "" + +#: build/models.py:1256 +msgid "Selected stock item not found in BOM" +msgstr "" + +#: build/models.py:1325 stock/templates/stock/item_base.html:329 +#: templates/InvenTree/search.html:137 templates/navbar.html:35 +msgid "Build" +msgstr "" + +#: build/models.py:1326 +msgid "Build to allocate parts" +msgstr "" + +#: build/models.py:1342 build/serializers.py:576 order/serializers.py:783 +#: order/serializers.py:801 stock/serializers.py:404 stock/serializers.py:635 +#: stock/serializers.py:753 stock/templates/stock/item_base.html:9 +#: stock/templates/stock/item_base.html:23 +#: stock/templates/stock/item_base.html:351 +msgid "Stock Item" +msgstr "" + +#: build/models.py:1343 +msgid "Source stock item" +msgstr "" + +#: build/models.py:1355 build/serializers.py:188 +#: build/templates/build/build_base.html:82 +#: build/templates/build/detail.html:34 common/models.py:1429 +#: company/forms.py:42 company/templates/company/supplier_part.html:251 +#: order/models.py:836 order/models.py:1265 order/serializers.py:903 +#: order/templates/order/order_wizard/match_parts.html:30 +#: order/templates/order/order_wizard/select_parts.html:34 part/forms.py:144 +#: part/forms.py:160 part/forms.py:176 part/models.py:2779 +#: part/templates/part/detail.html:965 part/templates/part/detail.html:1051 +#: part/templates/part/part_pricing.html:16 +#: part/templates/part/upload_bom.html:53 +#: report/templates/report/inventree_build_order_base.html:114 +#: report/templates/report/inventree_po_report.html:90 +#: report/templates/report/inventree_so_report.html:91 +#: report/templates/report/inventree_test_report_base.html:81 +#: report/templates/report/inventree_test_report_base.html:139 +#: stock/forms.py:139 stock/serializers.py:293 +#: stock/templates/stock/item_base.html:181 +#: stock/templates/stock/item_base.html:246 +#: stock/templates/stock/item_base.html:254 +msgid "Quantity" +msgstr "" + +#: build/models.py:1356 +msgid "Stock quantity to allocate to build" +msgstr "" + +#: build/models.py:1364 +msgid "Install into" +msgstr "" + +#: build/models.py:1365 +msgid "Destination stock item" +msgstr "" + +#: build/serializers.py:138 build/serializers.py:605 +msgid "Build Output" +msgstr "" + +#: build/serializers.py:150 +msgid "Build output does not match the parent build" +msgstr "" + +#: build/serializers.py:154 +msgid "Output part does not match BuildOrder part" +msgstr "" + +#: build/serializers.py:158 +msgid "This build output has already been completed" +msgstr "" + +#: build/serializers.py:164 +msgid "This build output is not fully allocated" +msgstr "" + +#: build/serializers.py:189 +msgid "Enter quantity for build output" +msgstr "" + +#: build/serializers.py:201 build/serializers.py:596 order/models.py:280 +#: order/serializers.py:267 part/serializers.py:556 part/serializers.py:1001 +#: stock/models.py:479 stock/models.py:1282 stock/serializers.py:305 +msgid "Quantity must be greater than zero" +msgstr "" + +#: build/serializers.py:208 +msgid "Integer quantity required for trackable parts" +msgstr "" + +#: build/serializers.py:211 +msgid "" +"Integer quantity required, as the bill of materials contains trackable parts" +msgstr "" + +#: build/serializers.py:225 order/serializers.py:280 order/serializers.py:907 +#: stock/forms.py:78 stock/serializers.py:314 +msgid "Serial Numbers" +msgstr "" + +#: build/serializers.py:226 +msgid "Enter serial numbers for build outputs" +msgstr "" + +#: build/serializers.py:240 +msgid "Auto Allocate Serial Numbers" +msgstr "" + +#: build/serializers.py:241 +msgid "Automatically allocate required items with matching serial numbers" +msgstr "" + +#: build/serializers.py:275 stock/api.py:591 +msgid "The following serial numbers already exist" +msgstr "" + +#: build/serializers.py:328 build/serializers.py:393 +msgid "A list of build outputs must be provided" +msgstr "" + +#: build/serializers.py:370 order/serializers.py:253 order/serializers.py:358 +#: stock/forms.py:169 stock/serializers.py:325 stock/serializers.py:788 +#: stock/serializers.py:1029 stock/templates/stock/item_base.html:297 +msgid "Location" +msgstr "" + +#: build/serializers.py:371 +msgid "Location for completed build outputs" +msgstr "" + +#: build/serializers.py:377 build/templates/build/build_base.html:142 +#: build/templates/build/detail.html:62 order/models.py:579 +#: order/serializers.py:290 stock/templates/stock/item_base.html:187 +msgid "Status" +msgstr "" + +#: build/serializers.py:434 +msgid "Accept Unallocated" +msgstr "" + +#: build/serializers.py:435 +msgid "" +"Accept that stock items have not been fully allocated to this build order" +msgstr "" + +#: build/serializers.py:445 +msgid "Required stock has not been fully allocated" +msgstr "" + +#: build/serializers.py:450 +msgid "Accept Incomplete" +msgstr "" + +#: build/serializers.py:451 +msgid "" +"Accept that the required number of build outputs have not been completed" +msgstr "" + +#: build/serializers.py:461 +msgid "Required build quantity has not been completed" +msgstr "" + +#: build/serializers.py:470 +msgid "Build order has incomplete outputs" +msgstr "" + +#: build/serializers.py:473 build/templates/build/build_base.html:95 +msgid "No build outputs have been created for this build order" +msgstr "" + +#: build/serializers.py:501 build/serializers.py:550 part/models.py:2903 +#: part/models.py:3045 +msgid "BOM Item" +msgstr "" + +#: build/serializers.py:511 +msgid "Build output" +msgstr "" + +#: build/serializers.py:520 +msgid "Build output must point to the same build" +msgstr "" + +#: build/serializers.py:567 +msgid "bom_item.part must point to the same part as the build order" +msgstr "" + +#: build/serializers.py:582 stock/serializers.py:642 +msgid "Item must be in stock" +msgstr "" + +#: build/serializers.py:638 order/serializers.py:834 +#, python-brace-format +msgid "Available quantity ({q}) exceeded" +msgstr "" + +#: build/serializers.py:644 +msgid "Build output must be specified for allocation of tracked parts" +msgstr "" + +#: build/serializers.py:651 +msgid "Build output cannot be specified for allocation of untracked parts" +msgstr "" + +#: build/serializers.py:679 order/serializers.py:1077 +msgid "Allocation items must be provided" +msgstr "" + +#: build/serializers.py:731 +msgid "" +"Stock location where parts are to be sourced (leave blank to take from any " +"location)" +msgstr "" + +#: build/serializers.py:739 +msgid "Exclude Location" +msgstr "" + +#: build/serializers.py:740 +msgid "Exclude stock items from this selected location" +msgstr "" + +#: build/serializers.py:745 +msgid "Interchangeable Stock" +msgstr "" + +#: build/serializers.py:746 +msgid "Stock items in multiple locations can be used interchangeably" +msgstr "" + +#: build/serializers.py:751 +msgid "Substitute Stock" +msgstr "" + +#: build/serializers.py:752 +msgid "Allow allocation of substitute parts" +msgstr "" + +#: build/tasks.py:98 +msgid "Stock required for build order" +msgstr "" + +#: build/templates/build/build_base.html:39 +#: order/templates/order/order_base.html:28 +#: order/templates/order/sales_order_base.html:38 +msgid "Print actions" +msgstr "" + +#: build/templates/build/build_base.html:43 +msgid "Print build order report" +msgstr "" + +#: build/templates/build/build_base.html:50 +msgid "Build actions" +msgstr "" + +#: build/templates/build/build_base.html:54 +msgid "Edit Build" +msgstr "" + +#: build/templates/build/build_base.html:56 +#: build/templates/build/build_base.html:220 build/views.py:53 +msgid "Cancel Build" +msgstr "" + +#: build/templates/build/build_base.html:59 +msgid "Delete Build" +msgstr "" + +#: build/templates/build/build_base.html:64 +#: build/templates/build/build_base.html:65 +msgid "Complete Build" +msgstr "" + +#: build/templates/build/build_base.html:87 +msgid "Build Description" +msgstr "" + +#: build/templates/build/build_base.html:101 +#, python-format +msgid "This Build Order is allocated to Sales Order %(link)s" +msgstr "" + +#: build/templates/build/build_base.html:108 +#, python-format +msgid "This Build Order is a child of Build Order %(link)s" +msgstr "" + +#: build/templates/build/build_base.html:115 +msgid "Build Order is ready to mark as completed" +msgstr "" + +#: build/templates/build/build_base.html:120 +msgid "Build Order cannot be completed as outstanding outputs remain" +msgstr "" + +#: build/templates/build/build_base.html:125 +msgid "Required build quantity has not yet been completed" +msgstr "" + +#: build/templates/build/build_base.html:130 +msgid "Stock has not been fully allocated to this Build Order" +msgstr "" + +#: build/templates/build/build_base.html:151 +#: build/templates/build/detail.html:131 order/models.py:849 +#: order/templates/order/order_base.html:156 +#: order/templates/order/sales_order_base.html:163 +#: report/templates/report/inventree_build_order_base.html:126 +msgid "Target Date" +msgstr "" + +#: build/templates/build/build_base.html:156 +#, python-format +msgid "This build was due on %(target)s" +msgstr "" + +#: build/templates/build/build_base.html:156 +#: build/templates/build/build_base.html:201 +#: order/templates/order/order_base.html:98 +#: order/templates/order/sales_order_base.html:93 +msgid "Overdue" +msgstr "" + +#: build/templates/build/build_base.html:163 +#: build/templates/build/detail.html:67 build/templates/build/detail.html:142 +#: order/templates/order/sales_order_base.html:170 +msgid "Completed" +msgstr "" + +#: build/templates/build/build_base.html:176 +#: build/templates/build/detail.html:94 order/models.py:983 +#: order/models.py:1079 order/templates/order/sales_order_base.html:9 +#: order/templates/order/sales_order_base.html:28 +#: report/templates/report/inventree_build_order_base.html:136 +#: report/templates/report/inventree_so_report.html:77 +#: stock/templates/stock/item_base.html:291 +msgid "Sales Order" +msgstr "" + +#: build/templates/build/build_base.html:183 +#: build/templates/build/detail.html:108 +#: report/templates/report/inventree_build_order_base.html:153 +msgid "Issued By" +msgstr "" + +#: build/templates/build/build_base.html:228 +#: build/templates/build/sidebar.html:12 +msgid "Incomplete Outputs" +msgstr "" + +#: build/templates/build/build_base.html:229 +msgid "Build Order cannot be completed as incomplete build outputs remain" +msgstr "" + +#: build/templates/build/cancel.html:5 +msgid "Are you sure you wish to cancel this build?" +msgstr "" + +#: build/templates/build/delete_build.html:5 +msgid "Are you sure you want to delete this build?" +msgstr "" + +#: build/templates/build/detail.html:15 +msgid "Build Details" +msgstr "" + +#: build/templates/build/detail.html:38 +msgid "Stock Source" +msgstr "" + +#: build/templates/build/detail.html:43 +msgid "Stock can be taken from any available location." +msgstr "" + +#: build/templates/build/detail.html:49 order/models.py:934 stock/forms.py:133 +msgid "Destination" +msgstr "" + +#: build/templates/build/detail.html:56 +msgid "Destination location not specified" +msgstr "" + +#: build/templates/build/detail.html:73 +msgid "Allocated Parts" +msgstr "" + +#: build/templates/build/detail.html:80 +#: stock/templates/stock/item_base.html:315 +msgid "Batch" +msgstr "" + +#: build/templates/build/detail.html:126 +#: order/templates/order/order_base.html:143 +#: order/templates/order/sales_order_base.html:157 +msgid "Created" +msgstr "" + +#: build/templates/build/detail.html:137 +msgid "No target date set" +msgstr "" + +#: build/templates/build/detail.html:146 +msgid "Build not complete" +msgstr "" + +#: build/templates/build/detail.html:157 build/templates/build/sidebar.html:17 +msgid "Child Build Orders" +msgstr "" + +#: build/templates/build/detail.html:172 +msgid "Allocate Stock to Build" +msgstr "" + +#: build/templates/build/detail.html:176 +msgid "Unallocate stock" +msgstr "" + +#: build/templates/build/detail.html:177 +msgid "Unallocate Stock" +msgstr "" + +#: build/templates/build/detail.html:179 +msgid "Automatically allocate stock to build" +msgstr "" + +#: build/templates/build/detail.html:180 +msgid "Auto Allocate" +msgstr "" + +#: build/templates/build/detail.html:182 +msgid "Manually allocate stock to build" +msgstr "" + +#: build/templates/build/detail.html:183 build/templates/build/sidebar.html:8 +msgid "Allocate Stock" +msgstr "" + +#: build/templates/build/detail.html:186 +msgid "Order required parts" +msgstr "" + +#: build/templates/build/detail.html:187 +#: company/templates/company/detail.html:37 +#: company/templates/company/detail.html:84 order/views.py:463 +#: part/templates/part/category.html:174 +msgid "Order Parts" +msgstr "" + +#: build/templates/build/detail.html:199 +msgid "Untracked stock has been fully allocated for this Build Order" +msgstr "" + +#: build/templates/build/detail.html:203 +msgid "Untracked stock has not been fully allocated for this Build Order" +msgstr "" + +#: build/templates/build/detail.html:210 +msgid "Allocate selected items" +msgstr "" + +#: build/templates/build/detail.html:220 +msgid "This Build Order does not have any associated untracked BOM items" +msgstr "" + +#: build/templates/build/detail.html:229 +msgid "Incomplete Build Outputs" +msgstr "" + +#: build/templates/build/detail.html:233 +msgid "Create new build output" +msgstr "" + +#: build/templates/build/detail.html:234 +msgid "New Build Output" +msgstr "" + +#: build/templates/build/detail.html:248 +msgid "Output Actions" +msgstr "" + +#: build/templates/build/detail.html:252 +msgid "Complete selected build outputs" +msgstr "" + +#: build/templates/build/detail.html:253 +msgid "Complete outputs" +msgstr "" + +#: build/templates/build/detail.html:255 +msgid "Delete selected build outputs" +msgstr "" + +#: build/templates/build/detail.html:256 +msgid "Delete outputs" +msgstr "" + +#: build/templates/build/detail.html:263 +#: stock/templates/stock/location.html:188 templates/stock_table.html:27 +msgid "Printing Actions" +msgstr "" + +#: build/templates/build/detail.html:267 build/templates/build/detail.html:268 +#: stock/templates/stock/location.html:192 templates/stock_table.html:31 +msgid "Print labels" +msgstr "" + +#: build/templates/build/detail.html:285 +msgid "Completed Build Outputs" +msgstr "" + +#: build/templates/build/detail.html:297 build/templates/build/sidebar.html:19 +#: order/templates/order/po_sidebar.html:9 +#: order/templates/order/purchase_order_detail.html:59 +#: order/templates/order/sales_order_detail.html:106 +#: order/templates/order/so_sidebar.html:15 part/templates/part/detail.html:206 +#: part/templates/part/part_sidebar.html:58 stock/templates/stock/item.html:122 +#: stock/templates/stock/stock_sidebar.html:23 +msgid "Attachments" +msgstr "" + +#: build/templates/build/detail.html:312 +msgid "Build Notes" +msgstr "" + +#: build/templates/build/detail.html:548 +msgid "Allocation Complete" +msgstr "" + +#: build/templates/build/detail.html:549 +msgid "All untracked stock items have been allocated" +msgstr "" + +#: build/templates/build/index.html:18 part/templates/part/detail.html:312 +msgid "New Build Order" +msgstr "" + +#: build/templates/build/index.html:37 build/templates/build/index.html:38 +msgid "Print Build Orders" +msgstr "" + +#: build/templates/build/index.html:44 +#: order/templates/order/purchase_orders.html:34 +#: order/templates/order/sales_orders.html:37 +msgid "Display calendar view" +msgstr "" + +#: build/templates/build/index.html:47 +#: order/templates/order/purchase_orders.html:37 +#: order/templates/order/sales_orders.html:40 +msgid "Display list view" +msgstr "" + +#: build/templates/build/sidebar.html:5 +msgid "Build Order Details" +msgstr "" + +#: build/templates/build/sidebar.html:15 +msgid "Completed Outputs" +msgstr "" + +#: build/views.py:73 +msgid "Build was cancelled" +msgstr "" + +#: build/views.py:114 +msgid "Delete Build Order" +msgstr "" + +#: common/files.py:65 +msgid "Unsupported file format: {ext.upper()}" +msgstr "" + +#: common/files.py:67 +msgid "Error reading file (invalid encoding)" +msgstr "" + +#: common/files.py:72 +msgid "Error reading file (invalid format)" +msgstr "" + +#: common/files.py:74 +msgid "Error reading file (incorrect dimension)" +msgstr "" + +#: common/files.py:76 +msgid "Error reading file (data could be corrupted)" +msgstr "" + +#: common/forms.py:34 +msgid "File" +msgstr "" + +#: common/forms.py:35 +msgid "Select file to upload" +msgstr "" + +#: common/forms.py:50 +msgid "{name.title()} File" +msgstr "" + +#: common/forms.py:51 +#, python-brace-format +msgid "Select {name} file to upload" +msgstr "" + +#: common/models.py:381 +msgid "Settings key (must be unique - case insensitive)" +msgstr "" + +#: common/models.py:383 +msgid "Settings value" +msgstr "" + +#: common/models.py:417 +msgid "Chosen value is not a valid option" +msgstr "" + +#: common/models.py:437 +msgid "Value must be a boolean value" +msgstr "" + +#: common/models.py:448 +msgid "Value must be an integer value" +msgstr "" + +#: common/models.py:490 +msgid "Key string must be unique" +msgstr "" + +#: common/models.py:637 +msgid "No group" +msgstr "" + +#: common/models.py:679 +msgid "Restart required" +msgstr "" + +#: common/models.py:680 +msgid "A setting has been changed which requires a server restart" +msgstr "" + +#: common/models.py:687 +msgid "InvenTree Instance Name" +msgstr "" + +#: common/models.py:689 +msgid "String descriptor for the server instance" +msgstr "" + +#: common/models.py:693 +msgid "Use instance name" +msgstr "" + +#: common/models.py:694 +msgid "Use the instance name in the title-bar" +msgstr "" + +#: common/models.py:700 company/models.py:100 company/models.py:101 +msgid "Company name" +msgstr "" + +#: common/models.py:701 +msgid "Internal company name" +msgstr "" + +#: common/models.py:706 +msgid "Base URL" +msgstr "" + +#: common/models.py:707 +msgid "Base URL for server instance" +msgstr "" + +#: common/models.py:713 +msgid "Default Currency" +msgstr "" + +#: common/models.py:714 +msgid "Default currency" +msgstr "" + +#: common/models.py:720 +msgid "Download from URL" +msgstr "" + +#: common/models.py:721 +msgid "Allow download of remote images and files from external URL" +msgstr "" + +#: common/models.py:727 templates/InvenTree/settings/sidebar.html:33 +msgid "Barcode Support" +msgstr "" + +#: common/models.py:728 +msgid "Enable barcode scanner support" +msgstr "" + +#: common/models.py:734 +msgid "IPN Regex" +msgstr "" + +#: common/models.py:735 +msgid "Regular expression pattern for matching Part IPN" +msgstr "" + +#: common/models.py:739 +msgid "Allow Duplicate IPN" +msgstr "" + +#: common/models.py:740 +msgid "Allow multiple parts to share the same IPN" +msgstr "" + +#: common/models.py:746 +msgid "Allow Editing IPN" +msgstr "" + +#: common/models.py:747 +msgid "Allow changing the IPN value while editing a part" +msgstr "" + +#: common/models.py:753 +msgid "Copy Part BOM Data" +msgstr "" + +#: common/models.py:754 +msgid "Copy BOM data by default when duplicating a part" +msgstr "" + +#: common/models.py:760 +msgid "Copy Part Parameter Data" +msgstr "" + +#: common/models.py:761 +msgid "Copy parameter data by default when duplicating a part" +msgstr "" + +#: common/models.py:767 +msgid "Copy Part Test Data" +msgstr "" + +#: common/models.py:768 +msgid "Copy test data by default when duplicating a part" +msgstr "" + +#: common/models.py:774 +msgid "Copy Category Parameter Templates" +msgstr "" + +#: common/models.py:775 +msgid "Copy category parameter templates when creating a part" +msgstr "" + +#: common/models.py:781 part/models.py:2598 report/models.py:183 +msgid "Template" +msgstr "" + +#: common/models.py:782 +msgid "Parts are templates by default" +msgstr "" + +#: common/models.py:788 part/models.py:964 +msgid "Assembly" +msgstr "" + +#: common/models.py:789 +msgid "Parts can be assembled from other components by default" +msgstr "" + +#: common/models.py:795 part/models.py:970 +msgid "Component" +msgstr "" + +#: common/models.py:796 +msgid "Parts can be used as sub-components by default" +msgstr "" + +#: common/models.py:802 part/models.py:981 +msgid "Purchaseable" +msgstr "" + +#: common/models.py:803 +msgid "Parts are purchaseable by default" +msgstr "" + +#: common/models.py:809 part/models.py:986 +msgid "Salable" +msgstr "" + +#: common/models.py:810 +msgid "Parts are salable by default" +msgstr "" + +#: common/models.py:816 part/models.py:976 +msgid "Trackable" +msgstr "" + +#: common/models.py:817 +msgid "Parts are trackable by default" +msgstr "" + +#: common/models.py:823 part/models.py:996 +#: part/templates/part/part_base.html:151 +msgid "Virtual" +msgstr "" + +#: common/models.py:824 +msgid "Parts are virtual by default" +msgstr "" + +#: common/models.py:830 +msgid "Show Import in Views" +msgstr "" + +#: common/models.py:831 +msgid "Display the import wizard in some part views" +msgstr "" + +#: common/models.py:837 +msgid "Show Price in Forms" +msgstr "" + +#: common/models.py:838 +msgid "Display part price in some forms" +msgstr "" + +#: common/models.py:849 +msgid "Show Price in BOM" +msgstr "" + +#: common/models.py:850 +msgid "Include pricing information in BOM tables" +msgstr "" + +#: common/models.py:861 +msgid "Show Price History" +msgstr "" + +#: common/models.py:862 +msgid "Display historical pricing for Part" +msgstr "" + +#: common/models.py:868 +msgid "Show related parts" +msgstr "" + +#: common/models.py:869 +msgid "Display related parts for a part" +msgstr "" + +#: common/models.py:875 +msgid "Create initial stock" +msgstr "" + +#: common/models.py:876 +msgid "Create initial stock on part creation" +msgstr "" + +#: common/models.py:882 +msgid "Internal Prices" +msgstr "" + +#: common/models.py:883 +msgid "Enable internal prices for parts" +msgstr "" + +#: common/models.py:889 +msgid "Internal Price as BOM-Price" +msgstr "" + +#: common/models.py:890 +msgid "Use the internal price (if set) in BOM-price calculations" +msgstr "" + +#: common/models.py:896 +msgid "Part Name Display Format" +msgstr "" + +#: common/models.py:897 +msgid "Format to display the part name" +msgstr "" + +#: common/models.py:904 +msgid "Enable Reports" +msgstr "" + +#: common/models.py:905 +msgid "Enable generation of reports" +msgstr "" + +#: common/models.py:911 templates/stats.html:25 +msgid "Debug Mode" +msgstr "" + +#: common/models.py:912 +msgid "Generate reports in debug mode (HTML output)" +msgstr "" + +#: common/models.py:918 +msgid "Page Size" +msgstr "" + +#: common/models.py:919 +msgid "Default page size for PDF reports" +msgstr "" + +#: common/models.py:929 +msgid "Test Reports" +msgstr "" + +#: common/models.py:930 +msgid "Enable generation of test reports" +msgstr "" + +#: common/models.py:936 +msgid "Stock Expiry" +msgstr "" + +#: common/models.py:937 +msgid "Enable stock expiry functionality" +msgstr "" + +#: common/models.py:943 +msgid "Sell Expired Stock" +msgstr "" + +#: common/models.py:944 +msgid "Allow sale of expired stock" +msgstr "" + +#: common/models.py:950 +msgid "Stock Stale Time" +msgstr "" + +#: common/models.py:951 +msgid "Number of days stock items are considered stale before expiring" +msgstr "" + +#: common/models.py:953 +msgid "days" +msgstr "" + +#: common/models.py:958 +msgid "Build Expired Stock" +msgstr "" + +#: common/models.py:959 +msgid "Allow building with expired stock" +msgstr "" + +#: common/models.py:965 +msgid "Stock Ownership Control" +msgstr "" + +#: common/models.py:966 +msgid "Enable ownership control over stock locations and items" +msgstr "" + +#: common/models.py:972 +msgid "Build Order Reference Prefix" +msgstr "" + +#: common/models.py:973 +msgid "Prefix value for build order reference" +msgstr "" + +#: common/models.py:978 +msgid "Build Order Reference Regex" +msgstr "" + +#: common/models.py:979 +msgid "Regular expression pattern for matching build order reference" +msgstr "" + +#: common/models.py:983 +msgid "Sales Order Reference Prefix" +msgstr "" + +#: common/models.py:984 +msgid "Prefix value for sales order reference" +msgstr "" + +#: common/models.py:989 +msgid "Purchase Order Reference Prefix" +msgstr "" + +#: common/models.py:990 +msgid "Prefix value for purchase order reference" +msgstr "" + +#: common/models.py:996 +msgid "Enable password forgot" +msgstr "" + +#: common/models.py:997 +msgid "Enable password forgot function on the login pages" +msgstr "" + +#: common/models.py:1002 +msgid "Enable registration" +msgstr "" + +#: common/models.py:1003 +msgid "Enable self-registration for users on the login pages" +msgstr "" + +#: common/models.py:1008 +msgid "Enable SSO" +msgstr "" + +#: common/models.py:1009 +msgid "Enable SSO on the login pages" +msgstr "" + +#: common/models.py:1014 +msgid "Email required" +msgstr "" + +#: common/models.py:1015 +msgid "Require user to supply mail on signup" +msgstr "" + +#: common/models.py:1020 +msgid "Auto-fill SSO users" +msgstr "" + +#: common/models.py:1021 +msgid "Automatically fill out user-details from SSO account-data" +msgstr "" + +#: common/models.py:1026 +msgid "Mail twice" +msgstr "" + +#: common/models.py:1027 +msgid "On signup ask users twice for their mail" +msgstr "" + +#: common/models.py:1032 +msgid "Password twice" +msgstr "" + +#: common/models.py:1033 +msgid "On signup ask users twice for their password" +msgstr "" + +#: common/models.py:1038 +msgid "Group on signup" +msgstr "" + +#: common/models.py:1039 +msgid "Group to which new users are assigned on registration" +msgstr "" + +#: common/models.py:1044 +msgid "Enforce MFA" +msgstr "" + +#: common/models.py:1045 +msgid "Users must use multifactor security." +msgstr "" + +#: common/models.py:1051 +msgid "Check plugins on startup" +msgstr "" + +#: common/models.py:1052 +msgid "" +"Check that all plugins are installed on startup - enable in container " +"enviroments" +msgstr "" + +#: common/models.py:1059 +msgid "Enable URL integration" +msgstr "" + +#: common/models.py:1060 +msgid "Enable plugins to add URL routes" +msgstr "" + +#: common/models.py:1066 +msgid "Enable navigation integration" +msgstr "" + +#: common/models.py:1067 +msgid "Enable plugins to integrate into navigation" +msgstr "" + +#: common/models.py:1073 +msgid "Enable app integration" +msgstr "" + +#: common/models.py:1074 +msgid "Enable plugins to add apps" +msgstr "" + +#: common/models.py:1080 +msgid "Enable schedule integration" +msgstr "" + +#: common/models.py:1081 +msgid "Enable plugins to run scheduled tasks" +msgstr "" + +#: common/models.py:1087 +msgid "Enable event integration" +msgstr "" + +#: common/models.py:1088 +msgid "Enable plugins to respond to internal events" +msgstr "" + +#: common/models.py:1103 common/models.py:1389 +msgid "Settings key (must be unique - case insensitive" +msgstr "" + +#: common/models.py:1134 +msgid "Show subscribed parts" +msgstr "" + +#: common/models.py:1135 +msgid "Show subscribed parts on the homepage" +msgstr "" + +#: common/models.py:1140 +msgid "Show subscribed categories" +msgstr "" + +#: common/models.py:1141 +msgid "Show subscribed part categories on the homepage" +msgstr "" + +#: common/models.py:1146 +msgid "Show latest parts" +msgstr "" + +#: common/models.py:1147 +msgid "Show latest parts on the homepage" +msgstr "" + +#: common/models.py:1152 +msgid "Recent Part Count" +msgstr "" + +#: common/models.py:1153 +msgid "Number of recent parts to display on index page" +msgstr "" + +#: common/models.py:1159 +msgid "Show unvalidated BOMs" +msgstr "" + +#: common/models.py:1160 +msgid "Show BOMs that await validation on the homepage" +msgstr "" + +#: common/models.py:1165 +msgid "Show recent stock changes" +msgstr "" + +#: common/models.py:1166 +msgid "Show recently changed stock items on the homepage" +msgstr "" + +#: common/models.py:1171 +msgid "Recent Stock Count" +msgstr "" + +#: common/models.py:1172 +msgid "Number of recent stock items to display on index page" +msgstr "" + +#: common/models.py:1177 +msgid "Show low stock" +msgstr "" + +#: common/models.py:1178 +msgid "Show low stock items on the homepage" +msgstr "" + +#: common/models.py:1183 +msgid "Show depleted stock" +msgstr "" + +#: common/models.py:1184 +msgid "Show depleted stock items on the homepage" +msgstr "" + +#: common/models.py:1189 +msgid "Show needed stock" +msgstr "" + +#: common/models.py:1190 +msgid "Show stock items needed for builds on the homepage" +msgstr "" + +#: common/models.py:1195 +msgid "Show expired stock" +msgstr "" + +#: common/models.py:1196 +msgid "Show expired stock items on the homepage" +msgstr "" + +#: common/models.py:1201 +msgid "Show stale stock" +msgstr "" + +#: common/models.py:1202 +msgid "Show stale stock items on the homepage" +msgstr "" + +#: common/models.py:1207 +msgid "Show pending builds" +msgstr "" + +#: common/models.py:1208 +msgid "Show pending builds on the homepage" +msgstr "" + +#: common/models.py:1213 +msgid "Show overdue builds" +msgstr "" + +#: common/models.py:1214 +msgid "Show overdue builds on the homepage" +msgstr "" + +#: common/models.py:1219 +msgid "Show outstanding POs" +msgstr "" + +#: common/models.py:1220 +msgid "Show outstanding POs on the homepage" +msgstr "" + +#: common/models.py:1225 +msgid "Show overdue POs" +msgstr "" + +#: common/models.py:1226 +msgid "Show overdue POs on the homepage" +msgstr "" + +#: common/models.py:1231 +msgid "Show outstanding SOs" +msgstr "" + +#: common/models.py:1232 +msgid "Show outstanding SOs on the homepage" +msgstr "" + +#: common/models.py:1237 +msgid "Show overdue SOs" +msgstr "" + +#: common/models.py:1238 +msgid "Show overdue SOs on the homepage" +msgstr "" + +#: common/models.py:1244 +msgid "Enable email notifications" +msgstr "" + +#: common/models.py:1245 +msgid "Allow sending of emails for event notifications" +msgstr "" + +#: common/models.py:1251 +msgid "Enable label printing" +msgstr "" + +#: common/models.py:1252 +msgid "Enable label printing from the web interface" +msgstr "" + +#: common/models.py:1258 +msgid "Inline label display" +msgstr "" + +#: common/models.py:1259 +msgid "Display PDF labels in the browser, instead of downloading as a file" +msgstr "" + +#: common/models.py:1265 +msgid "Inline report display" +msgstr "" + +#: common/models.py:1266 +msgid "Display PDF reports in the browser, instead of downloading as a file" +msgstr "" + +#: common/models.py:1272 +msgid "Search Parts" +msgstr "" + +#: common/models.py:1273 +msgid "Display parts in search preview window" +msgstr "" + +#: common/models.py:1279 +msgid "Search Categories" +msgstr "" + +#: common/models.py:1280 +msgid "Display part categories in search preview window" +msgstr "" + +#: common/models.py:1286 +msgid "Search Stock" +msgstr "" + +#: common/models.py:1287 +msgid "Display stock items in search preview window" +msgstr "" + +#: common/models.py:1293 +msgid "Search Locations" +msgstr "" + +#: common/models.py:1294 +msgid "Display stock locations in search preview window" +msgstr "" + +#: common/models.py:1300 +msgid "Search Companies" +msgstr "" + +#: common/models.py:1301 +msgid "Display companies in search preview window" +msgstr "" + +#: common/models.py:1307 +msgid "Search Purchase Orders" +msgstr "" + +#: common/models.py:1308 +msgid "Display purchase orders in search preview window" +msgstr "" + +#: common/models.py:1314 +msgid "Search Sales Orders" +msgstr "" + +#: common/models.py:1315 +msgid "Display sales orders in search preview window" +msgstr "" + +#: common/models.py:1321 +msgid "Search Preview Results" +msgstr "" + +#: common/models.py:1322 +msgid "Number of results to show in each section of the search preview window" +msgstr "" + +#: common/models.py:1328 +msgid "Hide Inactive Parts" +msgstr "" + +#: common/models.py:1329 +msgid "Hide inactive parts in search preview window" +msgstr "" + +#: common/models.py:1335 +msgid "Show Quantity in Forms" +msgstr "" + +#: common/models.py:1336 +msgid "Display available part quantity in some forms" +msgstr "" + +#: common/models.py:1342 +msgid "Escape Key Closes Forms" +msgstr "" + +#: common/models.py:1343 +msgid "Use the escape key to close modal forms" +msgstr "" + +#: common/models.py:1349 +msgid "Fixed Navbar" +msgstr "" + +#: common/models.py:1350 +msgid "InvenTree navbar position is fixed to the top of the screen" +msgstr "" + +#: common/models.py:1356 +msgid "Date Format" +msgstr "" + +#: common/models.py:1357 +msgid "Preferred format for displaying dates" +msgstr "" + +#: common/models.py:1371 part/templates/part/detail.html:39 +msgid "Part Scheduling" +msgstr "" + +#: common/models.py:1372 +msgid "Display part scheduling information" +msgstr "" + +#: common/models.py:1430 company/forms.py:43 +msgid "Price break quantity" +msgstr "" + +#: common/models.py:1437 company/serializers.py:264 +#: company/templates/company/supplier_part.html:256 +msgid "Price" +msgstr "" + +#: common/models.py:1438 +msgid "Unit price at specified quantity" +msgstr "" + +#: common/models.py:1595 common/models.py:1734 +msgid "Endpoint" +msgstr "" + +#: common/models.py:1596 +msgid "Endpoint at which this webhook is received" +msgstr "" + +#: common/models.py:1605 +msgid "Name for this webhook" +msgstr "" + +#: common/models.py:1610 part/models.py:991 plugin/models.py:46 +msgid "Active" +msgstr "" + +#: common/models.py:1611 +msgid "Is this webhook active" +msgstr "" + +#: common/models.py:1625 +msgid "Token" +msgstr "" + +#: common/models.py:1626 +msgid "Token for access" +msgstr "" + +#: common/models.py:1633 +msgid "Secret" +msgstr "" + +#: common/models.py:1634 +msgid "Shared secret for HMAC" +msgstr "" + +#: common/models.py:1701 +msgid "Message ID" +msgstr "" + +#: common/models.py:1702 +msgid "Unique identifier for this message" +msgstr "" + +#: common/models.py:1710 +msgid "Host" +msgstr "" + +#: common/models.py:1711 +msgid "Host from which this message was received" +msgstr "" + +#: common/models.py:1718 +msgid "Header" +msgstr "" + +#: common/models.py:1719 +msgid "Header of this message" +msgstr "" + +#: common/models.py:1725 +msgid "Body" +msgstr "" + +#: common/models.py:1726 +msgid "Body of this message" +msgstr "" + +#: common/models.py:1735 +msgid "Endpoint on which this message was received" +msgstr "" + +#: common/models.py:1740 +msgid "Worked on" +msgstr "" + +#: common/models.py:1741 +msgid "Was the work on this message finished?" +msgstr "" + +#: common/views.py:93 order/templates/order/purchase_order_detail.html:23 +#: order/views.py:243 part/views.py:208 +#: templates/patterns/wizard/upload.html:37 +msgid "Upload File" +msgstr "" + +#: common/views.py:94 order/views.py:244 +#: part/templates/part/import_wizard/ajax_match_fields.html:45 +#: part/views.py:209 templates/patterns/wizard/match_fields.html:51 +msgid "Match Fields" +msgstr "" + +#: common/views.py:95 +msgid "Match Items" +msgstr "" + +#: common/views.py:440 +msgid "Fields matching failed" +msgstr "" + +#: common/views.py:495 +msgid "Parts imported" +msgstr "" + +#: common/views.py:517 order/templates/order/order_wizard/match_parts.html:19 +#: part/templates/part/import_wizard/match_references.html:19 +#: templates/patterns/wizard/match_fields.html:26 +#: templates/patterns/wizard/upload.html:35 +msgid "Previous Step" +msgstr "" + +#: company/forms.py:24 part/forms.py:46 +#: templates/InvenTree/settings/mixins/urls.html:14 +msgid "URL" +msgstr "" + +#: company/forms.py:25 part/forms.py:47 +msgid "Image URL" +msgstr "" + +#: company/models.py:105 +msgid "Company description" +msgstr "" + +#: company/models.py:106 +msgid "Description of the company" +msgstr "" + +#: company/models.py:112 company/templates/company/company_base.html:97 +#: templates/InvenTree/settings/plugin_settings.html:55 +msgid "Website" +msgstr "" + +#: company/models.py:113 +msgid "Company website URL" +msgstr "" + +#: company/models.py:117 company/templates/company/company_base.html:115 +msgid "Address" +msgstr "" + +#: company/models.py:118 +msgid "Company address" +msgstr "" + +#: company/models.py:121 +msgid "Phone number" +msgstr "" + +#: company/models.py:122 +msgid "Contact phone number" +msgstr "" + +#: company/models.py:125 company/templates/company/company_base.html:129 +#: templates/InvenTree/settings/user.html:48 +msgid "Email" +msgstr "" + +#: company/models.py:125 +msgid "Contact email address" +msgstr "" + +#: company/models.py:128 company/templates/company/company_base.html:136 +msgid "Contact" +msgstr "" + +#: company/models.py:129 +msgid "Point of contact" +msgstr "" + +#: company/models.py:131 +msgid "Link to external company information" +msgstr "" + +#: company/models.py:139 part/models.py:883 +msgid "Image" +msgstr "" + +#: company/models.py:144 +msgid "is customer" +msgstr "" + +#: company/models.py:144 +msgid "Do you sell items to this company?" +msgstr "" + +#: company/models.py:146 +msgid "is supplier" +msgstr "" + +#: company/models.py:146 +msgid "Do you purchase items from this company?" +msgstr "" + +#: company/models.py:148 +msgid "is manufacturer" +msgstr "" + +#: company/models.py:148 +msgid "Does this company manufacture parts?" +msgstr "" + +#: company/models.py:152 company/serializers.py:270 +#: company/templates/company/company_base.html:103 stock/serializers.py:179 +msgid "Currency" +msgstr "" + +#: company/models.py:155 +msgid "Default currency used for this company" +msgstr "" + +#: company/models.py:320 company/models.py:535 stock/models.py:583 +#: stock/templates/stock/item_base.html:142 +msgid "Base Part" +msgstr "" + +#: company/models.py:324 company/models.py:539 +msgid "Select part" +msgstr "" + +#: company/models.py:335 company/templates/company/company_base.html:73 +#: company/templates/company/manufacturer_part.html:91 +#: company/templates/company/supplier_part.html:97 +#: stock/templates/stock/item_base.html:364 +msgid "Manufacturer" +msgstr "" + +#: company/models.py:336 +msgid "Select manufacturer" +msgstr "" + +#: company/models.py:342 company/templates/company/manufacturer_part.html:96 +#: company/templates/company/supplier_part.html:105 +msgid "MPN" +msgstr "" + +#: company/models.py:343 +msgid "Manufacturer Part Number" +msgstr "" + +#: company/models.py:349 +msgid "URL for external manufacturer part link" +msgstr "" + +#: company/models.py:355 +msgid "Manufacturer part description" +msgstr "" + +#: company/models.py:409 company/models.py:558 +#: company/templates/company/manufacturer_part.html:6 +#: company/templates/company/manufacturer_part.html:23 +#: stock/templates/stock/item_base.html:374 +msgid "Manufacturer Part" +msgstr "" + +#: company/models.py:416 +msgid "Parameter name" +msgstr "" + +#: company/models.py:422 +#: report/templates/report/inventree_test_report_base.html:95 +#: stock/models.py:2166 +msgid "Value" +msgstr "" + +#: company/models.py:423 +msgid "Parameter value" +msgstr "" + +#: company/models.py:429 part/models.py:958 part/models.py:2566 +#: part/templates/part/part_base.html:306 +#: templates/InvenTree/settings/settings.html:325 +msgid "Units" +msgstr "" + +#: company/models.py:430 +msgid "Parameter units" +msgstr "" + +#: company/models.py:502 +msgid "Linked manufacturer part must reference the same base part" +msgstr "" + +#: company/models.py:545 company/templates/company/company_base.html:78 +#: company/templates/company/supplier_part.html:87 order/models.py:227 +#: order/templates/order/order_base.html:112 +#: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:237 +#: part/bom.py:265 stock/templates/stock/item_base.html:381 +msgid "Supplier" +msgstr "" + +#: company/models.py:546 +msgid "Select supplier" +msgstr "" + +#: company/models.py:551 company/templates/company/supplier_part.html:91 +#: part/bom.py:238 part/bom.py:266 +msgid "SKU" +msgstr "" + +#: company/models.py:552 +msgid "Supplier stock keeping unit" +msgstr "" + +#: company/models.py:559 +msgid "Select manufacturer part" +msgstr "" + +#: company/models.py:565 +msgid "URL for external supplier part link" +msgstr "" + +#: company/models.py:571 +msgid "Supplier part description" +msgstr "" + +#: company/models.py:576 company/templates/company/supplier_part.html:119 +#: part/models.py:2791 part/templates/part/upload_bom.html:59 +#: report/templates/report/inventree_po_report.html:92 +#: report/templates/report/inventree_so_report.html:93 stock/serializers.py:409 +msgid "Note" +msgstr "" + +#: company/models.py:580 part/models.py:1876 +msgid "base cost" +msgstr "" + +#: company/models.py:580 part/models.py:1876 +msgid "Minimum charge (e.g. stocking fee)" +msgstr "" + +#: company/models.py:582 company/templates/company/supplier_part.html:112 +#: stock/models.py:607 stock/templates/stock/item_base.html:322 +msgid "Packaging" +msgstr "" + +#: company/models.py:582 +msgid "Part packaging" +msgstr "" + +#: company/models.py:584 part/models.py:1878 +msgid "multiple" +msgstr "" + +#: company/models.py:584 +msgid "Order multiple" +msgstr "" + +#: company/models.py:708 +msgid "last updated" +msgstr "" + +#: company/serializers.py:70 +msgid "Default currency used for this supplier" +msgstr "" + +#: company/serializers.py:71 +msgid "Currency Code" +msgstr "" + +#: company/templates/company/company_base.html:8 +#: company/templates/company/company_base.html:12 +#: templates/InvenTree/search.html:176 +msgid "Company" +msgstr "" + +#: company/templates/company/company_base.html:22 +msgid "Create Purchase Order" +msgstr "" + +#: company/templates/company/company_base.html:26 +msgid "Company actions" +msgstr "" + +#: company/templates/company/company_base.html:31 +msgid "Edit company information" +msgstr "" + +#: company/templates/company/company_base.html:32 +msgid "Edit Company" +msgstr "" + +#: company/templates/company/company_base.html:36 +msgid "Delete company" +msgstr "" + +#: company/templates/company/company_base.html:37 +#: company/templates/company/company_base.html:159 +msgid "Delete Company" +msgstr "" + +#: company/templates/company/company_base.html:53 +#: part/templates/part/part_thumb.html:12 +msgid "Upload new image" +msgstr "" + +#: company/templates/company/company_base.html:56 +#: part/templates/part/part_thumb.html:14 +msgid "Download image from URL" +msgstr "" + +#: company/templates/company/company_base.html:83 order/models.py:574 +#: order/templates/order/sales_order_base.html:115 stock/models.py:626 +#: stock/models.py:627 stock/serializers.py:683 +#: stock/templates/stock/item_base.html:274 +msgid "Customer" +msgstr "" + +#: company/templates/company/company_base.html:108 +msgid "Uses default currency" +msgstr "" + +#: company/templates/company/company_base.html:122 +msgid "Phone" +msgstr "" + +#: company/templates/company/company_base.html:205 +#: part/templates/part/part_base.html:491 +msgid "Upload Image" +msgstr "" + +#: company/templates/company/detail.html:14 +#: company/templates/company/manufacturer_part_sidebar.html:7 +#: templates/InvenTree/search.html:118 +msgid "Supplier Parts" +msgstr "" + +#: company/templates/company/detail.html:18 +#: order/templates/order/order_wizard/select_parts.html:44 +msgid "Create new supplier part" +msgstr "" + +#: company/templates/company/detail.html:19 +#: company/templates/company/manufacturer_part.html:118 +#: part/templates/part/detail.html:353 +msgid "New Supplier Part" +msgstr "" + +#: company/templates/company/detail.html:31 +#: company/templates/company/detail.html:78 +#: company/templates/company/manufacturer_part.html:127 +#: company/templates/company/manufacturer_part.html:156 +#: part/templates/part/category.html:168 part/templates/part/detail.html:362 +#: part/templates/part/detail.html:391 +msgid "Options" +msgstr "" + +#: company/templates/company/detail.html:36 +#: company/templates/company/detail.html:83 +#: part/templates/part/category.html:174 +msgid "Order parts" +msgstr "" + +#: company/templates/company/detail.html:41 +#: company/templates/company/detail.html:88 +msgid "Delete parts" +msgstr "" + +#: company/templates/company/detail.html:42 +#: company/templates/company/detail.html:89 +msgid "Delete Parts" +msgstr "" + +#: company/templates/company/detail.html:61 templates/InvenTree/search.html:103 +msgid "Manufacturer Parts" +msgstr "" + +#: company/templates/company/detail.html:65 +msgid "Create new manufacturer part" +msgstr "" + +#: company/templates/company/detail.html:66 part/templates/part/detail.html:381 +msgid "New Manufacturer Part" +msgstr "" + +#: company/templates/company/detail.html:106 +msgid "Supplier Stock" +msgstr "" + +#: company/templates/company/detail.html:116 +#: company/templates/company/sidebar.html:12 +#: company/templates/company/supplier_part_sidebar.html:7 +#: order/templates/order/order_base.html:13 +#: order/templates/order/purchase_orders.html:8 +#: order/templates/order/purchase_orders.html:12 +#: part/templates/part/detail.html:77 part/templates/part/part_sidebar.html:38 +#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:197 +#: templates/InvenTree/settings/sidebar.html:47 templates/navbar.html:47 +#: users/models.py:45 +msgid "Purchase Orders" +msgstr "" + +#: company/templates/company/detail.html:120 +#: order/templates/order/purchase_orders.html:17 +msgid "Create new purchase order" +msgstr "" + +#: company/templates/company/detail.html:121 +#: order/templates/order/purchase_orders.html:18 +msgid "New Purchase Order" +msgstr "" + +#: company/templates/company/detail.html:142 +#: company/templates/company/sidebar.html:20 +#: order/templates/order/sales_order_base.html:13 +#: order/templates/order/sales_orders.html:8 +#: order/templates/order/sales_orders.html:15 +#: part/templates/part/detail.html:100 part/templates/part/part_sidebar.html:42 +#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:217 +#: templates/InvenTree/settings/sidebar.html:49 templates/navbar.html:58 +#: users/models.py:46 +msgid "Sales Orders" +msgstr "" + +#: company/templates/company/detail.html:146 +#: order/templates/order/sales_orders.html:20 +msgid "Create new sales order" +msgstr "" + +#: company/templates/company/detail.html:147 +#: order/templates/order/sales_orders.html:21 +msgid "New Sales Order" +msgstr "" + +#: company/templates/company/detail.html:167 +msgid "Assigned Stock" +msgstr "" + +#: company/templates/company/detail.html:184 +msgid "Company Notes" +msgstr "" + +#: company/templates/company/detail.html:375 +#: company/templates/company/manufacturer_part.html:215 +#: part/templates/part/detail.html:452 +msgid "Delete Supplier Parts?" +msgstr "" + +#: company/templates/company/detail.html:376 +#: company/templates/company/manufacturer_part.html:216 +#: part/templates/part/detail.html:453 +msgid "All selected supplier parts will be deleted" +msgstr "" + +#: company/templates/company/index.html:8 +msgid "Supplier List" +msgstr "" + +#: company/templates/company/manufacturer_part.html:14 company/views.py:55 +#: part/templates/part/prices.html:167 templates/InvenTree/search.html:178 +#: templates/navbar.html:46 +msgid "Manufacturers" +msgstr "" + +#: company/templates/company/manufacturer_part.html:35 +#: company/templates/company/supplier_part.html:34 +#: company/templates/company/supplier_part.html:159 +#: part/templates/part/detail.html:80 part/templates/part/part_base.html:80 +msgid "Order part" +msgstr "" + +#: company/templates/company/manufacturer_part.html:40 +msgid "Edit manufacturer part" +msgstr "" + +#: company/templates/company/manufacturer_part.html:44 +msgid "Delete manufacturer part" +msgstr "" + +#: company/templates/company/manufacturer_part.html:66 +#: company/templates/company/supplier_part.html:63 +msgid "Internal Part" +msgstr "" + +#: company/templates/company/manufacturer_part.html:114 +#: company/templates/company/supplier_part.html:15 company/views.py:49 +#: part/templates/part/part_sidebar.html:36 part/templates/part/prices.html:163 +#: templates/InvenTree/search.html:188 templates/navbar.html:45 +msgid "Suppliers" +msgstr "" + +#: company/templates/company/manufacturer_part.html:129 +#: part/templates/part/detail.html:364 +msgid "Delete supplier parts" +msgstr "" + +#: company/templates/company/manufacturer_part.html:129 +#: company/templates/company/manufacturer_part.html:158 +#: company/templates/company/manufacturer_part.html:254 +#: part/templates/part/detail.html:364 part/templates/part/detail.html:393 +#: users/models.py:218 +msgid "Delete" +msgstr "" + +#: company/templates/company/manufacturer_part.html:143 +#: company/templates/company/manufacturer_part_sidebar.html:5 +#: part/templates/part/category_sidebar.html:19 +#: part/templates/part/detail.html:180 part/templates/part/part_sidebar.html:9 +msgid "Parameters" +msgstr "" + +#: company/templates/company/manufacturer_part.html:147 +#: part/templates/part/detail.html:185 +#: templates/InvenTree/settings/category.html:12 +#: templates/InvenTree/settings/part.html:66 +msgid "New Parameter" +msgstr "" + +#: company/templates/company/manufacturer_part.html:158 +msgid "Delete parameters" +msgstr "" + +#: company/templates/company/manufacturer_part.html:191 +#: part/templates/part/detail.html:865 +msgid "Add Parameter" +msgstr "" + +#: company/templates/company/manufacturer_part.html:239 +msgid "Selected parameters will be deleted" +msgstr "" + +#: company/templates/company/manufacturer_part.html:251 +msgid "Delete Parameters" +msgstr "" + +#: company/templates/company/sidebar.html:6 +msgid "Manufactured Parts" +msgstr "" + +#: company/templates/company/sidebar.html:10 +msgid "Supplied Parts" +msgstr "" + +#: company/templates/company/sidebar.html:16 +msgid "Supplied Stock Items" +msgstr "" + +#: company/templates/company/sidebar.html:22 +msgid "Assigned Stock Items" +msgstr "" + +#: company/templates/company/supplier_part.html:7 +#: company/templates/company/supplier_part.html:24 stock/models.py:591 +#: stock/templates/stock/item_base.html:386 +msgid "Supplier Part" +msgstr "" + +#: company/templates/company/supplier_part.html:38 +msgid "Edit supplier part" +msgstr "" + +#: company/templates/company/supplier_part.html:42 +msgid "Delete supplier part" +msgstr "" + +#: company/templates/company/supplier_part.html:138 +#: company/templates/company/supplier_part_navbar.html:12 +msgid "Supplier Part Stock" +msgstr "" + +#: company/templates/company/supplier_part.html:141 +#: part/templates/part/detail.html:23 stock/templates/stock/location.html:167 +msgid "Create new stock item" +msgstr "" + +#: company/templates/company/supplier_part.html:142 +#: part/templates/part/detail.html:24 stock/templates/stock/location.html:168 +msgid "New Stock Item" +msgstr "" + +#: company/templates/company/supplier_part.html:155 +#: company/templates/company/supplier_part_navbar.html:19 +msgid "Supplier Part Orders" +msgstr "" + +#: company/templates/company/supplier_part.html:160 +#: part/templates/part/detail.html:81 +msgid "Order Part" +msgstr "" + +#: company/templates/company/supplier_part.html:179 +#: part/templates/part/prices.html:7 +msgid "Pricing Information" +msgstr "" + +#: company/templates/company/supplier_part.html:184 +#: company/templates/company/supplier_part.html:298 +#: part/templates/part/prices.html:271 part/views.py:1238 +msgid "Add Price Break" +msgstr "" + +#: company/templates/company/supplier_part.html:210 +msgid "No price break information found" +msgstr "" + +#: company/templates/company/supplier_part.html:224 part/views.py:1300 +msgid "Delete Price Break" +msgstr "" + +#: company/templates/company/supplier_part.html:238 part/views.py:1286 +msgid "Edit Price Break" +msgstr "" + +#: company/templates/company/supplier_part.html:263 +msgid "Edit price break" +msgstr "" + +#: company/templates/company/supplier_part.html:264 +msgid "Delete price break" +msgstr "" + +#: company/templates/company/supplier_part.html:273 +msgid "Last updated" +msgstr "" + +#: company/templates/company/supplier_part_navbar.html:15 +#: part/templates/part/part_sidebar.html:15 +#: stock/templates/stock/loc_link.html:3 stock/templates/stock/location.html:18 +#: stock/templates/stock/stock_app_base.html:10 +#: templates/InvenTree/search.html:150 +#: templates/InvenTree/settings/sidebar.html:43 templates/navbar.html:28 +msgid "Stock" +msgstr "" + +#: company/templates/company/supplier_part_navbar.html:22 +msgid "Orders" +msgstr "" + +#: company/templates/company/supplier_part_navbar.html:26 +#: company/templates/company/supplier_part_sidebar.html:9 +msgid "Supplier Part Pricing" +msgstr "" + +#: company/templates/company/supplier_part_navbar.html:29 +#: part/templates/part/part_sidebar.html:32 +msgid "Pricing" +msgstr "" + +#: company/templates/company/supplier_part_sidebar.html:5 +#: part/templates/part/category.html:192 +#: part/templates/part/category_sidebar.html:17 +#: stock/templates/stock/location.html:138 +#: stock/templates/stock/location.html:152 +#: stock/templates/stock/location.html:164 +#: stock/templates/stock/location_sidebar.html:7 +#: templates/InvenTree/search.html:152 templates/stats.html:105 +#: templates/stats.html:114 users/models.py:43 +msgid "Stock Items" +msgstr "" + +#: company/views.py:50 +msgid "New Supplier" +msgstr "" + +#: company/views.py:56 +msgid "New Manufacturer" +msgstr "" + +#: company/views.py:61 templates/InvenTree/search.html:208 +#: templates/navbar.html:57 +msgid "Customers" +msgstr "" + +#: company/views.py:62 +msgid "New Customer" +msgstr "" + +#: company/views.py:69 +msgid "Companies" +msgstr "" + +#: company/views.py:70 +msgid "New Company" +msgstr "" + +#: company/views.py:129 part/views.py:589 +msgid "Download Image" +msgstr "" + +#: company/views.py:158 part/views.py:621 +msgid "Image size exceeds maximum allowable size for download" +msgstr "" + +#: company/views.py:165 part/views.py:628 +#, python-brace-format +msgid "Invalid response: {code}" +msgstr "" + +#: company/views.py:174 part/views.py:637 +msgid "Supplied URL is not a valid image file" +msgstr "" + +#: label/api.py:97 report/api.py:203 +msgid "No valid objects provided to template" +msgstr "" + +#: label/models.py:113 +msgid "Label name" +msgstr "" + +#: label/models.py:120 +msgid "Label description" +msgstr "" + +#: label/models.py:127 +msgid "Label" +msgstr "" + +#: label/models.py:128 +msgid "Label template file" +msgstr "" + +#: label/models.py:134 report/models.py:294 +msgid "Enabled" +msgstr "" + +#: label/models.py:135 +msgid "Label template is enabled" +msgstr "" + +#: label/models.py:140 +msgid "Width [mm]" +msgstr "" + +#: label/models.py:141 +msgid "Label width, specified in mm" +msgstr "" + +#: label/models.py:147 +msgid "Height [mm]" +msgstr "" + +#: label/models.py:148 +msgid "Label height, specified in mm" +msgstr "" + +#: label/models.py:154 report/models.py:287 +msgid "Filename Pattern" +msgstr "" + +#: label/models.py:155 +msgid "Pattern for generating label filenames" +msgstr "" + +#: label/models.py:258 +msgid "Query filters (comma-separated list of key=value pairs)," +msgstr "" + +#: label/models.py:259 label/models.py:319 label/models.py:366 +#: report/models.py:318 report/models.py:455 report/models.py:493 +msgid "Filters" +msgstr "" + +#: label/models.py:318 +msgid "Query filters (comma-separated list of key=value pairs" +msgstr "" + +#: label/models.py:365 +msgid "Part query filters (comma-separated value of key=value pairs)" +msgstr "" + +#: order/forms.py:24 order/templates/order/order_base.html:52 +msgid "Place order" +msgstr "" + +#: order/forms.py:35 order/templates/order/order_base.html:60 +msgid "Mark order as complete" +msgstr "" + +#: order/forms.py:46 order/forms.py:57 order/templates/order/order_base.html:47 +#: order/templates/order/sales_order_base.html:60 +msgid "Cancel order" +msgstr "" + +#: order/models.py:125 +msgid "Order description" +msgstr "" + +#: order/models.py:127 +msgid "Link to external page" +msgstr "" + +#: order/models.py:135 +msgid "Created By" +msgstr "" + +#: order/models.py:142 +msgid "User or group responsible for this order" +msgstr "" + +#: order/models.py:147 +msgid "Order notes" +msgstr "" + +#: order/models.py:214 order/models.py:564 +msgid "Order reference" +msgstr "" + +#: order/models.py:219 order/models.py:579 +msgid "Purchase order status" +msgstr "" + +#: order/models.py:228 +msgid "Company from which the items are being ordered" +msgstr "" + +#: order/models.py:231 order/templates/order/order_base.html:118 +msgid "Supplier Reference" +msgstr "" + +#: order/models.py:231 +msgid "Supplier order reference code" +msgstr "" + +#: order/models.py:238 +msgid "received by" +msgstr "" + +#: order/models.py:243 +msgid "Issue Date" +msgstr "" + +#: order/models.py:244 +msgid "Date order was issued" +msgstr "" + +#: order/models.py:249 +msgid "Target Delivery Date" +msgstr "" + +#: order/models.py:250 +msgid "" +"Expected date for order delivery. Order will be overdue after this date." +msgstr "" + +#: order/models.py:256 +msgid "Date order was completed" +msgstr "" + +#: order/models.py:285 +msgid "Part supplier must match PO supplier" +msgstr "" + +#: order/models.py:430 +msgid "Quantity must be a positive number" +msgstr "" + +#: order/models.py:575 +msgid "Company to which the items are being sold" +msgstr "" + +#: order/models.py:581 +msgid "Customer Reference " +msgstr "" + +#: order/models.py:581 +msgid "Customer order reference code" +msgstr "" + +#: order/models.py:586 +msgid "" +"Target date for order completion. Order will be overdue after this date." +msgstr "" + +#: order/models.py:589 order/models.py:1084 +msgid "Shipment Date" +msgstr "" + +#: order/models.py:596 +msgid "shipped by" +msgstr "" + +#: order/models.py:662 +msgid "Order cannot be completed as no parts have been assigned" +msgstr "" + +#: order/models.py:666 +msgid "Only a pending order can be marked as complete" +msgstr "" + +#: order/models.py:669 +msgid "Order cannot be completed as there are incomplete shipments" +msgstr "" + +#: order/models.py:672 +msgid "Order cannot be completed as there are incomplete line items" +msgstr "" + +#: order/models.py:837 +msgid "Item quantity" +msgstr "" + +#: order/models.py:843 +msgid "Line item reference" +msgstr "" + +#: order/models.py:845 +msgid "Line item notes" +msgstr "" + +#: order/models.py:850 +msgid "Target shipping date for this line item" +msgstr "" + +#: order/models.py:878 +msgid "Supplier part must match supplier" +msgstr "" + +#: order/models.py:891 order/models.py:982 order/models.py:1078 +msgid "Order" +msgstr "" + +#: order/models.py:892 order/templates/order/order_base.html:9 +#: order/templates/order/order_base.html:18 +#: report/templates/report/inventree_po_report.html:76 +#: stock/templates/stock/item_base.html:336 +msgid "Purchase Order" +msgstr "" + +#: order/models.py:913 +msgid "Supplier part" +msgstr "" + +#: order/models.py:920 order/templates/order/order_base.html:163 +msgid "Received" +msgstr "" + +#: order/models.py:921 +msgid "Number of items received" +msgstr "" + +#: order/models.py:928 part/templates/part/prices.html:176 stock/models.py:720 +#: stock/serializers.py:170 stock/templates/stock/item_base.html:343 +msgid "Purchase Price" +msgstr "" + +#: order/models.py:929 +msgid "Unit purchase price" +msgstr "" + +#: order/models.py:937 +msgid "Where does the Purchaser want this item to be stored?" +msgstr "" + +#: order/models.py:992 part/templates/part/part_pricing.html:112 +#: part/templates/part/prices.html:116 part/templates/part/prices.html:284 +msgid "Sale Price" +msgstr "" + +#: order/models.py:993 +msgid "Unit sale price" +msgstr "" + +#: order/models.py:998 +msgid "Shipped quantity" +msgstr "" + +#: order/models.py:1085 +msgid "Date of shipment" +msgstr "" + +#: order/models.py:1092 +msgid "Checked By" +msgstr "" + +#: order/models.py:1093 +msgid "User who checked this shipment" +msgstr "" + +#: order/models.py:1101 +msgid "Shipment number" +msgstr "" + +#: order/models.py:1108 +msgid "Shipment notes" +msgstr "" + +#: order/models.py:1115 +msgid "Tracking Number" +msgstr "" + +#: order/models.py:1116 +msgid "Shipment tracking information" +msgstr "" + +#: order/models.py:1126 +msgid "Shipment has already been sent" +msgstr "" + +#: order/models.py:1129 +msgid "Shipment has no allocated stock items" +msgstr "" + +#: order/models.py:1207 order/models.py:1209 +msgid "Stock item has not been assigned" +msgstr "" + +#: order/models.py:1213 +msgid "Cannot allocate stock item to a line with a different part" +msgstr "" + +#: order/models.py:1215 +msgid "Cannot allocate stock to a line without a part" +msgstr "" + +#: order/models.py:1218 +msgid "Allocation quantity cannot exceed stock quantity" +msgstr "" + +#: order/models.py:1222 +msgid "StockItem is over-allocated" +msgstr "" + +#: order/models.py:1228 order/serializers.py:827 +msgid "Quantity must be 1 for serialized stock item" +msgstr "" + +#: order/models.py:1231 +msgid "Sales order does not match shipment" +msgstr "" + +#: order/models.py:1232 +msgid "Shipment does not match sales order" +msgstr "" + +#: order/models.py:1240 +msgid "Line" +msgstr "" + +#: order/models.py:1248 order/serializers.py:918 order/serializers.py:1046 +msgid "Shipment" +msgstr "" + +#: order/models.py:1249 +msgid "Sales order shipment reference" +msgstr "" + +#: order/models.py:1261 templates/InvenTree/notifications/notifications.html:70 +msgid "Item" +msgstr "" + +#: order/models.py:1262 +msgid "Select stock item to allocate" +msgstr "" + +#: order/models.py:1265 +msgid "Enter stock allocation quantity" +msgstr "" + +#: order/serializers.py:187 +msgid "Purchase price currency" +msgstr "" + +#: order/serializers.py:238 order/serializers.py:883 +msgid "Line Item" +msgstr "" + +#: order/serializers.py:244 +msgid "Line item does not match purchase order" +msgstr "" + +#: order/serializers.py:254 order/serializers.py:359 +msgid "Select destination location for received items" +msgstr "" + +#: order/serializers.py:273 +msgid "Enter batch code for incoming stock items" +msgstr "" + +#: order/serializers.py:281 +msgid "Enter serial numbers for incoming stock items" +msgstr "" + +#: order/serializers.py:294 +msgid "Barcode Hash" +msgstr "" + +#: order/serializers.py:295 +msgid "Unique identifier field" +msgstr "" + +#: order/serializers.py:312 +msgid "Barcode is already in use" +msgstr "" + +#: order/serializers.py:331 +msgid "An integer quantity must be provided for trackable parts" +msgstr "" + +#: order/serializers.py:371 +msgid "Line items must be provided" +msgstr "" + +#: order/serializers.py:388 +msgid "Destination location must be specified" +msgstr "" + +#: order/serializers.py:399 +msgid "Supplied barcode values must be unique" +msgstr "" + +#: order/serializers.py:672 +msgid "Sale price currency" +msgstr "" + +#: order/serializers.py:742 +msgid "No shipment details provided" +msgstr "" + +#: order/serializers.py:792 order/serializers.py:895 +msgid "Line item is not associated with this order" +msgstr "" + +#: order/serializers.py:814 +msgid "Quantity must be positive" +msgstr "" + +#: order/serializers.py:908 +msgid "Enter serial numbers to allocate" +msgstr "" + +#: order/serializers.py:932 order/serializers.py:1057 +msgid "Shipment has already been shipped" +msgstr "" + +#: order/serializers.py:935 order/serializers.py:1060 +msgid "Shipment is not associated with this order" +msgstr "" + +#: order/serializers.py:987 +msgid "No match found for the following serial numbers" +msgstr "" + +#: order/serializers.py:997 +msgid "The following serial numbers are already allocated" +msgstr "" + +#: order/templates/order/delete_attachment.html:5 +#: stock/templates/stock/attachment_delete.html:5 +msgid "Are you sure you want to delete this attachment?" +msgstr "" + +#: order/templates/order/order_base.html:33 +msgid "Print purchase order report" +msgstr "" + +#: order/templates/order/order_base.html:35 +#: order/templates/order/sales_order_base.html:45 +msgid "Export order to file" +msgstr "" + +#: order/templates/order/order_base.html:41 +#: order/templates/order/sales_order_base.html:54 +msgid "Order actions" +msgstr "" + +#: order/templates/order/order_base.html:45 +#: order/templates/order/sales_order_base.html:58 +msgid "Edit order" +msgstr "" + +#: order/templates/order/order_base.html:56 +msgid "Receive items" +msgstr "" + +#: order/templates/order/order_base.html:58 +#: order/templates/order/purchase_order_detail.html:30 +msgid "Receive Items" +msgstr "" + +#: order/templates/order/order_base.html:62 +#: order/templates/order/sales_order_base.html:67 order/views.py:181 +msgid "Complete Order" +msgstr "" + +#: order/templates/order/order_base.html:84 +#: order/templates/order/sales_order_base.html:79 +msgid "Order Reference" +msgstr "" + +#: order/templates/order/order_base.html:89 +#: order/templates/order/sales_order_base.html:84 +msgid "Order Description" +msgstr "" + +#: order/templates/order/order_base.html:94 +#: order/templates/order/sales_order_base.html:89 +msgid "Order Status" +msgstr "" + +#: order/templates/order/order_base.html:124 +#: order/templates/order/sales_order_base.html:128 +msgid "Completed Line Items" +msgstr "" + +#: order/templates/order/order_base.html:130 +#: order/templates/order/sales_order_base.html:134 +#: order/templates/order/sales_order_base.html:144 +msgid "Incomplete" +msgstr "" + +#: order/templates/order/order_base.html:149 +#: report/templates/report/inventree_build_order_base.html:122 +msgid "Issued" +msgstr "" + +#: order/templates/order/order_base.html:219 +msgid "Edit Purchase Order" +msgstr "" + +#: order/templates/order/order_cancel.html:8 +msgid "" +"Cancelling this order means that the order and line items will no longer be " +"editable." +msgstr "" + +#: order/templates/order/order_complete.html:7 +msgid "Mark this order as complete?" +msgstr "" + +#: order/templates/order/order_complete.html:10 +msgid "This order has line items which have not been marked as received." +msgstr "" + +#: order/templates/order/order_complete.html:11 +msgid "" +"Completing this order means that the order and line items will no longer be " +"editable." +msgstr "" + +#: order/templates/order/order_issue.html:8 +msgid "" +"After placing this purchase order, line items will no longer be editable." +msgstr "" + +#: order/templates/order/order_wizard/match_parts.html:12 +#: part/templates/part/import_wizard/ajax_match_references.html:12 +#: part/templates/part/import_wizard/match_references.html:12 +msgid "Errors exist in the submitted data" +msgstr "" + +#: order/templates/order/order_wizard/match_parts.html:21 +#: part/templates/part/import_wizard/match_references.html:21 +#: templates/patterns/wizard/match_fields.html:28 +msgid "Submit Selections" +msgstr "" + +#: order/templates/order/order_wizard/match_parts.html:28 +#: part/templates/part/import_wizard/ajax_match_references.html:21 +#: part/templates/part/import_wizard/match_references.html:28 +msgid "Row" +msgstr "" + +#: order/templates/order/order_wizard/match_parts.html:29 +msgid "Select Supplier Part" +msgstr "" + +#: order/templates/order/order_wizard/match_parts.html:52 +#: part/templates/part/import_wizard/ajax_match_fields.html:64 +#: part/templates/part/import_wizard/ajax_match_references.html:42 +#: part/templates/part/import_wizard/match_references.html:49 +#: templates/patterns/wizard/match_fields.html:70 +msgid "Remove row" +msgstr "" + +#: order/templates/order/order_wizard/po_upload.html:8 +msgid "Return to Orders" +msgstr "" + +#: order/templates/order/order_wizard/po_upload.html:13 +msgid "Upload File for Purchase Order" +msgstr "" + +#: order/templates/order/order_wizard/po_upload.html:14 +msgid "Order is already processed. Files cannot be uploaded." +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:11 +msgid "Step 1 of 2 - Select Part Suppliers" +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:16 +msgid "Select suppliers" +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:20 +msgid "No purchaseable parts selected" +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:33 +msgid "Select Supplier" +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:57 +msgid "No price" +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:65 +#, python-format +msgid "Select a supplier for %(name)s" +msgstr "" + +#: order/templates/order/order_wizard/select_parts.html:77 +#: part/templates/part/set_category.html:32 +msgid "Remove part" +msgstr "" + +#: order/templates/order/order_wizard/select_pos.html:8 +msgid "Step 2 of 2 - Select Purchase Orders" +msgstr "" + +#: order/templates/order/order_wizard/select_pos.html:12 +msgid "Select existing purchase orders, or create new orders." +msgstr "" + +#: order/templates/order/order_wizard/select_pos.html:31 +msgid "Items" +msgstr "" + +#: order/templates/order/order_wizard/select_pos.html:32 +msgid "Select Purchase Order" +msgstr "" + +#: order/templates/order/order_wizard/select_pos.html:45 +#, python-format +msgid "Create new purchase order for %(name)s" +msgstr "" + +#: order/templates/order/order_wizard/select_pos.html:68 +#, python-format +msgid "Select a purchase order for %(name)s" +msgstr "" + +#: order/templates/order/po_sidebar.html:5 +#: order/templates/order/so_sidebar.html:5 +#: report/templates/report/inventree_po_report.html:84 +#: report/templates/report/inventree_so_report.html:85 +msgid "Line Items" +msgstr "" + +#: order/templates/order/po_sidebar.html:7 +msgid "Received Stock" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:17 +msgid "Purchase Order Items" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:26 +#: order/templates/order/purchase_order_detail.html:159 +#: order/templates/order/sales_order_detail.html:22 +#: order/templates/order/sales_order_detail.html:226 +msgid "Add Line Item" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:29 +msgid "Receive selected items" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:49 +msgid "Received Items" +msgstr "" + +#: order/templates/order/purchase_order_detail.html:74 +#: order/templates/order/sales_order_detail.html:121 +msgid "Order Notes" +msgstr "" + +#: order/templates/order/purchase_orders.html:30 +#: order/templates/order/sales_orders.html:33 +msgid "Print Order Reports" +msgstr "" + +#: order/templates/order/sales_order_base.html:43 +msgid "Print sales order report" +msgstr "" + +#: order/templates/order/sales_order_base.html:47 +msgid "Print packing list" +msgstr "" + +#: order/templates/order/sales_order_base.html:66 +#: order/templates/order/sales_order_base.html:229 +msgid "Complete Sales Order" +msgstr "" + +#: order/templates/order/sales_order_base.html:102 +msgid "This Sales Order has not been fully allocated" +msgstr "" + +#: order/templates/order/sales_order_base.html:122 +msgid "Customer Reference" +msgstr "" + +#: order/templates/order/sales_order_base.html:140 +#: order/templates/order/sales_order_detail.html:77 +#: order/templates/order/so_sidebar.html:11 +msgid "Completed Shipments" +msgstr "" + +#: order/templates/order/sales_order_base.html:215 +msgid "Edit Sales Order" +msgstr "" + +#: order/templates/order/sales_order_cancel.html:8 +#: stock/templates/stock/stockitem_convert.html:13 +msgid "Warning" +msgstr "" + +#: order/templates/order/sales_order_cancel.html:9 +msgid "Cancelling this order means that the order will no longer be editable." +msgstr "" + +#: order/templates/order/sales_order_detail.html:17 +msgid "Sales Order Items" +msgstr "" + +#: order/templates/order/sales_order_detail.html:43 +#: order/templates/order/so_sidebar.html:8 +msgid "Pending Shipments" +msgstr "" + +#: order/templates/order/sales_order_detail.html:47 +msgid "Actions" +msgstr "" + +#: order/templates/order/sales_order_detail.html:56 +msgid "New Shipment" +msgstr "" + +#: order/views.py:99 +msgid "Cancel Order" +msgstr "" + +#: order/views.py:108 order/views.py:134 +msgid "Confirm order cancellation" +msgstr "" + +#: order/views.py:111 order/views.py:137 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:125 +msgid "Cancel sales order" +msgstr "" + +#: order/views.py:151 +msgid "Issue Order" +msgstr "" + +#: order/views.py:160 +msgid "Confirm order placement" +msgstr "" + +#: order/views.py:170 +msgid "Purchase order issued" +msgstr "" + +#: order/views.py:197 +msgid "Confirm order completion" +msgstr "" + +#: order/views.py:208 +msgid "Purchase order completed" +msgstr "" + +#: order/views.py:245 +msgid "Match Supplier Parts" +msgstr "" + +#: order/views.py:489 +msgid "Update prices" +msgstr "" + +#: order/views.py:747 +#, python-brace-format +msgid "Ordered {n} parts" +msgstr "" + +#: order/views.py:858 +msgid "Sales order not found" +msgstr "" + +#: order/views.py:864 +msgid "Price not found" +msgstr "" + +#: order/views.py:867 +#, python-brace-format +msgid "Updated {part} unit-price to {price}" +msgstr "" + +#: order/views.py:872 +#, python-brace-format +msgid "Updated {part} unit-price to {price} and quantity to {qty}" +msgstr "" + +#: part/api.py:491 +msgid "Incoming Purchase Order" +msgstr "" + +#: part/api.py:511 +msgid "Outgoing Sales Order" +msgstr "" + +#: part/api.py:529 +msgid "Stock produced by Build Order" +msgstr "" + +#: part/api.py:561 +msgid "Stock required for Build Order" +msgstr "" + +#: part/api.py:641 +msgid "Valid" +msgstr "" + +#: part/api.py:642 +msgid "Validate entire Bill of Materials" +msgstr "" + +#: part/api.py:647 +msgid "This option must be selected" +msgstr "" + +#: part/api.py:1027 +msgid "Must be greater than zero" +msgstr "" + +#: part/api.py:1031 +msgid "Must be a valid quantity" +msgstr "" + +#: part/api.py:1046 +msgid "Specify location for initial part stock" +msgstr "" + +#: part/api.py:1077 part/api.py:1081 part/api.py:1096 part/api.py:1100 +msgid "This field is required" +msgstr "" + +#: part/bom.py:125 part/models.py:112 part/models.py:892 +#: part/templates/part/category.html:108 part/templates/part/part_base.html:356 +msgid "Default Location" +msgstr "" + +#: part/bom.py:126 templates/email/low_stock_notification.html:17 +msgid "Total Stock" +msgstr "" + +#: part/bom.py:127 part/templates/part/part_base.html:189 +msgid "Available Stock" +msgstr "" + +#: part/bom.py:128 part/templates/part/part_base.html:207 +msgid "On Order" +msgstr "" + +#: part/forms.py:84 +msgid "Select part category" +msgstr "" + +#: part/forms.py:121 +msgid "Add parameter template to same level categories" +msgstr "" + +#: part/forms.py:125 +msgid "Add parameter template to all categories" +msgstr "" + +#: part/forms.py:145 +msgid "Input quantity for price calculation" +msgstr "" + +#: part/models.py:113 +msgid "Default location for parts in this category" +msgstr "" + +#: part/models.py:116 +msgid "Default keywords" +msgstr "" + +#: part/models.py:116 +msgid "Default keywords for parts in this category" +msgstr "" + +#: part/models.py:126 part/models.py:2642 part/templates/part/category.html:15 +#: part/templates/part/part_app_base.html:10 +msgid "Part Category" +msgstr "" + +#: part/models.py:127 part/templates/part/category.html:128 +#: templates/InvenTree/search.html:95 templates/stats.html:96 +#: users/models.py:40 +msgid "Part Categories" +msgstr "" + +#: part/models.py:368 part/templates/part/cat_link.html:3 +#: part/templates/part/category.html:17 part/templates/part/category.html:133 +#: part/templates/part/category.html:153 +#: part/templates/part/category_sidebar.html:9 +#: templates/InvenTree/index.html:85 templates/InvenTree/search.html:82 +#: templates/InvenTree/settings/sidebar.html:39 templates/navbar.html:21 +#: templates/stats.html:92 templates/stats.html:101 users/models.py:41 +msgid "Parts" +msgstr "" + +#: part/models.py:460 +msgid "Invalid choice for parent part" +msgstr "" + +#: part/models.py:540 part/models.py:552 +#, python-brace-format +msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" +msgstr "" + +#: part/models.py:682 +msgid "Next available serial numbers are" +msgstr "" + +#: part/models.py:686 +msgid "Next available serial number is" +msgstr "" + +#: part/models.py:691 +msgid "Most recent serial number is" +msgstr "" + +#: part/models.py:787 +msgid "Duplicate IPN not allowed in part settings" +msgstr "" + +#: part/models.py:816 part/models.py:2695 +msgid "Part name" +msgstr "" + +#: part/models.py:823 +msgid "Is Template" +msgstr "" + +#: part/models.py:824 +msgid "Is this part a template part?" +msgstr "" + +#: part/models.py:834 +msgid "Is this part a variant of another part?" +msgstr "" + +#: part/models.py:835 +msgid "Variant Of" +msgstr "" + +#: part/models.py:841 +msgid "Part description" +msgstr "" + +#: part/models.py:846 part/templates/part/category.html:86 +#: part/templates/part/part_base.html:320 +msgid "Keywords" +msgstr "" + +#: part/models.py:847 +msgid "Part keywords to improve visibility in search results" +msgstr "" + +#: part/models.py:854 part/models.py:2392 part/models.py:2641 +#: part/templates/part/part_base.html:283 +#: part/templates/part/set_category.html:15 +#: templates/InvenTree/notifications/notifications.html:65 +#: templates/InvenTree/settings/settings.html:224 +msgid "Category" +msgstr "" + +#: part/models.py:855 +msgid "Part category" +msgstr "" + +#: part/models.py:860 part/templates/part/part_base.html:292 +msgid "IPN" +msgstr "" + +#: part/models.py:861 +msgid "Internal Part Number" +msgstr "" + +#: part/models.py:867 +msgid "Part revision or version number" +msgstr "" + +#: part/models.py:868 part/templates/part/part_base.html:299 +#: report/models.py:196 +msgid "Revision" +msgstr "" + +#: part/models.py:890 +msgid "Where is this item normally stored?" +msgstr "" + +#: part/models.py:937 part/templates/part/part_base.html:365 +msgid "Default Supplier" +msgstr "" + +#: part/models.py:938 +msgid "Default supplier part" +msgstr "" + +#: part/models.py:945 +msgid "Default Expiry" +msgstr "" + +#: part/models.py:946 +msgid "Expiry time (in days) for stock items of this part" +msgstr "" + +#: part/models.py:951 part/templates/part/part_base.html:200 +msgid "Minimum Stock" +msgstr "" + +#: part/models.py:952 +msgid "Minimum allowed stock level" +msgstr "" + +#: part/models.py:959 +msgid "Stock keeping units for this part" +msgstr "" + +#: part/models.py:965 +msgid "Can this part be built from other parts?" +msgstr "" + +#: part/models.py:971 +msgid "Can this part be used to build other parts?" +msgstr "" + +#: part/models.py:977 +msgid "Does this part have tracking for unique items?" +msgstr "" + +#: part/models.py:982 +msgid "Can this part be purchased from external suppliers?" +msgstr "" + +#: part/models.py:987 +msgid "Can this part be sold to customers?" +msgstr "" + +#: part/models.py:992 +msgid "Is this part active?" +msgstr "" + +#: part/models.py:997 +msgid "Is this a virtual part, such as a software product or license?" +msgstr "" + +#: part/models.py:1002 +msgid "Part notes - supports Markdown formatting" +msgstr "" + +#: part/models.py:1005 +msgid "BOM checksum" +msgstr "" + +#: part/models.py:1005 +msgid "Stored BOM checksum" +msgstr "" + +#: part/models.py:1008 +msgid "BOM checked by" +msgstr "" + +#: part/models.py:1010 +msgid "BOM checked date" +msgstr "" + +#: part/models.py:1014 +msgid "Creation User" +msgstr "" + +#: part/models.py:1878 +msgid "Sell multiple" +msgstr "" + +#: part/models.py:2442 +msgid "Test templates can only be created for trackable parts" +msgstr "" + +#: part/models.py:2459 +msgid "Test with this name already exists for this part" +msgstr "" + +#: part/models.py:2479 +msgid "Test Name" +msgstr "" + +#: part/models.py:2480 +msgid "Enter a name for the test" +msgstr "" + +#: part/models.py:2485 +msgid "Test Description" +msgstr "" + +#: part/models.py:2486 +msgid "Enter description for this test" +msgstr "" + +#: part/models.py:2491 +msgid "Required" +msgstr "" + +#: part/models.py:2492 +msgid "Is this test required to pass?" +msgstr "" + +#: part/models.py:2497 +msgid "Requires Value" +msgstr "" + +#: part/models.py:2498 +msgid "Does this test require a value when adding a test result?" +msgstr "" + +#: part/models.py:2503 +msgid "Requires Attachment" +msgstr "" + +#: part/models.py:2504 +msgid "Does this test require a file attachment when adding a test result?" +msgstr "" + +#: part/models.py:2515 +#, python-brace-format +msgid "Illegal character in template name ({c})" +msgstr "" + +#: part/models.py:2551 +msgid "Parameter template name must be unique" +msgstr "" + +#: part/models.py:2559 +msgid "Parameter Name" +msgstr "" + +#: part/models.py:2566 +msgid "Parameter Units" +msgstr "" + +#: part/models.py:2596 +msgid "Parent Part" +msgstr "" + +#: part/models.py:2598 part/models.py:2647 part/models.py:2648 +#: templates/InvenTree/settings/settings.html:219 +msgid "Parameter Template" +msgstr "" + +#: part/models.py:2600 +msgid "Data" +msgstr "" + +#: part/models.py:2600 +msgid "Parameter Value" +msgstr "" + +#: part/models.py:2652 templates/InvenTree/settings/settings.html:228 +msgid "Default Value" +msgstr "" + +#: part/models.py:2653 +msgid "Default Parameter Value" +msgstr "" + +#: part/models.py:2687 +msgid "Part ID or part name" +msgstr "" + +#: part/models.py:2690 +msgid "Part ID" +msgstr "" + +#: part/models.py:2691 +msgid "Unique part ID value" +msgstr "" + +#: part/models.py:2694 +msgid "Part Name" +msgstr "" + +#: part/models.py:2698 +msgid "Part IPN" +msgstr "" + +#: part/models.py:2699 +msgid "Part IPN value" +msgstr "" + +#: part/models.py:2702 +msgid "Level" +msgstr "" + +#: part/models.py:2703 +msgid "BOM level" +msgstr "" + +#: part/models.py:2764 +msgid "Select parent part" +msgstr "" + +#: part/models.py:2772 +msgid "Sub part" +msgstr "" + +#: part/models.py:2773 +msgid "Select part to be used in BOM" +msgstr "" + +#: part/models.py:2779 +msgid "BOM quantity for this BOM item" +msgstr "" + +#: part/models.py:2781 part/templates/part/upload_bom.html:58 +msgid "Optional" +msgstr "" + +#: part/models.py:2781 +msgid "This BOM item is optional" +msgstr "" + +#: part/models.py:2784 part/templates/part/upload_bom.html:55 +msgid "Overage" +msgstr "" + +#: part/models.py:2785 +msgid "Estimated build wastage quantity (absolute or percentage)" +msgstr "" + +#: part/models.py:2788 +msgid "BOM item reference" +msgstr "" + +#: part/models.py:2791 +msgid "BOM item notes" +msgstr "" + +#: part/models.py:2793 +msgid "Checksum" +msgstr "" + +#: part/models.py:2793 +msgid "BOM line checksum" +msgstr "" + +#: part/models.py:2797 part/templates/part/upload_bom.html:57 +msgid "Inherited" +msgstr "" + +#: part/models.py:2798 +msgid "This BOM item is inherited by BOMs for variant parts" +msgstr "" + +#: part/models.py:2803 part/templates/part/upload_bom.html:56 +msgid "Allow Variants" +msgstr "" + +#: part/models.py:2804 +msgid "Stock items for variant parts can be used for this BOM item" +msgstr "" + +#: part/models.py:2889 stock/models.py:469 +msgid "Quantity must be integer value for trackable parts" +msgstr "" + +#: part/models.py:2898 part/models.py:2900 +msgid "Sub part must be specified" +msgstr "" + +#: part/models.py:3012 +msgid "BOM Item Substitute" +msgstr "" + +#: part/models.py:3034 +msgid "Substitute part cannot be the same as the master part" +msgstr "" + +#: part/models.py:3046 +msgid "Parent BOM item" +msgstr "" + +#: part/models.py:3054 +msgid "Substitute part" +msgstr "" + +#: part/models.py:3065 +msgid "Part 1" +msgstr "" + +#: part/models.py:3069 +msgid "Part 2" +msgstr "" + +#: part/models.py:3069 +msgid "Select Related Part" +msgstr "" + +#: part/models.py:3101 +msgid "" +"Error creating relationship: check that the part is not related to itself " +"and that the relationship is unique" +msgstr "" + +#: part/serializers.py:835 +msgid "Select part to copy BOM from" +msgstr "" + +#: part/serializers.py:846 +msgid "Remove Existing Data" +msgstr "" + +#: part/serializers.py:847 +msgid "Remove existing BOM items before copying" +msgstr "" + +#: part/serializers.py:852 +msgid "Include Inherited" +msgstr "" + +#: part/serializers.py:853 +msgid "Include BOM items which are inherited from templated parts" +msgstr "" + +#: part/serializers.py:858 +msgid "Skip Invalid Rows" +msgstr "" + +#: part/serializers.py:859 +msgid "Enable this option to skip invalid rows" +msgstr "" + +#: part/serializers.py:864 +msgid "Copy Substitute Parts" +msgstr "" + +#: part/serializers.py:865 +msgid "Copy substitute parts when duplicate BOM items" +msgstr "" + +#: part/serializers.py:909 +msgid "Clear Existing BOM" +msgstr "" + +#: part/serializers.py:910 +msgid "Delete existing BOM items before uploading" +msgstr "" + +#: part/serializers.py:937 +msgid "No part column specified" +msgstr "" + +#: part/serializers.py:980 +msgid "Multiple matching parts found" +msgstr "" + +#: part/serializers.py:983 +msgid "No matching part found" +msgstr "" + +#: part/serializers.py:986 +msgid "Part is not designated as a component" +msgstr "" + +#: part/serializers.py:995 +msgid "Quantity not provided" +msgstr "" + +#: part/serializers.py:1003 +msgid "Invalid quantity" +msgstr "" + +#: part/serializers.py:1022 +msgid "At least one BOM item is required" +msgstr "" + +#: part/tasks.py:18 +msgid "Low stock notification" +msgstr "" + +#: part/tasks.py:19 +#, python-brace-format +msgid "" +"The available stock for {part.name} has fallen below the configured minimum " +"level" +msgstr "" + +#: part/templates/part/bom.html:6 +msgid "You do not have permission to edit the BOM." +msgstr "" + +#: part/templates/part/bom.html:15 +#, python-format +msgid "The BOM for %(part)s has changed, and must be validated.
" +msgstr "" + +#: part/templates/part/bom.html:17 +#, python-format +msgid "" +"The BOM for %(part)s was last checked by %(checker)s on " +"%(check_date)s" +msgstr "" + +#: part/templates/part/bom.html:21 +#, python-format +msgid "The BOM for %(part)s has not been validated." +msgstr "" + +#: part/templates/part/bom.html:30 part/templates/part/detail.html:263 +msgid "BOM actions" +msgstr "" + +#: part/templates/part/bom.html:34 +msgid "Delete Items" +msgstr "" + +#: part/templates/part/category.html:28 part/templates/part/category.html:32 +msgid "You are subscribed to notifications for this category" +msgstr "" + +#: part/templates/part/category.html:36 +msgid "Subscribe to notifications for this category" +msgstr "" + +#: part/templates/part/category.html:42 +msgid "Category Actions" +msgstr "" + +#: part/templates/part/category.html:47 +msgid "Edit category" +msgstr "" + +#: part/templates/part/category.html:48 +msgid "Edit Category" +msgstr "" + +#: part/templates/part/category.html:52 +msgid "Delete category" +msgstr "" + +#: part/templates/part/category.html:53 +msgid "Delete Category" +msgstr "" + +#: part/templates/part/category.html:61 +msgid "Create new part category" +msgstr "" + +#: part/templates/part/category.html:62 +msgid "New Category" +msgstr "" + +#: part/templates/part/category.html:80 part/templates/part/category.html:93 +msgid "Category Path" +msgstr "" + +#: part/templates/part/category.html:94 +msgid "Top level part category" +msgstr "" + +#: part/templates/part/category.html:114 part/templates/part/category.html:211 +#: part/templates/part/category_sidebar.html:7 +msgid "Subcategories" +msgstr "" + +#: part/templates/part/category.html:119 +msgid "Parts (Including subcategories)" +msgstr "" + +#: part/templates/part/category.html:157 +msgid "Create new part" +msgstr "" + +#: part/templates/part/category.html:158 +msgid "New Part" +msgstr "" + +#: part/templates/part/category.html:172 +msgid "Set category" +msgstr "" + +#: part/templates/part/category.html:172 +msgid "Set Category" +msgstr "" + +#: part/templates/part/category.html:176 +msgid "Print Labels" +msgstr "" + +#: part/templates/part/category.html:178 +msgid "Export" +msgstr "" + +#: part/templates/part/category.html:178 +msgid "Export Data" +msgstr "" + +#: part/templates/part/category.html:201 +msgid "Part Parameters" +msgstr "" + +#: part/templates/part/category.html:309 +msgid "Create Part Category" +msgstr "" + +#: part/templates/part/category.html:329 +msgid "Create Part" +msgstr "" + +#: part/templates/part/category.html:332 +msgid "Create another part after this one" +msgstr "" + +#: part/templates/part/category.html:333 +msgid "Part created successfully" +msgstr "" + +#: part/templates/part/category_delete.html:7 +msgid "Are you sure you want to delete this part category?" +msgstr "" + +#: part/templates/part/category_delete.html:12 +#, python-format +msgid "This category contains %(n)s child categories" +msgstr "" + +#: part/templates/part/category_delete.html:14 +#, python-format +msgid "" +"If this category is deleted, these child categories will be moved to " +"%(category)s" +msgstr "" + +#: part/templates/part/category_delete.html:16 +msgid "" +"If this category is deleted, these child categories will be moved to the top " +"level part category" +msgstr "" + +#: part/templates/part/category_delete.html:23 +#, python-format +msgid "This category contains %(n)s parts" +msgstr "" + +#: part/templates/part/category_delete.html:25 +#, python-format +msgid "If this category is deleted, these parts will be moved to %(category)s" +msgstr "" + +#: part/templates/part/category_delete.html:27 +msgid "" +"If this category is deleted, these parts will be moved to the top level part " +"category" +msgstr "" + +#: part/templates/part/category_sidebar.html:13 +msgid "Import Parts" +msgstr "" + +#: part/templates/part/copy_part.html:9 +msgid "Duplicate Part" +msgstr "" + +#: part/templates/part/copy_part.html:10 +#, python-format +msgid "Make a copy of part '%(full_name)s'." +msgstr "" + +#: part/templates/part/copy_part.html:14 +#: part/templates/part/create_part.html:11 +msgid "Possible Matching Parts" +msgstr "" + +#: part/templates/part/copy_part.html:15 +#: part/templates/part/create_part.html:12 +msgid "The new part may be a duplicate of these existing parts" +msgstr "" + +#: part/templates/part/create_part.html:17 +#, python-format +msgid "%(full_name)s - %(desc)s (%(match_per)s%% match)" +msgstr "" + +#: part/templates/part/detail.html:20 +msgid "Part Stock" +msgstr "" + +#: part/templates/part/detail.html:52 +msgid "Part Test Templates" +msgstr "" + +#: part/templates/part/detail.html:57 +msgid "Add Test Template" +msgstr "" + +#: part/templates/part/detail.html:114 stock/templates/stock/item.html:58 +msgid "Sales Order Allocations" +msgstr "" + +#: part/templates/part/detail.html:137 +msgid "Part Notes" +msgstr "" + +#: part/templates/part/detail.html:152 +msgid "Part Variants" +msgstr "" + +#: part/templates/part/detail.html:156 +msgid "Create new variant" +msgstr "" + +#: part/templates/part/detail.html:157 +msgid "New Variant" +msgstr "" + +#: part/templates/part/detail.html:184 +msgid "Add new parameter" +msgstr "" + +#: part/templates/part/detail.html:221 part/templates/part/part_sidebar.html:55 +msgid "Related Parts" +msgstr "" + +#: part/templates/part/detail.html:225 part/templates/part/detail.html:226 +msgid "Add Related" +msgstr "" + +#: part/templates/part/detail.html:246 part/templates/part/part_sidebar.html:18 +msgid "Bill of Materials" +msgstr "" + +#: part/templates/part/detail.html:251 +msgid "Export actions" +msgstr "" + +#: part/templates/part/detail.html:255 +msgid "Export BOM" +msgstr "" + +#: part/templates/part/detail.html:257 +msgid "Print BOM Report" +msgstr "" + +#: part/templates/part/detail.html:267 +msgid "Upload BOM" +msgstr "" + +#: part/templates/part/detail.html:268 +msgid "Copy BOM" +msgstr "" + +#: part/templates/part/detail.html:269 +msgid "Validate BOM" +msgstr "" + +#: part/templates/part/detail.html:274 +msgid "New BOM Item" +msgstr "" + +#: part/templates/part/detail.html:275 +msgid "Add BOM Item" +msgstr "" + +#: part/templates/part/detail.html:288 +msgid "Assemblies" +msgstr "" + +#: part/templates/part/detail.html:306 +msgid "Part Builds" +msgstr "" + +#: part/templates/part/detail.html:333 stock/templates/stock/item.html:43 +msgid "Build Order Allocations" +msgstr "" + +#: part/templates/part/detail.html:349 +msgid "Part Suppliers" +msgstr "" + +#: part/templates/part/detail.html:377 +msgid "Part Manufacturers" +msgstr "" + +#: part/templates/part/detail.html:393 +msgid "Delete manufacturer parts" +msgstr "" + +#: part/templates/part/detail.html:596 +msgid "Delete selected BOM items?" +msgstr "" + +#: part/templates/part/detail.html:597 +msgid "All selected BOM items will be deleted" +msgstr "" + +#: part/templates/part/detail.html:646 +msgid "Create BOM Item" +msgstr "" + +#: part/templates/part/detail.html:690 +msgid "Related Part" +msgstr "" + +#: part/templates/part/detail.html:698 +msgid "Add Related Part" +msgstr "" + +#: part/templates/part/detail.html:795 +msgid "Add Test Result Template" +msgstr "" + +#: part/templates/part/detail.html:928 +#, python-format +msgid "Purchase Unit Price - %(currency)s" +msgstr "" + +#: part/templates/part/detail.html:940 +#, python-format +msgid "Unit Price-Cost Difference - %(currency)s" +msgstr "" + +#: part/templates/part/detail.html:952 +#, python-format +msgid "Supplier Unit Cost - %(currency)s" +msgstr "" + +#: part/templates/part/detail.html:1041 +#, python-format +msgid "Unit Price - %(currency)s" +msgstr "" + +#: part/templates/part/import_wizard/ajax_match_fields.html:9 +#: templates/patterns/wizard/match_fields.html:8 +msgid "Missing selections for the following required columns" +msgstr "" + +#: part/templates/part/import_wizard/ajax_match_fields.html:20 +#: templates/patterns/wizard/match_fields.html:19 +msgid "Duplicate selections found, see below. Fix them then retry submitting." +msgstr "" + +#: part/templates/part/import_wizard/ajax_match_fields.html:28 +#: templates/patterns/wizard/match_fields.html:34 +msgid "File Fields" +msgstr "" + +#: part/templates/part/import_wizard/ajax_match_fields.html:35 +#: templates/patterns/wizard/match_fields.html:41 +msgid "Remove column" +msgstr "" + +#: part/templates/part/import_wizard/ajax_match_fields.html:53 +#: templates/patterns/wizard/match_fields.html:59 +msgid "Duplicate selection" +msgstr "" + +#: part/templates/part/import_wizard/ajax_part_upload.html:10 +#: templates/patterns/wizard/upload.html:13 +#, python-format +msgid "Step %(step)s of %(count)s" +msgstr "" + +#: part/templates/part/import_wizard/ajax_part_upload.html:29 +#: part/templates/part/import_wizard/part_upload.html:14 +msgid "Unsuffitient privileges." +msgstr "" + +#: part/templates/part/import_wizard/part_upload.html:8 +msgid "Return to Parts" +msgstr "" + +#: part/templates/part/import_wizard/part_upload.html:13 +msgid "Import Parts from File" +msgstr "" + +#: part/templates/part/part_app_base.html:12 +msgid "Part List" +msgstr "" + +#: part/templates/part/part_base.html:27 part/templates/part/part_base.html:31 +msgid "You are subscribed to notifications for this part" +msgstr "" + +#: part/templates/part/part_base.html:35 +msgid "Subscribe to notifications for this part" +msgstr "" + +#: part/templates/part/part_base.html:43 +#: stock/templates/stock/item_base.html:35 +#: stock/templates/stock/location.html:34 +msgid "Barcode actions" +msgstr "" + +#: part/templates/part/part_base.html:46 +#: stock/templates/stock/item_base.html:39 +#: stock/templates/stock/location.html:36 templates/qr_button.html:1 +msgid "Show QR Code" +msgstr "" + +#: part/templates/part/part_base.html:49 +#: stock/templates/stock/item_base.html:57 +#: stock/templates/stock/location.html:38 +msgid "Print Label" +msgstr "" + +#: part/templates/part/part_base.html:55 +msgid "Show pricing information" +msgstr "" + +#: part/templates/part/part_base.html:60 +#: stock/templates/stock/item_base.html:110 +#: stock/templates/stock/location.html:47 +msgid "Stock actions" +msgstr "" + +#: part/templates/part/part_base.html:67 +msgid "Count part stock" +msgstr "" + +#: part/templates/part/part_base.html:73 +msgid "Transfer part stock" +msgstr "" + +#: part/templates/part/part_base.html:88 +msgid "Part actions" +msgstr "" + +#: part/templates/part/part_base.html:91 +msgid "Duplicate part" +msgstr "" + +#: part/templates/part/part_base.html:94 +msgid "Edit part" +msgstr "" + +#: part/templates/part/part_base.html:97 +msgid "Delete part" +msgstr "" + +#: part/templates/part/part_base.html:116 +msgid "Part is a template part (variants can be made from this part)" +msgstr "" + +#: part/templates/part/part_base.html:120 +msgid "Part can be assembled from other parts" +msgstr "" + +#: part/templates/part/part_base.html:124 +msgid "Part can be used in assemblies" +msgstr "" + +#: part/templates/part/part_base.html:128 +msgid "Part stock is tracked by serial number" +msgstr "" + +#: part/templates/part/part_base.html:132 +msgid "Part can be purchased from external suppliers" +msgstr "" + +#: part/templates/part/part_base.html:136 +msgid "Part can be sold to customers" +msgstr "" + +#: part/templates/part/part_base.html:142 +#: part/templates/part/part_base.html:150 +msgid "Part is virtual (not a physical part)" +msgstr "" + +#: part/templates/part/part_base.html:143 +msgid "Inactive" +msgstr "" + +#: part/templates/part/part_base.html:160 +#: part/templates/part/part_base.html:599 +msgid "Show Part Details" +msgstr "" + +#: part/templates/part/part_base.html:177 +#, python-format +msgid "This part is a variant of %(link)s" +msgstr "" + +#: part/templates/part/part_base.html:194 +msgid "In Stock" +msgstr "" + +#: part/templates/part/part_base.html:215 templates/InvenTree/index.html:178 +msgid "Required for Build Orders" +msgstr "" + +#: part/templates/part/part_base.html:220 +msgid "Allocated to Build Orders" +msgstr "" + +#: part/templates/part/part_base.html:224 +#: part/templates/part/part_base.html:247 +msgid "Required quantity has not been allocated" +msgstr "" + +#: part/templates/part/part_base.html:226 +#: part/templates/part/part_base.html:249 +msgid "Required quantity has been allocated" +msgstr "" + +#: part/templates/part/part_base.html:236 +msgid "Required for Sales Orders" +msgstr "" + +#: part/templates/part/part_base.html:243 +msgid "Allocated to Sales Orders" +msgstr "" + +#: part/templates/part/part_base.html:258 +msgid "Can Build" +msgstr "" + +#: part/templates/part/part_base.html:264 +msgid "Building" +msgstr "" + +#: part/templates/part/part_base.html:313 +msgid "Minimum stock level" +msgstr "" + +#: part/templates/part/part_base.html:342 +msgid "Latest Serial Number" +msgstr "" + +#: part/templates/part/part_base.html:346 +#: stock/templates/stock/item_base.html:166 +msgid "Search for serial number" +msgstr "" + +#: part/templates/part/part_base.html:469 part/templates/part/prices.html:144 +msgid "Calculate" +msgstr "" + +#: part/templates/part/part_base.html:512 +msgid "No matching images found" +msgstr "" + +#: part/templates/part/part_base.html:593 +msgid "Hide Part Details" +msgstr "" + +#: part/templates/part/part_pricing.html:22 part/templates/part/prices.html:21 +msgid "Supplier Pricing" +msgstr "" + +#: part/templates/part/part_pricing.html:26 +#: part/templates/part/part_pricing.html:52 +#: part/templates/part/part_pricing.html:100 +#: part/templates/part/part_pricing.html:115 part/templates/part/prices.html:25 +#: part/templates/part/prices.html:52 part/templates/part/prices.html:103 +#: part/templates/part/prices.html:120 +msgid "Unit Cost" +msgstr "" + +#: part/templates/part/part_pricing.html:32 +#: part/templates/part/part_pricing.html:58 +#: part/templates/part/part_pricing.html:104 +#: part/templates/part/part_pricing.html:119 part/templates/part/prices.html:32 +#: part/templates/part/prices.html:59 part/templates/part/prices.html:108 +#: part/templates/part/prices.html:125 +msgid "Total Cost" +msgstr "" + +#: part/templates/part/part_pricing.html:40 part/templates/part/prices.html:40 +msgid "No supplier pricing available" +msgstr "" + +#: part/templates/part/part_pricing.html:48 part/templates/part/prices.html:49 +#: part/templates/part/prices.html:243 +msgid "BOM Pricing" +msgstr "" + +#: part/templates/part/part_pricing.html:65 part/templates/part/prices.html:69 +msgid "Unit Purchase Price" +msgstr "" + +#: part/templates/part/part_pricing.html:71 part/templates/part/prices.html:76 +msgid "Total Purchase Price" +msgstr "" + +#: part/templates/part/part_pricing.html:81 part/templates/part/prices.html:86 +msgid "Note: BOM pricing is incomplete for this part" +msgstr "" + +#: part/templates/part/part_pricing.html:88 part/templates/part/prices.html:93 +msgid "No BOM pricing available" +msgstr "" + +#: part/templates/part/part_pricing.html:97 part/templates/part/prices.html:102 +msgid "Internal Price" +msgstr "" + +#: part/templates/part/part_pricing.html:128 +#: part/templates/part/prices.html:134 +msgid "No pricing information is available for this part." +msgstr "" + +#: part/templates/part/part_sidebar.html:12 +msgid "Variants" +msgstr "" + +#: part/templates/part/part_sidebar.html:28 +msgid "Used In" +msgstr "" + +#: part/templates/part/part_sidebar.html:47 +msgid "Scheduling" +msgstr "" + +#: part/templates/part/part_sidebar.html:51 +msgid "Test Templates" +msgstr "" + +#: part/templates/part/part_thumb.html:11 +msgid "Select from existing images" +msgstr "" + +#: part/templates/part/partial_delete.html:9 +#, python-format +msgid "" +"Part '%(full_name)s' cannot be deleted as it is still " +"marked as active.\n" +"
Disable the \"Active\" part attribute and re-try.\n" +" " +msgstr "" + +#: part/templates/part/partial_delete.html:17 +#, python-format +msgid "Are you sure you want to delete part '%(full_name)s'?" +msgstr "" + +#: part/templates/part/partial_delete.html:22 +#, python-format +msgid "" +"This part is used in BOMs for %(count)s other parts. If you delete this " +"part, the BOMs for the following parts will be updated" +msgstr "" + +#: part/templates/part/partial_delete.html:32 +#, python-format +msgid "" +"There are %(count)s stock entries defined for this part. If you delete this " +"part, the following stock entries will also be deleted:" +msgstr "" + +#: part/templates/part/partial_delete.html:43 +#, python-format +msgid "" +"There are %(count)s manufacturers defined for this part. If you delete this " +"part, the following manufacturer parts will also be deleted:" +msgstr "" + +#: part/templates/part/partial_delete.html:54 +#, python-format +msgid "" +"There are %(count)s suppliers defined for this part. If you delete this " +"part, the following supplier parts will also be deleted:" +msgstr "" + +#: part/templates/part/partial_delete.html:65 +#, python-format +msgid "" +"There are %(count)s unique parts tracked for '%(full_name)s'. Deleting this " +"part will permanently remove this tracking information." +msgstr "" + +#: part/templates/part/prices.html:16 +msgid "Pricing ranges" +msgstr "" + +#: part/templates/part/prices.html:22 +msgid "Show supplier cost" +msgstr "" + +#: part/templates/part/prices.html:23 +msgid "Show purchase price" +msgstr "" + +#: part/templates/part/prices.html:50 +msgid "Show BOM cost" +msgstr "" + +#: part/templates/part/prices.html:117 +msgid "Show sale cost" +msgstr "" + +#: part/templates/part/prices.html:118 +msgid "Show sale price" +msgstr "" + +#: part/templates/part/prices.html:140 +msgid "Calculation parameters" +msgstr "" + +#: part/templates/part/prices.html:155 +msgid "Supplier Cost" +msgstr "" + +#: part/templates/part/prices.html:156 part/templates/part/prices.html:177 +#: part/templates/part/prices.html:201 part/templates/part/prices.html:231 +#: part/templates/part/prices.html:257 part/templates/part/prices.html:285 +msgid "Jump to overview" +msgstr "" + +#: part/templates/part/prices.html:181 +msgid "Stock Pricing" +msgstr "" + +#: part/templates/part/prices.html:190 +msgid "No stock pricing history is available for this part." +msgstr "" + +#: part/templates/part/prices.html:200 +msgid "Internal Cost" +msgstr "" + +#: part/templates/part/prices.html:215 part/views.py:1309 +msgid "Add Internal Price Break" +msgstr "" + +#: part/templates/part/prices.html:230 +msgid "BOM Cost" +msgstr "" + +#: part/templates/part/prices.html:256 +msgid "Sale Cost" +msgstr "" + +#: part/templates/part/prices.html:296 +msgid "No sale pice history available for this part." +msgstr "" + +#: part/templates/part/set_category.html:9 +msgid "Set category for the following parts" +msgstr "" + +#: part/templates/part/stock_count.html:7 +msgid "No Stock" +msgstr "" + +#: part/templates/part/stock_count.html:9 templates/InvenTree/index.html:158 +msgid "Low Stock" +msgstr "" + +#: part/templates/part/upload_bom.html:8 +msgid "Return to BOM" +msgstr "" + +#: part/templates/part/upload_bom.html:13 +msgid "Upload Bill of Materials" +msgstr "" + +#: part/templates/part/upload_bom.html:19 +msgid "BOM upload requirements" +msgstr "" + +#: part/templates/part/upload_bom.html:23 +#: part/templates/part/upload_bom.html:90 +msgid "Upload BOM File" +msgstr "" + +#: part/templates/part/upload_bom.html:29 +msgid "Submit BOM Data" +msgstr "" + +#: part/templates/part/upload_bom.html:37 +msgid "Requirements for BOM upload" +msgstr "" + +#: part/templates/part/upload_bom.html:39 +msgid "" +"The BOM file must contain the required named columns as provided in the " +msgstr "" + +#: part/templates/part/upload_bom.html:39 +msgid "BOM Upload Template" +msgstr "" + +#: part/templates/part/upload_bom.html:40 +msgid "Each part must already exist in the database" +msgstr "" + +#: part/templates/part/variant_part.html:9 +msgid "Create new part variant" +msgstr "" + +#: part/templates/part/variant_part.html:10 +#, python-format +msgid "Create a new variant of template '%(full_name)s'." +msgstr "" + +#: part/templatetags/inventree_extras.py:189 +msgid "Unknown database" +msgstr "" + +#: part/views.py:88 +msgid "Set Part Category" +msgstr "" + +#: part/views.py:138 +#, python-brace-format +msgid "Set category for {n} parts" +msgstr "" + +#: part/views.py:210 +msgid "Match References" +msgstr "" + +#: part/views.py:507 +msgid "None" +msgstr "" + +#: part/views.py:566 +msgid "Part QR Code" +msgstr "" + +#: part/views.py:668 +msgid "Select Part Image" +msgstr "" + +#: part/views.py:694 +msgid "Updated part image" +msgstr "" + +#: part/views.py:697 +msgid "Part image not found" +msgstr "" + +#: part/views.py:785 +msgid "Confirm Part Deletion" +msgstr "" + +#: part/views.py:792 +msgid "Part was deleted" +msgstr "" + +#: part/views.py:801 +msgid "Part Pricing" +msgstr "" + +#: part/views.py:950 +msgid "Create Part Parameter Template" +msgstr "" + +#: part/views.py:960 +msgid "Edit Part Parameter Template" +msgstr "" + +#: part/views.py:967 +msgid "Delete Part Parameter Template" +msgstr "" + +#: part/views.py:1010 +msgid "Edit Part Category" +msgstr "" + +#: part/views.py:1048 +msgid "Delete Part Category" +msgstr "" + +#: part/views.py:1054 +msgid "Part category was deleted" +msgstr "" + +#: part/views.py:1063 +msgid "Create Category Parameter Template" +msgstr "" + +#: part/views.py:1164 +msgid "Edit Category Parameter Template" +msgstr "" + +#: part/views.py:1220 +msgid "Delete Category Parameter Template" +msgstr "" + +#: part/views.py:1242 +msgid "Added new price break" +msgstr "" + +#: part/views.py:1318 +msgid "Edit Internal Price Break" +msgstr "" + +#: part/views.py:1326 +msgid "Delete Internal Price Break" +msgstr "" + +#: plugin/apps.py:52 +msgid "" +"Your enviroment has an outdated git version. This prevents InvenTree from " +"loading plugin details." +msgstr "" + +#: plugin/events.py:225 +msgid "Label printing failed" +msgstr "" + +#: plugin/integration.py:146 +msgid "No author found" +msgstr "" + +#: plugin/integration.py:160 +msgid "No date found" +msgstr "" + +#: plugin/models.py:26 +msgid "Plugin Configuration" +msgstr "" + +#: plugin/models.py:27 +msgid "Plugin Configurations" +msgstr "" + +#: plugin/models.py:32 +msgid "Key" +msgstr "" + +#: plugin/models.py:33 +msgid "Key of plugin" +msgstr "" + +#: plugin/models.py:41 +msgid "PluginName of the plugin" +msgstr "" + +#: plugin/models.py:47 +msgid "Is the plugin active" +msgstr "" + +#: plugin/models.py:182 +msgid "Plugin" +msgstr "" + +#: plugin/samples/integration/sample.py:42 +msgid "Enable PO" +msgstr "" + +#: plugin/samples/integration/sample.py:43 +msgid "Enable PO functionality in InvenTree interface" +msgstr "" + +#: plugin/samples/integration/sample.py:48 +msgid "API Key" +msgstr "" + +#: plugin/samples/integration/sample.py:49 +msgid "Key required for accessing external API" +msgstr "" + +#: plugin/samples/integration/sample.py:52 +msgid "Numerical" +msgstr "" + +#: plugin/samples/integration/sample.py:53 +msgid "A numerical setting" +msgstr "" + +#: plugin/samples/integration/sample.py:58 +msgid "Choice Setting" +msgstr "" + +#: plugin/samples/integration/sample.py:59 +msgid "A setting with multiple choices" +msgstr "" + +#: plugin/serializers.py:49 +msgid "Source URL" +msgstr "" + +#: plugin/serializers.py:50 +msgid "Source for the package - this can be a custom registry or a VCS path" +msgstr "" + +#: plugin/serializers.py:55 +msgid "Package Name" +msgstr "" + +#: plugin/serializers.py:56 +msgid "Name for the Plugin Package - can also contain a version indicator" +msgstr "" + +#: plugin/serializers.py:59 +msgid "Confirm plugin installation" +msgstr "" + +#: plugin/serializers.py:60 +msgid "" +"This will install this plugin now into the current instance. The instance " +"will go into maintenance." +msgstr "" + +#: plugin/serializers.py:75 +msgid "Installation not confirmed" +msgstr "" + +#: plugin/serializers.py:77 +msgid "Either packagename of URL must be provided" +msgstr "" + +#: report/api.py:235 report/api.py:282 +#, python-brace-format +msgid "Template file '{template}' is missing or does not exist" +msgstr "" + +#: report/models.py:178 +msgid "Template name" +msgstr "" + +#: report/models.py:184 +msgid "Report template file" +msgstr "" + +#: report/models.py:191 +msgid "Report template description" +msgstr "" + +#: report/models.py:197 +msgid "Report revision number (auto-increments)" +msgstr "" + +#: report/models.py:288 +msgid "Pattern for generating report filenames" +msgstr "" + +#: report/models.py:295 +msgid "Report template is enabled" +msgstr "" + +#: report/models.py:319 +msgid "StockItem query filters (comma-separated list of key=value pairs)" +msgstr "" + +#: report/models.py:327 +msgid "Include Installed Tests" +msgstr "" + +#: report/models.py:328 +msgid "Include test results for stock items installed inside assembled item" +msgstr "" + +#: report/models.py:378 +msgid "Build Filters" +msgstr "" + +#: report/models.py:379 +msgid "Build query filters (comma-separated list of key=value pairs" +msgstr "" + +#: report/models.py:421 +msgid "Part Filters" +msgstr "" + +#: report/models.py:422 +msgid "Part query filters (comma-separated list of key=value pairs" +msgstr "" + +#: report/models.py:456 +msgid "Purchase order query filters" +msgstr "" + +#: report/models.py:494 +msgid "Sales order query filters" +msgstr "" + +#: report/models.py:548 +msgid "Snippet" +msgstr "" + +#: report/models.py:549 +msgid "Report snippet file" +msgstr "" + +#: report/models.py:553 +msgid "Snippet file description" +msgstr "" + +#: report/models.py:588 +msgid "Asset" +msgstr "" + +#: report/models.py:589 +msgid "Report asset file" +msgstr "" + +#: report/models.py:592 +msgid "Asset file description" +msgstr "" + +#: report/templates/report/inventree_build_order_base.html:147 +msgid "Required For" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:21 +msgid "Stock Item Test Report" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:79 +#: stock/models.py:631 stock/templates/stock/item_base.html:156 +msgid "Serial Number" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:88 +msgid "Test Results" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:93 +#: stock/models.py:2154 +msgid "Test" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:94 +#: stock/models.py:2160 +msgid "Result" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:97 +#: templates/InvenTree/settings/plugin.html:51 +#: templates/InvenTree/settings/plugin_settings.html:38 +msgid "Date" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:108 +msgid "Pass" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:110 +msgid "Fail" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:123 +#: stock/templates/stock/stock_sidebar.html:16 +msgid "Installed Items" +msgstr "" + +#: report/templates/report/inventree_test_report_base.html:137 +msgid "Serial" +msgstr "" + +#: stock/api.py:543 +msgid "Quantity is required" +msgstr "" + +#: stock/api.py:550 +msgid "Valid part must be supplied" +msgstr "" + +#: stock/api.py:575 +msgid "Serial numbers cannot be supplied for a non-trackable part" +msgstr "" + +#: stock/forms.py:74 stock/forms.py:198 stock/models.py:688 +#: stock/templates/stock/item_base.html:193 +msgid "Expiry Date" +msgstr "" + +#: stock/forms.py:75 stock/forms.py:199 +msgid "Expiration date for this stock item" +msgstr "" + +#: stock/forms.py:78 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "" + +#: stock/forms.py:133 +msgid "" +"Destination for serialized stock (by default, will remain in current " +"location)" +msgstr "" + +#: stock/forms.py:135 +msgid "Serial numbers" +msgstr "" + +#: stock/forms.py:135 +msgid "Unique serial numbers (must match quantity)" +msgstr "" + +#: stock/forms.py:137 stock/forms.py:171 +msgid "Add transaction note (optional)" +msgstr "" + +#: stock/forms.py:169 +msgid "Destination location for uninstalled items" +msgstr "" + +#: stock/forms.py:173 +msgid "Confirm uninstall" +msgstr "" + +#: stock/forms.py:173 +msgid "Confirm removal of installed stock items" +msgstr "" + +#: stock/models.py:91 stock/models.py:725 +#: stock/templates/stock/item_base.html:407 +msgid "Owner" +msgstr "" + +#: stock/models.py:92 stock/models.py:726 +msgid "Select Owner" +msgstr "" + +#: stock/models.py:442 +msgid "StockItem with this serial number already exists" +msgstr "" + +#: stock/models.py:486 +#, python-brace-format +msgid "Part type ('{pf}') must be {pe}" +msgstr "" + +#: stock/models.py:496 stock/models.py:505 +msgid "Quantity must be 1 for item with a serial number" +msgstr "" + +#: stock/models.py:497 +msgid "Serial number cannot be set if quantity greater than 1" +msgstr "" + +#: stock/models.py:519 +msgid "Item cannot belong to itself" +msgstr "" + +#: stock/models.py:525 +msgid "Item must have a build reference if is_building=True" +msgstr "" + +#: stock/models.py:532 +msgid "Build reference does not point to the same part object" +msgstr "" + +#: stock/models.py:575 +msgid "Parent Stock Item" +msgstr "" + +#: stock/models.py:584 +msgid "Base part" +msgstr "" + +#: stock/models.py:592 +msgid "Select a matching supplier part for this stock item" +msgstr "" + +#: stock/models.py:598 stock/templates/stock/location.html:16 +#: stock/templates/stock/stock_app_base.html:8 +msgid "Stock Location" +msgstr "" + +#: stock/models.py:601 +msgid "Where is this stock item located?" +msgstr "" + +#: stock/models.py:608 +msgid "Packaging this stock item is stored in" +msgstr "" + +#: stock/models.py:614 stock/templates/stock/item_base.html:282 +msgid "Installed In" +msgstr "" + +#: stock/models.py:617 +msgid "Is this item installed in another item?" +msgstr "" + +#: stock/models.py:633 +msgid "Serial number for this item" +msgstr "" + +#: stock/models.py:647 +msgid "Batch code for this stock item" +msgstr "" + +#: stock/models.py:651 +msgid "Stock Quantity" +msgstr "" + +#: stock/models.py:660 +msgid "Source Build" +msgstr "" + +#: stock/models.py:662 +msgid "Build for this stock item" +msgstr "" + +#: stock/models.py:673 +msgid "Source Purchase Order" +msgstr "" + +#: stock/models.py:676 +msgid "Purchase order for this stock item" +msgstr "" + +#: stock/models.py:682 +msgid "Destination Sales Order" +msgstr "" + +#: stock/models.py:689 +msgid "" +"Expiry date for stock item. Stock will be considered expired after this date" +msgstr "" + +#: stock/models.py:702 +msgid "Delete on deplete" +msgstr "" + +#: stock/models.py:702 +msgid "Delete this Stock Item when stock is depleted" +msgstr "" + +#: stock/models.py:712 stock/templates/stock/item.html:137 +msgid "Stock Item Notes" +msgstr "" + +#: stock/models.py:721 +msgid "Single unit purchase price at time of purchase" +msgstr "" + +#: stock/models.py:753 +msgid "Converted to part" +msgstr "" + +#: stock/models.py:1273 +msgid "Part is not set as trackable" +msgstr "" + +#: stock/models.py:1279 +msgid "Quantity must be integer" +msgstr "" + +#: stock/models.py:1285 +#, python-brace-format +msgid "Quantity must not exceed available stock quantity ({n})" +msgstr "" + +#: stock/models.py:1288 +msgid "Serial numbers must be a list of integers" +msgstr "" + +#: stock/models.py:1291 +msgid "Quantity does not match serial numbers" +msgstr "" + +#: stock/models.py:1298 +#, python-brace-format +msgid "Serial numbers already exist: {exists}" +msgstr "" + +#: stock/models.py:1369 +msgid "Stock item has been assigned to a sales order" +msgstr "" + +#: stock/models.py:1372 +msgid "Stock item is installed in another item" +msgstr "" + +#: stock/models.py:1375 +msgid "Stock item contains other items" +msgstr "" + +#: stock/models.py:1378 +msgid "Stock item has been assigned to a customer" +msgstr "" + +#: stock/models.py:1381 +msgid "Stock item is currently in production" +msgstr "" + +#: stock/models.py:1384 +msgid "Serialized stock cannot be merged" +msgstr "" + +#: stock/models.py:1391 stock/serializers.py:832 +msgid "Duplicate stock items" +msgstr "" + +#: stock/models.py:1395 +msgid "Stock items must refer to the same part" +msgstr "" + +#: stock/models.py:1399 +msgid "Stock items must refer to the same supplier part" +msgstr "" + +#: stock/models.py:1403 +msgid "Stock status codes must match" +msgstr "" + +#: stock/models.py:1575 +msgid "StockItem cannot be moved as it is not in stock" +msgstr "" + +#: stock/models.py:2074 +msgid "Entry notes" +msgstr "" + +#: stock/models.py:2131 +msgid "Value must be provided for this test" +msgstr "" + +#: stock/models.py:2137 +msgid "Attachment must be uploaded for this test" +msgstr "" + +#: stock/models.py:2155 +msgid "Test name" +msgstr "" + +#: stock/models.py:2161 +msgid "Test result" +msgstr "" + +#: stock/models.py:2167 +msgid "Test output value" +msgstr "" + +#: stock/models.py:2174 +msgid "Test result attachment" +msgstr "" + +#: stock/models.py:2180 +msgid "Test notes" +msgstr "" + +#: stock/serializers.py:173 +msgid "Purchase price of this stock item" +msgstr "" + +#: stock/serializers.py:180 +msgid "Purchase currency of this stock item" +msgstr "" + +#: stock/serializers.py:294 +msgid "Enter number of stock items to serialize" +msgstr "" + +#: stock/serializers.py:309 +#, python-brace-format +msgid "Quantity must not exceed available stock quantity ({q})" +msgstr "" + +#: stock/serializers.py:315 +msgid "Enter serial numbers for new items" +msgstr "" + +#: stock/serializers.py:326 stock/serializers.py:789 stock/serializers.py:1030 +msgid "Destination stock location" +msgstr "" + +#: stock/serializers.py:333 +msgid "Optional note field" +msgstr "" + +#: stock/serializers.py:346 +msgid "Serial numbers cannot be assigned to this part" +msgstr "" + +#: stock/serializers.py:363 stock/views.py:1019 +msgid "Serial numbers already exist" +msgstr "" + +#: stock/serializers.py:405 +msgid "Select stock item to install" +msgstr "" + +#: stock/serializers.py:421 +msgid "Stock item is unavailable" +msgstr "" + +#: stock/serializers.py:428 +msgid "Selected part is not in the Bill of Materials" +msgstr "" + +#: stock/serializers.py:646 +msgid "Part must be salable" +msgstr "" + +#: stock/serializers.py:650 +msgid "Item is allocated to a sales order" +msgstr "" + +#: stock/serializers.py:654 +msgid "Item is allocated to a build order" +msgstr "" + +#: stock/serializers.py:684 +msgid "Customer to assign stock items" +msgstr "" + +#: stock/serializers.py:690 +msgid "Selected company is not a customer" +msgstr "" + +#: stock/serializers.py:698 +msgid "Stock assignment notes" +msgstr "" + +#: stock/serializers.py:708 stock/serializers.py:938 +msgid "A list of stock items must be provided" +msgstr "" + +#: stock/serializers.py:796 +msgid "Stock merging notes" +msgstr "" + +#: stock/serializers.py:801 +msgid "Allow mismatched suppliers" +msgstr "" + +#: stock/serializers.py:802 +msgid "Allow stock items with different supplier parts to be merged" +msgstr "" + +#: stock/serializers.py:807 +msgid "Allow mismatched status" +msgstr "" + +#: stock/serializers.py:808 +msgid "Allow stock items with different status codes to be merged" +msgstr "" + +#: stock/serializers.py:818 +msgid "At least two stock items must be provided" +msgstr "" + +#: stock/serializers.py:900 +msgid "StockItem primary key value" +msgstr "" + +#: stock/serializers.py:928 +msgid "Stock transaction notes" +msgstr "" + +#: stock/templates/stock/item.html:17 +msgid "Stock Tracking Information" +msgstr "" + +#: stock/templates/stock/item.html:22 +msgid "New Entry" +msgstr "" + +#: stock/templates/stock/item.html:74 +msgid "Child Stock Items" +msgstr "" + +#: stock/templates/stock/item.html:82 +msgid "This stock item does not have any child items" +msgstr "" + +#: stock/templates/stock/item.html:91 +#: stock/templates/stock/stock_sidebar.html:12 +msgid "Test Data" +msgstr "" + +#: stock/templates/stock/item.html:95 stock/templates/stock/item_base.html:60 +msgid "Test Report" +msgstr "" + +#: stock/templates/stock/item.html:99 +msgid "Delete Test Data" +msgstr "" + +#: stock/templates/stock/item.html:103 +msgid "Add Test Data" +msgstr "" + +#: stock/templates/stock/item.html:152 +msgid "Installed Stock Items" +msgstr "" + +#: stock/templates/stock/item.html:156 +msgid "Install Stock Item" +msgstr "" + +#: stock/templates/stock/item.html:316 +msgid "Add Test Result" +msgstr "" + +#: stock/templates/stock/item_base.html:42 +msgid "Unlink Barcode" +msgstr "" + +#: stock/templates/stock/item_base.html:44 +msgid "Link Barcode" +msgstr "" + +#: stock/templates/stock/item_base.html:46 templates/stock_table.html:21 +msgid "Scan to Location" +msgstr "" + +#: stock/templates/stock/item_base.html:54 +msgid "Printing actions" +msgstr "" + +#: stock/templates/stock/item_base.html:70 +msgid "Stock adjustment actions" +msgstr "" + +#: stock/templates/stock/item_base.html:74 +#: stock/templates/stock/location.html:54 templates/stock_table.html:47 +msgid "Count stock" +msgstr "" + +#: stock/templates/stock/item_base.html:77 templates/stock_table.html:45 +msgid "Add stock" +msgstr "" + +#: stock/templates/stock/item_base.html:80 templates/stock_table.html:46 +msgid "Remove stock" +msgstr "" + +#: stock/templates/stock/item_base.html:83 +msgid "Serialize stock" +msgstr "" + +#: stock/templates/stock/item_base.html:87 +#: stock/templates/stock/location.html:60 templates/stock_table.html:48 +msgid "Transfer stock" +msgstr "" + +#: stock/templates/stock/item_base.html:90 templates/stock_table.html:51 +msgid "Assign to customer" +msgstr "" + +#: stock/templates/stock/item_base.html:93 +msgid "Return to stock" +msgstr "" + +#: stock/templates/stock/item_base.html:96 +msgid "Uninstall stock item" +msgstr "" + +#: stock/templates/stock/item_base.html:96 +msgid "Uninstall" +msgstr "" + +#: stock/templates/stock/item_base.html:100 +msgid "Install stock item" +msgstr "" + +#: stock/templates/stock/item_base.html:100 +msgid "Install" +msgstr "" + +#: stock/templates/stock/item_base.html:115 +msgid "Convert to variant" +msgstr "" + +#: stock/templates/stock/item_base.html:118 +msgid "Duplicate stock item" +msgstr "" + +#: stock/templates/stock/item_base.html:120 +msgid "Edit stock item" +msgstr "" + +#: stock/templates/stock/item_base.html:123 +msgid "Delete stock item" +msgstr "" + +#: stock/templates/stock/item_base.html:161 +msgid "previous page" +msgstr "" + +#: stock/templates/stock/item_base.html:161 +msgid "Navigate to previous serial number" +msgstr "" + +#: stock/templates/stock/item_base.html:170 +msgid "next page" +msgstr "" + +#: stock/templates/stock/item_base.html:170 +msgid "Navigate to next serial number" +msgstr "" + +#: stock/templates/stock/item_base.html:197 +#, python-format +msgid "This StockItem expired on %(item.expiry_date)s" +msgstr "" + +#: stock/templates/stock/item_base.html:197 +msgid "Expired" +msgstr "" + +#: stock/templates/stock/item_base.html:199 +#, python-format +msgid "This StockItem expires on %(item.expiry_date)s" +msgstr "" + +#: stock/templates/stock/item_base.html:199 +msgid "Stale" +msgstr "" + +#: stock/templates/stock/item_base.html:206 +msgid "Last Updated" +msgstr "" + +#: stock/templates/stock/item_base.html:211 +msgid "Last Stocktake" +msgstr "" + +#: stock/templates/stock/item_base.html:215 +msgid "No stocktake performed" +msgstr "" + +#: stock/templates/stock/item_base.html:224 +msgid "This stock item is in production and cannot be edited." +msgstr "" + +#: stock/templates/stock/item_base.html:225 +msgid "Edit the stock item from the build view." +msgstr "" + +#: stock/templates/stock/item_base.html:238 +msgid "This stock item has not passed all required tests" +msgstr "" + +#: stock/templates/stock/item_base.html:246 +msgid "This stock item is allocated to Sales Order" +msgstr "" + +#: stock/templates/stock/item_base.html:254 +msgid "This stock item is allocated to Build Order" +msgstr "" + +#: stock/templates/stock/item_base.html:260 +msgid "" +"This stock item is serialized - it has a unique serial number and the " +"quantity cannot be adjusted." +msgstr "" + +#: stock/templates/stock/item_base.html:301 +msgid "No location set" +msgstr "" + +#: stock/templates/stock/item_base.html:308 +msgid "Barcode Identifier" +msgstr "" + +#: stock/templates/stock/item_base.html:350 +msgid "Parent Item" +msgstr "" + +#: stock/templates/stock/item_base.html:368 +msgid "No manufacturer set" +msgstr "" + +#: stock/templates/stock/item_base.html:393 +msgid "Tests" +msgstr "" + +#: stock/templates/stock/item_base.html:411 +msgid "" +"You are not in the list of owners of this item. This stock item cannot be " +"edited." +msgstr "" + +#: stock/templates/stock/item_base.html:412 +#: stock/templates/stock/location.html:118 +msgid "Read only" +msgstr "" + +#: stock/templates/stock/item_base.html:486 +msgid "Edit Stock Status" +msgstr "" + +#: stock/templates/stock/item_delete.html:9 +msgid "Are you sure you want to delete this stock item?" +msgstr "" + +#: stock/templates/stock/item_delete.html:12 +#, python-format +msgid "" +"This will remove %(qty)s units of %(full_name)s from stock." +msgstr "" + +#: stock/templates/stock/item_serialize.html:5 +msgid "Create serialized items from this stock item." +msgstr "" + +#: stock/templates/stock/item_serialize.html:7 +msgid "Select quantity to serialize, and unique serial numbers." +msgstr "" + +#: stock/templates/stock/location.html:40 +msgid "Check-in Items" +msgstr "" + +#: stock/templates/stock/location.html:68 +msgid "Location actions" +msgstr "" + +#: stock/templates/stock/location.html:70 +msgid "Edit location" +msgstr "" + +#: stock/templates/stock/location.html:72 +msgid "Delete location" +msgstr "" + +#: stock/templates/stock/location.html:81 +msgid "Create new stock location" +msgstr "" + +#: stock/templates/stock/location.html:82 +msgid "New Location" +msgstr "" + +#: stock/templates/stock/location.html:100 +#: stock/templates/stock/location.html:106 +msgid "Location Path" +msgstr "" + +#: stock/templates/stock/location.html:107 +msgid "Top level stock location" +msgstr "" + +#: stock/templates/stock/location.html:113 +msgid "Location Owner" +msgstr "" + +#: stock/templates/stock/location.html:117 +msgid "" +"You are not in the list of owners of this location. This stock location " +"cannot be edited." +msgstr "" + +#: stock/templates/stock/location.html:133 +#: stock/templates/stock/location.html:180 +#: stock/templates/stock/location_sidebar.html:5 +msgid "Sublocations" +msgstr "" + +#: stock/templates/stock/location.html:147 templates/InvenTree/search.html:164 +#: templates/stats.html:109 users/models.py:42 +msgid "Stock Locations" +msgstr "" + +#: stock/templates/stock/location_delete.html:8 +msgid "Are you sure you want to delete this stock location?" +msgstr "" + +#: stock/templates/stock/location_delete.html:13 +#, python-format +msgid "This location contains %(n)s child locations" +msgstr "" + +#: stock/templates/stock/location_delete.html:15 +#, python-format +msgid "" +"If this location is deleted, these child locations will be moved to " +"%(location)s" +msgstr "" + +#: stock/templates/stock/location_delete.html:17 +msgid "" +"If this location is deleted, these child locations will be moved to the top " +"level stock location" +msgstr "" + +#: stock/templates/stock/location_delete.html:25 +#, python-format +msgid "This location contains %(n)s stock items" +msgstr "" + +#: stock/templates/stock/location_delete.html:27 +#, python-format +msgid "" +"If this location is deleted, these stock items will be moved to %(location)s" +msgstr "" + +#: stock/templates/stock/location_delete.html:29 +msgid "" +"If this location is deleted, these stock items will be moved to the top " +"level stock location" +msgstr "" + +#: stock/templates/stock/stock_app_base.html:16 +msgid "Loading..." +msgstr "" + +#: stock/templates/stock/stock_sidebar.html:5 +msgid "Stock Tracking" +msgstr "" + +#: stock/templates/stock/stock_sidebar.html:8 +msgid "Allocations" +msgstr "" + +#: stock/templates/stock/stock_sidebar.html:20 +msgid "Child Items" +msgstr "" + +#: stock/templates/stock/stock_uninstall.html:8 +msgid "The following stock items will be uninstalled" +msgstr "" + +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:631 +msgid "Convert Stock Item" +msgstr "" + +#: stock/templates/stock/stockitem_convert.html:8 +#, python-format +msgid "This stock item is current an instance of %(part)s" +msgstr "" + +#: stock/templates/stock/stockitem_convert.html:9 +msgid "It can be converted to one of the part variants listed below." +msgstr "" + +#: stock/templates/stock/stockitem_convert.html:14 +msgid "This action cannot be easily undone" +msgstr "" + +#: stock/templates/stock/tracking_delete.html:6 +msgid "Are you sure you want to delete this stock tracking entry?" +msgstr "" + +#: stock/views.py:152 +msgid "Edit Stock Location" +msgstr "" + +#: stock/views.py:259 stock/views.py:610 stock/views.py:746 stock/views.py:1028 +msgid "Owner is required (ownership control is enabled)" +msgstr "" + +#: stock/views.py:274 +msgid "Stock Location QR code" +msgstr "" + +#: stock/views.py:293 +msgid "Return to Stock" +msgstr "" + +#: stock/views.py:302 +msgid "Specify a valid location" +msgstr "" + +#: stock/views.py:313 +msgid "Stock item returned from customer" +msgstr "" + +#: stock/views.py:324 +msgid "Delete All Test Data" +msgstr "" + +#: stock/views.py:341 +msgid "Confirm test data deletion" +msgstr "" + +#: stock/views.py:342 +msgid "Check the confirmation box" +msgstr "" + +#: stock/views.py:357 +msgid "Stock Item QR Code" +msgstr "" + +#: stock/views.py:382 +msgid "Uninstall Stock Items" +msgstr "" + +#: stock/views.py:479 +msgid "Confirm stock adjustment" +msgstr "" + +#: stock/views.py:490 +msgid "Uninstalled stock items" +msgstr "" + +#: stock/views.py:512 +msgid "Edit Stock Item" +msgstr "" + +#: stock/views.py:672 +msgid "Create new Stock Location" +msgstr "" + +#: stock/views.py:773 +msgid "Create new Stock Item" +msgstr "" + +#: stock/views.py:915 +msgid "Duplicate Stock Item" +msgstr "" + +#: stock/views.py:997 +msgid "Quantity cannot be negative" +msgstr "" + +#: stock/views.py:1097 +msgid "Delete Stock Location" +msgstr "" + +#: stock/views.py:1110 +msgid "Delete Stock Item" +msgstr "" + +#: stock/views.py:1121 +msgid "Delete Stock Tracking Entry" +msgstr "" + +#: stock/views.py:1128 +msgid "Edit Stock Tracking Entry" +msgstr "" + +#: stock/views.py:1137 +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/404.html:5 templates/404.html:11 +msgid "Page Not Found" +msgstr "" + +#: templates/404.html:14 +msgid "The requested page does not exist" +msgstr "" + +#: templates/500.html:5 templates/500.html:11 +msgid "Internal Server Error" +msgstr "" + +#: templates/500.html:14 +msgid "The InvenTree server raised an internal error" +msgstr "" + +#: templates/500.html:15 +msgid "Refer to the error log in the admin interface for further details" +msgstr "" + +#: templates/503.html:10 templates/503.html:35 +msgid "Site is in Maintenance" +msgstr "" + +#: templates/503.html:41 +msgid "The site is currently in maintenance and should be up again soon!" +msgstr "" + +#: templates/InvenTree/index.html:7 +msgid "Index" +msgstr "" + +#: templates/InvenTree/index.html:88 +msgid "Subscribed Parts" +msgstr "" + +#: templates/InvenTree/index.html:98 +msgid "Subscribed Categories" +msgstr "" + +#: templates/InvenTree/index.html:108 +msgid "Latest Parts" +msgstr "" + +#: templates/InvenTree/index.html:119 +msgid "BOM Waiting Validation" +msgstr "" + +#: templates/InvenTree/index.html:145 +msgid "Recently Updated" +msgstr "" + +#: templates/InvenTree/index.html:168 +msgid "Depleted Stock" +msgstr "" + +#: templates/InvenTree/index.html:191 +msgid "Expired Stock" +msgstr "" + +#: templates/InvenTree/index.html:202 +msgid "Stale Stock" +msgstr "" + +#: templates/InvenTree/index.html:224 +msgid "Build Orders In Progress" +msgstr "" + +#: templates/InvenTree/index.html:235 +msgid "Overdue Build Orders" +msgstr "" + +#: templates/InvenTree/index.html:255 +msgid "Outstanding Purchase Orders" +msgstr "" + +#: templates/InvenTree/index.html:266 +msgid "Overdue Purchase Orders" +msgstr "" + +#: templates/InvenTree/index.html:286 +msgid "Outstanding Sales Orders" +msgstr "" + +#: templates/InvenTree/index.html:297 +msgid "Overdue Sales Orders" +msgstr "" + +#: templates/InvenTree/notifications/history.html:9 +msgid "Notification History" +msgstr "" + +#: templates/InvenTree/notifications/history.html:13 +#: templates/InvenTree/notifications/history.html:14 +msgid "Refresh Notification History" +msgstr "" + +#: templates/InvenTree/notifications/inbox.html:9 +msgid "Pending Notifications" +msgstr "" + +#: templates/InvenTree/notifications/inbox.html:13 +#: templates/InvenTree/notifications/inbox.html:14 +msgid "Mark all as read" +msgstr "" + +#: templates/InvenTree/notifications/inbox.html:16 +#: templates/InvenTree/notifications/inbox.html:17 +msgid "Refresh Pending Notifications" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:10 +#: templates/InvenTree/notifications/sidebar.html:5 +#: templates/InvenTree/settings/sidebar.html:17 templates/notifications.html:5 +msgid "Notifications" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:51 +#: templates/InvenTree/settings/settings.html:314 +msgid "ID" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:57 +msgid "Age" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:88 +#: templates/InvenTree/settings/plugin.html:133 +msgid "Message" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:94 +#: templates/InvenTree/notifications/notifications.html:150 +msgid "Delete Notification" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:116 +msgid "No unread notifications found" +msgstr "" + +#: templates/InvenTree/notifications/notifications.html:140 +msgid "No notification history found" +msgstr "" + +#: templates/InvenTree/notifications/sidebar.html:8 +msgid "Inbox" +msgstr "" + +#: templates/InvenTree/notifications/sidebar.html:10 +msgid "History" +msgstr "" + +#: templates/InvenTree/search.html:8 +msgid "Search Results" +msgstr "" + +#: templates/InvenTree/settings/barcode.html:8 +msgid "Barcode Settings" +msgstr "" + +#: templates/InvenTree/settings/build.html:8 +msgid "Build Order Settings" +msgstr "" + +#: templates/InvenTree/settings/category.html:7 +msgid "Category Settings" +msgstr "" + +#: templates/InvenTree/settings/currencies.html:8 +msgid "Currency Settings" +msgstr "" + +#: templates/InvenTree/settings/currencies.html:19 +msgid "Base Currency" +msgstr "" + +#: templates/InvenTree/settings/currencies.html:24 +msgid "Exchange Rates" +msgstr "" + +#: templates/InvenTree/settings/currencies.html:38 +msgid "Last Update" +msgstr "" + +#: templates/InvenTree/settings/currencies.html:44 +msgid "Never" +msgstr "" + +#: templates/InvenTree/settings/currencies.html:49 +msgid "Update Now" +msgstr "" + +#: templates/InvenTree/settings/global.html:9 +msgid "Server Settings" +msgstr "" + +#: templates/InvenTree/settings/login.html:9 +#: templates/InvenTree/settings/sidebar.html:31 +msgid "Login Settings" +msgstr "" + +#: templates/InvenTree/settings/login.html:21 templates/account/signup.html:5 +msgid "Signup" +msgstr "" + +#: templates/InvenTree/settings/mixins/settings.html:5 +#: templates/InvenTree/settings/settings.html:12 templates/navbar.html:131 +msgid "Settings" +msgstr "" + +#: templates/InvenTree/settings/mixins/urls.html:5 +msgid "URLs" +msgstr "" + +#: templates/InvenTree/settings/mixins/urls.html:8 +#, python-format +msgid "" +"The Base-URL for this plugin is %(base)s." +msgstr "" + +#: templates/InvenTree/settings/mixins/urls.html:23 +msgid "Open in new tab" +msgstr "" + +#: templates/InvenTree/settings/part.html:7 +msgid "Part Settings" +msgstr "" + +#: templates/InvenTree/settings/part.html:44 +msgid "Part Import" +msgstr "" + +#: templates/InvenTree/settings/part.html:48 +msgid "Import Part" +msgstr "" + +#: templates/InvenTree/settings/part.html:62 +msgid "Part Parameter Templates" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:10 +msgid "Plugin Settings" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:16 +msgid "" +"Changing the settings below require you to immediatly restart InvenTree. Do " +"not change this while under active usage." +msgstr "" + +#: templates/InvenTree/settings/plugin.html:34 +msgid "Plugins" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:39 +msgid "Install Plugin" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:48 templates/navbar.html:129 +#: users/models.py:39 +msgid "Admin" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:50 +#: templates/InvenTree/settings/plugin_settings.html:28 +msgid "Author" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:52 +#: templates/InvenTree/settings/plugin_settings.html:43 +msgid "Version" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:82 +msgid "code sample" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:99 +msgid "Inactive plugins" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:122 +msgid "Plugin Error Stack" +msgstr "" + +#: templates/InvenTree/settings/plugin.html:131 +msgid "Stage" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:10 +#, python-format +msgid "Plugin details for %(name)s" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:17 +msgid "Plugin information" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:48 +msgid "no version information supplied" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:62 +msgid "License" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:71 +msgid "" +"The code information is pulled from the latest git commit for this plugin. " +"It might not reflect official version numbers or information but the actual " +"code running." +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:77 +msgid "Package information" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:83 +msgid "Installation method" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:86 +msgid "This plugin was installed as a package" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:88 +msgid "This plugin was found in a local InvenTree path" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:94 +msgid "Installation path" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:100 +msgid "Commit Author" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:104 +#: templates/about.html:47 +msgid "Commit Date" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:108 +#: templates/about.html:40 +msgid "Commit Hash" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:112 +msgid "Commit Message" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:117 +msgid "Sign Status" +msgstr "" + +#: templates/InvenTree/settings/plugin_settings.html:122 +msgid "Sign Key" +msgstr "" + +#: templates/InvenTree/settings/po.html:7 +msgid "Purchase Order Settings" +msgstr "" + +#: templates/InvenTree/settings/report.html:8 +#: templates/InvenTree/settings/user_reports.html:9 +msgid "Report Settings" +msgstr "" + +#: templates/InvenTree/settings/setting.html:37 +msgid "No value set" +msgstr "" + +#: templates/InvenTree/settings/setting.html:42 +msgid "Edit setting" +msgstr "" + +#: templates/InvenTree/settings/settings.html:116 +msgid "Edit Plugin Setting" +msgstr "" + +#: templates/InvenTree/settings/settings.html:118 +msgid "Edit Global Setting" +msgstr "" + +#: templates/InvenTree/settings/settings.html:120 +msgid "Edit User Setting" +msgstr "" + +#: templates/InvenTree/settings/settings.html:209 +msgid "No category parameter templates found" +msgstr "" + +#: templates/InvenTree/settings/settings.html:231 +#: templates/InvenTree/settings/settings.html:330 +msgid "Edit Template" +msgstr "" + +#: templates/InvenTree/settings/settings.html:232 +#: templates/InvenTree/settings/settings.html:331 +msgid "Delete Template" +msgstr "" + +#: templates/InvenTree/settings/settings.html:310 +msgid "No part parameter templates found" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:6 +#: templates/InvenTree/settings/user_settings.html:9 +msgid "User Settings" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:9 +#: templates/InvenTree/settings/user.html:12 +msgid "Account Settings" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:11 +#: templates/InvenTree/settings/user_display.html:9 +msgid "Display Settings" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:13 +msgid "Home Page" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:15 +#: templates/InvenTree/settings/user_search.html:9 +msgid "Search Settings" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:19 +msgid "Label Printing" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:21 +#: templates/InvenTree/settings/sidebar.html:37 +msgid "Reporting" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:26 +msgid "Global Settings" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:29 +msgid "Server Configuration" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:35 +msgid "Currencies" +msgstr "" + +#: templates/InvenTree/settings/sidebar.html:41 +msgid "Categories" +msgstr "" + +#: templates/InvenTree/settings/so.html:7 +msgid "Sales Order Settings" +msgstr "" + +#: templates/InvenTree/settings/stock.html:7 +msgid "Stock Settings" +msgstr "" + +#: templates/InvenTree/settings/user.html:18 +#: templates/account/password_reset_from_key.html:4 +#: templates/account/password_reset_from_key.html:7 +msgid "Change Password" +msgstr "" + +#: templates/InvenTree/settings/user.html:22 templates/notes_buttons.html:3 +#: templates/notes_buttons.html:4 +msgid "Edit" +msgstr "" + +#: templates/InvenTree/settings/user.html:32 +msgid "Username" +msgstr "" + +#: templates/InvenTree/settings/user.html:36 +msgid "First Name" +msgstr "" + +#: templates/InvenTree/settings/user.html:40 +msgid "Last Name" +msgstr "" + +#: templates/InvenTree/settings/user.html:54 +msgid "The following email addresses are associated with your account:" +msgstr "" + +#: templates/InvenTree/settings/user.html:75 +msgid "Verified" +msgstr "" + +#: templates/InvenTree/settings/user.html:77 +msgid "Unverified" +msgstr "" + +#: templates/InvenTree/settings/user.html:79 +msgid "Primary" +msgstr "" + +#: templates/InvenTree/settings/user.html:85 +msgid "Make Primary" +msgstr "" + +#: templates/InvenTree/settings/user.html:86 +msgid "Re-send Verification" +msgstr "" + +#: templates/InvenTree/settings/user.html:87 +#: templates/InvenTree/settings/user.html:149 +msgid "Remove" +msgstr "" + +#: templates/InvenTree/settings/user.html:95 +#: templates/InvenTree/settings/user.html:201 +msgid "Warning:" +msgstr "" + +#: templates/InvenTree/settings/user.html:96 +msgid "" +"You currently do not have any email address set up. You should really add an " +"email address so you can receive notifications, reset your password, etc." +msgstr "" + +#: templates/InvenTree/settings/user.html:104 +msgid "Add Email Address" +msgstr "" + +#: templates/InvenTree/settings/user.html:109 +msgid "Add Email" +msgstr "" + +#: templates/InvenTree/settings/user.html:117 +msgid "Social Accounts" +msgstr "" + +#: templates/InvenTree/settings/user.html:122 +msgid "" +"You can sign in to your account using any of the following third party " +"accounts:" +msgstr "" + +#: templates/InvenTree/settings/user.html:157 +msgid "" +"You currently have no social network accounts connected to this account." +msgstr "" + +#: templates/InvenTree/settings/user.html:162 +msgid "Add a 3rd Party Account" +msgstr "" + +#: templates/InvenTree/settings/user.html:172 +msgid "Multifactor" +msgstr "" + +#: templates/InvenTree/settings/user.html:177 +msgid "You have these factors available:" +msgstr "" + +#: templates/InvenTree/settings/user.html:187 +msgid "TOTP" +msgstr "" + +#: templates/InvenTree/settings/user.html:193 +msgid "Static" +msgstr "" + +#: templates/InvenTree/settings/user.html:202 +msgid "You currently do not have any factors set up." +msgstr "" + +#: templates/InvenTree/settings/user.html:209 +msgid "Change factors" +msgstr "" + +#: templates/InvenTree/settings/user.html:210 +msgid "Setup multifactor" +msgstr "" + +#: templates/InvenTree/settings/user.html:212 +msgid "Remove multifactor" +msgstr "" + +#: templates/InvenTree/settings/user.html:220 +msgid "Active Sessions" +msgstr "" + +#: templates/InvenTree/settings/user.html:226 +msgid "Log out active sessions (except this one)" +msgstr "" + +#: templates/InvenTree/settings/user.html:227 +msgid "Log Out Active Sessions" +msgstr "" + +#: templates/InvenTree/settings/user.html:236 +msgid "unknown on unknown" +msgstr "" + +#: templates/InvenTree/settings/user.html:237 +msgid "unknown" +msgstr "" + +#: templates/InvenTree/settings/user.html:241 +msgid "IP Address" +msgstr "" + +#: templates/InvenTree/settings/user.html:242 +msgid "Device" +msgstr "" + +#: templates/InvenTree/settings/user.html:243 +msgid "Last Activity" +msgstr "" + +#: templates/InvenTree/settings/user.html:252 +#, python-format +msgid "%(time)s ago (this session)" +msgstr "" + +#: templates/InvenTree/settings/user.html:254 +#, python-format +msgid "%(time)s ago" +msgstr "" + +#: templates/InvenTree/settings/user.html:266 +msgid "Do you really want to remove the selected email address?" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:27 +msgid "Theme Settings" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:37 +msgid "Select theme" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:48 +msgid "Set Theme" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:56 +msgid "Language Settings" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:65 +msgid "Select language" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:81 +#, python-format +msgid "%(lang_translated)s%% translated" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:83 +msgid "No translations available" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:90 +msgid "Set Language" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:93 +msgid "Some languages are not complete" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:95 +msgid "Show only sufficent" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:97 +msgid "and hidden." +msgstr "" + +#: templates/InvenTree/settings/user_display.html:97 +msgid "Show them too" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:103 +msgid "Help the translation efforts!" +msgstr "" + +#: templates/InvenTree/settings/user_display.html:104 +#, python-format +msgid "" +"Native language translation of the InvenTree web application is community contributed via crowdin. Contributions are " +"welcomed and encouraged." +msgstr "" + +#: templates/InvenTree/settings/user_homepage.html:9 +msgid "Home Page Settings" +msgstr "" + +#: templates/InvenTree/settings/user_labels.html:9 +msgid "Label Settings" +msgstr "" + +#: templates/InvenTree/settings/user_notifications.html:8 +msgid "Notification Settings" +msgstr "" + +#: templates/about.html:10 +msgid "InvenTree Version Information" +msgstr "" + +#: templates/about.html:11 templates/about.html:105 templates/modals.html:15 +#: templates/modals.html:27 templates/modals.html:39 templates/modals.html:50 +msgid "Close" +msgstr "" + +#: templates/about.html:20 +msgid "InvenTree Version" +msgstr "" + +#: templates/about.html:25 +msgid "Development Version" +msgstr "" + +#: templates/about.html:28 +msgid "Up to Date" +msgstr "" + +#: templates/about.html:30 +msgid "Update Available" +msgstr "" + +#: templates/about.html:53 +msgid "InvenTree Documentation" +msgstr "" + +#: templates/about.html:58 +msgid "API Version" +msgstr "" + +#: templates/about.html:63 +msgid "Python Version" +msgstr "" + +#: templates/about.html:68 +msgid "Django Version" +msgstr "" + +#: templates/about.html:73 +msgid "View Code on GitHub" +msgstr "" + +#: templates/about.html:78 +msgid "Credits" +msgstr "" + +#: templates/about.html:83 +msgid "Mobile App" +msgstr "" + +#: templates/about.html:88 +msgid "Submit Bug Report" +msgstr "" + +#: templates/about.html:95 templates/clip.html:4 +msgid "copy to clipboard" +msgstr "" + +#: templates/about.html:95 +msgid "copy version information" +msgstr "" + +#: templates/account/email_confirm.html:6 +#: templates/account/email_confirm.html:10 +msgid "Confirm Email Address" +msgstr "" + +#: templates/account/email_confirm.html:16 +#, python-format +msgid "" +"Please confirm that %(email)s is an email " +"address for user %(user_display)s." +msgstr "" + +#: templates/account/email_confirm.html:27 +#, python-format +msgid "" +"This email confirmation link expired or is invalid. Please issue a new email confirmation request." +msgstr "" + +#: templates/account/login.html:6 templates/account/login.html:16 +#: templates/account/login.html:39 +msgid "Sign In" +msgstr "" + +#: templates/account/login.html:21 +#, python-format +msgid "" +"Please sign in with one\n" +"of your existing third party accounts or sign up\n" +"for a account and sign in below:" +msgstr "" + +#: templates/account/login.html:25 +#, python-format +msgid "" +"If you have not created an account yet, then please\n" +"sign up first." +msgstr "" + +#: templates/account/login.html:42 +msgid "Forgot Password?" +msgstr "" + +#: templates/account/login.html:47 +msgid "InvenTree demo instance" +msgstr "" + +#: templates/account/login.html:47 +msgid "Click here for login details" +msgstr "" + +#: templates/account/login.html:55 +msgid "or use SSO" +msgstr "" + +#: templates/account/logout.html:5 templates/account/logout.html:8 +#: templates/account/logout.html:20 +msgid "Sign Out" +msgstr "" + +#: templates/account/logout.html:10 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: templates/account/logout.html:19 +msgid "Back to Site" +msgstr "" + +#: templates/account/password_reset.html:5 +#: templates/account/password_reset.html:12 +msgid "Password Reset" +msgstr "" + +#: templates/account/password_reset.html:18 +msgid "" +"Forgotten your password? Enter your email address below, and we'll send you " +"an email allowing you to reset it." +msgstr "" + +#: templates/account/password_reset.html:23 +msgid "Reset My Password" +msgstr "" + +#: templates/account/password_reset.html:27 templates/account/signup.html:36 +msgid "This function is currently disabled. Please contact an administrator." +msgstr "" + +#: templates/account/password_reset_from_key.html:7 +msgid "Bad Token" +msgstr "" + +#: templates/account/password_reset_from_key.html:11 +#, python-format +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "" + +#: templates/account/password_reset_from_key.html:18 +msgid "Change password" +msgstr "" + +#: templates/account/password_reset_from_key.html:22 +msgid "Your password is now changed." +msgstr "" + +#: templates/account/signup.html:11 templates/account/signup.html:22 +msgid "Sign Up" +msgstr "" + +#: templates/account/signup.html:13 +#, python-format +msgid "" +"Already have an account? Then please sign in." +msgstr "" + +#: templates/account/signup.html:27 +msgid "Or use a SSO-provider for signup" +msgstr "" + +#: templates/admin_button.html:2 +msgid "View in administration panel" +msgstr "" + +#: templates/allauth_2fa/authenticate.html:5 +msgid "Two-Factor Authentication" +msgstr "" + +#: templates/allauth_2fa/authenticate.html:12 +msgid "Authenticate" +msgstr "" + +#: templates/allauth_2fa/backup_tokens.html:6 +msgid "Two-Factor Authentication Backup Tokens" +msgstr "" + +#: templates/allauth_2fa/backup_tokens.html:17 +msgid "" +"Backup tokens have been generated, but are not revealed here for security " +"reasons. Press the button below to generate new ones." +msgstr "" + +#: templates/allauth_2fa/backup_tokens.html:20 +msgid "No tokens. Press the button below to generate some." +msgstr "" + +#: templates/allauth_2fa/backup_tokens.html:27 +msgid "Generate backup tokens" +msgstr "" + +#: templates/allauth_2fa/backup_tokens.html:31 +#: templates/allauth_2fa/setup.html:40 +msgid "Back to settings" +msgstr "" + +#: templates/allauth_2fa/remove.html:6 +msgid "Disable Two-Factor Authentication" +msgstr "" + +#: templates/allauth_2fa/remove.html:9 +msgid "Are you sure?" +msgstr "" + +#: templates/allauth_2fa/remove.html:14 +msgid "Disable Two-Factor" +msgstr "" + +#: templates/allauth_2fa/setup.html:6 +msgid "Setup Two-Factor Authentication" +msgstr "" + +#: templates/allauth_2fa/setup.html:10 +msgid "Step 1" +msgstr "" + +#: templates/allauth_2fa/setup.html:14 +msgid "" +"Scan the QR code below with a token generator of your choice (for instance " +"Google Authenticator)." +msgstr "" + +#: templates/allauth_2fa/setup.html:23 +msgid "Step 2" +msgstr "" + +#: templates/allauth_2fa/setup.html:27 +msgid "Input a token generated by the app:" +msgstr "" + +#: templates/allauth_2fa/setup.html:35 +msgid "Verify" +msgstr "" + +#: templates/attachment_button.html:4 +msgid "Add Link" +msgstr "" + +#: templates/attachment_button.html:7 +msgid "Add Attachment" +msgstr "" + +#: templates/base.html:99 +msgid "Server Restart Required" +msgstr "" + +#: templates/base.html:102 +msgid "A configuration option has been changed which requires a server restart" +msgstr "" + +#: templates/base.html:102 +msgid "Contact your system administrator for further information" +msgstr "" + +#: templates/email/build_order_required_stock.html:7 +msgid "Stock is required for the following build order" +msgstr "" + +#: templates/email/build_order_required_stock.html:8 +#, python-format +msgid "Build order %(build)s - building %(quantity)s x %(part)s" +msgstr "" + +#: templates/email/build_order_required_stock.html:10 +msgid "Click on the following link to view this build order" +msgstr "" + +#: templates/email/build_order_required_stock.html:14 +msgid "The following parts are low on required stock" +msgstr "" + +#: templates/email/build_order_required_stock.html:18 +msgid "Required Quantity" +msgstr "" + +#: templates/email/build_order_required_stock.html:19 +#: templates/email/low_stock_notification.html:18 +msgid "Available" +msgstr "" + +#: templates/email/build_order_required_stock.html:38 +#: templates/email/low_stock_notification.html:31 +msgid "" +"You are receiving this email because you are subscribed to notifications for " +"this part " +msgstr "" + +#: templates/email/email.html:35 +msgid "InvenTree version" +msgstr "" + +#: templates/email/low_stock_notification.html:9 +msgid "Click on the following link to view this part" +msgstr "" + +#: templates/email/low_stock_notification.html:19 +msgid "Minimum Quantity" +msgstr "" + +#: templates/image_download.html:8 +msgid "Specify URL for downloading image" +msgstr "" + +#: templates/image_download.html:11 +msgid "Must be a valid image URL" +msgstr "" + +#: templates/image_download.html:12 +msgid "Remote server must be accessible" +msgstr "" + +#: templates/image_download.html:13 +msgid "Remote image must not exceed maximum allowable file size" +msgstr "" + +#: templates/modals.html:19 templates/modals.html:43 +msgid "Form errors exist" +msgstr "" + +#: templates/modals.html:28 templates/modals.html:51 +msgid "Submit" +msgstr "" + +#: templates/navbar.html:42 +msgid "Buy" +msgstr "" + +#: templates/navbar.html:54 +msgid "Sell" +msgstr "" + +#: templates/navbar.html:94 templates/search.html:8 +#: templates/search_form.html:6 templates/search_form.html:7 +msgid "Search" +msgstr "" + +#: templates/navbar.html:101 +msgid "Scan Barcode" +msgstr "" + +#: templates/navbar.html:108 +msgid "Show Notifications" +msgstr "" + +#: templates/navbar.html:111 +msgid "New Notifications" +msgstr "" + +#: templates/navbar.html:132 +msgid "Logout" +msgstr "" + +#: templates/navbar.html:134 +msgid "Login" +msgstr "" + +#: templates/navbar.html:154 +msgid "About InvenTree" +msgstr "" + +#: templates/navbar_demo.html:5 +msgid "InvenTree demo mode" +msgstr "" + +#: templates/notes_buttons.html:6 templates/notes_buttons.html:7 +msgid "Save" +msgstr "" + +#: templates/notifications.html:10 +msgid "Notifications will load here" +msgstr "" + +#: templates/notifications.html:13 +msgid "Show all notifications and history" +msgstr "" + +#: templates/qr_code.html:11 +msgid "QR data not provided" +msgstr "" + +#: templates/registration/logged_out.html:6 +msgid "You were logged out successfully." +msgstr "" + +#: templates/registration/logged_out.html:8 +msgid "Log in again" +msgstr "" + +#: templates/search.html:9 +msgid "Show full search results" +msgstr "" + +#: templates/search.html:12 +msgid "Clear search" +msgstr "" + +#: templates/search.html:16 +msgid "Filter results" +msgstr "" + +#: templates/search.html:20 +msgid "Close search menu" +msgstr "" + +#: templates/search.html:29 +msgid "Searching" +msgstr "" + +#: templates/search.html:35 +msgid "No search results" +msgstr "" + +#: templates/stats.html:9 +msgid "Server" +msgstr "" + +#: templates/stats.html:13 +msgid "Instance Name" +msgstr "" + +#: templates/stats.html:18 +msgid "Database" +msgstr "" + +#: templates/stats.html:26 +msgid "Server is running in debug mode" +msgstr "" + +#: templates/stats.html:33 +msgid "Docker Mode" +msgstr "" + +#: templates/stats.html:34 +msgid "Server is deployed using docker" +msgstr "" + +#: templates/stats.html:39 +msgid "Plugin Support" +msgstr "" + +#: templates/stats.html:43 +msgid "Plugin support enabled" +msgstr "" + +#: templates/stats.html:45 +msgid "Plugin support disabled" +msgstr "" + +#: templates/stats.html:52 +msgid "Server status" +msgstr "" + +#: templates/stats.html:55 +msgid "Healthy" +msgstr "" + +#: templates/stats.html:57 +msgid "Issues detected" +msgstr "" + +#: templates/stats.html:64 +msgid "Background Worker" +msgstr "" + +#: templates/stats.html:67 +msgid "Background worker not running" +msgstr "" + +#: templates/stats.html:75 +msgid "Email Settings" +msgstr "" + +#: templates/stats.html:78 +msgid "Email settings not configured" +msgstr "" + +#: templates/stock_table.html:17 +msgid "Barcode Actions" +msgstr "" + +#: templates/stock_table.html:33 +msgid "Print test reports" +msgstr "" + +#: templates/stock_table.html:40 +msgid "Stock Options" +msgstr "" + +#: templates/stock_table.html:45 +msgid "Add to selected stock items" +msgstr "" + +#: templates/stock_table.html:46 +msgid "Remove from selected stock items" +msgstr "" + +#: templates/stock_table.html:47 +msgid "Stocktake selected stock items" +msgstr "" + +#: templates/stock_table.html:48 +msgid "Move selected stock items" +msgstr "" + +#: templates/stock_table.html:49 +msgid "Merge selected stock items" +msgstr "" + +#: templates/stock_table.html:49 +msgid "Merge stock" +msgstr "" + +#: templates/stock_table.html:50 +msgid "Order selected items" +msgstr "" + +#: templates/stock_table.html:50 +msgid "Order stock" +msgstr "" + +#: templates/stock_table.html:52 +msgid "Change status" +msgstr "" + +#: templates/stock_table.html:52 +msgid "Change stock status" +msgstr "" + +#: templates/stock_table.html:55 +msgid "Delete selected items" +msgstr "" + +#: templates/stock_table.html:55 +msgid "Delete stock" +msgstr "" + +#: templates/yesnolabel.html:4 +msgid "Yes" +msgstr "" + +#: templates/yesnolabel.html:6 +msgid "No" +msgstr "" + +#: users/admin.py:64 +msgid "Users" +msgstr "" + +#: users/admin.py:65 +msgid "Select which users are assigned to this group" +msgstr "" + +#: users/admin.py:187 +msgid "The following users are members of multiple groups:" +msgstr "" + +#: users/admin.py:210 +msgid "Personal info" +msgstr "" + +#: users/admin.py:211 +msgid "Permissions" +msgstr "" + +#: users/admin.py:214 +msgid "Important dates" +msgstr "" + +#: users/models.py:201 +msgid "Permission set" +msgstr "" + +#: users/models.py:209 +msgid "Group" +msgstr "" + +#: users/models.py:212 +msgid "View" +msgstr "" + +#: users/models.py:212 +msgid "Permission to view items" +msgstr "" + +#: users/models.py:214 +msgid "Add" +msgstr "" + +#: users/models.py:214 +msgid "Permission to add items" +msgstr "" + +#: users/models.py:216 +msgid "Change" +msgstr "" + +#: users/models.py:216 +msgid "Permissions to edit items" +msgstr "" + +#: users/models.py:218 +msgid "Permission to delete items" +msgstr "" From 64b1f56b98f4d198de034479f13fcaaa1fb1c235 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 11:09:21 +1000 Subject: [PATCH 20/38] Generate pt_br --- InvenTree/locale/pt_br/LC_MESSAGES/django.mo | Bin 0 -> 379 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 InvenTree/locale/pt_br/LC_MESSAGES/django.mo diff --git a/InvenTree/locale/pt_br/LC_MESSAGES/django.mo b/InvenTree/locale/pt_br/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..2c90dd0c81aca562856271a6885816b565885734 GIT binary patch literal 379 zcmYL@y-ve06h@0+%E-)c2L=$hP^RMksHSmCgq=ibS_wAOnqZXLk?kUQ5MGaG!2yYW z(vk0#Kg;L)_~@$>au7HPoCXd9mw`6@01213;cPqq$*p;lYmbr*T1o4a(HL?veIRoR zD_Sg)ER71;80!&tmD-@YUFA?|FhqHV3i+e|LVdy_A hALaGViW<#~-8u}q`CZ-UW&nTV=uE>Hdgp_v^8-$QX_^24 literal 0 HcmV?d00001 From c705bb57c63e9a5b307024035139b5c4065b2d5c Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 11:16:55 +1000 Subject: [PATCH 21/38] Fix language code --- InvenTree/InvenTree/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 2514d2a07f..04c9a9bc19 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -687,8 +687,8 @@ LANGUAGES = [ ('nl', _('Dutch')), ('no', _('Norwegian')), ('pl', _('Polish')), - ('pt', _('Portugese')), - ('pt-br', _('Portugese (Brazilian)')), + ('pt', _('Portuguese')), + ('pt-BR', _('Portuguese (Brazilian)')), ('ru', _('Russian')), ('sv', _('Swedish')), ('th', _('Thai')), From 70d4960fa30ae26afdb342931532f2a07bc70828 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 11:43:34 +1000 Subject: [PATCH 22/38] Add new unit tests for part API filters --- InvenTree/part/fixtures/part.yaml | 1 + InvenTree/part/test_api.py | 91 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/InvenTree/part/fixtures/part.yaml b/InvenTree/part/fixtures/part.yaml index d0a2d949b1..fd38036fa9 100644 --- a/InvenTree/part/fixtures/part.yaml +++ b/InvenTree/part/fixtures/part.yaml @@ -177,6 +177,7 @@ fields: name: 'Green chair variant' variant_of: 10003 + is_template: true category: 7 trackable: true tree_id: 1 diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 5a8acbecd9..ddfaa44e94 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -567,6 +567,97 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(response.data['name'], name) self.assertEqual(response.data['description'], description) + def test_template_filters(self): + """ + Unit tests for API filters related to template parts: + + - variant_of : Return children of specified part + - ancestor : Return descendants of specified part + + Uses the 'chair template' part (pk=10000) + """ + + # Rebuild the MPTT structure before running these tests + Part.objects.rebuild() + + url = reverse('api-part-list') + + response = self.get( + url, + { + 'variant_of': 10000, + }, + expected_code=200 + ) + + # 3 direct children of template part + self.assertEqual(len(response.data), 3) + + response = self.get( + url, + { + 'ancestor': 10000, + }, + expected_code=200, + ) + + # 4 total descendants + self.assertEqual(len(response.data), 4) + + # Use the 'green chair' as our reference + response = self.get( + url, + { + 'variant_of': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 1) + + response = self.get( + url, + { + 'ancestor': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 1) + + # Add some more variants + + p = Part.objects.get(pk=10004) + + for i in range(100): + Part.objects.create( + name=f'Chair variant {i}', + description='A new chair variant', + variant_of=p, + ) + + # There should still be only one direct variant + response = self.get( + url, + { + 'variant_of': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 1) + + # However, now should be 101 descendants + response = self.get( + url, + { + 'ancestor': 10003, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), 101) + class PartDetailTests(InvenTreeAPITestCase): """ From 85fd2478788e85256447806097510e61dba26250 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 15:54:03 +1000 Subject: [PATCH 23/38] Fix for 'auto-allocate' stock to build orders - Allocation of serialized stock items would cause issue - Exclude serialized stock from auto allocation process --- InvenTree/build/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 9ba348b0b4..e5189e6073 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -871,6 +871,9 @@ class Build(MPTTModel, ReferenceIndexingMixin): part__in=[p for p in available_parts], ) + # Filter out "serialized" stock items, these cannot be auto-allocated + available_stock = available_stock.filter(Q(serial=None) | Q(serial='')) + if location: # Filter only stock items located "below" the specified location sublocations = location.get_descendants(include_self=True) From 7aaa4a581231e6ff143c22802c76baf3c5f76ffd Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 16:39:03 +1000 Subject: [PATCH 24/38] add unit tests for new variant_stock annotations --- InvenTree/part/test_api.py | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index ddfaa44e94..08b06a0f36 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -658,6 +658,94 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(len(response.data), 101) + def test_variant_stock(self): + """ + Unit tests for the 'variant_stock' annotation, + which provides a stock count for *variant* parts + """ + + # Ensure the MPTT structure is in a known state before running tests + Part.objects.rebuild() + + # Initially, there are no "chairs" in stock, + # so each 'chair' template should report variant_stock=0 + url = reverse('api-part-list') + + # Look at the "detail" URL for the master chair template + response = self.get('/api/part/10000/', {}, expected_code=200) + + # This part should report 'zero' as variant stock + self.assertEqual(response.data['variant_stock'], 0) + + # Grab a list of all variant chairs *under* the master template + response = self.get( + url, + { + 'ancestor': 10000, + }, + expected_code=200, + ) + + # 4 total descendants + self.assertEqual(len(response.data), 4) + + for variant in response.data: + self.assertEqual(variant['variant_stock'], 0) + + # Now, let's make some variant stock + for variant in Part.objects.get(pk=10000).get_descendants(include_self=False): + StockItem.objects.create( + part=variant, + quantity=100, + ) + + response = self.get('/api/part/10000/', {}, expected_code=200) + + self.assertEqual(response.data['in_stock'], 0) + self.assertEqual(response.data['variant_stock'], 400) + + # Check that each variant reports the correct stock quantities + response = self.get( + url, + { + 'ancestor': 10000, + }, + expected_code=200, + ) + + expected_variant_stock = { + 10001: 0, + 10002: 0, + 10003: 100, + 10004: 0, + } + + for variant in response.data: + self.assertEqual(variant['in_stock'], 100) + self.assertEqual(variant['variant_stock'], expected_variant_stock[variant['pk']]) + + # Add some 'sub variants' for the green chair variant + green_chair = Part.objects.get(pk=10004) + + for i in range(10): + gcv = Part.objects.create( + name=f"GC Var {i}", + description="Green chair variant", + variant_of=green_chair, + ) + + StockItem.objects.create( + part=gcv, + quantity=50, + ) + + # Spot check of some values + response = self.get('/api/part/10000/', {}) + self.assertEqual(response.data['variant_stock'], 900) + + response = self.get('/api/part/10004/', {}) + self.assertEqual(response.data['variant_stock'], 500) + class PartDetailTests(InvenTreeAPITestCase): """ From ee47be4c9ed5999a115d808b6a945a5c7cbb83f4 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 17:05:21 +1000 Subject: [PATCH 25/38] Add unit test for 'available_variant_stock' in BomItem API serializer --- InvenTree/part/fixtures/bom.yaml | 1 + InvenTree/part/test_api.py | 40 +++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/fixtures/bom.yaml b/InvenTree/part/fixtures/bom.yaml index facb7e76ae..ac52452d75 100644 --- a/InvenTree/part/fixtures/bom.yaml +++ b/InvenTree/part/fixtures/bom.yaml @@ -7,6 +7,7 @@ part: 100 sub_part: 1 quantity: 10 + allow_variants: True # 40 x R_2K2_0805 - model: part.bomitem diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 08b06a0f36..f0770eb1f5 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -698,7 +698,7 @@ class PartAPITest(InvenTreeAPITestCase): part=variant, quantity=100, ) - + response = self.get('/api/part/10000/', {}, expected_code=200) self.assertEqual(response.data['in_stock'], 0) @@ -1629,6 +1629,44 @@ class BomItemTest(InvenTreeAPITestCase): self.assertEqual(len(response.data), i) + def test_bom_variant_stock(self): + """ + Test for 'available_variant_stock' annotation + """ + + Part.objects.rebuild() + + # BOM item we are interested in + bom_item = BomItem.objects.get(pk=1) + + response = self.get('/api/bom/1/', {}, expected_code=200) + + # Initially, no variant stock available + self.assertEqual(response.data['available_variant_stock'], 0) + + # Create some 'variants' of the referenced sub_part + bom_item.sub_part.is_template = True + bom_item.sub_part.save() + + for i in range(10): + # Create a variant part + vp = Part.objects.create( + name=f"Var {i}", + description="Variant part", + variant_of=bom_item.sub_part, + ) + + # Create a stock item + StockItem.objects.create( + part=vp, + quantity=100, + ) + + # There should now be variant stock available + response = self.get('/api/bom/1/', {}, expected_code=200) + + self.assertEqual(response.data['available_variant_stock'], 1000) + class PartParameterTest(InvenTreeAPITestCase): """ From 3ef0386593e49250f99cc3f0b4fe0ceff5a9b1a5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Apr 2022 17:14:47 +1000 Subject: [PATCH 26/38] Prevent inactive or trackable parts from being 'auto allocated' --- InvenTree/part/models.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index d6ca9f650c..1edae69351 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2732,7 +2732,21 @@ class BomItem(models.Model, DataImportMixin): for sub in self.substitutes.all(): parts.add(sub.part) - return parts + valid_parts = [] + + for p in parts: + + # Inactive parts cannot be 'auto allocated' + if not p.active: + continue + + # Trackable parts cannot be 'auto allocated' + if p.trackable: + continue + + valid_parts.append(p) + + return valid_parts def is_stock_item_valid(self, stock_item): """ From cb12222e17a11524583e4d74f09032bcfea24876 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 19:23:41 +1000 Subject: [PATCH 27/38] Re-enable the "pricing" tab - Previously was gated entirely on the "PART_SHOW_PRICE_HISTORY" setting --- InvenTree/part/templates/part/detail.html | 3 +-- InvenTree/part/templates/part/part_sidebar.html | 3 +-- InvenTree/part/templates/part/prices.html | 11 ++++++++--- InvenTree/part/views.py | 6 +++++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 2f13faab8a..a996151879 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -124,8 +124,7 @@ -{% settings_value "PART_SHOW_PRICE_HISTORY" as show_price_history %} -{% if show_price_history %} +{% if part.purchaseable or part.salable %}
{% include "part/prices.html" %}
diff --git a/InvenTree/part/templates/part/part_sidebar.html b/InvenTree/part/templates/part/part_sidebar.html index 3c1c647f86..e8763fb973 100644 --- a/InvenTree/part/templates/part/part_sidebar.html +++ b/InvenTree/part/templates/part/part_sidebar.html @@ -4,7 +4,6 @@ {% settings_value "PART_INTERNAL_PRICE" as show_internal_price %} {% settings_value 'PART_SHOW_RELATED' as show_related %} -{% settings_value "PART_SHOW_PRICE_HISTORY" as show_price_history %} {% trans "Parameters" as text %} {% include "sidebar_item.html" with label="part-parameters" text=text icon="fa-th-list" %} @@ -28,7 +27,7 @@ {% trans "Used In" as text %} {% include "sidebar_item.html" with label="used-in" text=text icon="fa-layer-group" %} {% endif %} -{% if show_price_history %} +{% if part.purchaseable or part.salable %} {% trans "Pricing" as text %} {% include "sidebar_item.html" with label="pricing" text=text icon="fa-dollar-sign" %} {% endif %} diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html index 9b829c179b..53723b729b 100644 --- a/InvenTree/part/templates/part/prices.html +++ b/InvenTree/part/templates/part/prices.html @@ -3,6 +3,9 @@ {% load crispy_forms_tags %} {% load inventree_extras %} +{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %} + +{% if show_price_history %}

{% trans "Pricing Information" %}

@@ -43,7 +46,7 @@ {% endif %} {% endif %} - {% if part.bom_count > 0 %} + {% if part.assembly and part.bom_count > 0 %} {% if min_total_bom_price %} {% trans 'BOM Pricing' %} @@ -147,7 +150,7 @@ -{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %} +{% endif %} {% if part.purchaseable and roles.purchase_order.view %} @@ -170,7 +173,7 @@ -{% if price_history %} +{% if show_price_history %}

{% trans "Purchase Price" %} @@ -279,6 +282,7 @@

+{% if show_price_history %}

{% trans "Sale Price" %} @@ -298,3 +302,4 @@ {% endif %}

{% endif %} +{% endif %} diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 9f3cd07f7c..ab4b3fec72 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -389,8 +389,12 @@ class PartDetail(InvenTreeRoleMixin, DetailView): context.update(**ctx) + show_price_history = InvenTreeSetting.get_setting('PART_SHOW_PRICE_HISTORY', False) + + context['show_price_history'] = show_price_history + # Pricing information - if InvenTreeSetting.get_setting('PART_SHOW_PRICE_HISTORY', False): + if show_price_history: ctx = self.get_pricing(self.get_quantity()) ctx['form'] = self.form_class(initial=self.get_initials()) From 7db11b627b3c03d114cda773dd3cf9160c2271b9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 19:53:09 +1000 Subject: [PATCH 28/38] Refactor PartSalePriceBreak and PartInternalPriceBreak tables to use the API - Remove old server-side views - Simplify code --- InvenTree/InvenTree/api_version.py | 6 +- InvenTree/part/api.py | 20 +++++ InvenTree/part/serializers.py | 18 +++++ InvenTree/part/templates/part/detail.html | 4 +- InvenTree/part/templates/part/prices.html | 1 + InvenTree/part/urls.py | 18 ----- InvenTree/part/views.py | 99 ----------------------- InvenTree/templates/js/translated/part.js | 53 +++++++----- 8 files changed, 78 insertions(+), 141 deletions(-) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index a49209dfe7..1ef1df6a8f 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -4,11 +4,15 @@ InvenTree API version information # InvenTree API version -INVENTREE_API_VERSION = 42 +INVENTREE_API_VERSION = 43 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v43 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2875 + - Adds API detail endpoint for PartSalePrice model + - Adds API detail endpoint for PartInternalPrice model + v42 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2833 - Adds variant stock information to the Part and BomItem serializers diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index b025791a7f..1a80c87322 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -262,6 +262,15 @@ class CategoryTree(generics.ListAPIView): ordering = ['level', 'name'] +class PartSalePriceDetail(generics.RetrieveUpdateDestroyAPIView): + """ + Detail endpoint for PartSellPriceBreak model + """ + + queryset = PartSellPriceBreak.objects.all() + serializer_class = part_serializers.PartSalePriceSerializer + + class PartSalePriceList(generics.ListCreateAPIView): """ API endpoint for list view of PartSalePriceBreak model @@ -279,6 +288,15 @@ class PartSalePriceList(generics.ListCreateAPIView): ] +class PartInternalPriceDetail(generics.RetrieveUpdateDestroyAPIView): + """ + Detail endpoint for PartInternalPriceBreak model + """ + + queryset = PartInternalPriceBreak.objects.all() + serializer_class = part_serializers.PartInternalPriceSerializer + + class PartInternalPriceList(generics.ListCreateAPIView): """ API endpoint for list view of PartInternalPriceBreak model @@ -1920,11 +1938,13 @@ part_api_urls = [ # Base URL for part sale pricing url(r'^sale-price/', include([ + url(r'^(?P\d+)/', PartSalePriceDetail.as_view(), name='api-part-sale-price-detail'), url(r'^.*$', PartSalePriceList.as_view(), name='api-part-sale-price-list'), ])), # Base URL for part internal pricing url(r'^internal-price/', include([ + url(r'^(?P\d+)/', PartInternalPriceDetail.as_view(), name='api-part-internal-price-detail'), url(r'^.*$', PartInternalPriceList.as_view(), name='api-part-internal-price-list'), ])), diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 387d3df1d5..0e865ea74b 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -17,6 +17,8 @@ from rest_framework import serializers from sql_util.utils import SubqueryCount, SubquerySum from djmoney.contrib.django_rest_framework import MoneyField +from common.settings import currency_code_default, currency_code_mappings + from InvenTree.serializers import (DataFileUploadSerializer, DataFileExtractSerializer, InvenTreeAttachmentSerializerField, @@ -148,6 +150,13 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): allow_null=True ) + price_currency = serializers.ChoiceField( + choices=currency_code_mappings(), + default=currency_code_default, + label=_('Currency'), + help_text=_('Purchase currency of this stock item'), + ) + price_string = serializers.CharField(source='price', read_only=True) class Meta: @@ -157,6 +166,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): 'part', 'quantity', 'price', + 'price_currency', 'price_string', ] @@ -172,6 +182,13 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer): allow_null=True ) + price_currency = serializers.ChoiceField( + choices=currency_code_mappings(), + default=currency_code_default, + label=_('Currency'), + help_text=_('Purchase currency of this stock item'), + ) + price_string = serializers.CharField(source='price', read_only=True) class Meta: @@ -181,6 +198,7 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer): 'part', 'quantity', 'price', + 'price_currency', 'price_string', ] diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index a996151879..5ec1821b3d 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -1008,7 +1008,7 @@ pb_url_slug: 'internal-price', pb_url: '{% url 'api-part-internal-price-list' %}', pb_new_btn: $('#new-internal-price-break'), - pb_new_url: '{% url 'internal-price-break-create' %}', + pb_new_url: '{% url 'api-part-internal-price-list' %}', linkedGraph: $('#InternalPriceBreakChart'), }, ); @@ -1024,7 +1024,7 @@ pb_url_slug: 'sale-price', pb_url: "{% url 'api-part-sale-price-list' %}", pb_new_btn: $('#new-price-break'), - pb_new_url: '{% url 'sale-price-break-create' %}', + pb_new_url: '{% url 'api-part-sale-price-list' %}', linkedGraph: $('#SalePriceBreakChart'), }, ); diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html index 53723b729b..d47f49ef76 100644 --- a/InvenTree/part/templates/part/prices.html +++ b/InvenTree/part/templates/part/prices.html @@ -303,3 +303,4 @@ {% endif %} {% endif %} + \ No newline at end of file diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 55a3dc52eb..04d2b0a5f8 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -13,18 +13,6 @@ from django.conf.urls import url, include from . import views -sale_price_break_urls = [ - url(r'^new/', views.PartSalePriceBreakCreate.as_view(), name='sale-price-break-create'), - url(r'^(?P\d+)/edit/', views.PartSalePriceBreakEdit.as_view(), name='sale-price-break-edit'), - url(r'^(?P\d+)/delete/', views.PartSalePriceBreakDelete.as_view(), name='sale-price-break-delete'), -] - -internal_price_break_urls = [ - url(r'^new/', views.PartInternalPriceBreakCreate.as_view(), name='internal-price-break-create'), - url(r'^(?P\d+)/edit/', views.PartInternalPriceBreakEdit.as_view(), name='internal-price-break-edit'), - url(r'^(?P\d+)/delete/', views.PartInternalPriceBreakDelete.as_view(), name='internal-price-break-delete'), -] - part_parameter_urls = [ url(r'^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'), url(r'^template/(?P\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'), @@ -86,12 +74,6 @@ part_urls = [ # Part category url(r'^category/', include(category_urls)), - # Part price breaks - url(r'^sale-price/', include(sale_price_break_urls)), - - # Part internal price breaks - url(r'^internal-price/', include(internal_price_break_urls)), - # Part parameters url(r'^parameter/', include(part_parameter_urls)), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index ab4b3fec72..b0633d8c32 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1230,102 +1230,3 @@ class CategoryParameterTemplateDelete(AjaxDeleteView): return None return self.object - - -class PartSalePriceBreakCreate(AjaxCreateView): - """ - View for creating a sale price break for a part - """ - - model = PartSellPriceBreak - form_class = part_forms.EditPartSalePriceBreakForm - ajax_form_title = _('Add Price Break') - - def get_data(self): - return { - 'success': _('Added new price break') - } - - def get_part(self): - try: - part = Part.objects.get(id=self.request.GET.get('part')) - except (ValueError, Part.DoesNotExist): - part = None - - if part is None: - try: - part = Part.objects.get(id=self.request.POST.get('part')) - except (ValueError, Part.DoesNotExist): - part = None - - return part - - def get_form(self): - - form = super(AjaxCreateView, self).get_form() - form.fields['part'].widget = HiddenInput() - - return form - - def get_initial(self): - - initials = super(AjaxCreateView, self).get_initial() - - initials['part'] = self.get_part() - - default_currency = inventree_settings.currency_code_default() - currency = CURRENCIES.get(default_currency, None) - - if currency is not None: - initials['price'] = [1.0, currency] - - return initials - - -class PartSalePriceBreakEdit(AjaxUpdateView): - """ View for editing a sale price break """ - - model = PartSellPriceBreak - form_class = part_forms.EditPartSalePriceBreakForm - ajax_form_title = _('Edit Price Break') - - def get_form(self): - - form = super().get_form() - form.fields['part'].widget = HiddenInput() - - return form - - -class PartSalePriceBreakDelete(AjaxDeleteView): - """ View for deleting a sale price break """ - - model = PartSellPriceBreak - ajax_form_title = _("Delete Price Break") - ajax_template_name = "modal_delete_form.html" - - -class PartInternalPriceBreakCreate(PartSalePriceBreakCreate): - """ View for creating a internal price break for a part """ - - model = PartInternalPriceBreak - form_class = part_forms.EditPartInternalPriceBreakForm - ajax_form_title = _('Add Internal Price Break') - permission_required = 'roles.sales_order.add' - - -class PartInternalPriceBreakEdit(PartSalePriceBreakEdit): - """ View for editing a internal price break """ - - model = PartInternalPriceBreak - form_class = part_forms.EditPartInternalPriceBreakForm - ajax_form_title = _('Edit Internal Price Break') - permission_required = 'roles.sales_order.change' - - -class PartInternalPriceBreakDelete(PartSalePriceBreakDelete): - """ View for deleting a internal price break """ - - model = PartInternalPriceBreak - ajax_form_title = _("Delete Internal Price Break") - permission_required = 'roles.sales_order.delete' diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 35d5d0d5a6..d552bcb9d7 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -1930,7 +1930,9 @@ function loadPriceBreakTable(table, options) { formatNoMatches: function() { return `{% trans "No ${human_name} information found" %}`; }, - queryParams: {part: options.part}, + queryParams: { + part: options.part + }, url: options.url, onLoadSuccess: function(tableData) { if (linkedGraph) { @@ -2036,36 +2038,45 @@ function initPriceBreakSet(table, options) { } pb_new_btn.click(function() { - launchModalForm(pb_new_url, - { - success: reloadPriceBreakTable, - data: { - part: part_id, - } - } - ); + + constructForm(pb_new_url, { + fields: { + part: { + hidden: true, + value: part_id, + }, + quantity: {}, + price: {}, + price_currency: {}, + }, + method: 'POST', + title: '{% trans "Add Price Break" %}', + onSuccess: reloadPriceBreakTable, + }); }); table.on('click', `.button-${pb_url_slug}-delete`, function() { var pk = $(this).attr('pk'); - launchModalForm( - `/part/${pb_url_slug}/${pk}/delete/`, - { - success: reloadPriceBreakTable - } - ); + constructForm(`${pb_url}${pk}/`, { + method: 'DELETE', + title: '{% trans "Delete Price Break" %}', + onSuccess: reloadPriceBreakTable, + }); }); table.on('click', `.button-${pb_url_slug}-edit`, function() { var pk = $(this).attr('pk'); - launchModalForm( - `/part/${pb_url_slug}/${pk}/edit/`, - { - success: reloadPriceBreakTable - } - ); + constructForm(`${pb_url}${pk}/`, { + fields: { + quantity: {}, + price: {}, + price_currency: {}, + }, + title: '{% trans "Edit Price Break" %}', + onSuccess: reloadPriceBreakTable, + }); }); } From 0b51ca290226e1234484ea2f04b949487c57d740 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 19:54:16 +1000 Subject: [PATCH 29/38] PEP fixes --- InvenTree/part/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index b0633d8c32..41e734ce2a 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -18,7 +18,6 @@ from django.forms import HiddenInput from django.conf import settings from django.contrib import messages -from moneyed import CURRENCIES from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.exceptions import MissingRate @@ -33,7 +32,6 @@ from decimal import Decimal from .models import PartCategory, Part from .models import PartParameterTemplate from .models import PartCategoryParameterTemplate -from .models import PartSellPriceBreak, PartInternalPriceBreak from common.models import InvenTreeSetting from company.models import SupplierPart From 3f92f009e4d361febc3da06b32fc4db368bd9ce7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 20:00:06 +1000 Subject: [PATCH 30/38] Add configurable setting for batch code generation --- InvenTree/common/models.py | 6 ++++++ InvenTree/templates/InvenTree/settings/stock.html | 1 + 2 files changed, 7 insertions(+) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index bb7de56e99..8bd100dcc9 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -932,6 +932,12 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'validator': bool, }, + 'STOCK_BATCH_CODE_TEMPLATE': { + 'name': _('Batch Code Template'), + 'description': _('Template for generating default batch codes for stock items'), + 'default': '', + }, + 'STOCK_ENABLE_EXPIRY': { 'name': _('Stock Expiry'), 'description': _('Enable stock expiry functionality'), diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index a3c0940c1f..f0cd403d68 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -11,6 +11,7 @@ + {% include "InvenTree/settings/setting.html" with key="STOCK_BATCH_CODE_TEMPLATE" icon="fa-layer-group" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ENABLE_EXPIRY" icon="fa-stopwatch" %} {% include "InvenTree/settings/setting.html" with key="STOCK_STALE_DAYS" icon="fa-calendar" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" icon="fa-truck" %} From 314cec5ad0ec9f51949bf9f6038cae8e0d839f25 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 20:18:33 +1000 Subject: [PATCH 31/38] Generate default batch code for stock items --- InvenTree/stock/models.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index fa343e9a9c..750b4df217 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -8,6 +8,8 @@ from __future__ import unicode_literals import os +from jinja2 import Template + from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError, FieldError from django.urls import reverse @@ -213,6 +215,30 @@ class StockItemManager(TreeManager): ) +def generate_batch_code(): + """ + Generate a default 'batch code' for a new StockItem. + + This uses the value of the 'STOCK_BATCH_CODE_TEMPLATE' setting (if configured), + which can be passed through a simple template. + """ + + batch_template = common.models.InvenTreeSetting.get_setting('STOCK_BATCH_CODE_TEMPLATE', '') + + now = datetime.now() + + context = { + 'date': now, + 'year': now.year, + 'month': now.month, + 'day': now.day, + 'hour': now.minute, + 'minute': now.minute, + } + + return Template(batch_template).render(context) + + class StockItem(MPTTModel): """ A StockItem object represents a quantity of physical instances of a part. @@ -644,7 +670,8 @@ class StockItem(MPTTModel): batch = models.CharField( verbose_name=_('Batch Code'), max_length=100, blank=True, null=True, - help_text=_('Batch code for this stock item') + help_text=_('Batch code for this stock item'), + default=generate_batch_code, ) quantity = models.DecimalField( From 24da5d41b863259eb199fef3f1a42345e0956fa5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 20:21:15 +1000 Subject: [PATCH 32/38] Add migration file --- .../migrations/0074_alter_stockitem_batch.py | 19 +++++++++++++++++++ InvenTree/stock/models.py | 2 ++ 2 files changed, 21 insertions(+) create mode 100644 InvenTree/stock/migrations/0074_alter_stockitem_batch.py diff --git a/InvenTree/stock/migrations/0074_alter_stockitem_batch.py b/InvenTree/stock/migrations/0074_alter_stockitem_batch.py new file mode 100644 index 0000000000..646e25199a --- /dev/null +++ b/InvenTree/stock/migrations/0074_alter_stockitem_batch.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.12 on 2022-04-26 10:19 + +from django.db import migrations, models +import stock.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0073_alter_stockitem_belongs_to'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='batch', + field=models.CharField(blank=True, default=stock.models.generate_batch_code, help_text='Batch code for this stock item', max_length=100, null=True, verbose_name='Batch Code'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 750b4df217..39697c1bca 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -227,6 +227,8 @@ def generate_batch_code(): now = datetime.now() + # Pass context data through to the template randering. + # The folowing context variables are availble for custom batch code generation context = { 'date': now, 'year': now.year, From d9c0698c345ff756d0b1032749a61bbd5abbdab1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Apr 2022 23:53:07 +0200 Subject: [PATCH 33/38] remove oauth-toolkit --- InvenTree/InvenTree/settings.py | 2 -- InvenTree/InvenTree/urls.py | 3 --- requirements.txt | 1 - 3 files changed, 6 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 3f1f41d24c..e1c584362f 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -269,8 +269,6 @@ INSTALLED_APPS = [ 'django_q', 'formtools', # Form wizard tools - 'oauth2_provider', # Oauth provider - 'allauth', # Base app for SSO 'allauth.account', # Extend user with accounts 'allauth.socialaccount', # Use 'social' providers diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 101130045e..ec8b891f93 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -194,9 +194,6 @@ frontendpatterns = [ url(r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"), url(r'^accounts/', include('allauth_2fa.urls')), # MFA support url(r'^accounts/', include('allauth.urls')), # included urlpatterns - - # Oauth provider urls - url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')), ] # Append custom plugin URLs (if plugin support is enabled) diff --git a/requirements.txt b/requirements.txt index 3777c24f12..9aef6607d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,6 @@ django-markdownify==0.8.0 # Markdown rendering django-markdownx==3.0.1 # Markdown form fields django-money==1.1 # Django app for currency management django-mptt==0.11.0 # Modified Preorder Tree Traversal -django-oauth-toolkit==1.7.1 # Oauth endpoints django-redis>=5.0.0 # Redis integration django-q==1.3.4 # Background task scheduling django-sql-utils==0.5.0 # Advanced query annotation / aggregation From 08ab99adc6181432a4fe949965c2931404895464 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 Apr 2022 09:04:57 +1000 Subject: [PATCH 34/38] Build order table improvements: - Display tick / cross badge for stock availability in build order table - Correctly order available quantity --- InvenTree/templates/js/translated/build.js | 34 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index eb955d7ff0..a084b6ecb4 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -1031,6 +1031,23 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { return row.required; } + function availableQuantity(row) { + + // Base stock + var available = row.available_stock; + + // Substitute stock + available += (row.available_substitute_stock || 0); + + // Variant stock + if (row.allow_variants) { + available += (row.available_variant_stock || 0); + } + + return available; + + } + function sumAllocations(row) { // Calculat total allocations for a given row if (!row.allocations) { @@ -1429,12 +1446,13 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`; // Calculate total "available" (unallocated) quantity - var base_stock = row.available_stock; var substitute_stock = row.available_substitute_stock || 0; var variant_stock = row.allow_variants ? (row.available_variant_stock || 0) : 0; - var available_stock = base_stock + substitute_stock + variant_stock; + var available_stock = availableQuantity(row); + var required = requiredQuantity(row); + var text = `${available_stock}`; if (available_stock <= 0) { @@ -1453,9 +1471,19 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { text += ``; } } + + if (available_stock < required) { + text += ``; + } else { + text += ``; + } return renderLink(text, url); - } + }, + sorter: function(valA, valB, rowA, rowB) { + + return availableQuantity(rowA) > availableQuantity(rowB) ? 1 : -1; + }, }, { field: 'allocated', From 2a24fc43e76df2dd2946c876aea7ca348980ebbb Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 Apr 2022 09:07:10 +1000 Subject: [PATCH 35/38] Text formatting --- InvenTree/templates/js/translated/build.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index a084b6ecb4..65fc3a4d6c 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -1453,10 +1453,20 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { var required = requiredQuantity(row); - var text = `${available_stock}`; + var text = ''; + + if (available_stock > 0) { + text += `${available_stock}`; + } + + if (available_stock < required) { + text += ``; + } else { + text += ``; + } if (available_stock <= 0) { - text = `{% trans "No Stock Available" %}`; + text += `{% trans "No Stock Available" %}`; } else { var extra = ''; if ((substitute_stock > 0) && (variant_stock > 0)) { @@ -1471,12 +1481,6 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { text += ``; } } - - if (available_stock < required) { - text += ``; - } else { - text += ``; - } return renderLink(text, url); }, From 481350b138af15eef6a21231d8ad1946aabd9bcd Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 Apr 2022 09:16:57 +1000 Subject: [PATCH 36/38] Refactor available quantity calculation for BOM table --- InvenTree/templates/js/translated/bom.js | 27 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 0d27a5e028..2d7796edcd 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -691,8 +691,24 @@ function loadBomTable(table, options={}) { setupFilterList('bom', $(table)); - // Construct the table columns + function availableQuantity(row) { + // Base stock + var available = row.available_stock; + + // Substitute stock + available += (row.available_substitute_stock || 0); + + // Variant stock + if (row.allow_variants) { + available += (row.available_variant_stock || 0); + } + + return available; + + } + + // Construct the table columns var cols = []; if (options.editable) { @@ -807,11 +823,10 @@ function loadBomTable(table, options={}) { var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`; // Calculate total "available" (unallocated) quantity - var base_stock = row.available_stock; var substitute_stock = row.available_substitute_stock || 0; var variant_stock = row.allow_variants ? (row.available_variant_stock || 0) : 0; - var available_stock = base_stock + substitute_stock + variant_stock; + var available_stock = availableQuantity(row); var text = `${available_stock}`; @@ -923,7 +938,7 @@ function loadBomTable(table, options={}) { formatter: function(value, row) { var can_build = 0; - var available = row.available_stock + (row.available_substitute_stock || 0) + (row.available_variant_stock || 0); + var available = availableQuantity(row); if (row.quantity > 0) { can_build = available / row.quantity; @@ -937,11 +952,11 @@ function loadBomTable(table, options={}) { var cb_b = 0; if (rowA.quantity > 0) { - cb_a = (rowA.available_stock + rowA.available_substitute_stock) / rowA.quantity; + cb_a = availableQuantity(rowA) / rowA.quantity; } if (rowB.quantity > 0) { - cb_b = (rowB.available_stock + rowB.available_substitute_stock) / rowB.quantity; + cb_b = availableQuantity(rowB) / rowB.quantity; } return (cb_a > cb_b) ? 1 : -1; From 86f562f3b8ecc49619e3298763d6b07b7f888d9f Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 Apr 2022 13:46:50 +1000 Subject: [PATCH 37/38] Add gdk-pixbuf - Required for weasyprint (reports) to render jpg files --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9978460e8f..63535bd83d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -65,8 +65,8 @@ RUN apk add --no-cache git make bash \ libjpeg-turbo libjpeg-turbo-dev jpeg jpeg-dev \ libffi libffi-dev \ zlib zlib-dev \ - # Cairo deps for WeasyPrint (these will be deprecated once WeasyPrint drops cairo requirement) - cairo cairo-dev pango pango-dev \ + # Special deps for WeasyPrint (these will be deprecated once WeasyPrint drops cairo requirement) + cairo cairo-dev pango pango-dev gdk-pixbuf \ # Fonts fontconfig ttf-droid ttf-liberation ttf-dejavu ttf-opensans ttf-ubuntu-font-family font-croscore font-noto \ # Core python From f98225334e13c63d7c945c20d5eb627fca89b4fa Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 27 Apr 2022 15:36:44 +1000 Subject: [PATCH 38/38] Simplify allocation display for part page --- InvenTree/part/templates/part/part_base.html | 34 +++---------------- .../part/templatetags/inventree_extras.py | 13 ++++--- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index e45620798e..56a96c60ed 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -211,44 +211,18 @@ {% if part.component %} {% if required_build_order_quantity > 0 %} - - - - - - + - + {% endif %} {% endif %} {% if part.salable %} {% if required_sales_order_quantity > 0 %} - - - - - - + - + {% endif %} {% endif %} diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index dc93e00efa..08fa8ce583 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -352,21 +352,24 @@ def visible_global_settings(*args, **kwargs): @register.simple_tag() -def progress_bar(val, max, *args, **kwargs): +def progress_bar(val, max_val, *args, **kwargs): """ Render a progress bar element """ item_id = kwargs.get('id', 'progress-bar') - if val > max: + val = InvenTree.helpers.normalize(val) + max_val = InvenTree.helpers.normalize(max_val) + + if val > max_val: style = 'progress-bar-over' - elif val < max: + elif val < max_val: style = 'progress-bar-under' else: style = '' - percent = float(val / max) * 100 + percent = float(val / max_val) * 100 if percent > 100: percent = 100 @@ -383,7 +386,7 @@ def progress_bar(val, max, *args, **kwargs): html = f"""
-
{val} / {max}
+
{val} / {max_val}
"""
{% trans "Required for Build Orders" %}{% decimal required_build_order_quantity %}
{% trans "Allocated to Build Orders" %} - {% decimal allocated_build_order_quantity %} - {% if allocated_build_order_quantity < required_build_order_quantity %} - - {% else %} - - {% endif %} - {% progress_bar allocated_build_order_quantity required_build_order_quantity id='build-order-allocated' max_width='150px' %}
{% trans "Required for Sales Orders" %} - {% decimal required_sales_order_quantity %} -
{% trans "Allocated to Sales Orders" %} - {% decimal allocated_sales_order_quantity %} - {% if allocated_sales_order_quantity < required_sales_order_quantity %} - - {% else %} - - {% endif %} - {% progress_bar allocated_sales_order_quantity required_sales_order_quantity id='sales-order-allocated' max_width='150px' %}