From 07cda765f03d3cef81c544ce7c504b0245ab4dfd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 3 Jan 2021 23:56:35 +1100 Subject: [PATCH 01/27] Add "expiry_date" field to StockItem model - Also adds "is_expired" function --- .../migrations/0056_stockitem_expiry_date.py | 18 ++++++ InvenTree/stock/models.py | 64 +++++++++++-------- InvenTree/stock/tests.py | 34 ++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 InvenTree/stock/migrations/0056_stockitem_expiry_date.py diff --git a/InvenTree/stock/migrations/0056_stockitem_expiry_date.py b/InvenTree/stock/migrations/0056_stockitem_expiry_date.py new file mode 100644 index 0000000000..f558d615a6 --- /dev/null +++ b/InvenTree/stock/migrations/0056_stockitem_expiry_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-01-03 12:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0055_auto_20201117_1453'), + ] + + operations = [ + migrations.AddField( + model_name='stockitem', + name='expiry_date', + field=models.DateField(blank=True, help_text='Expiry date for stock item. Stock will be considered expired after this date', null=True, verbose_name='Expiry Date'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index d1e46c53a7..24db434d38 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -125,6 +125,7 @@ class StockItem(MPTTModel): serial: Unique serial number for this StockItem link: Optional URL to link to external resource updated: Date that this stock item was last updated (auto) + expiry_date: Expiry date of the StockItem (optional) stocktake_date: Date of last stocktake for this item stocktake_user: User that performed the most recent stocktake review_needed: Flag if StockItem needs review @@ -428,11 +429,19 @@ class StockItem(MPTTModel): related_name='stock_items', null=True, blank=True) - # last time the stock was checked / counted + expiry_date = models.DateField( + blank=True, null=True, + verbose_name=_('Expiry Date'), + help_text=_('Expiry date for stock item. Stock will be considered expired after this date'), + ) + stocktake_date = models.DateField(blank=True, null=True) - stocktake_user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True, - related_name='stocktake_stock') + stocktake_user = models.ForeignKey( + User, on_delete=models.SET_NULL, + blank=True, null=True, + related_name='stocktake_stock' + ) review_needed = models.BooleanField(default=False) @@ -459,6 +468,27 @@ class StockItem(MPTTModel): help_text=_('Single unit purchase price at time of purchase'), ) + def is_expired(self): + """ + Returns true if this StockItem is "expired" + + To be "expired", the following conditions must be met: + + - Expiry date is not None + - Expiry date is "in the past" + - The StockItem is otherwise "in stock" + """ + + if self.expiry_date is None: + return False + + if not self.in_stock: + return False + + today = datetime.now().date() + + return self.expiry_date < today + def clearAllocations(self): """ Clear all order allocations for this StockItem: @@ -721,36 +751,16 @@ class StockItem(MPTTModel): @property def in_stock(self): """ - Returns True if this item is in stock + Returns True if this item is in stock. See also: IN_STOCK_FILTER """ - # Quantity must be above zero (unless infinite) - if self.quantity <= 0 and not self.infinite: - return False + query = StockItem.objects.filter(pk=self.pk) - # Not 'in stock' if it has been installed inside another StockItem - if self.belongs_to is not None: - return False - - # Not 'in stock' if it has been sent to a customer - if self.sales_order is not None: - return False + query = query.filter(StockItem.IN_STOCK_FILTER) - # Not 'in stock' if it has been assigned to a customer - if self.customer is not None: - return False - - # Not 'in stock' if it is building - if self.is_building: - return False - - # Not 'in stock' if the status code makes it unavailable - if self.status in StockStatus.UNAVAILABLE_CODES: - return False - - return True + return query.exists() @property def tracking_info_count(self): diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 3d309c0360..274a1328fe 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -49,6 +49,40 @@ class StockTest(TestCase): Part.objects.rebuild() StockItem.objects.rebuild() + def test_expiry(self): + """ + Test expiry date functionality for StockItem model. + """ + + today = datetime.datetime.now().date() + + item = StockItem.objects.create( + location=self.office, + part=Part.objects.get(pk=1), + quantity=10, + ) + + # Without an expiry_date set, item should not be "expired" + self.assertFalse(item.is_expired()) + + # Set the expiry date to today + item.expiry_date = today + item.save() + + self.assertFalse(item.is_expired()) + + # Set the expiry date in the future + item.expiry_date = today + datetime.timedelta(days=5) + item.save() + + self.assertFalse(item.is_expired()) + + # Set the expiry date in the past + item.expiry_date = today - datetime.timedelta(days=5) + item.save() + + self.assertTrue(item.is_expired()) + def test_is_building(self): """ Test that the is_building flag does not count towards stock. From 6d4c81e68beaab9f94d23ad4cc333fd6f11be0fa Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 00:19:48 +1100 Subject: [PATCH 02/27] Add ability to filter by 'expired' status in API --- InvenTree/stock/api.py | 11 +++++++++++ InvenTree/stock/models.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index b74ac200f7..571ec1dd6f 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -525,6 +525,17 @@ class StockList(generics.ListCreateAPIView): # Exclude items which are instaled in another item queryset = queryset.filter(belongs_to=None) + # Filter by 'expired' status + expired = params.get('expired', None) + + if expired is not None: + expired = str2bool(expired) + + if expired: + queryset = queryset.filter(StockItem.EXPIRED_FILTER) + else: + queryset = queryset.exclude(StockItem.EXPIRED_FILTER) + # Filter by customer customer = params.get('customer', None) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 24db434d38..d2d6ec51ee 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -150,6 +150,9 @@ class StockItem(MPTTModel): status__in=StockStatus.AVAILABLE_CODES ) + # A query filter which can be used to filter StockItem objects which have expired + EXPIRED_FILTER = IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=datetime.now().date()) + def save(self, *args, **kwargs): """ Save this StockItem to the database. Performs a number of checks: From 1a930f7f80d04118ac695e0fbc080355e101e087 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 00:20:02 +1100 Subject: [PATCH 03/27] Add ability to edit expiry_date for StockItem --- InvenTree/stock/forms.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 8ab88155e2..4ed7f73f49 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -16,6 +16,7 @@ from mptt.fields import TreeNodeChoiceField from InvenTree.helpers import GetExportFormats from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField +from InvenTree.fields import DatePickerFormField from report.models import TestReport @@ -392,6 +393,10 @@ class EditStockItemForm(HelperForm): part - Cannot be edited after creation """ + expiry_date = DatePickerFormField( + help_text=('Expiration date for this stock item'), + ) + class Meta: model = StockItem @@ -400,6 +405,7 @@ class EditStockItemForm(HelperForm): 'serial', 'batch', 'status', + 'expiry_date', 'purchase_price', 'link', 'delete_on_deplete', From 39b9dcfec9ca1c8d13049d529d27c66b86ab1965 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 00:20:19 +1100 Subject: [PATCH 04/27] Add 'expired' flag to StockItem serializer --- InvenTree/stock/serializers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index e8675a8fff..730018d98c 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -11,6 +11,9 @@ from .models import StockItemTestResult from django.db.models.functions import Coalesce +from django.db.models import Case, When, Value +from django.db.models import BooleanField + from sql_util.utils import SubquerySum, SubqueryCount from decimal import Decimal @@ -106,6 +109,16 @@ class StockItemSerializer(InvenTreeModelSerializer): tracking_items=SubqueryCount('tracking_info') ) + # Add flag to indicate if the StockItem has expired + queryset = queryset.annotate( + expired=Case( + When( + StockItem.EXPIRED_FILTER, then=Value(True, output_field=BooleanField()), + ), + default=Value(False, output_field=BooleanField()) + ) + ) + return queryset status_text = serializers.CharField(source='get_status_display', read_only=True) @@ -122,6 +135,8 @@ class StockItemSerializer(InvenTreeModelSerializer): allocated = serializers.FloatField(source='allocation_count', required=False) + expired = serializers.BooleanField() + serial = serializers.CharField(required=False) required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False) @@ -155,6 +170,8 @@ class StockItemSerializer(InvenTreeModelSerializer): 'belongs_to', 'build', 'customer', + 'expired', + 'expiry_date', 'in_stock', 'is_building', 'link', From a0c95579b42042ed992a3edb165a4aeb5d352b98 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 00:21:47 +1100 Subject: [PATCH 05/27] Display expiry status on StockItem page - Also adds ability to filter Stock table by expired status --- InvenTree/stock/templates/stock/item_base.html | 15 +++++++++++++++ InvenTree/templates/js/stock.js | 4 ++++ InvenTree/templates/js/table_filters.js | 5 +++++ 3 files changed, 24 insertions(+) diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 74e93fecc0..4d773b047d 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -71,6 +71,9 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}

{% trans "Stock Item" %} {% stock_status_label item.status large=True %} + {% if item.is_expired %} + {% trans "Expired" %} + {% endif %}


@@ -293,6 +296,18 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {{ item.supplier_part.SKU }} {% endif %} + {% if item.expiry_date %} + + + {% trans "Expiry Date" %} + + {{ item.expiry_date }} + {% if item.is_expired %} + {% trans "Expired" %} + {% endif %} + + + {% endif %} {% trans "Last Updated" %} diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index f8a7c38d2c..65df58c4b6 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -532,6 +532,10 @@ function loadStockTable(table, options) { html += makeIconBadge('fa-user', '{% trans "Stock item assigned to customer" %}'); } + if (row.expired) { + html += makeIconBadge('fa-stopwatch icon-red', '{% trans "Stock item has expired" %}'); + } + if (row.allocated) { html += makeIconBadge('fa-bookmark', '{% trans "Stock item has been allocated" %}'); } diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/table_filters.js index d2ea26d8c3..054542a4a3 100644 --- a/InvenTree/templates/js/table_filters.js +++ b/InvenTree/templates/js/table_filters.js @@ -106,6 +106,11 @@ function getAvailableTableFilters(tableKey) { title: '{% trans "Depleted" %}', description: '{% trans "Show stock items which are depleted" %}', }, + expired: { + type: 'bool', + title: '{% trans "Expired" %}', + description: '{% trans "Show stock items which have expired" %}', + }, in_stock: { type: 'bool', title: '{% trans "In Stock" %}', From d1ce0f062ecd231bd83dc0b004a0768d10f11d13 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 01:17:05 +1100 Subject: [PATCH 06/27] Improve unit testing for StockItem API --- InvenTree/build/serializers.py | 2 +- InvenTree/order/serializers.py | 2 +- InvenTree/part/fixtures/part.yaml | 1 + InvenTree/stock/fixtures/stock.yaml | 8 +- InvenTree/stock/serializers.py | 2 +- InvenTree/stock/test_api.py | 180 +++++++++++++++++++++++++++- InvenTree/stock/tests.py | 6 +- 7 files changed, 189 insertions(+), 12 deletions(-) diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index b71aaecc61..550a3c3a85 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -28,7 +28,7 @@ class BuildSerializer(InvenTreeModelSerializer): quantity = serializers.FloatField() - overdue = serializers.BooleanField() + overdue = serializers.BooleanField(required=False, read_only=True) @staticmethod def annotate_queryset(queryset): diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index f0505728f6..5463feb26f 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -181,7 +181,7 @@ class SalesOrderSerializer(InvenTreeModelSerializer): status_text = serializers.CharField(source='get_status_display', read_only=True) - overdue = serializers.BooleanField() + overdue = serializers.BooleanField(required=False, read_only=True) class Meta: model = SalesOrder diff --git a/InvenTree/part/fixtures/part.yaml b/InvenTree/part/fixtures/part.yaml index f6d9d246db..7d6c09707b 100644 --- a/InvenTree/part/fixtures/part.yaml +++ b/InvenTree/part/fixtures/part.yaml @@ -134,6 +134,7 @@ fields: name: 'Red chair' variant_of: 10000 + IPN: "R.CH" trackable: true category: 7 tree_id: 1 diff --git a/InvenTree/stock/fixtures/stock.yaml b/InvenTree/stock/fixtures/stock.yaml index 719d8a34ce..45a5f5dd7f 100644 --- a/InvenTree/stock/fixtures/stock.yaml +++ b/InvenTree/stock/fixtures/stock.yaml @@ -69,7 +69,7 @@ part: 25 batch: 'ABCDE' location: 7 - quantity: 3 + quantity: 0 level: 0 tree_id: 0 lft: 0 @@ -220,6 +220,7 @@ tree_id: 0 lft: 0 rght: 0 + expiry_date: "1990-10-10" - model: stock.stockitem pk: 521 @@ -232,6 +233,7 @@ tree_id: 0 lft: 0 rght: 0 + status: 60 - model: stock.stockitem pk: 522 @@ -243,4 +245,6 @@ level: 0 tree_id: 0 lft: 0 - rght: 0 \ No newline at end of file + rght: 0 + expiry_date: "1990-10-10" + status: 70 \ No newline at end of file diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 730018d98c..9db1d95dae 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -135,7 +135,7 @@ class StockItemSerializer(InvenTreeModelSerializer): allocated = serializers.FloatField(source='allocation_count', required=False) - expired = serializers.BooleanField() + expired = serializers.BooleanField(required=False, read_only=True) serial = serializers.CharField(required=False) diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index a34e895ed8..ecca10ba0a 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -1,11 +1,21 @@ +""" +Unit testing for the Stock API +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from datetime import datetime, timedelta + from rest_framework.test import APITestCase from rest_framework import status from django.urls import reverse from django.contrib.auth import get_user_model from InvenTree.helpers import addUserPermissions +from InvenTree.status_codes import StockStatus -from .models import StockLocation +from .models import StockItem, StockLocation class StockAPITestCase(APITestCase): @@ -76,6 +86,170 @@ class StockLocationTest(StockAPITestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) +class StockItemListTest(StockAPITestCase): + """ + Tests for the StockItem API LIST endpoint + """ + + list_url = reverse('api-stock-list') + + def get_stock(self, **kwargs): + """ + Filter stock and return JSON object + """ + + response = self.client.get(self.list_url, format='json', data=kwargs) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Return JSON-ified data + return response.data + + def test_get_stock_list(self): + """ + List *all* StockItem objects. + """ + + response = self.get_stock() + + self.assertEqual(len(response), 19) + + def test_filter_by_part(self): + """ + Filter StockItem by Part reference + """ + + response = self.get_stock(part=25) + + self.assertEqual(len(response), 7) + + response = self.get_stock(part=10004) + + self.assertEqual(len(response), 12) + + def test_filter_by_IPN(self): + """ + Filter StockItem by IPN reference + """ + + response = self.get_stock(IPN="R.CH") + self.assertEqual(len(response), 3) + + def test_filter_by_location(self): + """ + Filter StockItem by StockLocation reference + """ + + response = self.get_stock(location=5) + self.assertEqual(len(response), 1) + + response = self.get_stock(location=1, cascade=0) + self.assertEqual(len(response), 0) + + response = self.get_stock(location=1, cascade=1) + self.assertEqual(len(response), 2) + + response = self.get_stock(location=7) + self.assertEqual(len(response), 16) + + def test_filter_by_depleted(self): + """ + Filter StockItem by depleted status + """ + + response = self.get_stock(depleted=1) + self.assertEqual(len(response), 1) + + response = self.get_stock(depleted=0) + self.assertEqual(len(response), 18) + + def test_filter_by_in_stock(self): + """ + Filter StockItem by 'in stock' status + """ + + response = self.get_stock(in_stock=1) + self.assertEqual(len(response), 16) + + response = self.get_stock(in_stock=0) + self.assertEqual(len(response), 3) + + def test_filter_by_status(self): + """ + Filter StockItem by 'status' field + """ + + codes = { + StockStatus.OK: 17, + StockStatus.DESTROYED: 1, + StockStatus.LOST: 1, + StockStatus.DAMAGED: 0, + StockStatus.REJECTED: 0, + } + + for code in codes.keys(): + num = codes[code] + + response = self.get_stock(status=code) + self.assertEqual(len(response), num) + + def test_filter_by_batch(self): + """ + Filter StockItem by batch code + """ + + response = self.get_stock(batch='B123') + self.assertEqual(len(response), 1) + + def test_filter_by_serialized(self): + """ + Filter StockItem by serialized status + """ + + response = self.get_stock(serialized=1) + self.assertEqual(len(response), 12) + + for item in response: + self.assertIsNotNone(item['serial']) + + response = self.get_stock(serialized=0) + self.assertEqual(len(response), 7) + + for item in response: + self.assertIsNone(item['serial']) + + def test_filter_by_expired(self): + """ + Filter StockItem by expiry status + """ + + response = self.get_stock(expired=1) + self.assertEqual(len(response), 1) + + for item in response: + self.assertTrue(item['expired']) + + response = self.get_stock(expired=0) + self.assertEqual(len(response), 18) + + for item in response: + self.assertFalse(item['expired']) + + # Mark some other stock items as expired + today = datetime.now().date() + + for pk in [510, 511, 512]: + item = StockItem.objects.get(pk=pk) + item.expiry_date = today - timedelta(days=pk) + item.save() + + response = self.get_stock(expired=1) + self.assertEqual(len(response), 4) + + response = self.get_stock(expired=0) + self.assertEqual(len(response), 15) + + class StockItemTest(StockAPITestCase): """ Series of API tests for the StockItem API @@ -94,10 +268,6 @@ class StockItemTest(StockAPITestCase): StockLocation.objects.create(name='B', description='location b', parent=top) StockLocation.objects.create(name='C', description='location c', parent=top) - def test_get_stock_list(self): - response = self.client.get(self.list_url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_create_default_location(self): """ Test the default location functionality, diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 274a1328fe..b0b05b6326 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -177,8 +177,10 @@ class StockTest(TestCase): # There should be 9000 screws in stock self.assertEqual(part.total_stock, 9000) - # There should be 18 widgets in stock - self.assertEqual(StockItem.objects.filter(part=25).aggregate(Sum('quantity'))['quantity__sum'], 19) + # There should be 16 widgets "in stock" + self.assertEqual( + StockItem.objects.filter(part=25).aggregate(Sum('quantity'))['quantity__sum'], 16 + ) def test_delete_location(self): From 4d7c60a13038136e9d1e036a2252f24c6ec40f86 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 09:46:14 +1100 Subject: [PATCH 07/27] Add "expired stock" table to index page --- .../templates/InvenTree/expired_stock.html | 15 +++++++++++++ InvenTree/templates/InvenTree/index.html | 21 +++++++++++++++++-- InvenTree/templates/collapse_index.html | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 InvenTree/templates/InvenTree/expired_stock.html diff --git a/InvenTree/templates/InvenTree/expired_stock.html b/InvenTree/templates/InvenTree/expired_stock.html new file mode 100644 index 0000000000..7ecf117fa7 --- /dev/null +++ b/InvenTree/templates/InvenTree/expired_stock.html @@ -0,0 +1,15 @@ +{% extends "collapse_index.html" %} + +{% load i18n %} + +{% block collapse_title %} + +{% trans "Expired Stock" %} +{% endblock %} + +{% block collapse_content %} + + +
+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index f862175920..d5b2c87148 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -8,7 +8,7 @@ InvenTree | {% trans "Index" %}

InvenTree


-
+
{% if roles.part.view %} {% include "InvenTree/latest_parts.html" with collapse_id="latest_parts" %} {% include "InvenTree/bom_invalid.html" with collapse_id="bom_invalid" %} @@ -19,11 +19,14 @@ InvenTree | {% trans "Index" %} {% include "InvenTree/build_overdue.html" with collapse_id="build_overdue" %} {% endif %}
-
+
{% if roles.stock.view %} {% include "InvenTree/low_stock.html" with collapse_id="order" %} + {% include "InvenTree/expired_stock.html" with collapse_id="expired" %} {% include "InvenTree/required_stock_build.html" with collapse_id="stock_to_build" %} {% endif %} +
+
{% if roles.purchase_order.view %} {% include "InvenTree/po_outstanding.html" with collapse_id="po_outstanding" %} {% endif %} @@ -83,6 +86,14 @@ loadBuildTable("#build-overdue-table", { disableFilters: true, }); +loadStockTable($("#expired-stock-table"), { + params: { + expired: true, + location_detail: true, + part_detail: true, + }, +}); + loadSimplePartTable("#low-stock-table", "{% url 'api-part-list' %}", { params: { low_stock: true, @@ -151,6 +162,12 @@ $("#build-overdue-table").on('load-success.bs.table', function() { $("#build-overdue-count").html(count); }); +$("#expired-stock-table").on('load-success.bs.table', function() { + var count = $("#expired-stock-table").bootstrapTable('getData').length; + + $("#expired-stock-count").html(count); +}); + $("#low-stock-table").on('load-success.bs.table', function() { var count = $("#low-stock-table").bootstrapTable('getData').length; diff --git a/InvenTree/templates/collapse_index.html b/InvenTree/templates/collapse_index.html index d87f63b244..6e918d7217 100644 --- a/InvenTree/templates/collapse_index.html +++ b/InvenTree/templates/collapse_index.html @@ -1,6 +1,6 @@ {% block collapse_preamble %} {% endblock %} -
+
From 692cee113c0e2ae9aa3748c7de1836aa104baad7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 23:11:35 +1100 Subject: [PATCH 08/27] Display "expiry date" column in stock table --- InvenTree/templates/js/stock.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 65df58c4b6..af0c85fc02 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -587,6 +587,11 @@ function loadStockTable(table, options) { return locationDetail(row); } }, + { + field: 'expiry_date', + title: '{% trans "Expiry Date" %}', + sortable: true, + }, { field: 'notes', title: '{% trans "Notes" %}', From 37dcf1c1cf4aa148771b2a18c9f6db01bc692a69 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 23:36:11 +1100 Subject: [PATCH 09/27] Add "default_expiry" field to Part model --- InvenTree/part/forms.py | 2 ++ .../migrations/0061_auto_20210104_2331.py | 36 +++++++++++++++++++ InvenTree/part/models.py | 36 ++++++++++++++----- InvenTree/part/templates/part/detail.html | 7 ++++ 4 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 InvenTree/part/migrations/0061_auto_20210104_2331.py diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 1cbbec0b42..4f55ccb14d 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -181,6 +181,7 @@ class EditPartForm(HelperForm): 'keywords': 'fa-key', 'link': 'fa-link', 'IPN': 'fa-hashtag', + 'default_expiry': 'fa-stopwatch', } bom_copy = forms.BooleanField(required=False, @@ -228,6 +229,7 @@ class EditPartForm(HelperForm): 'link', 'default_location', 'default_supplier', + 'default_expiry', 'units', 'minimum_stock', 'trackable', diff --git a/InvenTree/part/migrations/0061_auto_20210104_2331.py b/InvenTree/part/migrations/0061_auto_20210104_2331.py new file mode 100644 index 0000000000..c40b611b29 --- /dev/null +++ b/InvenTree/part/migrations/0061_auto_20210104_2331.py @@ -0,0 +1,36 @@ +# Generated by Django 3.0.7 on 2021-01-04 12:31 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0031_auto_20210103_2215'), + ('part', '0060_merge_20201112_1722'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='default_expiry', + field=models.PositiveIntegerField(default=0, help_text='Expiry time (in days) for stock items of this part', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Default Expiry'), + ), + migrations.AlterField( + model_name='part', + name='default_supplier', + field=models.ForeignKey(blank=True, help_text='Default supplier part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_parts', to='company.SupplierPart', verbose_name='Default Supplier'), + ), + migrations.AlterField( + model_name='part', + name='minimum_stock', + field=models.PositiveIntegerField(default=0, help_text='Minimum allowed stock level', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Minimum Stock'), + ), + migrations.AlterField( + model_name='part', + name='units', + field=models.CharField(blank=True, default='', help_text='Stock keeping units for this part', max_length=20, null=True, verbose_name='Units'), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index b9d63979e7..390583bd97 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -291,11 +291,12 @@ class Part(MPTTModel): keywords: Optional keywords for improving part search results IPN: Internal part number (optional) revision: Part revision - is_template: If True, this part is a 'template' part and cannot be instantiated as a StockItem + is_template: If True, this part is a 'template' part link: Link to an external page with more information about this part (e.g. internal Wiki) image: Image of this part default_location: Where the item is normally stored (may be null) default_supplier: The default SupplierPart which should be used to procure and stock this part + default_expiry: The default expiry duration for any StockItem instances of this part minimum_stock: Minimum preferred quantity to keep in stock units: Units of measure for this part (default='pcs') salable: Can this part be sold to customers? @@ -722,15 +723,34 @@ class Part(MPTTModel): # Default to None if there are multiple suppliers to choose from return None - default_supplier = models.ForeignKey(SupplierPart, - on_delete=models.SET_NULL, - blank=True, null=True, - help_text=_('Default supplier part'), - related_name='default_parts') + default_supplier = models.ForeignKey( + SupplierPart, + on_delete=models.SET_NULL, + blank=True, null=True, + verbose_name=_('Default Supplier'), + help_text=_('Default supplier part'), + related_name='default_parts' + ) - minimum_stock = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)], help_text=_('Minimum allowed stock level')) + default_expiry = models.PositiveIntegerField( + default=0, + validators=[MinValueValidator(0)], + verbose_name=_('Default Expiry'), + help_text=_('Expiry time (in days) for stock items of this part'), + ) - units = models.CharField(max_length=20, default="", blank=True, null=True, help_text=_('Stock keeping units for this part')) + minimum_stock = models.PositiveIntegerField( + default=0, validators=[MinValueValidator(0)], + verbose_name=_('Minimum Stock'), + help_text=_('Minimum allowed stock level') + ) + + units = models.CharField( + max_length=20, default="", + blank=True, null=True, + verbose_name=_('Units'), + help_text=_('Stock keeping units for this part') + ) assembly = models.BooleanField( default=False, diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 9711c9fbc8..f723193abb 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -109,6 +109,13 @@ {{ part.minimum_stock }} {% endif %} + {% if part.default_expiry > 0 %} + + + {% trans "Stock Expiry Time" %} + {{ part.default_expiry }} {% trans "days" %} + + {% endif %} {% trans "Creation Date" %} From 7d7d5d24cc75e5b441ddb803d1d7823dc6976cc4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Jan 2021 23:40:51 +1100 Subject: [PATCH 10/27] Pre-fill stockitem expiry date in CreateStockItem form --- InvenTree/stock/forms.py | 5 +++++ InvenTree/stock/views.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 4ed7f73f49..5583411009 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -109,6 +109,10 @@ class ConvertStockItemForm(HelperForm): class CreateStockItemForm(HelperForm): """ Form for creating a new StockItem """ + expiry_date = DatePickerFormField( + help_text=('Expiration date for this stock item'), + ) + serial_numbers = forms.CharField(label=_('Serial numbers'), required=False, help_text=_('Enter unique serial numbers (or leave blank)')) def __init__(self, *args, **kwargs): @@ -130,6 +134,7 @@ class CreateStockItemForm(HelperForm): 'batch', 'serial_numbers', 'purchase_price', + 'expiry_date', 'link', 'delete_on_deplete', 'status', diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 582c1b5d89..963d357e31 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -26,7 +26,7 @@ from InvenTree.helpers import str2bool, DownloadFile, GetExportFormats from InvenTree.helpers import extract_serial_numbers from decimal import Decimal, InvalidOperation -from datetime import datetime +from datetime import datetime, timedelta from company.models import Company, SupplierPart from part.models import Part @@ -1596,6 +1596,11 @@ class StockItemCreate(AjaxCreateView): initials['location'] = part.get_default_location() initials['supplier_part'] = part.default_supplier + # If the part has a defined expiry period, extrapolate! + if part.default_expiry > 0: + expiry_date = datetime.now().date() + timedelta(days=part.default_expiry) + initials['expiry_date'] = expiry_date + currency_code = common.settings.currency_code_default() # SupplierPart field has been specified From da02ab3eac5e932b250b2a26f12af8cb4fe6965a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 00:01:44 +1100 Subject: [PATCH 11/27] Add unit testing for view --- InvenTree/part/fixtures/part.yaml | 1 + InvenTree/stock/test_views.py | 43 +++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/InvenTree/part/fixtures/part.yaml b/InvenTree/part/fixtures/part.yaml index 7d6c09707b..508a1577bb 100644 --- a/InvenTree/part/fixtures/part.yaml +++ b/InvenTree/part/fixtures/part.yaml @@ -74,6 +74,7 @@ level: 0 lft: 0 rght: 0 + default_expiry: 10 - model: part.part pk: 50 diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py index 1f55d74eec..021ca9e138 100644 --- a/InvenTree/stock/test_views.py +++ b/InvenTree/stock/test_views.py @@ -6,6 +6,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import Group import json +from datetime import datetime, timedelta class StockViewTestCase(TestCase): @@ -135,21 +136,53 @@ class StockItemTest(StockViewTestCase): self.assertEqual(response.status_code, 200) def test_create_item(self): - # Test creation of StockItem - response = self.client.get(reverse('stock-item-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + """ + Test creation of StockItem + """ + + url = reverse('stock-item-create') + + response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) - response = self.client.get(reverse('stock-item-create'), {'part': 999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get(url, {'part': 999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) # Copy from a valid item, valid location - response = self.client.get(reverse('stock-item-create'), {'location': 1, 'copy': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get(url, {'location': 1, 'copy': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) # Copy from an invalid item, invalid location - response = self.client.get(reverse('stock-item-create'), {'location': 999, 'copy': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + response = self.client.get(url, {'location': 999, 'copy': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) + def test_create_stock_with_expiry(self): + """ + Test creation of stock item of a part with an expiry date. + The initial value for the "expiry_date" field should be pre-filled, + and should be in the future! + """ + + url = reverse('stock-item-create') + + response = self.client.get(url, {'part': 25}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + self.assertEqual(response.status_code, 200) + + # We are expecting 10 days in the future + expiry = datetime.now().date() + timedelta(10) + + expected = f'name=\\\\"expiry_date\\\\" value=\\\\"{expiry.isoformat()}\\\\"' + + self.assertIn(expected, str(response.content)) + + # Now check with a part which does *not* have a default expiry period + response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + expected = 'name=\\\\"expiry_date\\\\" placeholder=\\\\"\\\\"' + + self.assertIn(expected, str(response.content)) + def test_serialize_item(self): # Test the serialization view From 9dc9c0fcb74891118b078c476a689944863771b9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 00:21:26 +1100 Subject: [PATCH 12/27] Auto-populate expiry date for stockitem when created via the API (Now with unit testing!) --- InvenTree/stock/api.py | 10 ++++ .../stock/templates/stock/item_base.html | 3 +- InvenTree/stock/test_api.py | 50 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 571ec1dd6f..97f5b5c42e 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -35,6 +35,8 @@ from InvenTree.api import AttachmentMixin from decimal import Decimal, InvalidOperation +from datetime import datetime, timedelta + from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response @@ -342,10 +344,18 @@ class StockList(generics.ListCreateAPIView): # A location was *not* specified - try to infer it if 'location' not in request.data: location = item.part.get_default_location() + if location is not None: item.location = location item.save() + # An expiry date was *not* specified - try to infer it! + if 'expiry_date' not in request.data: + + if item.part.default_expiry > 0: + item.expiry_date = datetime.now().date() + timedelta(days=item.part.default_expiry) + item.save() + # Return a response headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 4d773b047d..b8c143fb4f 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -70,9 +70,10 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% block page_data %}

{% trans "Stock Item" %} - {% stock_status_label item.status large=True %} {% if item.is_expired %} {% trans "Expired" %} + {% else %} + {% stock_status_label item.status large=True %} {% endif %}


diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index ecca10ba0a..520110469c 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -368,6 +368,56 @@ class StockItemTest(StockAPITestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) + def test_default_expiry(self): + """ + Test that the "default_expiry" functionality works via the API. + + - If an expiry_date is specified, use that + - Otherwise, check if the referenced part has a default_expiry defined + - If so, use that! + - Otherwise, no expiry + + Notes: + - Part <25> has a default_expiry of 10 days + + """ + + # First test - create a new StockItem without an expiry date + data = { + 'part': 4, + 'quantity': 10, + } + + response = self.client.post(self.list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + self.assertIsNone(response.data['expiry_date']) + + # Second test - create a new StockItem with an explicit expiry date + data['expiry_date'] = '2022-12-12' + + response = self.client.post(self.list_url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + self.assertIsNotNone(response.data['expiry_date']) + self.assertEqual(response.data['expiry_date'], '2022-12-12') + + # Third test - create a new StockItem for a Part which has a default expiry time + data = { + 'part': 25, + 'quantity': 10 + } + + response = self.client.post(self.list_url, data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Expected expiry date is 10 days in the future + expiry = datetime.now().date() + timedelta(10) + + self.assertEqual(response.data['expiry_date'], expiry.isoformat()) + class StocktakeTest(StockAPITestCase): """ From 9a30108b75c6e57a2ee8af2b1444b575ed719e22 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 00:37:42 +1100 Subject: [PATCH 13/27] Auto-update the expiry date in the StockItem form when switching Part selection --- .../static/script/inventree/modals.js | 7 ++++++- InvenTree/part/serializers.py | 1 + InvenTree/templates/js/stock.js | 19 +++++++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/modals.js b/InvenTree/InvenTree/static/script/inventree/modals.js index f731a5238b..12a496c481 100644 --- a/InvenTree/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/InvenTree/static/script/inventree/modals.js @@ -151,12 +151,17 @@ function enableField(fieldName, enabled, options={}) { } function clearField(fieldName, options={}) { + + setFieldValue(fieldName, '', options); +} + +function setFieldValue(fieldName, value, options={}) { var modal = options.modal || '#modal-form'; var field = getFieldByName(modal, fieldName); - field.val(""); + field.val(value); } diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 0eebe6617d..05fc3091f7 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -289,6 +289,7 @@ class PartSerializer(InvenTreeModelSerializer): 'component', 'description', 'default_location', + 'default_expiry', 'full_name', 'image', 'in_stock', diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index af0c85fc02..ef3ddef33c 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -618,8 +618,8 @@ function loadStockTable(table, options) { if (action == 'move') { secondary.push({ field: 'destination', - label: 'New Location', - title: 'Create new location', + label: '{% trans "New Location" %}', + title: '{% trans "Create new location" %}', url: "/stock/location/new/", }); } @@ -837,14 +837,25 @@ function createNewStockItem(options) { } ); - // Disable serial number field if the part is not trackable + // Request part information from the server inventreeGet( `/api/part/${value}/`, {}, { success: function(response) { - + + // Disable serial number field if the part is not trackable enableField('serial_numbers', response.trackable); clearField('serial_numbers'); + + // Populate the expiry date + if (response.default_expiry <= 0) { + // No expiry date + clearField('expiry_date'); + } else { + var expiry = moment().add(response.default_expiry, 'days'); + + setFieldValue('expiry_date', expiry.format("YYYY-MM-DD")); + } } } ); From 213d6550d3d81f011e5f16f13da9986ab0774858 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 00:54:05 +1100 Subject: [PATCH 14/27] Add new setock settings --- InvenTree/common/models.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index fa1d4dc5c9..ab1e7cf049 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -139,6 +139,20 @@ class InvenTreeSetting(models.Model): 'validator': bool, }, + 'STOCK_ALLOW_EXPIRED_SALE': { + 'name': _('Sell Expired Stock'), + 'description': _('Allow sale of expired stock'), + 'default': False, + 'validator': bool, + }, + + 'STOCK_ALLOW_EXPIRED_BUILD': { + 'name': _('Build Expired Stock'), + 'description': _('Allow building with expired stock'), + 'default': False, + 'validator': bool, + }, + 'BUILDORDER_REFERENCE_PREFIX': { 'name': _('Build Order Reference Prefix'), 'description': _('Prefix value for build order reference'), @@ -479,6 +493,19 @@ class InvenTreeSetting(models.Model): return InvenTree.helpers.str2bool(self.value) + def is_int(self): + """ + Check if the setting is required to be an integer value: + + - int / 'int' = any integer value + - 'pos' / 'positive' = any positive integer value (including zero) + - 'neg' / 'negative' = any negative integer value (including zero) + """ + + valiator = InvenTreeSetting.get_setting_validator(self.key) + + return validator in [int, 'int', 'pos', 'positive', 'neg', 'negative'] + class PriceBreak(models.Model): """ From 855098e30bcbe1330372cf025663cfd448b1dfa0 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 00:58:48 +1100 Subject: [PATCH 15/27] Merge conflicting migration files --- .../part/migrations/0062_merge_20210105_0056.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 InvenTree/part/migrations/0062_merge_20210105_0056.py diff --git a/InvenTree/part/migrations/0062_merge_20210105_0056.py b/InvenTree/part/migrations/0062_merge_20210105_0056.py new file mode 100644 index 0000000000..4a8f4378f4 --- /dev/null +++ b/InvenTree/part/migrations/0062_merge_20210105_0056.py @@ -0,0 +1,14 @@ +# Generated by Django 3.0.7 on 2021-01-04 13:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0061_auto_20210104_2331'), + ('part', '0061_auto_20210103_2313'), + ] + + operations = [ + ] From 1335c85de1e31a1e62a866fde1b77723ab574edf Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 00:59:10 +1100 Subject: [PATCH 16/27] Edit new stock settings on settings page --- InvenTree/common/models.py | 4 ++++ InvenTree/templates/InvenTree/settings/stock.html | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 6183d6cc78..ac1c0e70b3 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -156,6 +156,10 @@ class InvenTreeSetting(models.Model): 'PART_VIRTUAL': { 'name': _('Virtual'), 'description': _('Parts are virtual by default'), + 'default': False, + 'validator': bool, + }, + 'STOCK_ALLOW_EXPIRED_SALE': { 'name': _('Sell Expired Stock'), 'description': _('Allow sale of expired stock'), diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index 7bec8b566c..dda5f2c940 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -10,7 +10,13 @@ {% endblock %} {% block settings %} -
- No Stock settings available -
+

{% trans "Stock Options" %}

+ + + {% include "InvenTree/settings/header.html" %} + + {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_BUILD" %} + +
{% endblock %} \ No newline at end of file From d0fb69e67d4526d865bb1dbe6384ac26bb001afb Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 5 Jan 2021 08:50:07 +1100 Subject: [PATCH 17/27] Add option to enable / disable stock expiry feature - Simply hides fields in form views --- InvenTree/common/models.py | 15 +++++++++++++-- InvenTree/common/settings.py | 8 ++++++++ InvenTree/part/views.py | 10 ++++++++++ InvenTree/stock/views.py | 8 ++++++++ InvenTree/templates/InvenTree/settings/stock.html | 1 + 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index ac1c0e70b3..69c499f3b8 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -159,7 +159,14 @@ class InvenTreeSetting(models.Model): 'default': False, 'validator': bool, }, - + + 'STOCK_ENABLE_EXPIRY': { + 'name': _('Stock Expiry'), + 'description': _('Enable stock expiry functionality'), + 'default': False, + 'validator': bool, + }, + 'STOCK_ALLOW_EXPIRED_SALE': { 'name': _('Sell Expired Stock'), 'description': _('Allow sale of expired stock'), @@ -373,6 +380,10 @@ class InvenTreeSetting(models.Model): if setting.is_bool(): value = InvenTree.helpers.str2bool(value) + if setting.is_int(): + # TODO - Coerce to an integer value + pass + else: value = backup_value @@ -523,7 +534,7 @@ class InvenTreeSetting(models.Model): - 'neg' / 'negative' = any negative integer value (including zero) """ - valiator = InvenTreeSetting.get_setting_validator(self.key) + validator = InvenTreeSetting.get_setting_validator(self.key) return validator in [int, 'int', 'pos', 'positive', 'neg', 'negative'] diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py index 832a07f040..134d3f3f7c 100644 --- a/InvenTree/common/settings.py +++ b/InvenTree/common/settings.py @@ -21,3 +21,11 @@ def currency_code_default(): code = 'USD' return code + + +def stock_expiry_enabled(): + """ + Returns True if the stock expiry feature is enabled + """ + + return InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY') diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index ac4925685f..1d76860ac1 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -35,6 +35,8 @@ from .models import PartSellPriceBreak from common.models import InvenTreeSetting from company.models import SupplierPart +import common.settings as inventree_settings + from . import forms as part_forms from .bom import MakeBomTemplate, BomUploadManager, ExportBom, IsValidBOMFormat @@ -626,6 +628,10 @@ class PartCreate(AjaxCreateView): """ form = super(AjaxCreateView, self).get_form() + # Hide the "default expiry" field if the feature is not enabled + if not inventree_settings.stock_expiry_enabled(): + form.fields.pop('default_expiry') + # Hide the default_supplier field (there are no matching supplier parts yet!) form.fields['default_supplier'].widget = HiddenInput() @@ -918,6 +924,10 @@ class PartEdit(AjaxUpdateView): form = super(AjaxUpdateView, self).get_form() + # Hide the "default expiry" field if the feature is not enabled + if not inventree_settings.stock_expiry_enabled(): + form.fields.pop('default_expiry') + part = self.get_object() form.fields['default_supplier'].queryset = SupplierPart.objects.filter(part=part) diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 963d357e31..ab6f64fb44 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1302,6 +1302,10 @@ class StockItemEdit(AjaxUpdateView): form = super(AjaxUpdateView, self).get_form() + # Hide the "expiry date" field if the feature is not enabled + if not common.settings.stock_expiry_enabled(): + form.fields.pop('expiry_date') + item = self.get_object() # If the part cannot be purchased, hide the supplier_part field @@ -1513,6 +1517,10 @@ class StockItemCreate(AjaxCreateView): form = super().get_form() + # Hide the "expiry date" field if the feature is not enabled + if not common.settings.stock_expiry_enabled(): + form.fields.pop('expiry_date') + part = self.get_part(form=form) if part is not None: diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index dda5f2c940..051a251c24 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -15,6 +15,7 @@ {% include "InvenTree/settings/header.html" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_ENABLE_EXPIRY" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_BUILD" %} From 9b086560cbf2b5033b7e9529e00810a5032185a6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 12:09:48 +1100 Subject: [PATCH 18/27] Hide "expiry_date" column in Stock table if feature not enabled --- InvenTree/templates/js/stock.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index ef3ddef33c..d32f52f92b 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -1,4 +1,5 @@ {% load i18n %} +{% load inventree_extras %} {% load status_codes %} /* Stock API functions @@ -587,11 +588,14 @@ function loadStockTable(table, options) { return locationDetail(row); } }, + {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} + {% if expiry %} { field: 'expiry_date', title: '{% trans "Expiry Date" %}', sortable: true, }, + {% endif %} { field: 'notes', title: '{% trans "Notes" %}', From 580e7599a0b414d2f6dfee51846381eabfe558ba Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 20:22:56 +1100 Subject: [PATCH 19/27] Prevent expired stock from being added to a sales order --- InvenTree/order/views.py | 9 ++++++++- InvenTree/templates/InvenTree/index.html | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index d5658909bb..bd758e39fb 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -24,6 +24,8 @@ from company.models import Company, SupplierPart from stock.models import StockItem, StockLocation from part.models import Part +from common.models import InvenTreeSetting + from . import forms as order_forms from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView @@ -1359,7 +1361,8 @@ class SalesOrderAllocationCreate(AjaxCreateView): try: line = SalesOrderLineItem.objects.get(pk=line_id) - queryset = form.fields['item'].queryset + # Construct a queryset for allowable stock items + queryset = StockItem.objects.filter(StockItem.IN_STOCK_FILTER) # Ensure the part reference matches queryset = queryset.filter(part=line.part) @@ -1369,6 +1372,10 @@ class SalesOrderAllocationCreate(AjaxCreateView): queryset = queryset.exclude(pk__in=allocated) + # Exclude stock items which have expired + if not InvenTreeSetting.get_setting('STOCK_ALLOW_EXPIRED_SALE'): + queryset = queryset.exclude(StockItem.EXPIRED_FILTER) + form.fields['item'].queryset = queryset # Hide the 'line' field diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index d5b2c87148..9fcb818c7a 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load i18n %} +{% load inventree_extras %} {% block page_title %} InvenTree | {% trans "Index" %} {% endblock %} @@ -22,7 +23,10 @@ InvenTree | {% trans "Index" %}
{% if roles.stock.view %} {% include "InvenTree/low_stock.html" with collapse_id="order" %} + {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} + {% if expiry %} {% include "InvenTree/expired_stock.html" with collapse_id="expired" %} + {% endif %} {% include "InvenTree/required_stock_build.html" with collapse_id="stock_to_build" %} {% endif %}
From 33d6396a4eeba699c3b81b5b1ad43be847147b91 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 21:00:45 +1100 Subject: [PATCH 20/27] Exclude expired stock from builds --- InvenTree/build/models.py | 6 ++++++ InvenTree/build/views.py | 1 + InvenTree/stock/test_views.py | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 488a8b79e8..96101adafe 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -27,6 +27,8 @@ from InvenTree.helpers import increment, getSetting, normalize from InvenTree.validators import validate_build_order_reference from InvenTree.models import InvenTreeAttachment +from common.models import InvenTreeSetting + import InvenTree.fields from stock import models as StockModels @@ -819,6 +821,10 @@ class Build(MPTTModel): location__in=[loc for loc in self.take_from.getUniqueChildren()] ) + # Exclude expired stock items + if not InvenTreeSetting.get_setting('STOCK_ALLOW_EXPIRED_BUILD'): + items = items.exclude(StockModels.StockItem.EXPIRED_FILTER) + return items @property diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 69d1dd9415..0887c49397 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -903,6 +903,7 @@ class BuildItemCreate(AjaxCreateView): if self.build and self.part: available_items = self.build.availableStockItems(self.part, self.output) + form.fields['stock_item'].queryset = available_items self.available_stock = form.fields['stock_item'].queryset.all() diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py index 021ca9e138..b842de3836 100644 --- a/InvenTree/stock/test_views.py +++ b/InvenTree/stock/test_views.py @@ -5,6 +5,8 @@ from django.urls import reverse from django.contrib.auth import get_user_model from django.contrib.auth.models import Group +from common.models import InvenTreeSetting + import json from datetime import datetime, timedelta @@ -32,6 +34,9 @@ class StockViewTestCase(TestCase): password='password' ) + self.user.is_staff = True + self.user.save() + # Put the user into a group with the correct permissions group = Group.objects.create(name='mygroup') self.user.groups.add(group) @@ -163,6 +168,9 @@ class StockItemTest(StockViewTestCase): and should be in the future! """ + # First, ensure that the expiry date feature is enabled! + InvenTreeSetting.set_setting('STOCK_ENABLE_EXPIRY', True, self.user) + url = reverse('stock-item-create') response = self.client.get(url, {'part': 25}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') From ba915da22b8013f9bdb548a4d15dfbc8ed5b5def Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 22:20:54 +1100 Subject: [PATCH 21/27] Filter StockItem API by staleness --- InvenTree/build/models.py | 4 +- InvenTree/common/models.py | 68 ++++++++++++++----- InvenTree/stock/api.py | 40 ++++++++--- .../templates/InvenTree/settings/setting.html | 2 +- .../templates/InvenTree/settings/stock.html | 1 + 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 96101adafe..c3b399d820 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -27,7 +27,7 @@ from InvenTree.helpers import increment, getSetting, normalize from InvenTree.validators import validate_build_order_reference from InvenTree.models import InvenTreeAttachment -from common.models import InvenTreeSetting +import common.models import InvenTree.fields @@ -822,7 +822,7 @@ class Build(MPTTModel): ) # Exclude expired stock items - if not InvenTreeSetting.get_setting('STOCK_ALLOW_EXPIRED_BUILD'): + if not common.models.InvenTreeSetting.get_setting('STOCK_ALLOW_EXPIRED_BUILD'): items = items.exclude(StockModels.StockItem.EXPIRED_FILTER) return items diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 69c499f3b8..5651a401de 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -174,6 +174,14 @@ class InvenTreeSetting(models.Model): 'validator': bool, }, + 'STOCK_STALE_DAYS': { + 'name': _('Stock Stale Time'), + 'description': _('Number of days stock items are considered stale before expiring'), + 'default': 0, + 'units': _('days'), + 'validator': [int], + }, + 'STOCK_ALLOW_EXPIRED_BUILD': { 'name': _('Build Expired Stock'), 'description': _('Allow building with expired stock'), @@ -381,8 +389,10 @@ class InvenTreeSetting(models.Model): value = InvenTree.helpers.str2bool(value) if setting.is_int(): - # TODO - Coerce to an integer value - pass + try: + value = int(value) + except (ValueError, TypeError): + value = backup_value else: value = backup_value @@ -472,18 +482,26 @@ class InvenTreeSetting(models.Model): return - # Check if a 'type' has been specified for this value - if type(validator) == type: + # Boolean validator + if validator == bool: + # Value must "look like" a boolean value + if InvenTree.helpers.is_bool(self.value): + # Coerce into either "True" or "False" + self.value = str(InvenTree.helpers.str2bool(self.value)) + else: + raise ValidationError({ + 'value': _('Value must be a boolean value') + }) - if validator == bool: - # Value must "look like" a boolean value - if InvenTree.helpers.is_bool(self.value): - # Coerce into either "True" or "False" - self.value = str(InvenTree.helpers.str2bool(self.value)) - else: - raise ValidationError({ - 'value': _('Value must be a boolean value') - }) + # Integer validator + if validator == int: + try: + # Coerce into an integer value + self.value = str(int(self.value)) + except (ValueError, TypeError): + raise ValidationError({ + 'value': _('Value must be an integer value'), + }) def validate_unique(self, exclude=None): """ Ensure that the key:value pair is unique. @@ -528,15 +546,29 @@ class InvenTreeSetting(models.Model): def is_int(self): """ Check if the setting is required to be an integer value: - - - int / 'int' = any integer value - - 'pos' / 'positive' = any positive integer value (including zero) - - 'neg' / 'negative' = any negative integer value (including zero) """ validator = InvenTreeSetting.get_setting_validator(self.key) - return validator in [int, 'int', 'pos', 'positive', 'neg', 'negative'] + if validator == int: + return True + + if type(validator) in [list, tuple]: + for v in validator: + if v == int: + return True + + def as_int(self): + """ + Return the value of this setting converted to a boolean value. + + If an error occurs, return the default value + """ + + try: + value = int() + except (ValueError, TypeError): + return self.default_value() class PriceBreak(models.Model): diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 97f5b5c42e..9f0a4278f5 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -23,6 +23,9 @@ from part.serializers import PartBriefSerializer from company.models import SupplierPart from company.serializers import SupplierPartSerializer +import common.settings +import common.models + from .serializers import StockItemSerializer from .serializers import LocationSerializer, LocationBriefSerializer from .serializers import StockTrackingSerializer @@ -535,16 +538,37 @@ class StockList(generics.ListCreateAPIView): # Exclude items which are instaled in another item queryset = queryset.filter(belongs_to=None) - # Filter by 'expired' status - expired = params.get('expired', None) + if common.settings.stock_expiry_enabled(): - if expired is not None: - expired = str2bool(expired) + # Filter by 'expired' status + expired = params.get('expired', None) - if expired: - queryset = queryset.filter(StockItem.EXPIRED_FILTER) - else: - queryset = queryset.exclude(StockItem.EXPIRED_FILTER) + if expired is not None: + expired = str2bool(expired) + + if expired: + queryset = queryset.filter(StockItem.EXPIRED_FILTER) + else: + queryset = queryset.exclude(StockItem.EXPIRED_FILTER) + + # Filter by 'stale' status + stale = params.get('stale', None) + + if stale is not None: + stale = str2bool(stale) + + # How many days to account for "staleness"? + stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS') + + if stale_days > 0: + stale_date = datetime.now().date() + timedelta(days=stale_days) + + stale_filter = StockItem.IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=stale_date) + + if stale: + queryset = queryset.filter(stale_filter) + else: + queryset = queryset.exclude(stale_filter) # Filter by customer customer = params.get('customer', None) diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index b08f4aeb3a..b7932fc30a 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -17,7 +17,7 @@ {% else %} {% if setting.value %} - {{ setting.value }}{{ setting.units }} + {{ setting.value }} {{ setting.units }} {% else %} {% trans "No value set" %} diff --git a/InvenTree/templates/InvenTree/settings/stock.html b/InvenTree/templates/InvenTree/settings/stock.html index 051a251c24..5ad308decc 100644 --- a/InvenTree/templates/InvenTree/settings/stock.html +++ b/InvenTree/templates/InvenTree/settings/stock.html @@ -16,6 +16,7 @@ {% include "InvenTree/settings/header.html" %}
{% include "InvenTree/settings/setting.html" with key="STOCK_ENABLE_EXPIRY" %} + {% include "InvenTree/settings/setting.html" with key="STOCK_STALE_DAYS" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_SALE" %} {% include "InvenTree/settings/setting.html" with key="STOCK_ALLOW_EXPIRED_BUILD" %} From e62873a65028c0b051b055f86fc4530254d240dd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 22:21:18 +1100 Subject: [PATCH 22/27] Display "stale" status on StockItem info page --- InvenTree/stock/models.py | 35 +++++++++++++++++-- .../stock/templates/stock/item_base.html | 7 +++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index d2d6ec51ee..533aca7596 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -27,9 +27,11 @@ from mptt.models import MPTTModel, TreeForeignKey from djmoney.models.fields import MoneyField from decimal import Decimal, InvalidOperation -from datetime import datetime +from datetime import datetime, timedelta from InvenTree import helpers +import common.models + from InvenTree.status_codes import StockStatus from InvenTree.models import InvenTreeTree, InvenTreeAttachment from InvenTree.fields import InvenTreeURLField @@ -471,9 +473,38 @@ class StockItem(MPTTModel): help_text=_('Single unit purchase price at time of purchase'), ) + def is_stale(self): + """ + Returns True if this Stock item is "stale". + + To be "stale", the following conditions must be met: + + - Expiry date is not None + - Expiry date will "expire" within the configured stale date + - The StockItem is otherwise "in stock" + """ + + if self.expiry_date is None: + return False + + if not self.in_stock: + return False + + today = datetime.now().date() + + stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS') + + if stale_days <= 0: + return False + + expiry_date = today + timedelta(days=stale_days) + + return self.expiry_date < expiry_date + + def is_expired(self): """ - Returns true if this StockItem is "expired" + Returns True if this StockItem is "expired". To be "expired", the following conditions must be met: diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index b8c143fb4f..cbb6eeafb2 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -74,6 +74,9 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% trans "Expired" %} {% else %} {% stock_status_label item.status large=True %} + {% if item.is_stale %} + {% trans "Stale" %} + {% endif %} {% endif %}
@@ -304,7 +307,9 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
From a5b18640afc95218afc53802eca2eb726a5ac5fc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 22:30:12 +1100 Subject: [PATCH 23/27] Display stale stock on index page --- .../templates/InvenTree/expired_stock.html | 2 +- InvenTree/templates/InvenTree/index.html | 84 +++++-------------- .../templates/InvenTree/index/on_load.html | 5 ++ .../templates/InvenTree/stale_stock.html | 15 ++++ 4 files changed, 42 insertions(+), 64 deletions(-) create mode 100644 InvenTree/templates/InvenTree/index/on_load.html create mode 100644 InvenTree/templates/InvenTree/stale_stock.html diff --git a/InvenTree/templates/InvenTree/expired_stock.html b/InvenTree/templates/InvenTree/expired_stock.html index 7ecf117fa7..20e2591c16 100644 --- a/InvenTree/templates/InvenTree/expired_stock.html +++ b/InvenTree/templates/InvenTree/expired_stock.html @@ -3,7 +3,7 @@ {% load i18n %} {% block collapse_title %} - + {% trans "Expired Stock" %} {% endblock %} diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index 9fcb818c7a..d1e10cb00e 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -26,6 +26,7 @@ InvenTree | {% trans "Index" %} {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} {% if expiry %} {% include "InvenTree/expired_stock.html" with collapse_id="expired" %} + {% include "InvenTree/stale_stock.html" with collapse_id="stale" %} {% endif %} {% include "InvenTree/required_stock_build.html" with collapse_id="stock_to_build" %} {% endif %} @@ -98,6 +99,14 @@ loadStockTable($("#expired-stock-table"), { }, }); +loadStockTable($("#stale-stock-table"), { + params: { + stale: true, + location_detail: true, + part_detail: true, + }, +}); + loadSimplePartTable("#low-stock-table", "{% url 'api-part-list' %}", { params: { low_stock: true, @@ -136,70 +145,19 @@ loadSalesOrderTable("#so-overdue-table", { } }); -$("#latest-parts-table").on('load-success.bs.table', function() { - var count = $("#latest-parts-table").bootstrapTable('getData').length; +{% include "InvenTree/index/on_load.html" with label="latest-parts" %} +{% include "InvenTree/index/on_load.html" with label="starred-parts" %} +{% include "InvenTree/index/on_load.html" with label="bom-invalid" %} +{% include "InvenTree/index/on_load.html" with label="build-pending" %} +{% include "InvenTree/index/on_load.html" with label="build-overdue" %} - $("#latest-parts-count").html(count); -}); +{% include "InvenTree/index/on_load.html" with label="expired-stock" %} +{% include "InvenTree/index/on_load.html" with label="stale-stock" %} +{% include "InvenTree/index/on_load.html" with label="low-stock" %} +{% include "InvenTree/index/on_load.html" with label="stock-to-build" %} -$("#starred-parts-table").on('load-success.bs.table', function() { - var count = $("#starred-parts-table").bootstrapTable('getData').length; - - $("#starred-parts-count").html(count); -}); - -$("#bom-invalid-table").on('load-success.bs.table', function() { - var count = $("#bom-invalid-table").bootstrapTable('getData').length; - - $("#bom-invalid-count").html(count); -}); - -$("#build-pending-table").on('load-success.bs.table', function() { - var count = $("#build-pending-table").bootstrapTable('getData').length; - - $("#build-pending-count").html(count); -}); - -$("#build-overdue-table").on('load-success.bs.table', function() { - var count = $("#build-overdue-table").bootstrapTable('getData').length; - - $("#build-overdue-count").html(count); -}); - -$("#expired-stock-table").on('load-success.bs.table', function() { - var count = $("#expired-stock-table").bootstrapTable('getData').length; - - $("#expired-stock-count").html(count); -}); - -$("#low-stock-table").on('load-success.bs.table', function() { - var count = $("#low-stock-table").bootstrapTable('getData').length; - - $("#low-stock-count").html(count); -}); - -$("#stock-to-build-table").on('load-success.bs.table', function() { - var count = $("#stock-to-build-table").bootstrapTable('getData').length; - - $("#stock-to-build-count").html(count); -}); - -$("#po-outstanding-table").on('load-success.bs.table', function() { - var count = $("#po-outstanding-table").bootstrapTable('getData').length; - - $("#po-outstanding-count").html(count); -}); - -$("#so-outstanding-table").on('load-success.bs.table', function() { - var count = $("#so-outstanding-table").bootstrapTable('getData').length; - - $("#so-outstanding-count").html(count); -}); - -$("#so-overdue-table").on('load-success.bs.table', function() { - var count = $("#so-overdue-table").bootstrapTable('getData').length; - - $("#so-overdue-count").html(count); -}); +{% include "InvenTree/index/on_load.html" with label="po-outstanding" %} +{% include "InvenTree/index/on_load.html" with label="so-outstanding" %} +{% include "InvenTree/index/on_load.html" with label="so-overdue" %} {% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/index/on_load.html b/InvenTree/templates/InvenTree/index/on_load.html new file mode 100644 index 0000000000..a63479e60d --- /dev/null +++ b/InvenTree/templates/InvenTree/index/on_load.html @@ -0,0 +1,5 @@ +$("#{{ label }}-table").on('load-success.bs.table', function() { + var count = $("#{{ label }}-table").bootstrapTable('getData').length; + + $("#{{ label }}-count").html(count); +}); \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/stale_stock.html b/InvenTree/templates/InvenTree/stale_stock.html new file mode 100644 index 0000000000..3cbb74369c --- /dev/null +++ b/InvenTree/templates/InvenTree/stale_stock.html @@ -0,0 +1,15 @@ +{% extends "collapse_index.html" %} + +{% load i18n %} + +{% block collapse_title %} + +{% trans "Stale Stock" %} +{% endblock %} + +{% block collapse_content %} + +
{{ item.expiry_date }} {% if item.is_expired %} - {% trans "Expired" %} + {% trans "Expired" %} + {% elif item.is_stale %} + {% trans "Stale" %} {% endif %}
+
+ +{% endblock %} \ No newline at end of file From 1d6a049c5a3f0aed0c35cda4661d4a9d98281167 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 23:06:49 +1100 Subject: [PATCH 24/27] Annotate stock queryset with stale status --- InvenTree/stock/serializers.py | 21 +++++++++++++++++++++ InvenTree/templates/InvenTree/index.html | 1 + InvenTree/templates/js/stock.js | 4 +++- InvenTree/templates/js/table_filters.js | 5 +++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 9db1d95dae..70ad1abc18 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -13,11 +13,15 @@ from django.db.models.functions import Coalesce from django.db.models import Case, When, Value from django.db.models import BooleanField +from django.db.models import Q from sql_util.utils import SubquerySum, SubqueryCount from decimal import Decimal +from datetime import datetime, timedelta + +import common.models from company.serializers import SupplierPartSerializer from part.serializers import PartBriefSerializer from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer @@ -119,6 +123,20 @@ class StockItemSerializer(InvenTreeModelSerializer): ) ) + # Add flag to indicate if the StockItem is stale + stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS') + stale_date = datetime.now().date() + timedelta(days=stale_days) + stale_filter = StockItem.IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=stale_date) + + queryset = queryset.annotate( + stale=Case( + When( + stale_filter, then=Value(True, output_field=BooleanField()), + ), + default=Value(False, output_field=BooleanField()), + ) + ) + return queryset status_text = serializers.CharField(source='get_status_display', read_only=True) @@ -137,6 +155,8 @@ class StockItemSerializer(InvenTreeModelSerializer): expired = serializers.BooleanField(required=False, read_only=True) + stale = serializers.BooleanField(required=False, read_only=True) + serial = serializers.CharField(required=False) required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False) @@ -185,6 +205,7 @@ class StockItemSerializer(InvenTreeModelSerializer): 'required_tests', 'sales_order', 'serial', + 'stale', 'status', 'status_text', 'supplier_part', diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index d1e10cb00e..1b9a492a22 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -102,6 +102,7 @@ loadStockTable($("#expired-stock-table"), { loadStockTable($("#stale-stock-table"), { params: { stale: true, + expired: false, location_detail: true, part_detail: true, }, diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index d32f52f92b..e3f124d252 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -534,7 +534,9 @@ function loadStockTable(table, options) { } if (row.expired) { - html += makeIconBadge('fa-stopwatch icon-red', '{% trans "Stock item has expired" %}'); + html += makeIconBadge('fa-calendar-times icon-red', '{% trans "Stock item has expired" %}'); + } else if (row.stale) { + html += makeIconBadge('fa-stopwatch', '{% trans "Stock item will expire soon" %}'); } if (row.allocated) { diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/table_filters.js index 054542a4a3..84aa12c139 100644 --- a/InvenTree/templates/js/table_filters.js +++ b/InvenTree/templates/js/table_filters.js @@ -111,6 +111,11 @@ function getAvailableTableFilters(tableKey) { title: '{% trans "Expired" %}', description: '{% trans "Show stock items which have expired" %}', }, + stale: { + type: 'bool', + title: '{% trans "Stale" %}', + description: '{% trans "Show stock which is close to expiring" %}', + }, in_stock: { type: 'bool', title: '{% trans "In Stock" %}', From e5b346e7fa778294d15a5a30354eb1dbae8fcead Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 23:09:26 +1100 Subject: [PATCH 25/27] PEP fixes --- InvenTree/common/models.py | 6 ++++-- InvenTree/stock/models.py | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 5651a401de..5c04f9a7e9 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -566,10 +566,12 @@ class InvenTreeSetting(models.Model): """ try: - value = int() + value = int(self.value) except (ValueError, TypeError): - return self.default_value() + value = self.default_value() + return value + class PriceBreak(models.Model): """ diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 533aca7596..d399c0daf1 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -501,7 +501,6 @@ class StockItem(MPTTModel): return self.expiry_date < expiry_date - def is_expired(self): """ Returns True if this StockItem is "expired". From a8e3e6c8db7e4b3c783b2a122ac7075b00319dd5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 23:12:52 +1100 Subject: [PATCH 26/27] Update translation --- InvenTree/locale/de/LC_MESSAGES/django.po | 1300 ++++++++++++--------- InvenTree/locale/en/LC_MESSAGES/django.po | 1268 +++++++++++--------- InvenTree/locale/es/LC_MESSAGES/django.po | 1268 +++++++++++--------- 3 files changed, 2138 insertions(+), 1698 deletions(-) diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 74dfaadcaa..3dd1bc4ac8 100644 --- a/InvenTree/locale/de/LC_MESSAGES/django.po +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-03 22:16+1100\n" +"POT-Creation-Date: 2021-01-06 23:11+1100\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -62,7 +62,7 @@ msgid "Select Category" msgstr "Teilkategorie auswählen" #: InvenTree/helpers.py:361 order/models.py:178 order/models.py:260 -#: stock/views.py:1647 +#: stock/views.py:1660 msgid "Invalid quantity provided" msgstr "Keine gültige Menge" @@ -105,12 +105,12 @@ msgstr "Datei zum Anhängen auswählen" msgid "File comment" msgstr "Datei-Kommentar" -#: InvenTree/models.py:68 templates/js/stock.js:744 +#: InvenTree/models.py:68 templates/js/stock.js:759 msgid "User" msgstr "Benutzer" -#: InvenTree/models.py:106 part/templates/part/params.html:24 -#: templates/js/part.js:129 +#: InvenTree/models.py:106 part/models.py:647 +#: part/templates/part/params.html:24 templates/js/part.js:129 msgid "Name" msgstr "Name" @@ -331,14 +331,14 @@ msgstr "Bestell-Referenz" msgid "Order target date" msgstr "Kein Ziel gesetzt" -#: build/forms.py:39 build/models.py:173 +#: build/forms.py:39 build/models.py:175 msgid "" "Target date for build completion. Build will be overdue after this date." msgstr "" #: build/forms.py:78 build/templates/build/auto_allocate.html:17 #: build/templates/build/build_base.html:83 -#: build/templates/build/detail.html:29 common/models.py:496 +#: build/templates/build/detail.html:29 common/models.py:589 #: company/forms.py:112 company/templates/company/supplier_part_pricing.html:75 #: order/templates/order/order_wizard/select_parts.html:32 #: order/templates/order/purchase_order_detail.html:179 @@ -346,13 +346,13 @@ msgstr "" #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 #: part/templates/part/allocation.html:49 -#: part/templates/part/sale_prices.html:82 stock/forms.py:298 +#: part/templates/part/sale_prices.html:82 stock/forms.py:304 #: stock/templates/stock/item_base.html:40 #: stock/templates/stock/item_base.html:46 -#: stock/templates/stock/item_base.html:197 +#: stock/templates/stock/item_base.html:204 #: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:195 templates/js/build.js:420 templates/js/stock.js:735 -#: templates/js/stock.js:963 +#: templates/js/bom.js:195 templates/js/build.js:420 templates/js/stock.js:750 +#: templates/js/stock.js:989 msgid "Quantity" msgstr "Anzahl" @@ -362,7 +362,7 @@ msgstr "Anzahl" msgid "Enter quantity for build output" msgstr "Seriennummer für dieses Teil" -#: build/forms.py:83 stock/forms.py:111 +#: build/forms.py:83 stock/forms.py:116 #, fuzzy #| msgid "Serial Number" msgid "Serial numbers" @@ -428,91 +428,92 @@ msgstr "Bauabbruch bestätigen" msgid "Select quantity of stock to allocate" msgstr "Lagerobjekt für Zuordnung auswählen" -#: build/models.py:59 build/templates/build/build_base.html:8 +#: build/models.py:61 build/templates/build/build_base.html:8 #: build/templates/build/build_base.html:35 #: part/templates/part/allocation.html:20 msgid "Build Order" msgstr "Bauauftrag" -#: build/models.py:60 build/templates/build/index.html:6 +#: build/models.py:62 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 #: templates/InvenTree/settings/tabs.html:28 users/models.py:30 msgid "Build Orders" msgstr "Bauaufträge" -#: build/models.py:75 +#: build/models.py:77 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference" msgstr "Bestellreferenz" -#: build/models.py:76 order/templates/order/purchase_order_detail.html:174 +#: build/models.py:78 order/templates/order/purchase_order_detail.html:174 #: templates/js/bom.js:187 templates/js/build.js:509 msgid "Reference" msgstr "Referenz" -#: build/models.py:83 build/templates/build/detail.html:19 +#: build/models.py:85 build/templates/build/detail.html:19 #: company/models.py:359 company/templates/company/detail.html:23 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 -#: order/templates/order/purchase_order_detail.html:161 +#: order/templates/order/purchase_order_detail.html:161 part/models.py:671 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:180 +#: templates/InvenTree/search.html:147 +#: templates/InvenTree/settings/header.html:9 templates/js/bom.js:180 #: templates/js/bom.js:517 templates/js/build.js:664 templates/js/company.js:56 #: templates/js/order.js:175 templates/js/order.js:263 templates/js/part.js:188 #: templates/js/part.js:271 templates/js/part.js:391 templates/js/part.js:572 -#: templates/js/stock.js:500 templates/js/stock.js:716 +#: templates/js/stock.js:501 templates/js/stock.js:731 msgid "Description" msgstr "Beschreibung" -#: build/models.py:86 +#: build/models.py:88 msgid "Brief description of the build" msgstr "Kurze Beschreibung des Baus" -#: build/models.py:95 build/templates/build/build_base.html:104 +#: build/models.py:97 build/templates/build/build_base.html:104 #: build/templates/build/detail.html:75 msgid "Parent Build" msgstr "Eltern-Bau" -#: build/models.py:96 +#: build/models.py:98 #, fuzzy #| msgid "SalesOrder to which this build is allocated" msgid "BuildOrder to which this build is allocated" msgstr "Bestellung, die diesem Bau zugwiesen ist" -#: build/models.py:101 build/templates/build/auto_allocate.html:16 +#: build/models.py:103 build/templates/build/auto_allocate.html:16 #: build/templates/build/build_base.html:78 #: build/templates/build/detail.html:24 order/models.py:548 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 -#: order/templates/order/receive_parts.html:19 part/models.py:315 +#: order/templates/order/receive_parts.html:19 part/models.py:316 #: part/templates/part/part_app_base.html:7 part/templates/part/related.html:26 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 #: templates/js/barcode.js:336 templates/js/bom.js:153 templates/js/bom.js:502 #: templates/js/build.js:669 templates/js/company.js:138 -#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:474 -#: templates/js/stock.js:1035 +#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:475 +#: templates/js/stock.js:1061 msgid "Part" msgstr "Teil" -#: build/models.py:109 +#: build/models.py:111 msgid "Select part to build" msgstr "Teil für den Bau wählen" -#: build/models.py:114 +#: build/models.py:116 msgid "Sales Order Reference" msgstr "Bestellungsreferenz" -#: build/models.py:118 +#: build/models.py:120 msgid "SalesOrder to which this build is allocated" msgstr "Bestellung, die diesem Bau zugwiesen ist" -#: build/models.py:123 +#: build/models.py:125 msgid "Source Location" msgstr "Quell-Standort" -#: build/models.py:127 +#: build/models.py:129 msgid "" "Select location to take stock from for this build (leave blank to take from " "any stock location)" @@ -520,155 +521,155 @@ msgstr "" "Lager-Entnahmestandort für diesen Bau wählen (oder leer lassen für einen " "beliebigen Lager-Standort)" -#: build/models.py:132 +#: build/models.py:134 #, fuzzy #| msgid "Destination stock location" msgid "Destination Location" msgstr "Ziel-Lagerbestand" -#: build/models.py:136 +#: build/models.py:138 msgid "Select location where the completed items will be stored" msgstr "" -#: build/models.py:140 +#: build/models.py:142 msgid "Build Quantity" msgstr "Bau-Anzahl" -#: build/models.py:143 +#: build/models.py:145 #, fuzzy #| msgid "Number of parts to build" msgid "Number of stock items to build" msgstr "Anzahl der zu bauenden Teile" -#: build/models.py:147 +#: build/models.py:149 #, fuzzy #| msgid "Completed" msgid "Completed items" msgstr "Fertig" -#: build/models.py:149 +#: build/models.py:151 #, fuzzy #| msgid "Delete this Stock Item when stock is depleted" msgid "Number of stock items which have been completed" msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" -#: build/models.py:153 part/templates/part/part_base.html:155 +#: build/models.py:155 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "Bau-Status" -#: build/models.py:157 +#: build/models.py:159 msgid "Build status code" msgstr "Bau-Statuscode" -#: build/models.py:161 stock/models.py:390 +#: build/models.py:163 stock/models.py:396 msgid "Batch Code" msgstr "Losnummer" -#: build/models.py:165 +#: build/models.py:167 msgid "Batch code for this build output" msgstr "Chargennummer für diese Bau-Ausgabe" -#: build/models.py:172 order/models.py:329 +#: build/models.py:174 order/models.py:329 msgid "Target completion date" msgstr "" -#: build/models.py:186 build/templates/build/detail.html:89 +#: build/models.py:188 build/templates/build/detail.html:89 #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:384 stock/templates/stock/item_base.html:280 +#: stock/models.py:390 stock/templates/stock/item_base.html:287 msgid "External Link" msgstr "Externer Link" -#: build/models.py:187 part/models.py:672 stock/models.py:386 +#: build/models.py:189 part/models.py:705 stock/models.py:392 msgid "Link to external URL" msgstr "Link zu einer externen URL" -#: build/models.py:191 build/templates/build/tabs.html:23 company/models.py:366 +#: build/models.py:193 build/templates/build/tabs.html:23 company/models.py:366 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 #: order/templates/order/purchase_order_detail.html:213 -#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 -#: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 -#: stock/models.py:1433 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:263 -#: templates/js/stock.js:116 templates/js/stock.js:588 +#: order/templates/order/so_tabs.html:23 part/models.py:831 +#: part/templates/part/tabs.html:73 stock/forms.py:313 stock/forms.py:345 +#: stock/forms.py:373 stock/models.py:462 stock/models.py:1476 +#: stock/templates/stock/tabs.html:26 templates/js/barcode.js:391 +#: templates/js/bom.js:263 templates/js/stock.js:117 templates/js/stock.js:603 msgid "Notes" msgstr "Notizen" -#: build/models.py:192 +#: build/models.py:194 msgid "Extra build notes" msgstr "Notizen für den Bau" -#: build/models.py:577 +#: build/models.py:579 #, fuzzy #| msgid "No action specified" msgid "No build output specified" msgstr "Keine Aktion angegeben" -#: build/models.py:580 +#: build/models.py:582 msgid "Build output is already completed" msgstr "" -#: build/models.py:583 +#: build/models.py:585 #, fuzzy #| msgid "Quantity does not match serial numbers" msgid "Build output does not match Build Order" msgstr "Anzahl stimmt nicht mit den Seriennummern überein" -#: build/models.py:658 +#: build/models.py:660 #, fuzzy #| msgid "Complete Build" msgid "Completed build output" msgstr "Bau fertigstellen" -#: build/models.py:896 +#: build/models.py:902 msgid "BuildItem must be unique for build, stock_item and install_into" msgstr "" -#: build/models.py:918 +#: build/models.py:924 #, fuzzy #| msgid "Allocate Stock to Build" msgid "Build item must specify a build output" msgstr "Lagerbestand dem Bau zuweisen" -#: build/models.py:923 +#: build/models.py:929 #, python-brace-format msgid "Selected stock item not found in BOM for part '{p}'" msgstr "Ausgewähltes Lagerobjekt nicht in BOM für Teil '{p}' gefunden" -#: build/models.py:927 +#: build/models.py:933 #, python-brace-format msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" "zugewiesene Anzahl ({n}) darf nicht die verfügbare ({q}) Anzahl überschreiten" -#: build/models.py:934 order/models.py:632 +#: build/models.py:940 order/models.py:632 msgid "StockItem is over-allocated" msgstr "Zu viele Lagerobjekte zugewiesen" -#: build/models.py:938 order/models.py:635 +#: build/models.py:944 order/models.py:635 msgid "Allocation quantity must be greater than zero" msgstr "Anzahl muss größer null sein" -#: build/models.py:942 +#: build/models.py:948 msgid "Quantity must be 1 for serialized stock" msgstr "Anzahl muss 1 für Objekte mit Seriennummer sein" -#: build/models.py:982 +#: build/models.py:988 msgid "Build to allocate parts" msgstr "Bau starten um Teile zuzuweisen" -#: build/models.py:989 +#: build/models.py:995 #, fuzzy #| msgid "Remove stock" msgid "Source stock item" msgstr "Bestand entfernen" -#: build/models.py:1001 +#: build/models.py:1007 msgid "Stock quantity to allocate to build" msgstr "Lagerobjekt-Anzahl dem Bau zuweisen" -#: build/models.py:1009 +#: build/models.py:1015 #, fuzzy #| msgid "Destination stock location" msgid "Destination stock item" @@ -705,7 +706,7 @@ msgid "Order required parts" msgstr "Teil bestellen" #: build/templates/build/allocate.html:30 -#: company/templates/company/detail_part.html:28 order/views.py:803 +#: company/templates/company/detail_part.html:28 order/views.py:805 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "Teile bestellen" @@ -753,11 +754,11 @@ msgid "" "The following stock items will be allocated to the specified build output" msgstr "Lagerobjekt dem Bau zuweisen" -#: build/templates/build/auto_allocate.html:18 stock/forms.py:337 -#: stock/templates/stock/item_base.html:227 +#: build/templates/build/auto_allocate.html:18 stock/forms.py:343 +#: stock/templates/stock/item_base.html:234 #: stock/templates/stock/stock_adjust.html:17 #: templates/InvenTree/search.html:183 templates/js/barcode.js:337 -#: templates/js/build.js:434 templates/js/stock.js:580 +#: templates/js/build.js:434 templates/js/stock.js:587 msgid "Location" msgstr "Standort" @@ -790,7 +791,7 @@ msgstr "Dieser Bau ist Kind von Bau" #: order/templates/order/order_base.html:26 #: order/templates/order/sales_order_base.html:35 #: part/templates/part/category.html:13 part/templates/part/part_base.html:32 -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:97 #: stock/templates/stock/location.html:12 #, fuzzy #| msgid "Admin" @@ -801,7 +802,7 @@ msgstr "Admin" #: build/templates/build/build_base.html:92 #: order/templates/order/sales_order_base.html:41 #: order/templates/order/sales_order_base.html:83 -#: templates/js/table_filters.js:190 templates/js/table_filters.js:222 +#: templates/js/table_filters.js:200 templates/js/table_filters.js:232 msgid "Overdue" msgstr "" @@ -830,10 +831,10 @@ msgstr "Bau-Status" #: build/templates/build/build_base.html:88 #: build/templates/build/detail.html:57 #: order/templates/order/receive_parts.html:24 -#: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 +#: stock/templates/stock/item_base.html:333 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:697 #: templates/js/order.js:180 templates/js/order.js:268 -#: templates/js/stock.js:567 templates/js/stock.js:971 +#: templates/js/stock.js:574 templates/js/stock.js:997 msgid "Status" msgstr "Status" @@ -853,7 +854,7 @@ msgstr "" #: order/templates/order/sales_order_notes.html:10 #: order/templates/order/sales_order_ship.html:25 #: part/templates/part/allocation.html:27 -#: stock/templates/stock/item_base.html:221 templates/js/order.js:229 +#: stock/templates/stock/item_base.html:228 templates/js/order.js:229 msgid "Sales Order" msgstr "Bestellung" @@ -974,7 +975,7 @@ msgstr "Lagerobjekt" msgid "Stock can be taken from any available location." msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden." -#: build/templates/build/detail.html:44 stock/forms.py:365 +#: build/templates/build/detail.html:44 stock/forms.py:371 #, fuzzy #| msgid "Description" msgid "Destination" @@ -987,9 +988,9 @@ msgid "Destination location not specified" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" #: build/templates/build/detail.html:68 -#: stock/templates/stock/item_base.html:245 templates/js/stock.js:575 -#: templates/js/stock.js:978 templates/js/table_filters.js:80 -#: templates/js/table_filters.js:151 +#: stock/templates/stock/item_base.html:252 templates/js/stock.js:582 +#: templates/js/stock.js:1004 templates/js/table_filters.js:80 +#: templates/js/table_filters.js:161 msgid "Batch" msgstr "Los" @@ -1100,7 +1101,7 @@ msgstr "Lagerbestand dem Bau zuweisen" msgid "Create Build Output" msgstr "Bau-Ausgabe" -#: build/views.py:207 stock/models.py:828 stock/views.py:1668 +#: build/views.py:207 stock/models.py:871 stock/views.py:1681 #, fuzzy #| msgid "Serial numbers already exist: " msgid "Serial numbers already exist" @@ -1233,38 +1234,38 @@ msgstr "verfügbar" msgid "Stock item must be selected" msgstr "Lagerobjekt wurde zugewiesen" -#: build/views.py:1011 +#: build/views.py:1012 msgid "Edit Stock Allocation" msgstr "Teilzuordnung bearbeiten" -#: build/views.py:1016 +#: build/views.py:1017 msgid "Updated Build Item" msgstr "Bauobjekt aktualisiert" -#: build/views.py:1045 +#: build/views.py:1046 #, fuzzy #| msgid "Add Sales Order Attachment" msgid "Add Build Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:168 +#: build/views.py:1060 order/views.py:113 order/views.py:166 part/views.py:170 #: stock/views.py:180 msgid "Added attachment" msgstr "Anhang hinzugefügt" -#: build/views.py:1095 order/views.py:191 order/views.py:213 +#: build/views.py:1096 order/views.py:193 order/views.py:215 msgid "Edit Attachment" msgstr "Anhang bearbeiten" -#: build/views.py:1106 order/views.py:196 order/views.py:218 +#: build/views.py:1107 order/views.py:198 order/views.py:220 msgid "Attachment updated" msgstr "Anhang aktualisiert" -#: build/views.py:1116 order/views.py:233 order/views.py:248 +#: build/views.py:1117 order/views.py:235 order/views.py:250 msgid "Delete Attachment" msgstr "Anhang löschen" -#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:238 +#: build/views.py:1123 order/views.py:242 order/views.py:257 stock/views.py:238 msgid "Deleted attachment" msgstr "Anhang gelöscht" @@ -1360,125 +1361,208 @@ msgstr "Teilparametervorlage bearbeiten" msgid "Copy category parameter templates when creating a part" msgstr "" -#: common/models.py:115 part/models.py:743 part/templates/part/detail.html:168 -#: templates/js/table_filters.js:272 +#: common/models.py:115 part/templates/part/detail.html:155 stock/forms.py:255 +#: templates/js/table_filters.js:23 templates/js/table_filters.js:266 +msgid "Template" +msgstr "Vorlage" + +#: common/models.py:116 +#, fuzzy +#| msgid "Part is not a virtual part" +msgid "Parts are templates by default" +msgstr "Teil ist nicht virtuell" + +#: common/models.py:122 part/models.py:794 part/templates/part/detail.html:165 +#: templates/js/table_filters.js:278 +msgid "Assembly" +msgstr "Baugruppe" + +#: common/models.py:123 +#, fuzzy +#| msgid "Part can be assembled from other parts" +msgid "Parts can be assembled from other components by default" +msgstr "Teil kann aus anderen Teilen angefertigt werden" + +#: common/models.py:129 part/models.py:800 part/templates/part/detail.html:175 +#: templates/js/table_filters.js:282 msgid "Component" msgstr "Komponente" -#: common/models.py:116 +#: common/models.py:130 #, fuzzy #| msgid "Part can be used in assemblies" msgid "Parts can be used as sub-components by default" msgstr "Teil kann in Baugruppen benutzt werden" -#: common/models.py:122 part/models.py:754 part/templates/part/detail.html:188 +#: common/models.py:136 part/models.py:811 part/templates/part/detail.html:195 msgid "Purchaseable" msgstr "Kaufbar" -#: common/models.py:123 +#: common/models.py:137 msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:129 part/models.py:759 part/templates/part/detail.html:198 -#: templates/js/table_filters.js:280 +#: common/models.py:143 part/models.py:816 part/templates/part/detail.html:205 +#: templates/js/table_filters.js:290 msgid "Salable" msgstr "Verkäuflich" -#: common/models.py:130 +#: common/models.py:144 msgid "Parts are salable by default" msgstr "" -#: common/models.py:136 part/models.py:749 part/templates/part/detail.html:178 -#: templates/js/table_filters.js:31 templates/js/table_filters.js:284 +#: common/models.py:150 part/models.py:806 part/templates/part/detail.html:185 +#: templates/js/table_filters.js:31 templates/js/table_filters.js:294 msgid "Trackable" msgstr "nachverfolgbar" -#: common/models.py:137 +#: common/models.py:151 msgid "Parts are trackable by default" msgstr "" -#: common/models.py:143 +#: common/models.py:157 part/models.py:826 part/templates/part/detail.html:145 +#: templates/js/table_filters.js:27 +msgid "Virtual" +msgstr "Virtuell" + +#: common/models.py:158 +#, fuzzy +#| msgid "Part is not a virtual part" +msgid "Parts are virtual by default" +msgstr "Teil ist nicht virtuell" + +#: common/models.py:164 +#, fuzzy +#| msgid "Stock Export Options" +msgid "Stock Expiry" +msgstr "Lagerbestandsexportoptionen" + +#: common/models.py:165 +msgid "Enable stock expiry functionality" +msgstr "" + +#: common/models.py:171 +#, fuzzy +#| msgid "Serialize Stock" +msgid "Sell Expired Stock" +msgstr "Lagerbestand erfassen" + +#: common/models.py:172 +msgid "Allow sale of expired stock" +msgstr "" + +#: common/models.py:178 +#, fuzzy +#| msgid "Stock Item" +msgid "Stock Stale Time" +msgstr "Lagerobjekt" + +#: common/models.py:179 +msgid "Number of days stock items are considered stale before expiring" +msgstr "" + +#: common/models.py:181 part/templates/part/detail.html:116 +msgid "days" +msgstr "" + +#: common/models.py:186 +#, fuzzy +#| msgid "Builds" +msgid "Build Expired Stock" +msgstr "Baue" + +#: common/models.py:187 +msgid "Allow building with expired stock" +msgstr "" + +#: common/models.py:193 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference Prefix" msgstr "Bestellreferenz" -#: common/models.py:144 +#: common/models.py:194 #, fuzzy #| msgid "Order reference" msgid "Prefix value for build order reference" msgstr "Bestell-Referenz" -#: common/models.py:149 +#: common/models.py:199 #, fuzzy #| msgid "Order Reference" msgid "Build Order Reference Regex" msgstr "Bestellreferenz" -#: common/models.py:150 +#: common/models.py:200 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:154 +#: common/models.py:204 #, fuzzy #| msgid "Sales Order Reference" msgid "Sales Order Reference Prefix" msgstr "Bestellungsreferenz" -#: common/models.py:155 +#: common/models.py:205 #, fuzzy #| msgid "Order reference" msgid "Prefix value for sales order reference" msgstr "Bestell-Referenz" -#: common/models.py:160 +#: common/models.py:210 #, fuzzy #| msgid "Order reference" msgid "Purchase Order Reference Prefix" msgstr "Bestell-Referenz" -#: common/models.py:161 +#: common/models.py:211 #, fuzzy #| msgid "Order reference" msgid "Prefix value for purchase order reference" msgstr "Bestell-Referenz" -#: common/models.py:378 +#: common/models.py:434 msgid "Settings key (must be unique - case insensitive" msgstr "" "Einstellungs-Schlüssel (muss einzigartig sein, Groß-/ Kleinschreibung wird " "nicht beachtet)" -#: common/models.py:380 +#: common/models.py:436 msgid "Settings value" msgstr "Einstellungs-Wert" -#: common/models.py:439 +#: common/models.py:493 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:453 +#: common/models.py:503 +#, fuzzy +#| msgid "Must enter integer value" +msgid "Value must be an integer value" +msgstr "Nur Ganzzahl eingeben" + +#: common/models.py:517 msgid "Key string must be unique" msgstr "Schlüsseltext muss eindeutig sein" -#: common/models.py:497 company/forms.py:113 +#: common/models.py:590 company/forms.py:113 #, fuzzy #| msgid "Price Breaks" msgid "Price break quantity" msgstr "Preisstaffelung" -#: common/models.py:505 company/templates/company/supplier_part_pricing.html:80 +#: common/models.py:598 company/templates/company/supplier_part_pricing.html:80 #: part/templates/part/sale_prices.html:87 templates/js/bom.js:246 msgid "Price" msgstr "Preis" -#: common/models.py:506 +#: common/models.py:599 #, fuzzy #| msgid "Enter a valid quantity" msgid "Unit price at specified quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: common/models.py:529 +#: common/models.py:622 #, fuzzy #| msgid "Default Location" msgid "Default" @@ -1599,8 +1683,8 @@ msgstr "Produziert diese Firma Teile?" msgid "Currency" msgstr "Währung bearbeiten" -#: company/models.py:313 stock/models.py:338 -#: stock/templates/stock/item_base.html:177 +#: company/models.py:313 stock/models.py:344 +#: stock/templates/stock/item_base.html:184 msgid "Base Part" msgstr "Basisteil" @@ -1613,7 +1697,7 @@ msgstr "Teil auswählen" #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 -#: stock/templates/stock/item_base.html:287 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:294 templates/js/company.js:48 #: templates/js/company.js:164 templates/js/order.js:162 msgid "Supplier" msgstr "Zulieferer" @@ -1652,7 +1736,7 @@ msgstr "MPN" msgid "Manufacturer part number" msgstr "Hersteller-Teilenummer" -#: company/models.py:353 templates/js/company.js:208 +#: company/models.py:353 part/models.py:704 templates/js/company.js:208 msgid "Link" msgstr "Link" @@ -1717,8 +1801,8 @@ msgid "Uses default currency" msgstr "Währung entfernen" #: company/templates/company/detail.html:62 -#: order/templates/order/sales_order_base.html:89 stock/models.py:373 -#: stock/models.py:374 stock/templates/stock/item_base.html:204 +#: order/templates/order/sales_order_base.html:89 stock/models.py:379 +#: stock/models.py:380 stock/templates/stock/item_base.html:211 #: templates/js/company.js:40 templates/js/order.js:250 msgid "Customer" msgstr "Kunde" @@ -1734,7 +1818,7 @@ msgstr "Neues Zuliefererteil anlegen" #: company/templates/company/detail_part.html:18 #: order/templates/order/purchase_order_detail.html:68 -#: part/templates/part/supplier.html:14 templates/js/stock.js:855 +#: part/templates/part/supplier.html:14 templates/js/stock.js:881 msgid "New Supplier Part" msgstr "Neues Zulieferer-Teil" @@ -1762,7 +1846,7 @@ msgid "Delete Parts" msgstr "Teile löschen" #: company/templates/company/detail_part.html:63 -#: part/templates/part/category.html:116 templates/js/stock.js:849 +#: part/templates/part/category.html:116 templates/js/stock.js:875 msgid "New Part" msgstr "Neues Teil" @@ -1855,8 +1939,8 @@ msgid "New Sales Order" msgstr "Neuer Auftrag" #: company/templates/company/supplier_part_base.html:6 -#: company/templates/company/supplier_part_base.html:19 stock/models.py:347 -#: stock/templates/stock/item_base.html:292 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:353 +#: stock/templates/stock/item_base.html:299 templates/js/company.js:180 msgid "Supplier Part" msgstr "Zulieferer-Teil" @@ -1897,7 +1981,7 @@ msgid "Pricing Information" msgstr "Preisinformationen ansehen" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:486 -#: part/templates/part/sale_prices.html:14 part/views.py:2555 +#: part/templates/part/sale_prices.html:14 part/views.py:2565 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1934,7 +2018,7 @@ msgstr "Bepreisung" #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 -#: templates/js/part.js:418 templates/js/stock.js:508 templates/navbar.html:22 +#: templates/js/part.js:418 templates/js/stock.js:509 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" msgstr "Lagerbestand" @@ -1944,7 +2028,7 @@ msgid "Orders" msgstr "Bestellungen" #: company/templates/company/tabs.html:9 -#: order/templates/order/receive_parts.html:14 part/models.py:316 +#: order/templates/order/receive_parts.html:14 part/models.py:317 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 #: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 @@ -2017,7 +2101,7 @@ msgstr "Firma gelöscht" msgid "Edit Supplier Part" msgstr "Zuliefererteil bearbeiten" -#: company/views.py:295 templates/js/stock.js:856 +#: company/views.py:295 templates/js/stock.js:882 msgid "Create new Supplier Part" msgstr "Neues Zuliefererteil anlegen" @@ -2025,17 +2109,17 @@ msgstr "Neues Zuliefererteil anlegen" msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:492 part/views.py:2561 +#: company/views.py:492 part/views.py:2571 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:548 part/views.py:2605 +#: company/views.py:548 part/views.py:2615 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:564 part/views.py:2621 +#: company/views.py:564 part/views.py:2631 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -2145,8 +2229,8 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:176 order/models.py:258 part/views.py:1494 -#: stock/models.py:244 stock/models.py:812 +#: order/models.py:176 order/models.py:258 part/views.py:1504 +#: stock/models.py:250 stock/models.py:855 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -2184,7 +2268,7 @@ msgstr "Position - Notizen" #: order/models.py:504 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:259 templates/js/order.js:146 +#: stock/templates/stock/item_base.html:266 templates/js/order.js:146 msgid "Purchase Order" msgstr "Kaufvertrag" @@ -2196,8 +2280,8 @@ msgstr "Zulieferer-Teil" msgid "Number of items received" msgstr "Empfangene Objekt-Anzahl" -#: order/models.py:527 stock/models.py:458 -#: stock/templates/stock/item_base.html:266 +#: order/models.py:527 stock/models.py:472 +#: stock/templates/stock/item_base.html:273 #, fuzzy #| msgid "Purchase Order" msgid "Purchase Price" @@ -2360,8 +2444,8 @@ msgid "Line Items" msgstr "Position hinzufügen" #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1201 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1119 +#: order/views.py:1203 msgid "Add Line Item" msgstr "Position hinzufügen" @@ -2372,7 +2456,7 @@ msgstr "Bestellpositionen" #: order/templates/order/purchase_order_detail.html:39 #: order/templates/order/purchase_order_detail.html:119 #: part/templates/part/category.html:173 part/templates/part/category.html:215 -#: templates/js/stock.js:861 +#: templates/js/stock.js:627 templates/js/stock.js:887 msgid "New Location" msgstr "Neuer Standort" @@ -2461,8 +2545,8 @@ msgid "Sales Order Items" msgstr "Auftragspositionen" #: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:378 -#: stock/templates/stock/item_base.html:191 templates/js/build.js:418 +#: order/templates/order/sales_order_detail.html:154 stock/models.py:384 +#: stock/templates/stock/item_base.html:198 templates/js/build.js:418 msgid "Serial Number" msgstr "Seriennummer" @@ -2544,151 +2628,151 @@ msgstr "Sind Sie sicher, dass Sie diese Position löschen möchten?" msgid "Order Items" msgstr "Bestellungspositionen" -#: order/views.py:99 +#: order/views.py:101 msgid "Add Purchase Order Attachment" msgstr "Bestellanhang hinzufügen" -#: order/views.py:150 +#: order/views.py:152 msgid "Add Sales Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: order/views.py:310 +#: order/views.py:312 msgid "Create Purchase Order" msgstr "Bestellung anlegen" -#: order/views.py:346 +#: order/views.py:348 msgid "Create Sales Order" msgstr "Auftrag anlegen" -#: order/views.py:382 +#: order/views.py:384 msgid "Edit Purchase Order" msgstr "Bestellung bearbeiten" -#: order/views.py:403 +#: order/views.py:405 msgid "Edit Sales Order" msgstr "Auftrag bearbeiten" -#: order/views.py:420 +#: order/views.py:422 msgid "Cancel Order" msgstr "Bestellung stornieren" -#: order/views.py:430 order/views.py:457 +#: order/views.py:432 order/views.py:459 msgid "Confirm order cancellation" msgstr "Bestellstornierung bestätigen" -#: order/views.py:433 +#: order/views.py:435 msgid "Order cannot be cancelled as either pending or placed" msgstr "" -#: order/views.py:447 +#: order/views.py:449 msgid "Cancel sales order" msgstr "Auftrag stornieren" -#: order/views.py:460 +#: order/views.py:462 msgid "Order cannot be cancelled" msgstr "" -#: order/views.py:474 +#: order/views.py:476 msgid "Issue Order" msgstr "Bestellung aufgeben" -#: order/views.py:484 +#: order/views.py:486 msgid "Confirm order placement" msgstr "Bestellungstätigung bestätigen" -#: order/views.py:494 +#: order/views.py:496 #, fuzzy #| msgid "Purchase Order Details" msgid "Purchase order issued" msgstr "Bestelldetails" -#: order/views.py:505 +#: order/views.py:507 msgid "Complete Order" msgstr "Auftrag fertigstellen" -#: order/views.py:522 +#: order/views.py:524 #, fuzzy #| msgid "Confirm build completion" msgid "Confirm order completion" msgstr "Bau-Fertigstellung bestätigen" -#: order/views.py:533 +#: order/views.py:535 #, fuzzy #| msgid "Mark order as complete" msgid "Purchase order completed" msgstr "Bestellung als vollständig markieren" -#: order/views.py:543 +#: order/views.py:545 msgid "Ship Order" msgstr "Versenden" -#: order/views.py:560 +#: order/views.py:562 msgid "Confirm order shipment" msgstr "Versand bestätigen" -#: order/views.py:566 +#: order/views.py:568 msgid "Could not ship order" msgstr "Versand fehlgeschlagen" -#: order/views.py:618 +#: order/views.py:620 msgid "Receive Parts" msgstr "Teile empfangen" -#: order/views.py:686 +#: order/views.py:688 msgid "Items received" msgstr "Anzahl empfangener Positionen" -#: order/views.py:700 +#: order/views.py:702 msgid "No destination set" msgstr "Kein Ziel gesetzt" -#: order/views.py:745 +#: order/views.py:747 msgid "Error converting quantity to number" msgstr "Fehler beim Konvertieren zu Zahl" -#: order/views.py:751 +#: order/views.py:753 msgid "Receive quantity less than zero" msgstr "Anzahl kleiner null empfangen" -#: order/views.py:757 +#: order/views.py:759 msgid "No lines specified" msgstr "Keine Zeilen angegeben" -#: order/views.py:1127 +#: order/views.py:1129 #, fuzzy #| msgid "Supplier part description" msgid "Supplier part must be specified" msgstr "Zuliefererbeschreibung des Teils" -#: order/views.py:1133 +#: order/views.py:1135 msgid "Supplier must match for Part and Order" msgstr "Zulieferer muss zum Teil und zur Bestellung passen" -#: order/views.py:1253 order/views.py:1272 +#: order/views.py:1255 order/views.py:1274 msgid "Edit Line Item" msgstr "Position bearbeiten" -#: order/views.py:1289 order/views.py:1302 +#: order/views.py:1291 order/views.py:1304 msgid "Delete Line Item" msgstr "Position löschen" -#: order/views.py:1295 order/views.py:1308 +#: order/views.py:1297 order/views.py:1310 msgid "Deleted line item" msgstr "Position gelöscht" -#: order/views.py:1317 +#: order/views.py:1319 msgid "Allocate Stock to Order" msgstr "Lagerbestand dem Auftrag zuweisen" -#: order/views.py:1387 +#: order/views.py:1394 msgid "Edit Allocation Quantity" msgstr "Zuordnung bearbeiten" -#: order/views.py:1403 +#: order/views.py:1410 msgid "Remove allocation" msgstr "Zuordnung entfernen" -#: part/bom.py:138 part/templates/part/category.html:61 +#: part/bom.py:138 part/models.py:722 part/templates/part/category.html:61 #: part/templates/part/detail.html:87 msgid "Default Location" msgstr "Standard-Lagerort" @@ -2710,11 +2794,11 @@ msgstr "Fehler beim Lesen der Stückliste (ungültige Daten)" msgid "Error reading BOM file (incorrect row size)" msgstr "Fehler beim Lesen der Stückliste (ungültige Zeilengröße)" -#: part/forms.py:61 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:261 msgid "File Format" msgstr "Dateiformat" -#: part/forms.py:61 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:261 msgid "Select output file format" msgstr "Ausgabe-Dateiformat auswählen" @@ -2766,7 +2850,7 @@ msgstr "Neues Zulieferer-Teil" msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:92 part/models.py:1720 +#: part/forms.py:92 part/models.py:1781 msgid "Parent Part" msgstr "Ausgangsteil" @@ -2806,7 +2890,7 @@ msgstr "Teile löschen" msgid "Select part category" msgstr "Teilekategorie wählen" -#: part/forms.py:188 +#: part/forms.py:189 #, fuzzy #| msgid "Perform 'deep copy' which will duplicate all BOM data for this part" msgid "Duplicate all BOM data for this part" @@ -2814,49 +2898,49 @@ msgstr "" "Tiefe Kopie ausführen. Dies wird alle Daten der Stückliste für dieses Teil " "duplizieren" -#: part/forms.py:189 +#: part/forms.py:190 msgid "Copy BOM" msgstr "" -#: part/forms.py:194 +#: part/forms.py:195 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:195 +#: part/forms.py:196 #, fuzzy #| msgid "Parameters" msgid "Copy Parameters" msgstr "Parameter" -#: part/forms.py:200 +#: part/forms.py:201 msgid "Confirm part creation" msgstr "Erstellen des Teils bestätigen" -#: part/forms.py:205 +#: part/forms.py:206 #, fuzzy #| msgid "No part parameter templates found" msgid "Include category parameter templates" msgstr "Keine Teilparametervorlagen gefunden" -#: part/forms.py:210 +#: part/forms.py:211 #, fuzzy #| msgid "No part parameter templates found" msgid "Include parent categories parameter templates" msgstr "Keine Teilparametervorlagen gefunden" -#: part/forms.py:285 +#: part/forms.py:291 #, fuzzy #| msgid "Parameter template name must be unique" msgid "Add parameter template to same level categories" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/forms.py:289 +#: part/forms.py:295 #, fuzzy #| msgid "Parameter template name must be unique" msgid "Add parameter template to all categories" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/forms.py:333 +#: part/forms.py:339 msgid "Input quantity for price calculation" msgstr "Eintragsmenge zur Preisberechnung" @@ -2868,7 +2952,7 @@ msgstr "Standard-Standort für Teile dieser Kategorie" msgid "Default keywords for parts in this category" msgstr "Standard-Stichworte für Teile dieser Kategorie" -#: part/models.py:77 part/models.py:1765 +#: part/models.py:77 part/models.py:1826 #: part/templates/part/part_app_base.html:9 msgid "Part Category" msgstr "Teilkategorie" @@ -2878,142 +2962,185 @@ msgstr "Teilkategorie" msgid "Part Categories" msgstr "Teile-Kategorien" -#: part/models.py:408 part/models.py:418 +#: part/models.py:409 part/models.py:419 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)" -#: part/models.py:515 +#: part/models.py:516 #, fuzzy #| msgid "No serial numbers found" msgid "Next available serial numbers are" msgstr "Keine Seriennummern gefunden" -#: part/models.py:519 +#: part/models.py:520 msgid "Next available serial number is" msgstr "" -#: part/models.py:524 +#: part/models.py:525 #, fuzzy #| msgid "Empty serial number string" msgid "Most recent serial number is" msgstr "Keine Seriennummer angegeben" -#: part/models.py:603 +#: part/models.py:604 msgid "Duplicate IPN not allowed in part settings" msgstr "" -#: part/models.py:614 +#: part/models.py:615 msgid "Part must be unique for name, IPN and revision" msgstr "Namen, Teile- und Revisionsnummern müssen eindeutig sein" -#: part/models.py:644 part/templates/part/detail.html:19 +#: part/models.py:646 part/templates/part/detail.html:19 msgid "Part name" msgstr "Name des Teils" -#: part/models.py:648 +#: part/models.py:653 +#, fuzzy +#| msgid "Template" +msgid "Is Template" +msgstr "Vorlage" + +#: part/models.py:654 msgid "Is this part a template part?" msgstr "Ist dieses Teil eine Vorlage?" -#: part/models.py:657 +#: part/models.py:665 msgid "Is this part a variant of another part?" msgstr "Ist dieses Teil eine Variante eines anderen Teils?" -#: part/models.py:659 +#: part/models.py:666 part/templates/part/detail.html:57 +msgid "Variant Of" +msgstr "Variante von" + +#: part/models.py:672 msgid "Part description" msgstr "Beschreibung des Teils" -#: part/models.py:661 +#: part/models.py:677 part/templates/part/category.html:68 +#: part/templates/part/detail.html:64 +msgid "Keywords" +msgstr "Schlüsselwörter" + +#: part/models.py:678 msgid "Part keywords to improve visibility in search results" msgstr "Schlüsselworte um die Sichtbarkeit in Suchergebnissen zu verbessern" -#: part/models.py:666 +#: part/models.py:685 part/templates/part/detail.html:70 +#: part/templates/part/set_category.html:15 templates/js/part.js:405 +msgid "Category" +msgstr "Kategorie" + +#: part/models.py:686 msgid "Part category" msgstr "Teile-Kategorie" -#: part/models.py:668 +#: part/models.py:691 part/templates/part/detail.html:25 +#: part/templates/part/part_base.html:95 templates/js/part.js:180 +msgid "IPN" +msgstr "IPN (Interne Produktnummer)" + +#: part/models.py:692 msgid "Internal Part Number" msgstr "Interne Teilenummer" -#: part/models.py:670 +#: part/models.py:698 msgid "Part revision or version number" msgstr "Revisions- oder Versionsnummer" -#: part/models.py:684 +#: part/models.py:699 part/templates/part/detail.html:32 +#: templates/js/part.js:184 +msgid "Revision" +msgstr "Revision" + +#: part/models.py:720 msgid "Where is this item normally stored?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: part/models.py:728 +#: part/models.py:767 part/templates/part/detail.html:94 +msgid "Default Supplier" +msgstr "Standard-Zulieferer" + +#: part/models.py:768 msgid "Default supplier part" msgstr "Standard-Zulieferer?" -#: part/models.py:731 +#: part/models.py:775 +#, fuzzy +#| msgid "Default Supplier" +msgid "Default Expiry" +msgstr "Standard-Zulieferer" + +#: part/models.py:776 +msgid "Expiry time (in days) for stock items of this part" +msgstr "" + +#: part/models.py:781 part/templates/part/detail.html:108 +msgid "Minimum Stock" +msgstr "Minimaler Lagerbestand" + +#: part/models.py:782 msgid "Minimum allowed stock level" msgstr "Minimal zulässiger Lagerbestand" -#: part/models.py:733 +#: part/models.py:788 part/templates/part/detail.html:102 +#: part/templates/part/params.html:26 +msgid "Units" +msgstr "Einheiten" + +#: part/models.py:789 msgid "Stock keeping units for this part" msgstr "Stock Keeping Units (SKU) für dieses Teil" -#: part/models.py:737 part/templates/part/detail.html:158 -#: templates/js/table_filters.js:268 -msgid "Assembly" -msgstr "Baugruppe" - -#: part/models.py:738 +#: part/models.py:795 msgid "Can this part be built from other parts?" msgstr "Kann dieses Teil aus anderen Teilen angefertigt werden?" -#: part/models.py:744 +#: part/models.py:801 msgid "Can this part be used to build other parts?" msgstr "Kann dieses Teil zum Bau von anderen genutzt werden?" -#: part/models.py:750 +#: part/models.py:807 msgid "Does this part have tracking for unique items?" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: part/models.py:755 +#: part/models.py:812 msgid "Can this part be purchased from external suppliers?" msgstr "Kann dieses Teil von externen Zulieferern gekauft werden?" -#: part/models.py:760 +#: part/models.py:817 msgid "Can this part be sold to customers?" msgstr "Kann dieses Teil an Kunden verkauft werden?" -#: part/models.py:764 part/templates/part/detail.html:215 +#: part/models.py:821 part/templates/part/detail.html:222 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 -#: templates/js/table_filters.js:186 templates/js/table_filters.js:251 +#: templates/js/table_filters.js:196 templates/js/table_filters.js:261 msgid "Active" msgstr "Aktiv" -#: part/models.py:765 +#: part/models.py:822 msgid "Is this part active?" msgstr "Ist dieses Teil aktiv?" -#: part/models.py:769 part/templates/part/detail.html:138 -#: templates/js/table_filters.js:27 -msgid "Virtual" -msgstr "Virtuell" - -#: part/models.py:770 +#: part/models.py:827 msgid "Is this a virtual part, such as a software product or license?" msgstr "Ist dieses Teil virtuell, wie zum Beispiel eine Software oder Lizenz?" -#: part/models.py:772 +#: part/models.py:832 msgid "Part notes - supports Markdown formatting" msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" -#: part/models.py:774 +#: part/models.py:835 msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1593 +#: part/models.py:1654 #, fuzzy #| msgid "Stock item cannot be created for a template Part" msgid "Test templates can only be created for trackable parts" msgstr "Lagerobjekt kann nicht für Vorlagen-Teile angelegt werden" -#: part/models.py:1610 +#: part/models.py:1671 #, fuzzy #| msgid "" #| "A stock item with this serial number already exists for template part " @@ -3023,146 +3150,146 @@ msgstr "" "Ein Teil mit dieser Seriennummer existiert bereits für die Teilevorlage " "{part}" -#: part/models.py:1629 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1690 templates/js/part.js:567 templates/js/stock.js:93 #, fuzzy #| msgid "Instance Name" msgid "Test Name" msgstr "Instanzname" -#: part/models.py:1630 +#: part/models.py:1691 #, fuzzy #| msgid "Serial number for this item" msgid "Enter a name for the test" msgstr "Seriennummer für dieses Teil" -#: part/models.py:1635 +#: part/models.py:1696 #, fuzzy #| msgid "Description" msgid "Test Description" msgstr "Beschreibung" -#: part/models.py:1636 +#: part/models.py:1697 #, fuzzy #| msgid "Brief description of the build" msgid "Enter description for this test" msgstr "Kurze Beschreibung des Baus" -#: part/models.py:1641 templates/js/part.js:576 -#: templates/js/table_filters.js:172 +#: part/models.py:1702 templates/js/part.js:576 +#: templates/js/table_filters.js:182 msgid "Required" msgstr "benötigt" -#: part/models.py:1642 +#: part/models.py:1703 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1647 templates/js/part.js:584 +#: part/models.py:1708 templates/js/part.js:584 #, fuzzy #| msgid "Required Parts" msgid "Requires Value" msgstr "benötigte Teile" -#: part/models.py:1648 +#: part/models.py:1709 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1653 templates/js/part.js:591 +#: part/models.py:1714 templates/js/part.js:591 #, fuzzy #| msgid "Delete Attachment" msgid "Requires Attachment" msgstr "Anhang löschen" -#: part/models.py:1654 +#: part/models.py:1715 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1687 +#: part/models.py:1748 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1692 +#: part/models.py:1753 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1694 +#: part/models.py:1755 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1722 part/models.py:1770 +#: part/models.py:1783 part/models.py:1831 #: templates/InvenTree/settings/category.html:62 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1724 +#: part/models.py:1785 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1774 +#: part/models.py:1835 #, fuzzy #| msgid "Parameter Value" msgid "Default Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1804 +#: part/models.py:1865 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1812 +#: part/models.py:1873 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1818 +#: part/models.py:1879 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1820 +#: part/models.py:1881 #, fuzzy #| msgid "Confim BOM item deletion" msgid "This BOM item is optional" msgstr "Löschung von BOM-Position bestätigen" -#: part/models.py:1823 +#: part/models.py:1884 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1826 +#: part/models.py:1887 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1829 +#: part/models.py:1890 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1831 +#: part/models.py:1892 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1902 part/views.py:1500 part/views.py:1552 -#: stock/models.py:234 +#: part/models.py:1963 part/views.py:1510 part/views.py:1562 +#: stock/models.py:240 #, fuzzy #| msgid "Overage must be an integer value or a percentage" msgid "Quantity must be integer value for trackable parts" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: part/models.py:1911 part/models.py:1913 +#: part/models.py:1972 part/models.py:1974 #, fuzzy #| msgid "Supplier part description" msgid "Sub part must be specified" msgstr "Zuliefererbeschreibung des Teils" -#: part/models.py:1916 +#: part/models.py:1977 #, fuzzy #| msgid "New BOM Item" msgid "BOM Item" msgstr "Neue Stücklistenposition" -#: part/models.py:2031 +#: part/models.py:2092 #, fuzzy #| msgid "Select a part" msgid "Select Related Part" msgstr "Teil auswählen" -#: part/models.py:2063 +#: part/models.py:2124 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -3183,9 +3310,9 @@ msgstr "Bestellung" #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 #: stock/templates/stock/item_base.html:72 -#: stock/templates/stock/item_base.html:274 +#: stock/templates/stock/item_base.html:281 #: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:751 -#: templates/js/stock.js:705 templates/js/stock.js:954 +#: templates/js/stock.js:720 templates/js/stock.js:980 msgid "Stock Item" msgstr "Lagerobjekt" @@ -3258,7 +3385,7 @@ msgstr "Stückliste validieren" msgid "Validate" msgstr "BOM validieren" -#: part/templates/part/bom.html:62 part/views.py:1791 +#: part/templates/part/bom.html:62 part/views.py:1801 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" @@ -3386,7 +3513,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2182 +#: part/templates/part/category.html:24 part/views.py:2192 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -3414,10 +3541,6 @@ msgstr "Pfad zur Kategorie" msgid "Category Description" msgstr "Kategorie-Beschreibung" -#: part/templates/part/category.html:68 part/templates/part/detail.html:64 -msgid "Keywords" -msgstr "Schlüsselwörter" - #: part/templates/part/category.html:74 msgid "Subcategories" msgstr "Unter-Kategorien" @@ -3452,7 +3575,7 @@ msgstr "Teilkategorie auswählen" msgid "Export Data" msgstr "Exportieren" -#: part/templates/part/category.html:174 +#: part/templates/part/category.html:174 templates/js/stock.js:628 #, fuzzy #| msgid "Create New Location" msgid "Create new location" @@ -3476,7 +3599,7 @@ msgstr "Teilkategorie anlegen" msgid "Create new Part Category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:216 stock/views.py:1359 +#: part/templates/part/category.html:216 stock/views.py:1363 msgid "Create new Stock Location" msgstr "Neuen Lager-Standort erstellen" @@ -3506,15 +3629,6 @@ msgstr "Los" msgid "Part Details" msgstr "Teile-Details" -#: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 -#: templates/js/part.js:180 -msgid "IPN" -msgstr "IPN (Interne Produktnummer)" - -#: part/templates/part/detail.html:32 templates/js/part.js:184 -msgid "Revision" -msgstr "Revision" - #: part/templates/part/detail.html:39 #, fuzzy #| msgid "Serial Number" @@ -3527,107 +3641,87 @@ msgstr "Seriennummer" msgid "No serial numbers recorded" msgstr "Keine Seriennummern gefunden" -#: part/templates/part/detail.html:57 -msgid "Variant Of" -msgstr "Variante von" +#: part/templates/part/detail.html:115 +#, fuzzy +#| msgid "Stock Export Options" +msgid "Stock Expiry Time" +msgstr "Lagerbestandsexportoptionen" -#: part/templates/part/detail.html:70 part/templates/part/set_category.html:15 -#: templates/js/part.js:405 -msgid "Category" -msgstr "Kategorie" - -#: part/templates/part/detail.html:94 -msgid "Default Supplier" -msgstr "Standard-Zulieferer" - -#: part/templates/part/detail.html:102 part/templates/part/params.html:26 -msgid "Units" -msgstr "Einheiten" - -#: part/templates/part/detail.html:108 -msgid "Minimum Stock" -msgstr "Minimaler Lagerbestand" - -#: part/templates/part/detail.html:114 templates/js/order.js:276 +#: part/templates/part/detail.html:121 templates/js/order.js:276 msgid "Creation Date" msgstr "Erstelldatum" -#: part/templates/part/detail.html:120 +#: part/templates/part/detail.html:127 msgid "Created By" msgstr "Erstellt von" -#: part/templates/part/detail.html:127 +#: part/templates/part/detail.html:134 msgid "Responsible User" msgstr "Verantwortlicher Benutzer" -#: part/templates/part/detail.html:141 +#: part/templates/part/detail.html:148 msgid "Part is virtual (not a physical part)" msgstr "Teil ist virtuell (kein physisches Teil)" -#: part/templates/part/detail.html:143 +#: part/templates/part/detail.html:150 msgid "Part is not a virtual part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:148 stock/forms.py:249 -#: templates/js/table_filters.js:23 templates/js/table_filters.js:256 -msgid "Template" -msgstr "Vorlage" - -#: part/templates/part/detail.html:151 +#: part/templates/part/detail.html:158 #, fuzzy #| msgid "Part cannot be a template part if it is a variant of another part" msgid "Part is a template part (variants can be made from this part)" msgstr "Teil kann keine Vorlage sein wenn es Variante eines anderen Teils ist" -#: part/templates/part/detail.html:153 +#: part/templates/part/detail.html:160 #, fuzzy #| msgid "Part is not a virtual part" msgid "Part is not a template part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:161 +#: part/templates/part/detail.html:168 msgid "Part can be assembled from other parts" msgstr "Teil kann aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:163 +#: part/templates/part/detail.html:170 msgid "Part cannot be assembled from other parts" msgstr "Teil kann nicht aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:171 +#: part/templates/part/detail.html:178 msgid "Part can be used in assemblies" msgstr "Teil kann in Baugruppen benutzt werden" -#: part/templates/part/detail.html:173 +#: part/templates/part/detail.html:180 msgid "Part cannot be used in assemblies" msgstr "Teil kann nicht in Baugruppen benutzt werden" -#: part/templates/part/detail.html:181 +#: part/templates/part/detail.html:188 msgid "Part stock is tracked by serial number" msgstr "Teilebestand in der Seriennummer hinterlegt" -#: part/templates/part/detail.html:183 +#: part/templates/part/detail.html:190 msgid "Part stock is not tracked by serial number" msgstr "Teilebestand ist nicht in der Seriennummer hinterlegt" -#: part/templates/part/detail.html:191 part/templates/part/detail.html:193 +#: part/templates/part/detail.html:198 part/templates/part/detail.html:200 msgid "Part can be purchased from external suppliers" msgstr "Teil kann von externen Zulieferern gekauft werden" -#: part/templates/part/detail.html:201 +#: part/templates/part/detail.html:208 msgid "Part can be sold to customers" msgstr "Teil kann an Kunden verkauft werden" -#: part/templates/part/detail.html:203 +#: part/templates/part/detail.html:210 msgid "Part cannot be sold to customers" msgstr "Teil kann nicht an Kunden verkauft werden" -#: part/templates/part/detail.html:218 +#: part/templates/part/detail.html:225 #, fuzzy #| msgid "This part is not active" msgid "Part is active" msgstr "Dieses Teil ist nicht aktiv" -#: part/templates/part/detail.html:220 +#: part/templates/part/detail.html:227 #, fuzzy #| msgid "This part is not active" msgid "Part is not active" @@ -3647,12 +3741,12 @@ msgstr "Parameter hinzufügen" #: part/templates/part/params.html:15 #: templates/InvenTree/settings/category.html:29 -#: templates/InvenTree/settings/part.html:38 +#: templates/InvenTree/settings/part.html:41 msgid "New Parameter" msgstr "Neuer Parameter" -#: part/templates/part/params.html:25 stock/models.py:1420 -#: templates/js/stock.js:112 +#: part/templates/part/params.html:25 stock/models.py:1463 +#: templates/InvenTree/settings/header.html:8 templates/js/stock.js:113 msgid "Value" msgstr "Wert" @@ -3689,7 +3783,7 @@ msgid "Star this part" msgstr "Teil favorisieren" #: part/templates/part/part_base.html:49 -#: stock/templates/stock/item_base.html:101 +#: stock/templates/stock/item_base.html:108 #: stock/templates/stock/location.html:29 #, fuzzy #| msgid "Source Location" @@ -3697,7 +3791,7 @@ msgid "Barcode actions" msgstr "Quell-Standort" #: part/templates/part/part_base.html:51 -#: stock/templates/stock/item_base.html:103 +#: stock/templates/stock/item_base.html:110 #: stock/templates/stock/location.html:31 #, fuzzy #| msgid "Part QR Code" @@ -3705,7 +3799,7 @@ msgid "Show QR Code" msgstr "Teil-QR-Code" #: part/templates/part/part_base.html:52 -#: stock/templates/stock/item_base.html:104 +#: stock/templates/stock/item_base.html:111 #: stock/templates/stock/location.html:32 msgid "Print Label" msgstr "" @@ -3744,7 +3838,7 @@ msgstr "Vorlage bearbeiten" msgid "Delete part" msgstr "Teile löschen" -#: part/templates/part/part_base.html:124 templates/js/table_filters.js:111 +#: part/templates/part/part_base.html:124 templates/js/table_filters.js:121 msgid "In Stock" msgstr "Auf Lager" @@ -3871,7 +3965,7 @@ msgstr "Stückliste" msgid "Used In" msgstr "Benutzt in" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:318 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:339 msgid "Tests" msgstr "" @@ -3907,248 +4001,248 @@ msgstr "Neues Teil hinzufügen" msgid "New Variant" msgstr "Varianten" -#: part/views.py:84 +#: part/views.py:86 #, fuzzy #| msgid "Allocated Parts" msgid "Add Related Part" msgstr "Zugeordnete Teile" -#: part/views.py:140 +#: part/views.py:142 #, fuzzy #| msgid "Delete Supplier Part" msgid "Delete Related Part" msgstr "Zuliefererteil entfernen" -#: part/views.py:152 +#: part/views.py:154 msgid "Add part attachment" msgstr "Teilanhang hinzufügen" -#: part/views.py:207 templates/attachment_table.html:34 +#: part/views.py:209 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "Anhang bearbeiten" -#: part/views.py:213 +#: part/views.py:215 msgid "Part attachment updated" msgstr "Teilanhang aktualisiert" -#: part/views.py:228 +#: part/views.py:230 msgid "Delete Part Attachment" msgstr "Teilanhang löschen" -#: part/views.py:236 +#: part/views.py:238 msgid "Deleted part attachment" msgstr "Teilanhang gelöscht" -#: part/views.py:245 +#: part/views.py:247 #, fuzzy #| msgid "Create Part Parameter Template" msgid "Create Test Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:274 +#: part/views.py:276 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Template" msgstr "Vorlage bearbeiten" -#: part/views.py:290 +#: part/views.py:292 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Template" msgstr "Vorlage löschen" -#: part/views.py:299 +#: part/views.py:301 msgid "Set Part Category" msgstr "Teilkategorie auswählen" -#: part/views.py:349 +#: part/views.py:351 #, python-brace-format msgid "Set category for {n} parts" msgstr "Kategorie für {n} Teile setzen" -#: part/views.py:384 +#: part/views.py:386 msgid "Create Variant" msgstr "Variante anlegen" -#: part/views.py:466 +#: part/views.py:468 msgid "Duplicate Part" msgstr "Teil duplizieren" -#: part/views.py:473 +#: part/views.py:475 msgid "Copied part" msgstr "Teil kopiert" -#: part/views.py:527 part/views.py:661 +#: part/views.py:529 part/views.py:667 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:592 templates/js/stock.js:850 +#: part/views.py:594 templates/js/stock.js:876 msgid "Create New Part" msgstr "Neues Teil anlegen" -#: part/views.py:599 +#: part/views.py:601 msgid "Created new part" msgstr "Neues Teil angelegt" -#: part/views.py:830 +#: part/views.py:836 msgid "Part QR Code" msgstr "Teil-QR-Code" -#: part/views.py:849 +#: part/views.py:855 msgid "Upload Part Image" msgstr "Teilbild hochladen" -#: part/views.py:857 part/views.py:894 +#: part/views.py:863 part/views.py:900 msgid "Updated part image" msgstr "Teilbild aktualisiert" -#: part/views.py:866 +#: part/views.py:872 msgid "Select Part Image" msgstr "Teilbild auswählen" -#: part/views.py:897 +#: part/views.py:903 msgid "Part image not found" msgstr "Teilbild nicht gefunden" -#: part/views.py:908 +#: part/views.py:914 msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:935 +#: part/views.py:945 #, fuzzy #| msgid "Duplicate Part" msgid "Duplicate BOM" msgstr "Teil duplizieren" -#: part/views.py:966 +#: part/views.py:976 #, fuzzy #| msgid "Confirm unallocation of build stock" msgid "Confirm duplication of BOM from parent" msgstr "Zuweisungsaufhebung bestätigen" -#: part/views.py:987 +#: part/views.py:997 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:1010 +#: part/views.py:1020 #, fuzzy #| msgid "Confirm that the BOM is correct" msgid "Confirm that the BOM is valid" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/views.py:1021 +#: part/views.py:1031 #, fuzzy #| msgid "Validate Bill of Materials" msgid "Validated Bill of Materials" msgstr "Stückliste validieren" -#: part/views.py:1155 +#: part/views.py:1165 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1503 +#: part/views.py:1513 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1528 part/views.py:1531 +#: part/views.py:1538 part/views.py:1541 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1537 +#: part/views.py:1547 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1575 +#: part/views.py:1585 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1581 +#: part/views.py:1591 #, fuzzy #| msgid "Select part to be used in BOM" msgid "Selected part creates a circular BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/views.py:1585 +#: part/views.py:1595 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1841 +#: part/views.py:1851 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1850 +#: part/views.py:1860 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1859 +#: part/views.py:1869 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1973 +#: part/views.py:1983 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1983 +#: part/views.py:1993 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1992 +#: part/views.py:2002 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:2002 +#: part/views.py:2012 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:2054 +#: part/views.py:2064 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:2070 +#: part/views.py:2080 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:2129 +#: part/views.py:2139 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2166 +#: part/views.py:2176 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2174 +#: part/views.py:2184 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2230 +#: part/views.py:2240 #, fuzzy #| msgid "Create Part Parameter Template" msgid "Create Category Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:2333 +#: part/views.py:2343 #, fuzzy #| msgid "Edit Part Parameter Template" msgid "Edit Category Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:2391 +#: part/views.py:2401 #, fuzzy #| msgid "Delete Part Parameter Template" msgid "Delete Category Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:2416 +#: part/views.py:2426 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2488 +#: part/views.py:2498 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2545 +#: part/views.py:2555 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -4188,346 +4282,358 @@ msgstr "" msgid "Asset file description" msgstr "Einstellungs-Beschreibung" -#: stock/forms.py:111 +#: stock/forms.py:116 msgid "Enter unique serial numbers (or leave blank)" msgstr "Eindeutige Seriennummern eingeben (oder leer lassen)" -#: stock/forms.py:192 +#: stock/forms.py:198 msgid "Label" msgstr "" -#: stock/forms.py:193 stock/forms.py:249 +#: stock/forms.py:199 stock/forms.py:255 #, fuzzy #| msgid "Select stock item to allocate" msgid "Select test report template" msgstr "Lagerobjekt für Zuordnung auswählen" -#: stock/forms.py:257 +#: stock/forms.py:263 msgid "Include stock items in sub locations" msgstr "Lagerobjekte in untergeordneten Lagerorten einschließen" -#: stock/forms.py:292 +#: stock/forms.py:298 #, fuzzy #| msgid "No stock items matching query" msgid "Stock item to install" msgstr "Keine zur Anfrage passenden Lagerobjekte" -#: stock/forms.py:299 +#: stock/forms.py:305 #, fuzzy #| msgid "Stock Quantity" msgid "Stock quantity to assign" msgstr "Bestand" -#: stock/forms.py:327 +#: stock/forms.py:333 #, fuzzy #| msgid "Quantity must not exceed available stock quantity ({n})" msgid "Must not exceed available quantity" msgstr "Anzahl darf nicht die verfügbare Anzahl überschreiten ({n})" -#: stock/forms.py:337 +#: stock/forms.py:343 #, fuzzy #| msgid "Does this part have tracking for unique items?" msgid "Destination location for uninstalled items" msgstr "Hat dieses Teil Tracking für einzelne Objekte?" -#: stock/forms.py:339 +#: stock/forms.py:345 #, fuzzy #| msgid "Description of the company" msgid "Add transaction note (optional)" msgstr "Firmenbeschreibung" -#: stock/forms.py:341 +#: stock/forms.py:347 #, fuzzy #| msgid "Confirm stock allocation" msgid "Confirm uninstall" msgstr "Lagerbestandszuordnung bestätigen" -#: stock/forms.py:341 +#: stock/forms.py:347 #, fuzzy #| msgid "Confirm movement of stock items" msgid "Confirm removal of installed stock items" msgstr "Bewegung der Lagerobjekte bestätigen" -#: stock/forms.py:365 +#: stock/forms.py:371 msgid "Destination stock location" msgstr "Ziel-Lagerbestand" -#: stock/forms.py:367 +#: stock/forms.py:373 msgid "Add note (required)" msgstr "" -#: stock/forms.py:371 stock/views.py:935 stock/views.py:1133 +#: stock/forms.py:377 stock/views.py:935 stock/views.py:1133 msgid "Confirm stock adjustment" msgstr "Bestands-Anpassung bestätigen" -#: stock/forms.py:371 +#: stock/forms.py:377 msgid "Confirm movement of stock items" msgstr "Bewegung der Lagerobjekte bestätigen" -#: stock/forms.py:373 +#: stock/forms.py:379 #, fuzzy #| msgid "Default Location" msgid "Set Default Location" msgstr "Standard-Lagerort" -#: stock/forms.py:373 +#: stock/forms.py:379 msgid "Set the destination as the default location for selected parts" msgstr "Setze das Ziel als Standard-Ziel für ausgewählte Teile" -#: stock/models.py:179 +#: stock/models.py:185 #, fuzzy #| msgid "Created new stock item" msgid "Created stock item" msgstr "Neues Lagerobjekt erstellt" -#: stock/models.py:215 +#: stock/models.py:221 #, fuzzy #| msgid "A stock item with this serial number already exists" msgid "StockItem with this serial number already exists" msgstr "Ein Teil mit dieser Seriennummer existiert bereits" -#: stock/models.py:251 +#: stock/models.py:257 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "Teile-Typ ('{pf}') muss {pe} sein" -#: stock/models.py:261 stock/models.py:270 +#: stock/models.py:267 stock/models.py:276 msgid "Quantity must be 1 for item with a serial number" msgstr "Anzahl muss für Objekte mit Seriennummer \"1\" sein" -#: stock/models.py:262 +#: stock/models.py:268 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" "Seriennummer kann nicht gesetzt werden wenn die Anzahl größer als \"1\" ist" -#: stock/models.py:284 +#: stock/models.py:290 msgid "Item cannot belong to itself" msgstr "Teil kann nicht zu sich selbst gehören" -#: stock/models.py:290 +#: stock/models.py:296 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:297 +#: stock/models.py:303 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:330 +#: stock/models.py:336 msgid "Parent Stock Item" msgstr "Eltern-Lagerobjekt" -#: stock/models.py:339 +#: stock/models.py:345 msgid "Base part" msgstr "Basis-Teil" -#: stock/models.py:348 +#: stock/models.py:354 msgid "Select a matching supplier part for this stock item" msgstr "Passenden Zulieferer für dieses Lagerobjekt auswählen" -#: stock/models.py:353 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:359 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "Lagerort" -#: stock/models.py:356 +#: stock/models.py:362 msgid "Where is this stock item located?" msgstr "Wo wird dieses Teil normalerweise gelagert?" -#: stock/models.py:361 stock/templates/stock/item_base.html:212 +#: stock/models.py:367 stock/templates/stock/item_base.html:219 msgid "Installed In" msgstr "Installiert in" -#: stock/models.py:364 +#: stock/models.py:370 msgid "Is this item installed in another item?" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: stock/models.py:380 +#: stock/models.py:386 msgid "Serial number for this item" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:392 +#: stock/models.py:398 msgid "Batch code for this stock item" msgstr "Losnummer für dieses Lagerobjekt" -#: stock/models.py:396 +#: stock/models.py:402 msgid "Stock Quantity" msgstr "Bestand" -#: stock/models.py:405 +#: stock/models.py:411 msgid "Source Build" msgstr "Quellbau" -#: stock/models.py:407 +#: stock/models.py:413 msgid "Build for this stock item" msgstr "Bau für dieses Lagerobjekt" -#: stock/models.py:418 +#: stock/models.py:424 msgid "Source Purchase Order" msgstr "Quellbestellung" -#: stock/models.py:421 +#: stock/models.py:427 msgid "Purchase order for this stock item" msgstr "Bestellung für dieses Teil" -#: stock/models.py:427 +#: stock/models.py:433 msgid "Destination Sales Order" msgstr "Zielauftrag" -#: stock/models.py:439 +#: stock/models.py:439 stock/templates/stock/item_base.html:306 +#: templates/js/stock.js:597 +#, fuzzy +#| msgid "Export" +msgid "Expiry Date" +msgstr "Exportieren" + +#: stock/models.py:440 +msgid "" +"Expiry date for stock item. Stock will be considered expired after this date" +msgstr "" + +#: stock/models.py:453 msgid "Delete this Stock Item when stock is depleted" msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" -#: stock/models.py:449 stock/templates/stock/item_notes.html:14 +#: stock/models.py:463 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "Lagerobjekt-Notizen" -#: stock/models.py:459 +#: stock/models.py:473 msgid "Single unit purchase price at time of purchase" msgstr "" -#: stock/models.py:510 +#: stock/models.py:573 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assigned to Customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:512 +#: stock/models.py:575 #, fuzzy #| msgid "Item assigned to customer?" msgid "Manually assigned to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:525 +#: stock/models.py:588 #, fuzzy #| msgid "Item assigned to customer?" msgid "Returned from customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/models.py:527 +#: stock/models.py:590 #, fuzzy #| msgid "Create new stock location" msgid "Returned to location" msgstr "Neuen Lagerort anlegen" -#: stock/models.py:652 +#: stock/models.py:715 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed into stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:660 +#: stock/models.py:723 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:684 +#: stock/models.py:747 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstalled stock item" msgstr "In Lagerobjekt installiert" -#: stock/models.py:703 +#: stock/models.py:766 #, fuzzy #| msgid "Include sublocations" msgid "Uninstalled into location" msgstr "Unterlagerorte einschließen" -#: stock/models.py:803 +#: stock/models.py:846 #, fuzzy #| msgid "Part is not a virtual part" msgid "Part is not set as trackable" msgstr "Teil ist nicht virtuell" -#: stock/models.py:809 +#: stock/models.py:852 msgid "Quantity must be integer" msgstr "Anzahl muss eine Ganzzahl sein" -#: stock/models.py:815 +#: stock/models.py:858 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "Anzahl darf nicht die verfügbare Anzahl überschreiten ({n})" -#: stock/models.py:818 +#: stock/models.py:861 msgid "Serial numbers must be a list of integers" msgstr "Seriennummern muss eine Liste von Ganzzahlen sein" -#: stock/models.py:821 +#: stock/models.py:864 msgid "Quantity does not match serial numbers" msgstr "Anzahl stimmt nicht mit den Seriennummern überein" -#: stock/models.py:853 +#: stock/models.py:896 msgid "Add serial number" msgstr "Seriennummer hinzufügen" -#: stock/models.py:856 +#: stock/models.py:899 #, python-brace-format msgid "Serialized {n} items" msgstr "{n} Teile serialisiert" -#: stock/models.py:967 +#: stock/models.py:1010 msgid "StockItem cannot be moved as it is not in stock" msgstr "Lagerobjekt kann nicht bewegt werden, da kein Bestand vorhanden ist" -#: stock/models.py:1321 +#: stock/models.py:1364 msgid "Tracking entry title" msgstr "Name des Eintrags-Trackings" -#: stock/models.py:1323 +#: stock/models.py:1366 msgid "Entry notes" msgstr "Eintrags-Notizen" -#: stock/models.py:1325 +#: stock/models.py:1368 msgid "Link to external page for further information" msgstr "Link auf externe Seite für weitere Informationen" -#: stock/models.py:1385 +#: stock/models.py:1428 #, fuzzy #| msgid "Serial number for this item" msgid "Value must be provided for this test" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:1391 +#: stock/models.py:1434 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1408 +#: stock/models.py:1451 msgid "Test" msgstr "" -#: stock/models.py:1409 +#: stock/models.py:1452 #, fuzzy #| msgid "Part name" msgid "Test name" msgstr "Name des Teils" -#: stock/models.py:1414 +#: stock/models.py:1457 #, fuzzy #| msgid "Search Results" msgid "Result" msgstr "Suchergebnisse" -#: stock/models.py:1415 templates/js/table_filters.js:162 +#: stock/models.py:1458 templates/js/table_filters.js:172 msgid "Test result" msgstr "" -#: stock/models.py:1421 +#: stock/models.py:1464 msgid "Test output value" msgstr "" -#: stock/models.py:1427 +#: stock/models.py:1470 #, fuzzy #| msgid "Attachments" msgid "Attachment" msgstr "Anhänge" -#: stock/models.py:1428 +#: stock/models.py:1471 #, fuzzy #| msgid "Delete attachment" msgid "Test result attachment" msgstr "Anhang löschen" -#: stock/models.py:1434 +#: stock/models.py:1477 #, fuzzy #| msgid "Edit notes" msgid "Test notes" @@ -4592,137 +4698,159 @@ msgstr "" "Dieses Lagerobjekt wird automatisch gelöscht wenn der Lagerbestand " "aufgebraucht ist." -#: stock/templates/stock/item_base.html:107 templates/js/barcode.js:283 +#: stock/templates/stock/item_base.html:74 +#: stock/templates/stock/item_base.html:310 templates/js/table_filters.js:111 +msgid "Expired" +msgstr "" + +#: stock/templates/stock/item_base.html:78 +#: stock/templates/stock/item_base.html:312 templates/js/table_filters.js:116 +msgid "Stale" +msgstr "" + +#: stock/templates/stock/item_base.html:114 templates/js/barcode.js:283 #: templates/js/barcode.js:288 msgid "Unlink Barcode" msgstr "" -#: stock/templates/stock/item_base.html:109 +#: stock/templates/stock/item_base.html:116 msgid "Link Barcode" msgstr "" -#: stock/templates/stock/item_base.html:117 +#: stock/templates/stock/item_base.html:124 #, fuzzy #| msgid "Confirm stock adjustment" msgid "Stock adjustment actions" msgstr "Bestands-Anpassung bestätigen" -#: stock/templates/stock/item_base.html:121 +#: stock/templates/stock/item_base.html:128 #: stock/templates/stock/location.html:41 templates/stock_table.html:23 msgid "Count stock" msgstr "Bestand zählen" -#: stock/templates/stock/item_base.html:122 templates/stock_table.html:21 +#: stock/templates/stock/item_base.html:129 templates/stock_table.html:21 msgid "Add stock" msgstr "Bestand hinzufügen" -#: stock/templates/stock/item_base.html:123 templates/stock_table.html:22 +#: stock/templates/stock/item_base.html:130 templates/stock_table.html:22 msgid "Remove stock" msgstr "Bestand entfernen" -#: stock/templates/stock/item_base.html:125 +#: stock/templates/stock/item_base.html:132 #, fuzzy #| msgid "Order stock" msgid "Transfer stock" msgstr "Bestand bestellen" -#: stock/templates/stock/item_base.html:127 +#: stock/templates/stock/item_base.html:134 #, fuzzy #| msgid "Serialize Stock" msgid "Serialize stock" msgstr "Lagerbestand erfassen" -#: stock/templates/stock/item_base.html:131 +#: stock/templates/stock/item_base.html:138 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assign to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/templates/stock/item_base.html:134 +#: stock/templates/stock/item_base.html:141 #, fuzzy #| msgid "Count stock" msgid "Return to stock" msgstr "Bestand zählen" -#: stock/templates/stock/item_base.html:138 templates/js/stock.js:991 +#: stock/templates/stock/item_base.html:145 templates/js/stock.js:1017 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstall stock item" msgstr "In Lagerobjekt installiert" -#: stock/templates/stock/item_base.html:138 +#: stock/templates/stock/item_base.html:145 msgid "Uninstall" msgstr "" -#: stock/templates/stock/item_base.html:147 +#: stock/templates/stock/item_base.html:154 #: stock/templates/stock/location.html:38 #, fuzzy #| msgid "Stock Locations" msgid "Stock actions" msgstr "Lagerobjekt-Standorte" -#: stock/templates/stock/item_base.html:150 +#: stock/templates/stock/item_base.html:157 #, fuzzy #| msgid "Count stock items" msgid "Convert to variant" msgstr "Lagerobjekte zählen" -#: stock/templates/stock/item_base.html:153 +#: stock/templates/stock/item_base.html:160 #, fuzzy #| msgid "Count stock items" msgid "Duplicate stock item" msgstr "Lagerobjekte zählen" -#: stock/templates/stock/item_base.html:155 +#: stock/templates/stock/item_base.html:162 #, fuzzy #| msgid "Edit Stock Item" msgid "Edit stock item" msgstr "Lagerobjekt bearbeiten" -#: stock/templates/stock/item_base.html:158 +#: stock/templates/stock/item_base.html:165 #, fuzzy #| msgid "Delete Stock Item" msgid "Delete stock item" msgstr "Lagerobjekt löschen" -#: stock/templates/stock/item_base.html:164 +#: stock/templates/stock/item_base.html:171 msgid "Generate test report" msgstr "" -#: stock/templates/stock/item_base.html:172 +#: stock/templates/stock/item_base.html:179 msgid "Stock Item Details" msgstr "Lagerbestands-Details" -#: stock/templates/stock/item_base.html:231 templates/js/build.js:442 +#: stock/templates/stock/item_base.html:238 templates/js/build.js:442 #, fuzzy #| msgid "No stock location set" msgid "No location set" msgstr "Kein Lagerort gesetzt" -#: stock/templates/stock/item_base.html:238 +#: stock/templates/stock/item_base.html:245 #, fuzzy #| msgid "Unique Identifier" msgid "Barcode Identifier" msgstr "Eindeutiger Bezeichner" -#: stock/templates/stock/item_base.html:252 templates/js/build.js:642 +#: stock/templates/stock/item_base.html:259 templates/js/build.js:642 #: templates/navbar.html:25 msgid "Build" msgstr "Bau" -#: stock/templates/stock/item_base.html:273 +#: stock/templates/stock/item_base.html:280 msgid "Parent Item" msgstr "Elternposition" -#: stock/templates/stock/item_base.html:298 +#: stock/templates/stock/item_base.html:310 +#, fuzzy +#| msgid "This stock item is allocated to Build" +msgid "This StockItem expired on" +msgstr "Dieses Lagerobjekt ist dem Bau zugewiesen" + +#: stock/templates/stock/item_base.html:312 +#, fuzzy +#| msgid "Child Stock Items" +msgid "This StockItem expires on" +msgstr "Kind-Lagerobjekte" + +#: stock/templates/stock/item_base.html:319 msgid "Last Updated" msgstr "Zuletzt aktualisiert" -#: stock/templates/stock/item_base.html:303 +#: stock/templates/stock/item_base.html:324 msgid "Last Stocktake" msgstr "Letzte Inventur" -#: stock/templates/stock/item_base.html:307 +#: stock/templates/stock/item_base.html:328 msgid "No stocktake performed" msgstr "Keine Inventur ausgeführt" @@ -4866,7 +4994,7 @@ msgstr "Sind Sie sicher, dass Sie diesen Anhang löschen wollen?" msgid "The following stock items will be uninstalled" msgstr "Die folgenden Objekte werden erstellt" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1331 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1335 #, fuzzy #| msgid "Count Stock Items" msgid "Convert Stock Item" @@ -5104,43 +5232,43 @@ msgstr "{n} Teile im Lager gelöscht" msgid "Edit Stock Item" msgstr "Lagerobjekt bearbeiten" -#: stock/views.py:1381 +#: stock/views.py:1385 msgid "Serialize Stock" msgstr "Lagerbestand erfassen" -#: stock/views.py:1475 templates/js/build.js:210 +#: stock/views.py:1479 templates/js/build.js:210 msgid "Create new Stock Item" msgstr "Neues Lagerobjekt hinzufügen" -#: stock/views.py:1579 +#: stock/views.py:1587 #, fuzzy #| msgid "Count stock items" msgid "Duplicate Stock Item" msgstr "Lagerobjekte zählen" -#: stock/views.py:1651 +#: stock/views.py:1664 #, fuzzy #| msgid "Quantity must be greater than zero" msgid "Quantity cannot be negative" msgstr "Anzahl muss größer Null sein" -#: stock/views.py:1737 +#: stock/views.py:1750 msgid "Delete Stock Location" msgstr "Standort löschen" -#: stock/views.py:1751 +#: stock/views.py:1764 msgid "Delete Stock Item" msgstr "Lagerobjekt löschen" -#: stock/views.py:1763 +#: stock/views.py:1776 msgid "Delete Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag löschen" -#: stock/views.py:1782 +#: stock/views.py:1795 msgid "Edit Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag bearbeiten" -#: stock/views.py:1792 +#: stock/views.py:1805 msgid "Add Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag hinzufügen" @@ -5180,7 +5308,13 @@ msgstr "Eltern-Bau" msgid "Pending Builds" msgstr "Eltern-Bau" -#: templates/InvenTree/index.html:4 +#: templates/InvenTree/expired_stock.html:7 +#, fuzzy +#| msgid "Assigned" +msgid "Expired Stock" +msgstr "Zugewiesen" + +#: templates/InvenTree/index.html:5 msgid "Index" msgstr "" @@ -5218,13 +5352,13 @@ msgstr "Keine Ergebnisse gefunden" msgid "Enter a search query" msgstr "Auftrag stornieren" -#: templates/InvenTree/search.html:191 templates/js/stock.js:289 +#: templates/InvenTree/search.html:191 templates/js/stock.js:290 #, fuzzy #| msgid "Item assigned to customer?" msgid "Shipped to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: templates/InvenTree/search.html:194 templates/js/stock.js:299 +#: templates/InvenTree/search.html:194 templates/js/stock.js:300 msgid "No stock location set" msgstr "Kein Lagerort gesetzt" @@ -5265,12 +5399,12 @@ msgid "Default Value" msgstr "Standard-Lagerort" #: templates/InvenTree/settings/category.html:70 -#: templates/InvenTree/settings/part.html:75 +#: templates/InvenTree/settings/part.html:78 msgid "Edit Template" msgstr "Vorlage bearbeiten" #: templates/InvenTree/settings/category.html:71 -#: templates/InvenTree/settings/part.html:76 +#: templates/InvenTree/settings/part.html:79 msgid "Delete Template" msgstr "Vorlage löschen" @@ -5280,6 +5414,12 @@ msgstr "Vorlage löschen" msgid "Global InvenTree Settings" msgstr "InvenTree-Version" +#: templates/InvenTree/settings/header.html:7 +#, fuzzy +#| msgid "Settings" +msgid "Setting" +msgstr "Einstellungen" + #: templates/InvenTree/settings/part.html:9 #, fuzzy #| msgid "Settings" @@ -5292,13 +5432,13 @@ msgstr "Einstellungen" msgid "Part Options" msgstr "Quell-Standort" -#: templates/InvenTree/settings/part.html:34 +#: templates/InvenTree/settings/part.html:37 #, fuzzy #| msgid "Edit Part Parameter Template" msgid "Part Parameter Templates" msgstr "Teilparametervorlage bearbeiten" -#: templates/InvenTree/settings/part.html:55 +#: templates/InvenTree/settings/part.html:58 msgid "No part parameter templates found" msgstr "Keine Teilparametervorlagen gefunden" @@ -5308,11 +5448,11 @@ msgstr "Keine Teilparametervorlagen gefunden" msgid "Purchase Order Settings" msgstr "Bestelldetails" -#: templates/InvenTree/settings/setting.html:16 +#: templates/InvenTree/settings/setting.html:23 msgid "No value set" msgstr "" -#: templates/InvenTree/settings/setting.html:24 +#: templates/InvenTree/settings/setting.html:31 #, fuzzy #| msgid "Settings" msgid "Edit setting" @@ -5335,6 +5475,12 @@ msgstr "Auftragsdetails" msgid "Stock Settings" msgstr "Lagerobjekt-Standorte" +#: templates/InvenTree/settings/stock.html:13 +#, fuzzy +#| msgid "Stock Locations" +msgid "Stock Options" +msgstr "Lagerobjekt-Standorte" + #: templates/InvenTree/settings/tabs.html:3 #: templates/InvenTree/settings/user.html:10 #, fuzzy @@ -5434,6 +5580,12 @@ msgstr "Zielauftrag" msgid "Overdue Sales Orders" msgstr "Bestellungen" +#: templates/InvenTree/stale_stock.html:7 +#, fuzzy +#| msgid "Serialize Stock" +msgid "Stale Stock" +msgstr "Lagerbestand erfassen" + #: templates/InvenTree/starred_parts.html:7 msgid "Starred Parts" msgstr "Teilfavoriten" @@ -5747,7 +5899,7 @@ msgstr "Baugruppe" msgid "No purchase orders found" msgstr "Keine Bestellungen gefunden" -#: templates/js/order.js:188 templates/js/stock.js:687 +#: templates/js/order.js:188 templates/js/stock.js:702 msgid "Date" msgstr "Datum" @@ -5795,8 +5947,8 @@ msgstr "Keine Teile gefunden" msgid "No parts found" msgstr "Keine Teile gefunden" -#: templates/js/part.js:343 templates/js/stock.js:462 -#: templates/js/stock.js:1023 +#: templates/js/part.js:343 templates/js/stock.js:463 +#: templates/js/stock.js:1049 msgid "Select" msgstr "Auswählen" @@ -5804,7 +5956,7 @@ msgstr "Auswählen" msgid "No category" msgstr "Keine Kategorie" -#: templates/js/part.js:429 templates/js/table_filters.js:264 +#: templates/js/part.js:429 templates/js/table_filters.js:274 msgid "Low stock" msgstr "Bestand niedrig" @@ -5826,13 +5978,13 @@ msgstr "" msgid "No test templates matching query" msgstr "Keine zur Anfrage passenden Lagerobjekte" -#: templates/js/part.js:604 templates/js/stock.js:63 +#: templates/js/part.js:604 templates/js/stock.js:64 #, fuzzy #| msgid "Edit Sales Order" msgid "Edit test result" msgstr "Auftrag bearbeiten" -#: templates/js/part.js:605 templates/js/stock.js:64 +#: templates/js/part.js:605 templates/js/stock.js:65 #, fuzzy #| msgid "Delete attachment" msgid "Delete test result" @@ -5842,137 +5994,149 @@ msgstr "Anhang löschen" msgid "This test is defined for a parent part" msgstr "" -#: templates/js/stock.js:26 +#: templates/js/stock.js:27 msgid "PASS" msgstr "" -#: templates/js/stock.js:28 +#: templates/js/stock.js:29 msgid "FAIL" msgstr "" -#: templates/js/stock.js:33 +#: templates/js/stock.js:34 msgid "NO RESULT" msgstr "" -#: templates/js/stock.js:59 +#: templates/js/stock.js:60 #, fuzzy #| msgid "Edit Sales Order" msgid "Add test result" msgstr "Auftrag bearbeiten" -#: templates/js/stock.js:78 +#: templates/js/stock.js:79 #, fuzzy #| msgid "No results found" msgid "No test results found" msgstr "Keine Ergebnisse gefunden" -#: templates/js/stock.js:120 +#: templates/js/stock.js:121 #, fuzzy #| msgid "Shipment Date" msgid "Test Date" msgstr "Versanddatum" -#: templates/js/stock.js:281 +#: templates/js/stock.js:282 msgid "In production" msgstr "" -#: templates/js/stock.js:285 +#: templates/js/stock.js:286 #, fuzzy #| msgid "Installed in Stock Item" msgid "Installed in Stock Item" msgstr "In Lagerobjekt installiert" -#: templates/js/stock.js:293 +#: templates/js/stock.js:294 #, fuzzy #| msgid "Item assigned to customer?" msgid "Assigned to Sales Order" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: templates/js/stock.js:313 +#: templates/js/stock.js:314 msgid "No stock items matching query" msgstr "Keine zur Anfrage passenden Lagerobjekte" -#: templates/js/stock.js:430 +#: templates/js/stock.js:431 #, fuzzy #| msgid "Include sublocations" msgid "Undefined location" msgstr "Unterlagerorte einschließen" -#: templates/js/stock.js:524 +#: templates/js/stock.js:525 #, fuzzy #| msgid "StockItem is lost" msgid "Stock item is in production" msgstr "Lagerobjekt verloren" -#: templates/js/stock.js:529 +#: templates/js/stock.js:530 #, fuzzy #| msgid "This stock item is allocated to Sales Order" msgid "Stock item assigned to sales order" msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" -#: templates/js/stock.js:532 +#: templates/js/stock.js:533 #, fuzzy #| msgid "StockItem has been allocated" msgid "Stock item assigned to customer" msgstr "Lagerobjekt wurde zugewiesen" -#: templates/js/stock.js:536 +#: templates/js/stock.js:537 +#, fuzzy +#| msgid "StockItem has been allocated" +msgid "Stock item has expired" +msgstr "Lagerobjekt wurde zugewiesen" + +#: templates/js/stock.js:539 +#, fuzzy +#| msgid "StockItem is lost" +msgid "Stock item will expire soon" +msgstr "Lagerobjekt verloren" + +#: templates/js/stock.js:543 #, fuzzy #| msgid "StockItem has been allocated" msgid "Stock item has been allocated" msgstr "Lagerobjekt wurde zugewiesen" -#: templates/js/stock.js:540 +#: templates/js/stock.js:547 #, fuzzy #| msgid "Is this item installed in another item?" msgid "Stock item has been installed in another item" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: templates/js/stock.js:548 +#: templates/js/stock.js:555 #, fuzzy #| msgid "StockItem has been allocated" msgid "Stock item has been rejected" msgstr "Lagerobjekt wurde zugewiesen" -#: templates/js/stock.js:552 +#: templates/js/stock.js:559 #, fuzzy #| msgid "StockItem is lost" msgid "Stock item is lost" msgstr "Lagerobjekt verloren" -#: templates/js/stock.js:555 +#: templates/js/stock.js:562 #, fuzzy #| msgid "StockItem is lost" msgid "Stock item is destroyed" msgstr "Lagerobjekt verloren" -#: templates/js/stock.js:559 templates/js/table_filters.js:106 +#: templates/js/stock.js:566 templates/js/table_filters.js:106 #, fuzzy #| msgid "Delete" msgid "Depleted" msgstr "Löschen" -#: templates/js/stock.js:753 +#: templates/js/stock.js:768 msgid "No user information" msgstr "Keine Benutzerinformation" -#: templates/js/stock.js:862 +#: templates/js/stock.js:888 msgid "Create New Location" msgstr "Neuen Standort anlegen" -#: templates/js/stock.js:961 +#: templates/js/stock.js:987 #, fuzzy #| msgid "Serial Number" msgid "Serial" msgstr "Seriennummer" -#: templates/js/stock.js:1054 templates/js/table_filters.js:121 +#: templates/js/stock.js:1080 templates/js/table_filters.js:131 #, fuzzy #| msgid "Installed In" msgid "Installed" msgstr "Installiert in" -#: templates/js/stock.js:1079 +#: templates/js/stock.js:1105 #, fuzzy #| msgid "Installed In" msgid "Install item" @@ -5990,50 +6154,50 @@ msgstr "nachverfolgbar" msgid "Validated" msgstr "BOM validieren" -#: templates/js/table_filters.js:65 templates/js/table_filters.js:131 +#: templates/js/table_filters.js:65 templates/js/table_filters.js:141 #, fuzzy #| msgid "Serialize Stock" msgid "Is Serialized" msgstr "Lagerbestand erfassen" -#: templates/js/table_filters.js:68 templates/js/table_filters.js:138 +#: templates/js/table_filters.js:68 templates/js/table_filters.js:148 #, fuzzy #| msgid "Serial Number" msgid "Serial number GTE" msgstr "Seriennummer" -#: templates/js/table_filters.js:69 templates/js/table_filters.js:139 +#: templates/js/table_filters.js:69 templates/js/table_filters.js:149 #, fuzzy #| msgid "Serial number for this item" msgid "Serial number greater than or equal to" msgstr "Seriennummer für dieses Teil" -#: templates/js/table_filters.js:72 templates/js/table_filters.js:142 +#: templates/js/table_filters.js:72 templates/js/table_filters.js:152 #, fuzzy #| msgid "Serial Number" msgid "Serial number LTE" msgstr "Seriennummer" -#: templates/js/table_filters.js:73 templates/js/table_filters.js:143 +#: templates/js/table_filters.js:73 templates/js/table_filters.js:153 #, fuzzy #| msgid "Serial numbers already exist: " msgid "Serial number less than or equal to" msgstr "Seriennummern existieren bereits:" #: templates/js/table_filters.js:76 templates/js/table_filters.js:77 -#: templates/js/table_filters.js:134 templates/js/table_filters.js:135 +#: templates/js/table_filters.js:144 templates/js/table_filters.js:145 #, fuzzy #| msgid "Serial Number" msgid "Serial number" msgstr "Seriennummer" -#: templates/js/table_filters.js:81 templates/js/table_filters.js:152 +#: templates/js/table_filters.js:81 templates/js/table_filters.js:162 #, fuzzy #| msgid "Batch Code" msgid "Batch code" msgstr "Losnummer" -#: templates/js/table_filters.js:91 templates/js/table_filters.js:231 +#: templates/js/table_filters.js:91 templates/js/table_filters.js:241 msgid "Active parts" msgstr "Aktive Teile" @@ -6064,84 +6228,96 @@ msgid "Show stock items which are depleted" msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" #: templates/js/table_filters.js:112 +#, fuzzy +#| msgid "Delete this Stock Item when stock is depleted" +msgid "Show stock items which have expired" +msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" + +#: templates/js/table_filters.js:117 +#, fuzzy +#| msgid "Delete this Stock Item when stock is depleted" +msgid "Show stock which is close to expiring" +msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" + +#: templates/js/table_filters.js:122 msgid "Show items which are in stock" msgstr "" -#: templates/js/table_filters.js:116 +#: templates/js/table_filters.js:126 msgid "In Production" msgstr "" -#: templates/js/table_filters.js:117 +#: templates/js/table_filters.js:127 #, fuzzy #| msgid "Delete this Stock Item when stock is depleted" msgid "Show items which are in production" msgstr "Objekt löschen wenn Lagerbestand aufgebraucht" -#: templates/js/table_filters.js:122 +#: templates/js/table_filters.js:132 #, fuzzy #| msgid "Is this item installed in another item?" msgid "Show stock items which are installed in another item" msgstr "Ist dieses Teil in einem anderen verbaut?" -#: templates/js/table_filters.js:126 +#: templates/js/table_filters.js:136 #, fuzzy #| msgid "Item assigned to customer?" msgid "Sent to customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: templates/js/table_filters.js:127 +#: templates/js/table_filters.js:137 msgid "Show items which have been assigned to a customer" msgstr "" -#: templates/js/table_filters.js:147 templates/js/table_filters.js:148 +#: templates/js/table_filters.js:157 templates/js/table_filters.js:158 msgid "Stock status" msgstr "Bestandsstatus" -#: templates/js/table_filters.js:181 +#: templates/js/table_filters.js:191 msgid "Build status" msgstr "Bau-Status" -#: templates/js/table_filters.js:200 templates/js/table_filters.js:213 +#: templates/js/table_filters.js:210 templates/js/table_filters.js:223 msgid "Order status" msgstr "Bestellstatus" -#: templates/js/table_filters.js:205 templates/js/table_filters.js:218 +#: templates/js/table_filters.js:215 templates/js/table_filters.js:228 #, fuzzy #| msgid "Cascading" msgid "Outstanding" msgstr "Kaskadierend" -#: templates/js/table_filters.js:241 +#: templates/js/table_filters.js:251 msgid "Include subcategories" msgstr "Unterkategorien einschließen" -#: templates/js/table_filters.js:242 +#: templates/js/table_filters.js:252 msgid "Include parts in subcategories" msgstr "Teile in Unterkategorien einschließen" -#: templates/js/table_filters.js:246 +#: templates/js/table_filters.js:256 msgid "Has IPN" msgstr "" -#: templates/js/table_filters.js:247 +#: templates/js/table_filters.js:257 #, fuzzy #| msgid "Internal Part Number" msgid "Part has internal part number" msgstr "Interne Teilenummer" -#: templates/js/table_filters.js:252 +#: templates/js/table_filters.js:262 msgid "Show active parts" msgstr "Aktive Teile anzeigen" -#: templates/js/table_filters.js:260 +#: templates/js/table_filters.js:270 msgid "Stock available" msgstr "Bestand verfügbar" -#: templates/js/table_filters.js:276 +#: templates/js/table_filters.js:286 msgid "Starred" msgstr "Favorit" -#: templates/js/table_filters.js:288 +#: templates/js/table_filters.js:298 msgid "Purchasable" msgstr "Käuflich" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index a2b2081ed3..395f42c4e6 100644 --- a/InvenTree/locale/en/LC_MESSAGES/django.po +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-03 22:16+1100\n" +"POT-Creation-Date: 2021-01-06 23:11+1100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -55,7 +55,7 @@ msgid "Select Category" msgstr "" #: InvenTree/helpers.py:361 order/models.py:178 order/models.py:260 -#: stock/views.py:1647 +#: stock/views.py:1660 msgid "Invalid quantity provided" msgstr "" @@ -95,12 +95,12 @@ msgstr "" msgid "File comment" msgstr "" -#: InvenTree/models.py:68 templates/js/stock.js:744 +#: InvenTree/models.py:68 templates/js/stock.js:759 msgid "User" msgstr "" -#: InvenTree/models.py:106 part/templates/part/params.html:24 -#: templates/js/part.js:129 +#: InvenTree/models.py:106 part/models.py:647 +#: part/templates/part/params.html:24 templates/js/part.js:129 msgid "Name" msgstr "" @@ -295,14 +295,14 @@ msgstr "" msgid "Order target date" msgstr "" -#: build/forms.py:39 build/models.py:173 +#: build/forms.py:39 build/models.py:175 msgid "" "Target date for build completion. Build will be overdue after this date." msgstr "" #: build/forms.py:78 build/templates/build/auto_allocate.html:17 #: build/templates/build/build_base.html:83 -#: build/templates/build/detail.html:29 common/models.py:496 +#: build/templates/build/detail.html:29 common/models.py:589 #: company/forms.py:112 company/templates/company/supplier_part_pricing.html:75 #: order/templates/order/order_wizard/select_parts.html:32 #: order/templates/order/purchase_order_detail.html:179 @@ -310,13 +310,13 @@ msgstr "" #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 #: part/templates/part/allocation.html:49 -#: part/templates/part/sale_prices.html:82 stock/forms.py:298 +#: part/templates/part/sale_prices.html:82 stock/forms.py:304 #: stock/templates/stock/item_base.html:40 #: stock/templates/stock/item_base.html:46 -#: stock/templates/stock/item_base.html:197 +#: stock/templates/stock/item_base.html:204 #: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:195 templates/js/build.js:420 templates/js/stock.js:735 -#: templates/js/stock.js:963 +#: templates/js/bom.js:195 templates/js/build.js:420 templates/js/stock.js:750 +#: templates/js/stock.js:989 msgid "Quantity" msgstr "" @@ -324,7 +324,7 @@ msgstr "" msgid "Enter quantity for build output" msgstr "" -#: build/forms.py:83 stock/forms.py:111 +#: build/forms.py:83 stock/forms.py:116 msgid "Serial numbers" msgstr "" @@ -372,222 +372,223 @@ msgstr "" msgid "Select quantity of stock to allocate" msgstr "" -#: build/models.py:59 build/templates/build/build_base.html:8 +#: build/models.py:61 build/templates/build/build_base.html:8 #: build/templates/build/build_base.html:35 #: part/templates/part/allocation.html:20 msgid "Build Order" msgstr "" -#: build/models.py:60 build/templates/build/index.html:6 +#: build/models.py:62 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 #: templates/InvenTree/settings/tabs.html:28 users/models.py:30 msgid "Build Orders" msgstr "" -#: build/models.py:75 +#: build/models.py:77 msgid "Build Order Reference" msgstr "" -#: build/models.py:76 order/templates/order/purchase_order_detail.html:174 +#: build/models.py:78 order/templates/order/purchase_order_detail.html:174 #: templates/js/bom.js:187 templates/js/build.js:509 msgid "Reference" msgstr "" -#: build/models.py:83 build/templates/build/detail.html:19 +#: build/models.py:85 build/templates/build/detail.html:19 #: company/models.py:359 company/templates/company/detail.html:23 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 -#: order/templates/order/purchase_order_detail.html:161 +#: order/templates/order/purchase_order_detail.html:161 part/models.py:671 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:180 +#: templates/InvenTree/search.html:147 +#: templates/InvenTree/settings/header.html:9 templates/js/bom.js:180 #: templates/js/bom.js:517 templates/js/build.js:664 templates/js/company.js:56 #: templates/js/order.js:175 templates/js/order.js:263 templates/js/part.js:188 #: templates/js/part.js:271 templates/js/part.js:391 templates/js/part.js:572 -#: templates/js/stock.js:500 templates/js/stock.js:716 +#: templates/js/stock.js:501 templates/js/stock.js:731 msgid "Description" msgstr "" -#: build/models.py:86 +#: build/models.py:88 msgid "Brief description of the build" msgstr "" -#: build/models.py:95 build/templates/build/build_base.html:104 +#: build/models.py:97 build/templates/build/build_base.html:104 #: build/templates/build/detail.html:75 msgid "Parent Build" msgstr "" -#: build/models.py:96 +#: build/models.py:98 msgid "BuildOrder to which this build is allocated" msgstr "" -#: build/models.py:101 build/templates/build/auto_allocate.html:16 +#: build/models.py:103 build/templates/build/auto_allocate.html:16 #: build/templates/build/build_base.html:78 #: build/templates/build/detail.html:24 order/models.py:548 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 -#: order/templates/order/receive_parts.html:19 part/models.py:315 +#: order/templates/order/receive_parts.html:19 part/models.py:316 #: part/templates/part/part_app_base.html:7 part/templates/part/related.html:26 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 #: templates/js/barcode.js:336 templates/js/bom.js:153 templates/js/bom.js:502 #: templates/js/build.js:669 templates/js/company.js:138 -#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:474 -#: templates/js/stock.js:1035 +#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:475 +#: templates/js/stock.js:1061 msgid "Part" msgstr "" -#: build/models.py:109 +#: build/models.py:111 msgid "Select part to build" msgstr "" -#: build/models.py:114 +#: build/models.py:116 msgid "Sales Order Reference" msgstr "" -#: build/models.py:118 +#: build/models.py:120 msgid "SalesOrder to which this build is allocated" msgstr "" -#: build/models.py:123 +#: build/models.py:125 msgid "Source Location" msgstr "" -#: build/models.py:127 +#: build/models.py:129 msgid "" "Select location to take stock from for this build (leave blank to take from " "any stock location)" msgstr "" -#: build/models.py:132 +#: build/models.py:134 msgid "Destination Location" msgstr "" -#: build/models.py:136 +#: build/models.py:138 msgid "Select location where the completed items will be stored" msgstr "" -#: build/models.py:140 +#: build/models.py:142 msgid "Build Quantity" msgstr "" -#: build/models.py:143 +#: build/models.py:145 msgid "Number of stock items to build" msgstr "" -#: build/models.py:147 +#: build/models.py:149 msgid "Completed items" msgstr "" -#: build/models.py:149 +#: build/models.py:151 msgid "Number of stock items which have been completed" msgstr "" -#: build/models.py:153 part/templates/part/part_base.html:155 +#: build/models.py:155 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "" -#: build/models.py:157 +#: build/models.py:159 msgid "Build status code" msgstr "" -#: build/models.py:161 stock/models.py:390 +#: build/models.py:163 stock/models.py:396 msgid "Batch Code" msgstr "" -#: build/models.py:165 +#: build/models.py:167 msgid "Batch code for this build output" msgstr "" -#: build/models.py:172 order/models.py:329 +#: build/models.py:174 order/models.py:329 msgid "Target completion date" msgstr "" -#: build/models.py:186 build/templates/build/detail.html:89 +#: build/models.py:188 build/templates/build/detail.html:89 #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:384 stock/templates/stock/item_base.html:280 +#: stock/models.py:390 stock/templates/stock/item_base.html:287 msgid "External Link" msgstr "" -#: build/models.py:187 part/models.py:672 stock/models.py:386 +#: build/models.py:189 part/models.py:705 stock/models.py:392 msgid "Link to external URL" msgstr "" -#: build/models.py:191 build/templates/build/tabs.html:23 company/models.py:366 +#: build/models.py:193 build/templates/build/tabs.html:23 company/models.py:366 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 #: order/templates/order/purchase_order_detail.html:213 -#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 -#: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 -#: stock/models.py:1433 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:263 -#: templates/js/stock.js:116 templates/js/stock.js:588 +#: order/templates/order/so_tabs.html:23 part/models.py:831 +#: part/templates/part/tabs.html:73 stock/forms.py:313 stock/forms.py:345 +#: stock/forms.py:373 stock/models.py:462 stock/models.py:1476 +#: stock/templates/stock/tabs.html:26 templates/js/barcode.js:391 +#: templates/js/bom.js:263 templates/js/stock.js:117 templates/js/stock.js:603 msgid "Notes" msgstr "" -#: build/models.py:192 +#: build/models.py:194 msgid "Extra build notes" msgstr "" -#: build/models.py:577 +#: build/models.py:579 msgid "No build output specified" msgstr "" -#: build/models.py:580 +#: build/models.py:582 msgid "Build output is already completed" msgstr "" -#: build/models.py:583 +#: build/models.py:585 msgid "Build output does not match Build Order" msgstr "" -#: build/models.py:658 +#: build/models.py:660 msgid "Completed build output" msgstr "" -#: build/models.py:896 +#: build/models.py:902 msgid "BuildItem must be unique for build, stock_item and install_into" msgstr "" -#: build/models.py:918 +#: build/models.py:924 msgid "Build item must specify a build output" msgstr "" -#: build/models.py:923 +#: build/models.py:929 #, python-brace-format msgid "Selected stock item not found in BOM for part '{p}'" msgstr "" -#: build/models.py:927 +#: build/models.py:933 #, python-brace-format msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:934 order/models.py:632 +#: build/models.py:940 order/models.py:632 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:938 order/models.py:635 +#: build/models.py:944 order/models.py:635 msgid "Allocation quantity must be greater than zero" msgstr "" -#: build/models.py:942 +#: build/models.py:948 msgid "Quantity must be 1 for serialized stock" msgstr "" -#: build/models.py:982 +#: build/models.py:988 msgid "Build to allocate parts" msgstr "" -#: build/models.py:989 +#: build/models.py:995 msgid "Source stock item" msgstr "" -#: build/models.py:1001 +#: build/models.py:1007 msgid "Stock quantity to allocate to build" msgstr "" -#: build/models.py:1009 +#: build/models.py:1015 msgid "Destination stock item" msgstr "" @@ -612,7 +613,7 @@ msgid "Order required parts" msgstr "" #: build/templates/build/allocate.html:30 -#: company/templates/company/detail_part.html:28 order/views.py:803 +#: company/templates/company/detail_part.html:28 order/views.py:805 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "" @@ -652,11 +653,11 @@ msgid "" "The following stock items will be allocated to the specified build output" msgstr "" -#: build/templates/build/auto_allocate.html:18 stock/forms.py:337 -#: stock/templates/stock/item_base.html:227 +#: build/templates/build/auto_allocate.html:18 stock/forms.py:343 +#: stock/templates/stock/item_base.html:234 #: stock/templates/stock/stock_adjust.html:17 #: templates/InvenTree/search.html:183 templates/js/barcode.js:337 -#: templates/js/build.js:434 templates/js/stock.js:580 +#: templates/js/build.js:434 templates/js/stock.js:587 msgid "Location" msgstr "" @@ -681,7 +682,7 @@ msgstr "" #: order/templates/order/order_base.html:26 #: order/templates/order/sales_order_base.html:35 #: part/templates/part/category.html:13 part/templates/part/part_base.html:32 -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:97 #: stock/templates/stock/location.html:12 msgid "Admin view" msgstr "" @@ -690,7 +691,7 @@ msgstr "" #: build/templates/build/build_base.html:92 #: order/templates/order/sales_order_base.html:41 #: order/templates/order/sales_order_base.html:83 -#: templates/js/table_filters.js:190 templates/js/table_filters.js:222 +#: templates/js/table_filters.js:200 templates/js/table_filters.js:232 msgid "Overdue" msgstr "" @@ -717,10 +718,10 @@ msgstr "" #: build/templates/build/build_base.html:88 #: build/templates/build/detail.html:57 #: order/templates/order/receive_parts.html:24 -#: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 +#: stock/templates/stock/item_base.html:333 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:697 #: templates/js/order.js:180 templates/js/order.js:268 -#: templates/js/stock.js:567 templates/js/stock.js:971 +#: templates/js/stock.js:574 templates/js/stock.js:997 msgid "Status" msgstr "" @@ -740,7 +741,7 @@ msgstr "" #: order/templates/order/sales_order_notes.html:10 #: order/templates/order/sales_order_ship.html:25 #: part/templates/part/allocation.html:27 -#: stock/templates/stock/item_base.html:221 templates/js/order.js:229 +#: stock/templates/stock/item_base.html:228 templates/js/order.js:229 msgid "Sales Order" msgstr "" @@ -832,7 +833,7 @@ msgstr "" msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:44 stock/forms.py:365 +#: build/templates/build/detail.html:44 stock/forms.py:371 msgid "Destination" msgstr "" @@ -841,9 +842,9 @@ msgid "Destination location not specified" msgstr "" #: build/templates/build/detail.html:68 -#: stock/templates/stock/item_base.html:245 templates/js/stock.js:575 -#: templates/js/stock.js:978 templates/js/table_filters.js:80 -#: templates/js/table_filters.js:151 +#: stock/templates/stock/item_base.html:252 templates/js/stock.js:582 +#: templates/js/stock.js:1004 templates/js/table_filters.js:80 +#: templates/js/table_filters.js:161 msgid "Batch" msgstr "" @@ -935,7 +936,7 @@ msgstr "" msgid "Create Build Output" msgstr "" -#: build/views.py:207 stock/models.py:828 stock/views.py:1668 +#: build/views.py:207 stock/models.py:871 stock/views.py:1681 msgid "Serial numbers already exist" msgstr "" @@ -1032,36 +1033,36 @@ msgstr "" msgid "Stock item must be selected" msgstr "" -#: build/views.py:1011 +#: build/views.py:1012 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:1016 +#: build/views.py:1017 msgid "Updated Build Item" msgstr "" -#: build/views.py:1045 +#: build/views.py:1046 msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:168 +#: build/views.py:1060 order/views.py:113 order/views.py:166 part/views.py:170 #: stock/views.py:180 msgid "Added attachment" msgstr "" -#: build/views.py:1095 order/views.py:191 order/views.py:213 +#: build/views.py:1096 order/views.py:193 order/views.py:215 msgid "Edit Attachment" msgstr "" -#: build/views.py:1106 order/views.py:196 order/views.py:218 +#: build/views.py:1107 order/views.py:198 order/views.py:220 msgid "Attachment updated" msgstr "" -#: build/views.py:1116 order/views.py:233 order/views.py:248 +#: build/views.py:1117 order/views.py:235 order/views.py:250 msgid "Delete Attachment" msgstr "" -#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:238 +#: build/views.py:1123 order/views.py:242 order/views.py:257 stock/views.py:238 msgid "Deleted attachment" msgstr "" @@ -1137,103 +1138,170 @@ msgstr "" msgid "Copy category parameter templates when creating a part" msgstr "" -#: common/models.py:115 part/models.py:743 part/templates/part/detail.html:168 -#: templates/js/table_filters.js:272 -msgid "Component" +#: common/models.py:115 part/templates/part/detail.html:155 stock/forms.py:255 +#: templates/js/table_filters.js:23 templates/js/table_filters.js:266 +msgid "Template" msgstr "" #: common/models.py:116 -msgid "Parts can be used as sub-components by default" +msgid "Parts are templates by default" msgstr "" -#: common/models.py:122 part/models.py:754 part/templates/part/detail.html:188 -msgid "Purchaseable" +#: common/models.py:122 part/models.py:794 part/templates/part/detail.html:165 +#: templates/js/table_filters.js:278 +msgid "Assembly" msgstr "" #: common/models.py:123 -msgid "Parts are purchaseable by default" +msgid "Parts can be assembled from other components by default" msgstr "" -#: common/models.py:129 part/models.py:759 part/templates/part/detail.html:198 -#: templates/js/table_filters.js:280 -msgid "Salable" +#: common/models.py:129 part/models.py:800 part/templates/part/detail.html:175 +#: templates/js/table_filters.js:282 +msgid "Component" msgstr "" #: common/models.py:130 -msgid "Parts are salable by default" +msgid "Parts can be used as sub-components by default" msgstr "" -#: common/models.py:136 part/models.py:749 part/templates/part/detail.html:178 -#: templates/js/table_filters.js:31 templates/js/table_filters.js:284 -msgid "Trackable" +#: common/models.py:136 part/models.py:811 part/templates/part/detail.html:195 +msgid "Purchaseable" msgstr "" #: common/models.py:137 -msgid "Parts are trackable by default" +msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:143 -msgid "Build Order Reference Prefix" +#: common/models.py:143 part/models.py:816 part/templates/part/detail.html:205 +#: templates/js/table_filters.js:290 +msgid "Salable" msgstr "" #: common/models.py:144 +msgid "Parts are salable by default" +msgstr "" + +#: common/models.py:150 part/models.py:806 part/templates/part/detail.html:185 +#: templates/js/table_filters.js:31 templates/js/table_filters.js:294 +msgid "Trackable" +msgstr "" + +#: common/models.py:151 +msgid "Parts are trackable by default" +msgstr "" + +#: common/models.py:157 part/models.py:826 part/templates/part/detail.html:145 +#: templates/js/table_filters.js:27 +msgid "Virtual" +msgstr "" + +#: common/models.py:158 +msgid "Parts are virtual by default" +msgstr "" + +#: common/models.py:164 +msgid "Stock Expiry" +msgstr "" + +#: common/models.py:165 +msgid "Enable stock expiry functionality" +msgstr "" + +#: common/models.py:171 +msgid "Sell Expired Stock" +msgstr "" + +#: common/models.py:172 +msgid "Allow sale of expired stock" +msgstr "" + +#: common/models.py:178 +msgid "Stock Stale Time" +msgstr "" + +#: common/models.py:179 +msgid "Number of days stock items are considered stale before expiring" +msgstr "" + +#: common/models.py:181 part/templates/part/detail.html:116 +msgid "days" +msgstr "" + +#: common/models.py:186 +msgid "Build Expired Stock" +msgstr "" + +#: common/models.py:187 +msgid "Allow building with expired stock" +msgstr "" + +#: common/models.py:193 +msgid "Build Order Reference Prefix" +msgstr "" + +#: common/models.py:194 msgid "Prefix value for build order reference" msgstr "" -#: common/models.py:149 +#: common/models.py:199 msgid "Build Order Reference Regex" msgstr "" -#: common/models.py:150 +#: common/models.py:200 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:154 +#: common/models.py:204 msgid "Sales Order Reference Prefix" msgstr "" -#: common/models.py:155 +#: common/models.py:205 msgid "Prefix value for sales order reference" msgstr "" -#: common/models.py:160 +#: common/models.py:210 msgid "Purchase Order Reference Prefix" msgstr "" -#: common/models.py:161 +#: common/models.py:211 msgid "Prefix value for purchase order reference" msgstr "" -#: common/models.py:378 +#: common/models.py:434 msgid "Settings key (must be unique - case insensitive" msgstr "" -#: common/models.py:380 +#: common/models.py:436 msgid "Settings value" msgstr "" -#: common/models.py:439 +#: common/models.py:493 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:453 +#: common/models.py:503 +msgid "Value must be an integer value" +msgstr "" + +#: common/models.py:517 msgid "Key string must be unique" msgstr "" -#: common/models.py:497 company/forms.py:113 +#: common/models.py:590 company/forms.py:113 msgid "Price break quantity" msgstr "" -#: common/models.py:505 company/templates/company/supplier_part_pricing.html:80 +#: common/models.py:598 company/templates/company/supplier_part_pricing.html:80 #: part/templates/part/sale_prices.html:87 templates/js/bom.js:246 msgid "Price" msgstr "" -#: common/models.py:506 +#: common/models.py:599 msgid "Unit price at specified quantity" msgstr "" -#: common/models.py:529 +#: common/models.py:622 msgid "Default" msgstr "" @@ -1334,8 +1402,8 @@ msgstr "" msgid "Currency" msgstr "" -#: company/models.py:313 stock/models.py:338 -#: stock/templates/stock/item_base.html:177 +#: company/models.py:313 stock/models.py:344 +#: stock/templates/stock/item_base.html:184 msgid "Base Part" msgstr "" @@ -1348,7 +1416,7 @@ msgstr "" #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 -#: stock/templates/stock/item_base.html:287 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:294 templates/js/company.js:48 #: templates/js/company.js:164 templates/js/order.js:162 msgid "Supplier" msgstr "" @@ -1387,7 +1455,7 @@ msgstr "" msgid "Manufacturer part number" msgstr "" -#: company/models.py:353 templates/js/company.js:208 +#: company/models.py:353 part/models.py:704 templates/js/company.js:208 msgid "Link" msgstr "" @@ -1444,8 +1512,8 @@ msgid "Uses default currency" msgstr "" #: company/templates/company/detail.html:62 -#: order/templates/order/sales_order_base.html:89 stock/models.py:373 -#: stock/models.py:374 stock/templates/stock/item_base.html:204 +#: order/templates/order/sales_order_base.html:89 stock/models.py:379 +#: stock/models.py:380 stock/templates/stock/item_base.html:211 #: templates/js/company.js:40 templates/js/order.js:250 msgid "Customer" msgstr "" @@ -1461,7 +1529,7 @@ msgstr "" #: company/templates/company/detail_part.html:18 #: order/templates/order/purchase_order_detail.html:68 -#: part/templates/part/supplier.html:14 templates/js/stock.js:855 +#: part/templates/part/supplier.html:14 templates/js/stock.js:881 msgid "New Supplier Part" msgstr "" @@ -1485,7 +1553,7 @@ msgid "Delete Parts" msgstr "" #: company/templates/company/detail_part.html:63 -#: part/templates/part/category.html:116 templates/js/stock.js:849 +#: part/templates/part/category.html:116 templates/js/stock.js:875 msgid "New Part" msgstr "" @@ -1577,8 +1645,8 @@ msgid "New Sales Order" msgstr "" #: company/templates/company/supplier_part_base.html:6 -#: company/templates/company/supplier_part_base.html:19 stock/models.py:347 -#: stock/templates/stock/item_base.html:292 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:353 +#: stock/templates/stock/item_base.html:299 templates/js/company.js:180 msgid "Supplier Part" msgstr "" @@ -1619,7 +1687,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:486 -#: part/templates/part/sale_prices.html:14 part/views.py:2555 +#: part/templates/part/sale_prices.html:14 part/views.py:2565 msgid "Add Price Break" msgstr "" @@ -1650,7 +1718,7 @@ msgstr "" #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 -#: templates/js/part.js:418 templates/js/stock.js:508 templates/navbar.html:22 +#: templates/js/part.js:418 templates/js/stock.js:509 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" msgstr "" @@ -1660,7 +1728,7 @@ msgid "Orders" msgstr "" #: company/templates/company/tabs.html:9 -#: order/templates/order/receive_parts.html:14 part/models.py:316 +#: order/templates/order/receive_parts.html:14 part/models.py:317 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 #: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 @@ -1733,7 +1801,7 @@ msgstr "" msgid "Edit Supplier Part" msgstr "" -#: company/views.py:295 templates/js/stock.js:856 +#: company/views.py:295 templates/js/stock.js:882 msgid "Create new Supplier Part" msgstr "" @@ -1741,15 +1809,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:492 part/views.py:2561 +#: company/views.py:492 part/views.py:2571 msgid "Added new price break" msgstr "" -#: company/views.py:548 part/views.py:2605 +#: company/views.py:548 part/views.py:2615 msgid "Edit Price Break" msgstr "" -#: company/views.py:564 part/views.py:2621 +#: company/views.py:564 part/views.py:2631 msgid "Delete Price Break" msgstr "" @@ -1847,8 +1915,8 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:176 order/models.py:258 part/views.py:1494 -#: stock/models.py:244 stock/models.py:812 +#: order/models.py:176 order/models.py:258 part/views.py:1504 +#: stock/models.py:250 stock/models.py:855 msgid "Quantity must be greater than zero" msgstr "" @@ -1886,7 +1954,7 @@ msgstr "" #: order/models.py:504 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:259 templates/js/order.js:146 +#: stock/templates/stock/item_base.html:266 templates/js/order.js:146 msgid "Purchase Order" msgstr "" @@ -1898,8 +1966,8 @@ msgstr "" msgid "Number of items received" msgstr "" -#: order/models.py:527 stock/models.py:458 -#: stock/templates/stock/item_base.html:266 +#: order/models.py:527 stock/models.py:472 +#: stock/templates/stock/item_base.html:273 msgid "Purchase Price" msgstr "" @@ -2045,8 +2113,8 @@ msgid "Line Items" msgstr "" #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1201 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1119 +#: order/views.py:1203 msgid "Add Line Item" msgstr "" @@ -2057,7 +2125,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:39 #: order/templates/order/purchase_order_detail.html:119 #: part/templates/part/category.html:173 part/templates/part/category.html:215 -#: templates/js/stock.js:861 +#: templates/js/stock.js:627 templates/js/stock.js:887 msgid "New Location" msgstr "" @@ -2142,8 +2210,8 @@ msgid "Sales Order Items" msgstr "" #: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:378 -#: stock/templates/stock/item_base.html:191 templates/js/build.js:418 +#: order/templates/order/sales_order_detail.html:154 stock/models.py:384 +#: stock/templates/stock/item_base.html:198 templates/js/build.js:418 msgid "Serial Number" msgstr "" @@ -2221,143 +2289,143 @@ msgstr "" msgid "Order Items" msgstr "" -#: order/views.py:99 +#: order/views.py:101 msgid "Add Purchase Order Attachment" msgstr "" -#: order/views.py:150 +#: order/views.py:152 msgid "Add Sales Order Attachment" msgstr "" -#: order/views.py:310 +#: order/views.py:312 msgid "Create Purchase Order" msgstr "" -#: order/views.py:346 +#: order/views.py:348 msgid "Create Sales Order" msgstr "" -#: order/views.py:382 +#: order/views.py:384 msgid "Edit Purchase Order" msgstr "" -#: order/views.py:403 +#: order/views.py:405 msgid "Edit Sales Order" msgstr "" -#: order/views.py:420 +#: order/views.py:422 msgid "Cancel Order" msgstr "" -#: order/views.py:430 order/views.py:457 +#: order/views.py:432 order/views.py:459 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:433 +#: order/views.py:435 msgid "Order cannot be cancelled as either pending or placed" msgstr "" -#: order/views.py:447 +#: order/views.py:449 msgid "Cancel sales order" msgstr "" -#: order/views.py:460 +#: order/views.py:462 msgid "Order cannot be cancelled" msgstr "" -#: order/views.py:474 +#: order/views.py:476 msgid "Issue Order" msgstr "" -#: order/views.py:484 +#: order/views.py:486 msgid "Confirm order placement" msgstr "" -#: order/views.py:494 +#: order/views.py:496 msgid "Purchase order issued" msgstr "" -#: order/views.py:505 +#: order/views.py:507 msgid "Complete Order" msgstr "" -#: order/views.py:522 +#: order/views.py:524 msgid "Confirm order completion" msgstr "" -#: order/views.py:533 +#: order/views.py:535 msgid "Purchase order completed" msgstr "" -#: order/views.py:543 +#: order/views.py:545 msgid "Ship Order" msgstr "" -#: order/views.py:560 +#: order/views.py:562 msgid "Confirm order shipment" msgstr "" -#: order/views.py:566 +#: order/views.py:568 msgid "Could not ship order" msgstr "" -#: order/views.py:618 +#: order/views.py:620 msgid "Receive Parts" msgstr "" -#: order/views.py:686 +#: order/views.py:688 msgid "Items received" msgstr "" -#: order/views.py:700 +#: order/views.py:702 msgid "No destination set" msgstr "" -#: order/views.py:745 +#: order/views.py:747 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:751 +#: order/views.py:753 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:757 +#: order/views.py:759 msgid "No lines specified" msgstr "" -#: order/views.py:1127 +#: order/views.py:1129 msgid "Supplier part must be specified" msgstr "" -#: order/views.py:1133 +#: order/views.py:1135 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1253 order/views.py:1272 +#: order/views.py:1255 order/views.py:1274 msgid "Edit Line Item" msgstr "" -#: order/views.py:1289 order/views.py:1302 +#: order/views.py:1291 order/views.py:1304 msgid "Delete Line Item" msgstr "" -#: order/views.py:1295 order/views.py:1308 +#: order/views.py:1297 order/views.py:1310 msgid "Deleted line item" msgstr "" -#: order/views.py:1317 +#: order/views.py:1319 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1387 +#: order/views.py:1394 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1403 +#: order/views.py:1410 msgid "Remove allocation" msgstr "" -#: part/bom.py:138 part/templates/part/category.html:61 +#: part/bom.py:138 part/models.py:722 part/templates/part/category.html:61 #: part/templates/part/detail.html:87 msgid "Default Location" msgstr "" @@ -2379,11 +2447,11 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:61 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:261 msgid "File Format" msgstr "" -#: part/forms.py:61 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:261 msgid "Select output file format" msgstr "" @@ -2427,7 +2495,7 @@ msgstr "" msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:92 part/models.py:1720 +#: part/forms.py:92 part/models.py:1781 msgid "Parent Part" msgstr "" @@ -2459,43 +2527,43 @@ msgstr "" msgid "Select part category" msgstr "" -#: part/forms.py:188 +#: part/forms.py:189 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:189 +#: part/forms.py:190 msgid "Copy BOM" msgstr "" -#: part/forms.py:194 +#: part/forms.py:195 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:195 +#: part/forms.py:196 msgid "Copy Parameters" msgstr "" -#: part/forms.py:200 +#: part/forms.py:201 msgid "Confirm part creation" msgstr "" -#: part/forms.py:205 +#: part/forms.py:206 msgid "Include category parameter templates" msgstr "" -#: part/forms.py:210 +#: part/forms.py:211 msgid "Include parent categories parameter templates" msgstr "" -#: part/forms.py:285 +#: part/forms.py:291 msgid "Add parameter template to same level categories" msgstr "" -#: part/forms.py:289 +#: part/forms.py:295 msgid "Add parameter template to all categories" msgstr "" -#: part/forms.py:333 +#: part/forms.py:339 msgid "Input quantity for price calculation" msgstr "" @@ -2507,7 +2575,7 @@ msgstr "" msgid "Default keywords for parts in this category" msgstr "" -#: part/models.py:77 part/models.py:1765 +#: part/models.py:77 part/models.py:1826 #: part/templates/part/part_app_base.html:9 msgid "Part Category" msgstr "" @@ -2517,255 +2585,294 @@ msgstr "" msgid "Part Categories" msgstr "" -#: part/models.py:408 part/models.py:418 +#: part/models.py:409 part/models.py:419 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" -#: part/models.py:515 +#: part/models.py:516 msgid "Next available serial numbers are" msgstr "" -#: part/models.py:519 +#: part/models.py:520 msgid "Next available serial number is" msgstr "" -#: part/models.py:524 +#: part/models.py:525 msgid "Most recent serial number is" msgstr "" -#: part/models.py:603 +#: part/models.py:604 msgid "Duplicate IPN not allowed in part settings" msgstr "" -#: part/models.py:614 +#: part/models.py:615 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:644 part/templates/part/detail.html:19 +#: part/models.py:646 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:648 +#: part/models.py:653 +msgid "Is Template" +msgstr "" + +#: part/models.py:654 msgid "Is this part a template part?" msgstr "" -#: part/models.py:657 +#: part/models.py:665 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:659 +#: part/models.py:666 part/templates/part/detail.html:57 +msgid "Variant Of" +msgstr "" + +#: part/models.py:672 msgid "Part description" msgstr "" -#: part/models.py:661 +#: part/models.py:677 part/templates/part/category.html:68 +#: part/templates/part/detail.html:64 +msgid "Keywords" +msgstr "" + +#: part/models.py:678 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:666 +#: part/models.py:685 part/templates/part/detail.html:70 +#: part/templates/part/set_category.html:15 templates/js/part.js:405 +msgid "Category" +msgstr "" + +#: part/models.py:686 msgid "Part category" msgstr "" -#: part/models.py:668 +#: part/models.py:691 part/templates/part/detail.html:25 +#: part/templates/part/part_base.html:95 templates/js/part.js:180 +msgid "IPN" +msgstr "" + +#: part/models.py:692 msgid "Internal Part Number" msgstr "" -#: part/models.py:670 +#: part/models.py:698 msgid "Part revision or version number" msgstr "" -#: part/models.py:684 +#: part/models.py:699 part/templates/part/detail.html:32 +#: templates/js/part.js:184 +msgid "Revision" +msgstr "" + +#: part/models.py:720 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:728 +#: part/models.py:767 part/templates/part/detail.html:94 +msgid "Default Supplier" +msgstr "" + +#: part/models.py:768 msgid "Default supplier part" msgstr "" -#: part/models.py:731 +#: part/models.py:775 +msgid "Default Expiry" +msgstr "" + +#: part/models.py:776 +msgid "Expiry time (in days) for stock items of this part" +msgstr "" + +#: part/models.py:781 part/templates/part/detail.html:108 +msgid "Minimum Stock" +msgstr "" + +#: part/models.py:782 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:733 +#: part/models.py:788 part/templates/part/detail.html:102 +#: part/templates/part/params.html:26 +msgid "Units" +msgstr "" + +#: part/models.py:789 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:737 part/templates/part/detail.html:158 -#: templates/js/table_filters.js:268 -msgid "Assembly" -msgstr "" - -#: part/models.py:738 +#: part/models.py:795 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:744 +#: part/models.py:801 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:750 +#: part/models.py:807 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:755 +#: part/models.py:812 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:760 +#: part/models.py:817 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:764 part/templates/part/detail.html:215 +#: part/models.py:821 part/templates/part/detail.html:222 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 -#: templates/js/table_filters.js:186 templates/js/table_filters.js:251 +#: templates/js/table_filters.js:196 templates/js/table_filters.js:261 msgid "Active" msgstr "" -#: part/models.py:765 +#: part/models.py:822 msgid "Is this part active?" msgstr "" -#: part/models.py:769 part/templates/part/detail.html:138 -#: templates/js/table_filters.js:27 -msgid "Virtual" -msgstr "" - -#: part/models.py:770 +#: part/models.py:827 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:772 +#: part/models.py:832 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:774 +#: part/models.py:835 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1593 +#: part/models.py:1654 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1610 +#: part/models.py:1671 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1629 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1690 templates/js/part.js:567 templates/js/stock.js:93 msgid "Test Name" msgstr "" -#: part/models.py:1630 +#: part/models.py:1691 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1635 +#: part/models.py:1696 msgid "Test Description" msgstr "" -#: part/models.py:1636 +#: part/models.py:1697 msgid "Enter description for this test" msgstr "" -#: part/models.py:1641 templates/js/part.js:576 -#: templates/js/table_filters.js:172 +#: part/models.py:1702 templates/js/part.js:576 +#: templates/js/table_filters.js:182 msgid "Required" msgstr "" -#: part/models.py:1642 +#: part/models.py:1703 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1647 templates/js/part.js:584 +#: part/models.py:1708 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1648 +#: part/models.py:1709 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1653 templates/js/part.js:591 +#: part/models.py:1714 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1654 +#: part/models.py:1715 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1687 +#: part/models.py:1748 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1692 +#: part/models.py:1753 msgid "Parameter Name" msgstr "" -#: part/models.py:1694 +#: part/models.py:1755 msgid "Parameter Units" msgstr "" -#: part/models.py:1722 part/models.py:1770 +#: part/models.py:1783 part/models.py:1831 #: templates/InvenTree/settings/category.html:62 msgid "Parameter Template" msgstr "" -#: part/models.py:1724 +#: part/models.py:1785 msgid "Parameter Value" msgstr "" -#: part/models.py:1774 +#: part/models.py:1835 msgid "Default Parameter Value" msgstr "" -#: part/models.py:1804 +#: part/models.py:1865 msgid "Select parent part" msgstr "" -#: part/models.py:1812 +#: part/models.py:1873 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1818 +#: part/models.py:1879 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1820 +#: part/models.py:1881 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1823 +#: part/models.py:1884 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1826 +#: part/models.py:1887 msgid "BOM item reference" msgstr "" -#: part/models.py:1829 +#: part/models.py:1890 msgid "BOM item notes" msgstr "" -#: part/models.py:1831 +#: part/models.py:1892 msgid "BOM line checksum" msgstr "" -#: part/models.py:1902 part/views.py:1500 part/views.py:1552 -#: stock/models.py:234 +#: part/models.py:1963 part/views.py:1510 part/views.py:1562 +#: stock/models.py:240 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1911 part/models.py:1913 +#: part/models.py:1972 part/models.py:1974 msgid "Sub part must be specified" msgstr "" -#: part/models.py:1916 +#: part/models.py:1977 msgid "BOM Item" msgstr "" -#: part/models.py:2031 +#: part/models.py:2092 msgid "Select Related Part" msgstr "" -#: part/models.py:2063 +#: part/models.py:2124 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2786,9 +2893,9 @@ msgstr "" #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 #: stock/templates/stock/item_base.html:72 -#: stock/templates/stock/item_base.html:274 +#: stock/templates/stock/item_base.html:281 #: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:751 -#: templates/js/stock.js:705 templates/js/stock.js:954 +#: templates/js/stock.js:720 templates/js/stock.js:980 msgid "Stock Item" msgstr "" @@ -2853,7 +2960,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:62 part/views.py:1791 +#: part/templates/part/bom.html:62 part/views.py:1801 msgid "Export Bill of Materials" msgstr "" @@ -2953,7 +3060,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2182 +#: part/templates/part/category.html:24 part/views.py:2192 msgid "Create new part category" msgstr "" @@ -2977,10 +3084,6 @@ msgstr "" msgid "Category Description" msgstr "" -#: part/templates/part/category.html:68 part/templates/part/detail.html:64 -msgid "Keywords" -msgstr "" - #: part/templates/part/category.html:74 msgid "Subcategories" msgstr "" @@ -3009,7 +3112,7 @@ msgstr "" msgid "Export Data" msgstr "" -#: part/templates/part/category.html:174 +#: part/templates/part/category.html:174 templates/js/stock.js:628 msgid "Create new location" msgstr "" @@ -3025,7 +3128,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1359 +#: part/templates/part/category.html:216 stock/views.py:1363 msgid "Create new Stock Location" msgstr "" @@ -3049,15 +3152,6 @@ msgstr "" msgid "Part Details" msgstr "" -#: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 -#: templates/js/part.js:180 -msgid "IPN" -msgstr "" - -#: part/templates/part/detail.html:32 templates/js/part.js:184 -msgid "Revision" -msgstr "" - #: part/templates/part/detail.html:39 msgid "Latest Serial Number" msgstr "" @@ -3066,101 +3160,79 @@ msgstr "" msgid "No serial numbers recorded" msgstr "" -#: part/templates/part/detail.html:57 -msgid "Variant Of" +#: part/templates/part/detail.html:115 +msgid "Stock Expiry Time" msgstr "" -#: part/templates/part/detail.html:70 part/templates/part/set_category.html:15 -#: templates/js/part.js:405 -msgid "Category" -msgstr "" - -#: part/templates/part/detail.html:94 -msgid "Default Supplier" -msgstr "" - -#: part/templates/part/detail.html:102 part/templates/part/params.html:26 -msgid "Units" -msgstr "" - -#: part/templates/part/detail.html:108 -msgid "Minimum Stock" -msgstr "" - -#: part/templates/part/detail.html:114 templates/js/order.js:276 +#: part/templates/part/detail.html:121 templates/js/order.js:276 msgid "Creation Date" msgstr "" -#: part/templates/part/detail.html:120 +#: part/templates/part/detail.html:127 msgid "Created By" msgstr "" -#: part/templates/part/detail.html:127 +#: part/templates/part/detail.html:134 msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:141 +#: part/templates/part/detail.html:148 msgid "Part is virtual (not a physical part)" msgstr "" -#: part/templates/part/detail.html:143 +#: part/templates/part/detail.html:150 msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:148 stock/forms.py:249 -#: templates/js/table_filters.js:23 templates/js/table_filters.js:256 -msgid "Template" -msgstr "" - -#: part/templates/part/detail.html:151 +#: part/templates/part/detail.html:158 msgid "Part is a template part (variants can be made from this part)" msgstr "" -#: part/templates/part/detail.html:153 +#: part/templates/part/detail.html:160 msgid "Part is not a template part" msgstr "" -#: part/templates/part/detail.html:161 +#: part/templates/part/detail.html:168 msgid "Part can be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:163 +#: part/templates/part/detail.html:170 msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:171 +#: part/templates/part/detail.html:178 msgid "Part can be used in assemblies" msgstr "" -#: part/templates/part/detail.html:173 +#: part/templates/part/detail.html:180 msgid "Part cannot be used in assemblies" msgstr "" -#: part/templates/part/detail.html:181 +#: part/templates/part/detail.html:188 msgid "Part stock is tracked by serial number" msgstr "" -#: part/templates/part/detail.html:183 +#: part/templates/part/detail.html:190 msgid "Part stock is not tracked by serial number" msgstr "" -#: part/templates/part/detail.html:191 part/templates/part/detail.html:193 +#: part/templates/part/detail.html:198 part/templates/part/detail.html:200 msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:201 +#: part/templates/part/detail.html:208 msgid "Part can be sold to customers" msgstr "" -#: part/templates/part/detail.html:203 +#: part/templates/part/detail.html:210 msgid "Part cannot be sold to customers" msgstr "" -#: part/templates/part/detail.html:218 +#: part/templates/part/detail.html:225 msgid "Part is active" msgstr "" -#: part/templates/part/detail.html:220 +#: part/templates/part/detail.html:227 msgid "Part is not active" msgstr "" @@ -3178,12 +3250,12 @@ msgstr "" #: part/templates/part/params.html:15 #: templates/InvenTree/settings/category.html:29 -#: templates/InvenTree/settings/part.html:38 +#: templates/InvenTree/settings/part.html:41 msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1420 -#: templates/js/stock.js:112 +#: part/templates/part/params.html:25 stock/models.py:1463 +#: templates/InvenTree/settings/header.html:8 templates/js/stock.js:113 msgid "Value" msgstr "" @@ -3218,19 +3290,19 @@ msgid "Star this part" msgstr "" #: part/templates/part/part_base.html:49 -#: stock/templates/stock/item_base.html:101 +#: stock/templates/stock/item_base.html:108 #: stock/templates/stock/location.html:29 msgid "Barcode actions" msgstr "" #: part/templates/part/part_base.html:51 -#: stock/templates/stock/item_base.html:103 +#: stock/templates/stock/item_base.html:110 #: stock/templates/stock/location.html:31 msgid "Show QR Code" msgstr "" #: part/templates/part/part_base.html:52 -#: stock/templates/stock/item_base.html:104 +#: stock/templates/stock/item_base.html:111 #: stock/templates/stock/location.html:32 msgid "Print Label" msgstr "" @@ -3259,7 +3331,7 @@ msgstr "" msgid "Delete part" msgstr "" -#: part/templates/part/part_base.html:124 templates/js/table_filters.js:111 +#: part/templates/part/part_base.html:124 templates/js/table_filters.js:121 msgid "In Stock" msgstr "" @@ -3368,7 +3440,7 @@ msgstr "" msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:318 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:339 msgid "Tests" msgstr "" @@ -3396,220 +3468,220 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:84 +#: part/views.py:86 msgid "Add Related Part" msgstr "" -#: part/views.py:140 +#: part/views.py:142 msgid "Delete Related Part" msgstr "" -#: part/views.py:152 +#: part/views.py:154 msgid "Add part attachment" msgstr "" -#: part/views.py:207 templates/attachment_table.html:34 +#: part/views.py:209 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:213 +#: part/views.py:215 msgid "Part attachment updated" msgstr "" -#: part/views.py:228 +#: part/views.py:230 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:236 +#: part/views.py:238 msgid "Deleted part attachment" msgstr "" -#: part/views.py:245 +#: part/views.py:247 msgid "Create Test Template" msgstr "" -#: part/views.py:274 +#: part/views.py:276 msgid "Edit Test Template" msgstr "" -#: part/views.py:290 +#: part/views.py:292 msgid "Delete Test Template" msgstr "" -#: part/views.py:299 +#: part/views.py:301 msgid "Set Part Category" msgstr "" -#: part/views.py:349 +#: part/views.py:351 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:384 +#: part/views.py:386 msgid "Create Variant" msgstr "" -#: part/views.py:466 +#: part/views.py:468 msgid "Duplicate Part" msgstr "" -#: part/views.py:473 +#: part/views.py:475 msgid "Copied part" msgstr "" -#: part/views.py:527 part/views.py:661 +#: part/views.py:529 part/views.py:667 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:592 templates/js/stock.js:850 +#: part/views.py:594 templates/js/stock.js:876 msgid "Create New Part" msgstr "" -#: part/views.py:599 +#: part/views.py:601 msgid "Created new part" msgstr "" -#: part/views.py:830 +#: part/views.py:836 msgid "Part QR Code" msgstr "" -#: part/views.py:849 +#: part/views.py:855 msgid "Upload Part Image" msgstr "" -#: part/views.py:857 part/views.py:894 +#: part/views.py:863 part/views.py:900 msgid "Updated part image" msgstr "" -#: part/views.py:866 +#: part/views.py:872 msgid "Select Part Image" msgstr "" -#: part/views.py:897 +#: part/views.py:903 msgid "Part image not found" msgstr "" -#: part/views.py:908 +#: part/views.py:914 msgid "Edit Part Properties" msgstr "" -#: part/views.py:935 +#: part/views.py:945 msgid "Duplicate BOM" msgstr "" -#: part/views.py:966 +#: part/views.py:976 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:987 +#: part/views.py:997 msgid "Validate BOM" msgstr "" -#: part/views.py:1010 +#: part/views.py:1020 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1021 +#: part/views.py:1031 msgid "Validated Bill of Materials" msgstr "" -#: part/views.py:1155 +#: part/views.py:1165 msgid "No BOM file provided" msgstr "" -#: part/views.py:1503 +#: part/views.py:1513 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1528 part/views.py:1531 +#: part/views.py:1538 part/views.py:1541 msgid "Select valid part" msgstr "" -#: part/views.py:1537 +#: part/views.py:1547 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1575 +#: part/views.py:1585 msgid "Select a part" msgstr "" -#: part/views.py:1581 +#: part/views.py:1591 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1585 +#: part/views.py:1595 msgid "Specify quantity" msgstr "" -#: part/views.py:1841 +#: part/views.py:1851 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1850 +#: part/views.py:1860 msgid "Part was deleted" msgstr "" -#: part/views.py:1859 +#: part/views.py:1869 msgid "Part Pricing" msgstr "" -#: part/views.py:1973 +#: part/views.py:1983 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1983 +#: part/views.py:1993 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1992 +#: part/views.py:2002 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:2002 +#: part/views.py:2012 msgid "Create Part Parameter" msgstr "" -#: part/views.py:2054 +#: part/views.py:2064 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:2070 +#: part/views.py:2080 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2129 +#: part/views.py:2139 msgid "Edit Part Category" msgstr "" -#: part/views.py:2166 +#: part/views.py:2176 msgid "Delete Part Category" msgstr "" -#: part/views.py:2174 +#: part/views.py:2184 msgid "Part category was deleted" msgstr "" -#: part/views.py:2230 +#: part/views.py:2240 msgid "Create Category Parameter Template" msgstr "" -#: part/views.py:2333 +#: part/views.py:2343 msgid "Edit Category Parameter Template" msgstr "" -#: part/views.py:2391 +#: part/views.py:2401 msgid "Delete Category Parameter Template" msgstr "" -#: part/views.py:2416 +#: part/views.py:2426 msgid "Create BOM Item" msgstr "" -#: part/views.py:2488 +#: part/views.py:2498 msgid "Edit BOM item" msgstr "" -#: part/views.py:2545 +#: part/views.py:2555 msgid "Confim BOM item deletion" msgstr "" @@ -3641,295 +3713,305 @@ msgstr "" msgid "Asset file description" msgstr "" -#: stock/forms.py:111 +#: stock/forms.py:116 msgid "Enter unique serial numbers (or leave blank)" msgstr "" -#: stock/forms.py:192 +#: stock/forms.py:198 msgid "Label" msgstr "" -#: stock/forms.py:193 stock/forms.py:249 +#: stock/forms.py:199 stock/forms.py:255 msgid "Select test report template" msgstr "" -#: stock/forms.py:257 +#: stock/forms.py:263 msgid "Include stock items in sub locations" msgstr "" -#: stock/forms.py:292 +#: stock/forms.py:298 msgid "Stock item to install" msgstr "" -#: stock/forms.py:299 +#: stock/forms.py:305 msgid "Stock quantity to assign" msgstr "" -#: stock/forms.py:327 +#: stock/forms.py:333 msgid "Must not exceed available quantity" msgstr "" -#: stock/forms.py:337 +#: stock/forms.py:343 msgid "Destination location for uninstalled items" msgstr "" -#: stock/forms.py:339 +#: stock/forms.py:345 msgid "Add transaction note (optional)" msgstr "" -#: stock/forms.py:341 +#: stock/forms.py:347 msgid "Confirm uninstall" msgstr "" -#: stock/forms.py:341 +#: stock/forms.py:347 msgid "Confirm removal of installed stock items" msgstr "" -#: stock/forms.py:365 +#: stock/forms.py:371 msgid "Destination stock location" msgstr "" -#: stock/forms.py:367 +#: stock/forms.py:373 msgid "Add note (required)" msgstr "" -#: stock/forms.py:371 stock/views.py:935 stock/views.py:1133 +#: stock/forms.py:377 stock/views.py:935 stock/views.py:1133 msgid "Confirm stock adjustment" msgstr "" -#: stock/forms.py:371 +#: stock/forms.py:377 msgid "Confirm movement of stock items" msgstr "" -#: stock/forms.py:373 +#: stock/forms.py:379 msgid "Set Default Location" msgstr "" -#: stock/forms.py:373 +#: stock/forms.py:379 msgid "Set the destination as the default location for selected parts" msgstr "" -#: stock/models.py:179 +#: stock/models.py:185 msgid "Created stock item" msgstr "" -#: stock/models.py:215 +#: stock/models.py:221 msgid "StockItem with this serial number already exists" msgstr "" -#: stock/models.py:251 +#: stock/models.py:257 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "" -#: stock/models.py:261 stock/models.py:270 +#: stock/models.py:267 stock/models.py:276 msgid "Quantity must be 1 for item with a serial number" msgstr "" -#: stock/models.py:262 +#: stock/models.py:268 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" -#: stock/models.py:284 +#: stock/models.py:290 msgid "Item cannot belong to itself" msgstr "" -#: stock/models.py:290 +#: stock/models.py:296 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:297 +#: stock/models.py:303 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:330 +#: stock/models.py:336 msgid "Parent Stock Item" msgstr "" -#: stock/models.py:339 +#: stock/models.py:345 msgid "Base part" msgstr "" -#: stock/models.py:348 +#: stock/models.py:354 msgid "Select a matching supplier part for this stock item" msgstr "" -#: stock/models.py:353 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:359 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "" -#: stock/models.py:356 +#: stock/models.py:362 msgid "Where is this stock item located?" msgstr "" -#: stock/models.py:361 stock/templates/stock/item_base.html:212 +#: stock/models.py:367 stock/templates/stock/item_base.html:219 msgid "Installed In" msgstr "" -#: stock/models.py:364 +#: stock/models.py:370 msgid "Is this item installed in another item?" msgstr "" -#: stock/models.py:380 +#: stock/models.py:386 msgid "Serial number for this item" msgstr "" -#: stock/models.py:392 +#: stock/models.py:398 msgid "Batch code for this stock item" msgstr "" -#: stock/models.py:396 +#: stock/models.py:402 msgid "Stock Quantity" msgstr "" -#: stock/models.py:405 +#: stock/models.py:411 msgid "Source Build" msgstr "" -#: stock/models.py:407 +#: stock/models.py:413 msgid "Build for this stock item" msgstr "" -#: stock/models.py:418 +#: stock/models.py:424 msgid "Source Purchase Order" msgstr "" -#: stock/models.py:421 +#: stock/models.py:427 msgid "Purchase order for this stock item" msgstr "" -#: stock/models.py:427 +#: stock/models.py:433 msgid "Destination Sales Order" msgstr "" -#: stock/models.py:439 +#: stock/models.py:439 stock/templates/stock/item_base.html:306 +#: templates/js/stock.js:597 +msgid "Expiry Date" +msgstr "" + +#: stock/models.py:440 +msgid "" +"Expiry date for stock item. Stock will be considered expired after this date" +msgstr "" + +#: stock/models.py:453 msgid "Delete this Stock Item when stock is depleted" msgstr "" -#: stock/models.py:449 stock/templates/stock/item_notes.html:14 +#: stock/models.py:463 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "" -#: stock/models.py:459 +#: stock/models.py:473 msgid "Single unit purchase price at time of purchase" msgstr "" -#: stock/models.py:510 +#: stock/models.py:573 msgid "Assigned to Customer" msgstr "" -#: stock/models.py:512 +#: stock/models.py:575 msgid "Manually assigned to customer" msgstr "" -#: stock/models.py:525 +#: stock/models.py:588 msgid "Returned from customer" msgstr "" -#: stock/models.py:527 +#: stock/models.py:590 msgid "Returned to location" msgstr "" -#: stock/models.py:652 +#: stock/models.py:715 msgid "Installed into stock item" msgstr "" -#: stock/models.py:660 +#: stock/models.py:723 msgid "Installed stock item" msgstr "" -#: stock/models.py:684 +#: stock/models.py:747 msgid "Uninstalled stock item" msgstr "" -#: stock/models.py:703 +#: stock/models.py:766 msgid "Uninstalled into location" msgstr "" -#: stock/models.py:803 +#: stock/models.py:846 msgid "Part is not set as trackable" msgstr "" -#: stock/models.py:809 +#: stock/models.py:852 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:815 +#: stock/models.py:858 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:818 +#: stock/models.py:861 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:821 +#: stock/models.py:864 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:853 +#: stock/models.py:896 msgid "Add serial number" msgstr "" -#: stock/models.py:856 +#: stock/models.py:899 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:967 +#: stock/models.py:1010 msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1321 +#: stock/models.py:1364 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1323 +#: stock/models.py:1366 msgid "Entry notes" msgstr "" -#: stock/models.py:1325 +#: stock/models.py:1368 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1385 +#: stock/models.py:1428 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1391 +#: stock/models.py:1434 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1408 +#: stock/models.py:1451 msgid "Test" msgstr "" -#: stock/models.py:1409 +#: stock/models.py:1452 msgid "Test name" msgstr "" -#: stock/models.py:1414 +#: stock/models.py:1457 msgid "Result" msgstr "" -#: stock/models.py:1415 templates/js/table_filters.js:162 +#: stock/models.py:1458 templates/js/table_filters.js:172 msgid "Test result" msgstr "" -#: stock/models.py:1421 +#: stock/models.py:1464 msgid "Test output value" msgstr "" -#: stock/models.py:1427 +#: stock/models.py:1470 msgid "Attachment" msgstr "" -#: stock/models.py:1428 +#: stock/models.py:1471 msgid "Test result attachment" msgstr "" -#: stock/models.py:1434 +#: stock/models.py:1477 msgid "Test notes" msgstr "" @@ -3980,111 +4062,129 @@ msgid "" "This stock item will be automatically deleted when all stock is depleted." msgstr "" -#: stock/templates/stock/item_base.html:107 templates/js/barcode.js:283 +#: stock/templates/stock/item_base.html:74 +#: stock/templates/stock/item_base.html:310 templates/js/table_filters.js:111 +msgid "Expired" +msgstr "" + +#: stock/templates/stock/item_base.html:78 +#: stock/templates/stock/item_base.html:312 templates/js/table_filters.js:116 +msgid "Stale" +msgstr "" + +#: stock/templates/stock/item_base.html:114 templates/js/barcode.js:283 #: templates/js/barcode.js:288 msgid "Unlink Barcode" msgstr "" -#: stock/templates/stock/item_base.html:109 +#: stock/templates/stock/item_base.html:116 msgid "Link Barcode" msgstr "" -#: stock/templates/stock/item_base.html:117 +#: stock/templates/stock/item_base.html:124 msgid "Stock adjustment actions" msgstr "" -#: stock/templates/stock/item_base.html:121 +#: stock/templates/stock/item_base.html:128 #: stock/templates/stock/location.html:41 templates/stock_table.html:23 msgid "Count stock" msgstr "" -#: stock/templates/stock/item_base.html:122 templates/stock_table.html:21 +#: stock/templates/stock/item_base.html:129 templates/stock_table.html:21 msgid "Add stock" msgstr "" -#: stock/templates/stock/item_base.html:123 templates/stock_table.html:22 +#: stock/templates/stock/item_base.html:130 templates/stock_table.html:22 msgid "Remove stock" msgstr "" -#: stock/templates/stock/item_base.html:125 +#: stock/templates/stock/item_base.html:132 msgid "Transfer stock" msgstr "" -#: stock/templates/stock/item_base.html:127 +#: stock/templates/stock/item_base.html:134 msgid "Serialize stock" msgstr "" -#: stock/templates/stock/item_base.html:131 +#: stock/templates/stock/item_base.html:138 msgid "Assign to customer" msgstr "" -#: stock/templates/stock/item_base.html:134 +#: stock/templates/stock/item_base.html:141 msgid "Return to stock" msgstr "" -#: stock/templates/stock/item_base.html:138 templates/js/stock.js:991 +#: stock/templates/stock/item_base.html:145 templates/js/stock.js:1017 msgid "Uninstall stock item" msgstr "" -#: stock/templates/stock/item_base.html:138 +#: stock/templates/stock/item_base.html:145 msgid "Uninstall" msgstr "" -#: stock/templates/stock/item_base.html:147 +#: stock/templates/stock/item_base.html:154 #: stock/templates/stock/location.html:38 msgid "Stock actions" msgstr "" -#: stock/templates/stock/item_base.html:150 +#: stock/templates/stock/item_base.html:157 msgid "Convert to variant" msgstr "" -#: stock/templates/stock/item_base.html:153 +#: stock/templates/stock/item_base.html:160 msgid "Duplicate stock item" msgstr "" -#: stock/templates/stock/item_base.html:155 +#: stock/templates/stock/item_base.html:162 msgid "Edit stock item" msgstr "" -#: stock/templates/stock/item_base.html:158 +#: stock/templates/stock/item_base.html:165 msgid "Delete stock item" msgstr "" -#: stock/templates/stock/item_base.html:164 +#: stock/templates/stock/item_base.html:171 msgid "Generate test report" msgstr "" -#: stock/templates/stock/item_base.html:172 +#: stock/templates/stock/item_base.html:179 msgid "Stock Item Details" msgstr "" -#: stock/templates/stock/item_base.html:231 templates/js/build.js:442 +#: stock/templates/stock/item_base.html:238 templates/js/build.js:442 msgid "No location set" msgstr "" -#: stock/templates/stock/item_base.html:238 +#: stock/templates/stock/item_base.html:245 msgid "Barcode Identifier" msgstr "" -#: stock/templates/stock/item_base.html:252 templates/js/build.js:642 +#: stock/templates/stock/item_base.html:259 templates/js/build.js:642 #: templates/navbar.html:25 msgid "Build" msgstr "" -#: stock/templates/stock/item_base.html:273 +#: stock/templates/stock/item_base.html:280 msgid "Parent Item" msgstr "" -#: stock/templates/stock/item_base.html:298 +#: stock/templates/stock/item_base.html:310 +msgid "This StockItem expired on" +msgstr "" + +#: stock/templates/stock/item_base.html:312 +msgid "This StockItem expires on" +msgstr "" + +#: stock/templates/stock/item_base.html:319 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:303 +#: stock/templates/stock/item_base.html:324 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:307 +#: stock/templates/stock/item_base.html:328 msgid "No stocktake performed" msgstr "" @@ -4204,7 +4304,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1331 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1335 msgid "Convert Stock Item" msgstr "" @@ -4402,39 +4502,39 @@ msgstr "" msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1381 +#: stock/views.py:1385 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1475 templates/js/build.js:210 +#: stock/views.py:1479 templates/js/build.js:210 msgid "Create new Stock Item" msgstr "" -#: stock/views.py:1579 +#: stock/views.py:1587 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1651 +#: stock/views.py:1664 msgid "Quantity cannot be negative" msgstr "" -#: stock/views.py:1737 +#: stock/views.py:1750 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1751 +#: stock/views.py:1764 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1763 +#: stock/views.py:1776 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1782 +#: stock/views.py:1795 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1792 +#: stock/views.py:1805 msgid "Add Stock Tracking Entry" msgstr "" @@ -4466,7 +4566,11 @@ msgstr "" msgid "Pending Builds" msgstr "" -#: templates/InvenTree/index.html:4 +#: templates/InvenTree/expired_stock.html:7 +msgid "Expired Stock" +msgstr "" + +#: templates/InvenTree/index.html:5 msgid "Index" msgstr "" @@ -4494,11 +4598,11 @@ msgstr "" msgid "Enter a search query" msgstr "" -#: templates/InvenTree/search.html:191 templates/js/stock.js:289 +#: templates/InvenTree/search.html:191 templates/js/stock.js:290 msgid "Shipped to customer" msgstr "" -#: templates/InvenTree/search.html:194 templates/js/stock.js:299 +#: templates/InvenTree/search.html:194 templates/js/stock.js:300 msgid "No stock location set" msgstr "" @@ -4527,12 +4631,12 @@ msgid "Default Value" msgstr "" #: templates/InvenTree/settings/category.html:70 -#: templates/InvenTree/settings/part.html:75 +#: templates/InvenTree/settings/part.html:78 msgid "Edit Template" msgstr "" #: templates/InvenTree/settings/category.html:71 -#: templates/InvenTree/settings/part.html:76 +#: templates/InvenTree/settings/part.html:79 msgid "Delete Template" msgstr "" @@ -4540,6 +4644,10 @@ msgstr "" msgid "Global InvenTree Settings" msgstr "" +#: templates/InvenTree/settings/header.html:7 +msgid "Setting" +msgstr "" + #: templates/InvenTree/settings/part.html:9 msgid "Part Settings" msgstr "" @@ -4548,11 +4656,11 @@ msgstr "" msgid "Part Options" msgstr "" -#: templates/InvenTree/settings/part.html:34 +#: templates/InvenTree/settings/part.html:37 msgid "Part Parameter Templates" msgstr "" -#: templates/InvenTree/settings/part.html:55 +#: templates/InvenTree/settings/part.html:58 msgid "No part parameter templates found" msgstr "" @@ -4560,11 +4668,11 @@ msgstr "" msgid "Purchase Order Settings" msgstr "" -#: templates/InvenTree/settings/setting.html:16 +#: templates/InvenTree/settings/setting.html:23 msgid "No value set" msgstr "" -#: templates/InvenTree/settings/setting.html:24 +#: templates/InvenTree/settings/setting.html:31 msgid "Edit setting" msgstr "" @@ -4581,6 +4689,10 @@ msgstr "" msgid "Stock Settings" msgstr "" +#: templates/InvenTree/settings/stock.html:13 +msgid "Stock Options" +msgstr "" + #: templates/InvenTree/settings/tabs.html:3 #: templates/InvenTree/settings/user.html:10 msgid "User Settings" @@ -4656,6 +4768,10 @@ msgstr "" msgid "Overdue Sales Orders" msgstr "" +#: templates/InvenTree/stale_stock.html:7 +msgid "Stale Stock" +msgstr "" + #: templates/InvenTree/starred_parts.html:7 msgid "Starred Parts" msgstr "" @@ -4917,7 +5033,7 @@ msgstr "" msgid "No purchase orders found" msgstr "" -#: templates/js/order.js:188 templates/js/stock.js:687 +#: templates/js/order.js:188 templates/js/stock.js:702 msgid "Date" msgstr "" @@ -4957,8 +5073,8 @@ msgstr "" msgid "No parts found" msgstr "" -#: templates/js/part.js:343 templates/js/stock.js:462 -#: templates/js/stock.js:1023 +#: templates/js/part.js:343 templates/js/stock.js:463 +#: templates/js/stock.js:1049 msgid "Select" msgstr "" @@ -4966,7 +5082,7 @@ msgstr "" msgid "No category" msgstr "" -#: templates/js/part.js:429 templates/js/table_filters.js:264 +#: templates/js/part.js:429 templates/js/table_filters.js:274 msgid "Low stock" msgstr "" @@ -4986,11 +5102,11 @@ msgstr "" msgid "No test templates matching query" msgstr "" -#: templates/js/part.js:604 templates/js/stock.js:63 +#: templates/js/part.js:604 templates/js/stock.js:64 msgid "Edit test result" msgstr "" -#: templates/js/part.js:605 templates/js/stock.js:64 +#: templates/js/part.js:605 templates/js/stock.js:65 msgid "Delete test result" msgstr "" @@ -4998,103 +5114,111 @@ msgstr "" msgid "This test is defined for a parent part" msgstr "" -#: templates/js/stock.js:26 +#: templates/js/stock.js:27 msgid "PASS" msgstr "" -#: templates/js/stock.js:28 +#: templates/js/stock.js:29 msgid "FAIL" msgstr "" -#: templates/js/stock.js:33 +#: templates/js/stock.js:34 msgid "NO RESULT" msgstr "" -#: templates/js/stock.js:59 +#: templates/js/stock.js:60 msgid "Add test result" msgstr "" -#: templates/js/stock.js:78 +#: templates/js/stock.js:79 msgid "No test results found" msgstr "" -#: templates/js/stock.js:120 +#: templates/js/stock.js:121 msgid "Test Date" msgstr "" -#: templates/js/stock.js:281 +#: templates/js/stock.js:282 msgid "In production" msgstr "" -#: templates/js/stock.js:285 +#: templates/js/stock.js:286 msgid "Installed in Stock Item" msgstr "" -#: templates/js/stock.js:293 +#: templates/js/stock.js:294 msgid "Assigned to Sales Order" msgstr "" -#: templates/js/stock.js:313 +#: templates/js/stock.js:314 msgid "No stock items matching query" msgstr "" -#: templates/js/stock.js:430 +#: templates/js/stock.js:431 msgid "Undefined location" msgstr "" -#: templates/js/stock.js:524 +#: templates/js/stock.js:525 msgid "Stock item is in production" msgstr "" -#: templates/js/stock.js:529 +#: templates/js/stock.js:530 msgid "Stock item assigned to sales order" msgstr "" -#: templates/js/stock.js:532 +#: templates/js/stock.js:533 msgid "Stock item assigned to customer" msgstr "" -#: templates/js/stock.js:536 +#: templates/js/stock.js:537 +msgid "Stock item has expired" +msgstr "" + +#: templates/js/stock.js:539 +msgid "Stock item will expire soon" +msgstr "" + +#: templates/js/stock.js:543 msgid "Stock item has been allocated" msgstr "" -#: templates/js/stock.js:540 +#: templates/js/stock.js:547 msgid "Stock item has been installed in another item" msgstr "" -#: templates/js/stock.js:548 +#: templates/js/stock.js:555 msgid "Stock item has been rejected" msgstr "" -#: templates/js/stock.js:552 +#: templates/js/stock.js:559 msgid "Stock item is lost" msgstr "" -#: templates/js/stock.js:555 +#: templates/js/stock.js:562 msgid "Stock item is destroyed" msgstr "" -#: templates/js/stock.js:559 templates/js/table_filters.js:106 +#: templates/js/stock.js:566 templates/js/table_filters.js:106 msgid "Depleted" msgstr "" -#: templates/js/stock.js:753 +#: templates/js/stock.js:768 msgid "No user information" msgstr "" -#: templates/js/stock.js:862 +#: templates/js/stock.js:888 msgid "Create New Location" msgstr "" -#: templates/js/stock.js:961 +#: templates/js/stock.js:987 msgid "Serial" msgstr "" -#: templates/js/stock.js:1054 templates/js/table_filters.js:121 +#: templates/js/stock.js:1080 templates/js/table_filters.js:131 msgid "Installed" msgstr "" -#: templates/js/stock.js:1079 +#: templates/js/stock.js:1105 msgid "Install item" msgstr "" @@ -5106,36 +5230,36 @@ msgstr "" msgid "Validated" msgstr "" -#: templates/js/table_filters.js:65 templates/js/table_filters.js:131 +#: templates/js/table_filters.js:65 templates/js/table_filters.js:141 msgid "Is Serialized" msgstr "" -#: templates/js/table_filters.js:68 templates/js/table_filters.js:138 +#: templates/js/table_filters.js:68 templates/js/table_filters.js:148 msgid "Serial number GTE" msgstr "" -#: templates/js/table_filters.js:69 templates/js/table_filters.js:139 +#: templates/js/table_filters.js:69 templates/js/table_filters.js:149 msgid "Serial number greater than or equal to" msgstr "" -#: templates/js/table_filters.js:72 templates/js/table_filters.js:142 +#: templates/js/table_filters.js:72 templates/js/table_filters.js:152 msgid "Serial number LTE" msgstr "" -#: templates/js/table_filters.js:73 templates/js/table_filters.js:143 +#: templates/js/table_filters.js:73 templates/js/table_filters.js:153 msgid "Serial number less than or equal to" msgstr "" #: templates/js/table_filters.js:76 templates/js/table_filters.js:77 -#: templates/js/table_filters.js:134 templates/js/table_filters.js:135 +#: templates/js/table_filters.js:144 templates/js/table_filters.js:145 msgid "Serial number" msgstr "" -#: templates/js/table_filters.js:81 templates/js/table_filters.js:152 +#: templates/js/table_filters.js:81 templates/js/table_filters.js:162 msgid "Batch code" msgstr "" -#: templates/js/table_filters.js:91 templates/js/table_filters.js:231 +#: templates/js/table_filters.js:91 templates/js/table_filters.js:241 msgid "Active parts" msgstr "" @@ -5164,74 +5288,82 @@ msgid "Show stock items which are depleted" msgstr "" #: templates/js/table_filters.js:112 -msgid "Show items which are in stock" -msgstr "" - -#: templates/js/table_filters.js:116 -msgid "In Production" +msgid "Show stock items which have expired" msgstr "" #: templates/js/table_filters.js:117 -msgid "Show items which are in production" +msgid "Show stock which is close to expiring" msgstr "" #: templates/js/table_filters.js:122 -msgid "Show stock items which are installed in another item" +msgid "Show items which are in stock" msgstr "" #: templates/js/table_filters.js:126 -msgid "Sent to customer" +msgid "In Production" msgstr "" #: templates/js/table_filters.js:127 +msgid "Show items which are in production" +msgstr "" + +#: templates/js/table_filters.js:132 +msgid "Show stock items which are installed in another item" +msgstr "" + +#: templates/js/table_filters.js:136 +msgid "Sent to customer" +msgstr "" + +#: templates/js/table_filters.js:137 msgid "Show items which have been assigned to a customer" msgstr "" -#: templates/js/table_filters.js:147 templates/js/table_filters.js:148 +#: templates/js/table_filters.js:157 templates/js/table_filters.js:158 msgid "Stock status" msgstr "" -#: templates/js/table_filters.js:181 +#: templates/js/table_filters.js:191 msgid "Build status" msgstr "" -#: templates/js/table_filters.js:200 templates/js/table_filters.js:213 +#: templates/js/table_filters.js:210 templates/js/table_filters.js:223 msgid "Order status" msgstr "" -#: templates/js/table_filters.js:205 templates/js/table_filters.js:218 +#: templates/js/table_filters.js:215 templates/js/table_filters.js:228 msgid "Outstanding" msgstr "" -#: templates/js/table_filters.js:241 +#: templates/js/table_filters.js:251 msgid "Include subcategories" msgstr "" -#: templates/js/table_filters.js:242 +#: templates/js/table_filters.js:252 msgid "Include parts in subcategories" msgstr "" -#: templates/js/table_filters.js:246 +#: templates/js/table_filters.js:256 msgid "Has IPN" msgstr "" -#: templates/js/table_filters.js:247 +#: templates/js/table_filters.js:257 msgid "Part has internal part number" msgstr "" -#: templates/js/table_filters.js:252 +#: templates/js/table_filters.js:262 msgid "Show active parts" msgstr "" -#: templates/js/table_filters.js:260 +#: templates/js/table_filters.js:270 msgid "Stock available" msgstr "" -#: templates/js/table_filters.js:276 +#: templates/js/table_filters.js:286 msgid "Starred" msgstr "" -#: templates/js/table_filters.js:288 +#: templates/js/table_filters.js:298 msgid "Purchasable" msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index a2b2081ed3..395f42c4e6 100644 --- a/InvenTree/locale/es/LC_MESSAGES/django.po +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-03 22:16+1100\n" +"POT-Creation-Date: 2021-01-06 23:11+1100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -55,7 +55,7 @@ msgid "Select Category" msgstr "" #: InvenTree/helpers.py:361 order/models.py:178 order/models.py:260 -#: stock/views.py:1647 +#: stock/views.py:1660 msgid "Invalid quantity provided" msgstr "" @@ -95,12 +95,12 @@ msgstr "" msgid "File comment" msgstr "" -#: InvenTree/models.py:68 templates/js/stock.js:744 +#: InvenTree/models.py:68 templates/js/stock.js:759 msgid "User" msgstr "" -#: InvenTree/models.py:106 part/templates/part/params.html:24 -#: templates/js/part.js:129 +#: InvenTree/models.py:106 part/models.py:647 +#: part/templates/part/params.html:24 templates/js/part.js:129 msgid "Name" msgstr "" @@ -295,14 +295,14 @@ msgstr "" msgid "Order target date" msgstr "" -#: build/forms.py:39 build/models.py:173 +#: build/forms.py:39 build/models.py:175 msgid "" "Target date for build completion. Build will be overdue after this date." msgstr "" #: build/forms.py:78 build/templates/build/auto_allocate.html:17 #: build/templates/build/build_base.html:83 -#: build/templates/build/detail.html:29 common/models.py:496 +#: build/templates/build/detail.html:29 common/models.py:589 #: company/forms.py:112 company/templates/company/supplier_part_pricing.html:75 #: order/templates/order/order_wizard/select_parts.html:32 #: order/templates/order/purchase_order_detail.html:179 @@ -310,13 +310,13 @@ msgstr "" #: order/templates/order/sales_order_detail.html:156 #: part/templates/part/allocation.html:16 #: part/templates/part/allocation.html:49 -#: part/templates/part/sale_prices.html:82 stock/forms.py:298 +#: part/templates/part/sale_prices.html:82 stock/forms.py:304 #: stock/templates/stock/item_base.html:40 #: stock/templates/stock/item_base.html:46 -#: stock/templates/stock/item_base.html:197 +#: stock/templates/stock/item_base.html:204 #: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:195 templates/js/build.js:420 templates/js/stock.js:735 -#: templates/js/stock.js:963 +#: templates/js/bom.js:195 templates/js/build.js:420 templates/js/stock.js:750 +#: templates/js/stock.js:989 msgid "Quantity" msgstr "" @@ -324,7 +324,7 @@ msgstr "" msgid "Enter quantity for build output" msgstr "" -#: build/forms.py:83 stock/forms.py:111 +#: build/forms.py:83 stock/forms.py:116 msgid "Serial numbers" msgstr "" @@ -372,222 +372,223 @@ msgstr "" msgid "Select quantity of stock to allocate" msgstr "" -#: build/models.py:59 build/templates/build/build_base.html:8 +#: build/models.py:61 build/templates/build/build_base.html:8 #: build/templates/build/build_base.html:35 #: part/templates/part/allocation.html:20 msgid "Build Order" msgstr "" -#: build/models.py:60 build/templates/build/index.html:6 +#: build/models.py:62 build/templates/build/index.html:6 #: build/templates/build/index.html:14 order/templates/order/so_builds.html:11 #: order/templates/order/so_tabs.html:9 part/templates/part/tabs.html:31 #: templates/InvenTree/settings/tabs.html:28 users/models.py:30 msgid "Build Orders" msgstr "" -#: build/models.py:75 +#: build/models.py:77 msgid "Build Order Reference" msgstr "" -#: build/models.py:76 order/templates/order/purchase_order_detail.html:174 +#: build/models.py:78 order/templates/order/purchase_order_detail.html:174 #: templates/js/bom.js:187 templates/js/build.js:509 msgid "Reference" msgstr "" -#: build/models.py:83 build/templates/build/detail.html:19 +#: build/models.py:85 build/templates/build/detail.html:19 #: company/models.py:359 company/templates/company/detail.html:23 #: company/templates/company/supplier_part_base.html:61 #: company/templates/company/supplier_part_detail.html:27 -#: order/templates/order/purchase_order_detail.html:161 +#: order/templates/order/purchase_order_detail.html:161 part/models.py:671 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:180 +#: templates/InvenTree/search.html:147 +#: templates/InvenTree/settings/header.html:9 templates/js/bom.js:180 #: templates/js/bom.js:517 templates/js/build.js:664 templates/js/company.js:56 #: templates/js/order.js:175 templates/js/order.js:263 templates/js/part.js:188 #: templates/js/part.js:271 templates/js/part.js:391 templates/js/part.js:572 -#: templates/js/stock.js:500 templates/js/stock.js:716 +#: templates/js/stock.js:501 templates/js/stock.js:731 msgid "Description" msgstr "" -#: build/models.py:86 +#: build/models.py:88 msgid "Brief description of the build" msgstr "" -#: build/models.py:95 build/templates/build/build_base.html:104 +#: build/models.py:97 build/templates/build/build_base.html:104 #: build/templates/build/detail.html:75 msgid "Parent Build" msgstr "" -#: build/models.py:96 +#: build/models.py:98 msgid "BuildOrder to which this build is allocated" msgstr "" -#: build/models.py:101 build/templates/build/auto_allocate.html:16 +#: build/models.py:103 build/templates/build/auto_allocate.html:16 #: build/templates/build/build_base.html:78 #: build/templates/build/detail.html:24 order/models.py:548 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 -#: order/templates/order/receive_parts.html:19 part/models.py:315 +#: order/templates/order/receive_parts.html:19 part/models.py:316 #: part/templates/part/part_app_base.html:7 part/templates/part/related.html:26 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 #: templates/js/barcode.js:336 templates/js/bom.js:153 templates/js/bom.js:502 #: templates/js/build.js:669 templates/js/company.js:138 -#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:474 -#: templates/js/stock.js:1035 +#: templates/js/part.js:252 templates/js/part.js:357 templates/js/stock.js:475 +#: templates/js/stock.js:1061 msgid "Part" msgstr "" -#: build/models.py:109 +#: build/models.py:111 msgid "Select part to build" msgstr "" -#: build/models.py:114 +#: build/models.py:116 msgid "Sales Order Reference" msgstr "" -#: build/models.py:118 +#: build/models.py:120 msgid "SalesOrder to which this build is allocated" msgstr "" -#: build/models.py:123 +#: build/models.py:125 msgid "Source Location" msgstr "" -#: build/models.py:127 +#: build/models.py:129 msgid "" "Select location to take stock from for this build (leave blank to take from " "any stock location)" msgstr "" -#: build/models.py:132 +#: build/models.py:134 msgid "Destination Location" msgstr "" -#: build/models.py:136 +#: build/models.py:138 msgid "Select location where the completed items will be stored" msgstr "" -#: build/models.py:140 +#: build/models.py:142 msgid "Build Quantity" msgstr "" -#: build/models.py:143 +#: build/models.py:145 msgid "Number of stock items to build" msgstr "" -#: build/models.py:147 +#: build/models.py:149 msgid "Completed items" msgstr "" -#: build/models.py:149 +#: build/models.py:151 msgid "Number of stock items which have been completed" msgstr "" -#: build/models.py:153 part/templates/part/part_base.html:155 +#: build/models.py:155 part/templates/part/part_base.html:155 msgid "Build Status" msgstr "" -#: build/models.py:157 +#: build/models.py:159 msgid "Build status code" msgstr "" -#: build/models.py:161 stock/models.py:390 +#: build/models.py:163 stock/models.py:396 msgid "Batch Code" msgstr "" -#: build/models.py:165 +#: build/models.py:167 msgid "Batch code for this build output" msgstr "" -#: build/models.py:172 order/models.py:329 +#: build/models.py:174 order/models.py:329 msgid "Target completion date" msgstr "" -#: build/models.py:186 build/templates/build/detail.html:89 +#: build/models.py:188 build/templates/build/detail.html:89 #: company/templates/company/supplier_part_base.html:68 #: company/templates/company/supplier_part_detail.html:24 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102 -#: stock/models.py:384 stock/templates/stock/item_base.html:280 +#: stock/models.py:390 stock/templates/stock/item_base.html:287 msgid "External Link" msgstr "" -#: build/models.py:187 part/models.py:672 stock/models.py:386 +#: build/models.py:189 part/models.py:705 stock/models.py:392 msgid "Link to external URL" msgstr "" -#: build/models.py:191 build/templates/build/tabs.html:23 company/models.py:366 +#: build/models.py:193 build/templates/build/tabs.html:23 company/models.py:366 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:18 #: order/templates/order/purchase_order_detail.html:213 -#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:73 -#: stock/forms.py:307 stock/forms.py:339 stock/forms.py:367 stock/models.py:448 -#: stock/models.py:1433 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:263 -#: templates/js/stock.js:116 templates/js/stock.js:588 +#: order/templates/order/so_tabs.html:23 part/models.py:831 +#: part/templates/part/tabs.html:73 stock/forms.py:313 stock/forms.py:345 +#: stock/forms.py:373 stock/models.py:462 stock/models.py:1476 +#: stock/templates/stock/tabs.html:26 templates/js/barcode.js:391 +#: templates/js/bom.js:263 templates/js/stock.js:117 templates/js/stock.js:603 msgid "Notes" msgstr "" -#: build/models.py:192 +#: build/models.py:194 msgid "Extra build notes" msgstr "" -#: build/models.py:577 +#: build/models.py:579 msgid "No build output specified" msgstr "" -#: build/models.py:580 +#: build/models.py:582 msgid "Build output is already completed" msgstr "" -#: build/models.py:583 +#: build/models.py:585 msgid "Build output does not match Build Order" msgstr "" -#: build/models.py:658 +#: build/models.py:660 msgid "Completed build output" msgstr "" -#: build/models.py:896 +#: build/models.py:902 msgid "BuildItem must be unique for build, stock_item and install_into" msgstr "" -#: build/models.py:918 +#: build/models.py:924 msgid "Build item must specify a build output" msgstr "" -#: build/models.py:923 +#: build/models.py:929 #, python-brace-format msgid "Selected stock item not found in BOM for part '{p}'" msgstr "" -#: build/models.py:927 +#: build/models.py:933 #, python-brace-format msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:934 order/models.py:632 +#: build/models.py:940 order/models.py:632 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:938 order/models.py:635 +#: build/models.py:944 order/models.py:635 msgid "Allocation quantity must be greater than zero" msgstr "" -#: build/models.py:942 +#: build/models.py:948 msgid "Quantity must be 1 for serialized stock" msgstr "" -#: build/models.py:982 +#: build/models.py:988 msgid "Build to allocate parts" msgstr "" -#: build/models.py:989 +#: build/models.py:995 msgid "Source stock item" msgstr "" -#: build/models.py:1001 +#: build/models.py:1007 msgid "Stock quantity to allocate to build" msgstr "" -#: build/models.py:1009 +#: build/models.py:1015 msgid "Destination stock item" msgstr "" @@ -612,7 +613,7 @@ msgid "Order required parts" msgstr "" #: build/templates/build/allocate.html:30 -#: company/templates/company/detail_part.html:28 order/views.py:803 +#: company/templates/company/detail_part.html:28 order/views.py:805 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "" @@ -652,11 +653,11 @@ msgid "" "The following stock items will be allocated to the specified build output" msgstr "" -#: build/templates/build/auto_allocate.html:18 stock/forms.py:337 -#: stock/templates/stock/item_base.html:227 +#: build/templates/build/auto_allocate.html:18 stock/forms.py:343 +#: stock/templates/stock/item_base.html:234 #: stock/templates/stock/stock_adjust.html:17 #: templates/InvenTree/search.html:183 templates/js/barcode.js:337 -#: templates/js/build.js:434 templates/js/stock.js:580 +#: templates/js/build.js:434 templates/js/stock.js:587 msgid "Location" msgstr "" @@ -681,7 +682,7 @@ msgstr "" #: order/templates/order/order_base.html:26 #: order/templates/order/sales_order_base.html:35 #: part/templates/part/category.html:13 part/templates/part/part_base.html:32 -#: stock/templates/stock/item_base.html:90 +#: stock/templates/stock/item_base.html:97 #: stock/templates/stock/location.html:12 msgid "Admin view" msgstr "" @@ -690,7 +691,7 @@ msgstr "" #: build/templates/build/build_base.html:92 #: order/templates/order/sales_order_base.html:41 #: order/templates/order/sales_order_base.html:83 -#: templates/js/table_filters.js:190 templates/js/table_filters.js:222 +#: templates/js/table_filters.js:200 templates/js/table_filters.js:232 msgid "Overdue" msgstr "" @@ -717,10 +718,10 @@ msgstr "" #: build/templates/build/build_base.html:88 #: build/templates/build/detail.html:57 #: order/templates/order/receive_parts.html:24 -#: stock/templates/stock/item_base.html:312 templates/InvenTree/search.html:175 +#: stock/templates/stock/item_base.html:333 templates/InvenTree/search.html:175 #: templates/js/barcode.js:42 templates/js/build.js:697 #: templates/js/order.js:180 templates/js/order.js:268 -#: templates/js/stock.js:567 templates/js/stock.js:971 +#: templates/js/stock.js:574 templates/js/stock.js:997 msgid "Status" msgstr "" @@ -740,7 +741,7 @@ msgstr "" #: order/templates/order/sales_order_notes.html:10 #: order/templates/order/sales_order_ship.html:25 #: part/templates/part/allocation.html:27 -#: stock/templates/stock/item_base.html:221 templates/js/order.js:229 +#: stock/templates/stock/item_base.html:228 templates/js/order.js:229 msgid "Sales Order" msgstr "" @@ -832,7 +833,7 @@ msgstr "" msgid "Stock can be taken from any available location." msgstr "" -#: build/templates/build/detail.html:44 stock/forms.py:365 +#: build/templates/build/detail.html:44 stock/forms.py:371 msgid "Destination" msgstr "" @@ -841,9 +842,9 @@ msgid "Destination location not specified" msgstr "" #: build/templates/build/detail.html:68 -#: stock/templates/stock/item_base.html:245 templates/js/stock.js:575 -#: templates/js/stock.js:978 templates/js/table_filters.js:80 -#: templates/js/table_filters.js:151 +#: stock/templates/stock/item_base.html:252 templates/js/stock.js:582 +#: templates/js/stock.js:1004 templates/js/table_filters.js:80 +#: templates/js/table_filters.js:161 msgid "Batch" msgstr "" @@ -935,7 +936,7 @@ msgstr "" msgid "Create Build Output" msgstr "" -#: build/views.py:207 stock/models.py:828 stock/views.py:1668 +#: build/views.py:207 stock/models.py:871 stock/views.py:1681 msgid "Serial numbers already exist" msgstr "" @@ -1032,36 +1033,36 @@ msgstr "" msgid "Stock item must be selected" msgstr "" -#: build/views.py:1011 +#: build/views.py:1012 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:1016 +#: build/views.py:1017 msgid "Updated Build Item" msgstr "" -#: build/views.py:1045 +#: build/views.py:1046 msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:1059 order/views.py:111 order/views.py:164 part/views.py:168 +#: build/views.py:1060 order/views.py:113 order/views.py:166 part/views.py:170 #: stock/views.py:180 msgid "Added attachment" msgstr "" -#: build/views.py:1095 order/views.py:191 order/views.py:213 +#: build/views.py:1096 order/views.py:193 order/views.py:215 msgid "Edit Attachment" msgstr "" -#: build/views.py:1106 order/views.py:196 order/views.py:218 +#: build/views.py:1107 order/views.py:198 order/views.py:220 msgid "Attachment updated" msgstr "" -#: build/views.py:1116 order/views.py:233 order/views.py:248 +#: build/views.py:1117 order/views.py:235 order/views.py:250 msgid "Delete Attachment" msgstr "" -#: build/views.py:1122 order/views.py:240 order/views.py:255 stock/views.py:238 +#: build/views.py:1123 order/views.py:242 order/views.py:257 stock/views.py:238 msgid "Deleted attachment" msgstr "" @@ -1137,103 +1138,170 @@ msgstr "" msgid "Copy category parameter templates when creating a part" msgstr "" -#: common/models.py:115 part/models.py:743 part/templates/part/detail.html:168 -#: templates/js/table_filters.js:272 -msgid "Component" +#: common/models.py:115 part/templates/part/detail.html:155 stock/forms.py:255 +#: templates/js/table_filters.js:23 templates/js/table_filters.js:266 +msgid "Template" msgstr "" #: common/models.py:116 -msgid "Parts can be used as sub-components by default" +msgid "Parts are templates by default" msgstr "" -#: common/models.py:122 part/models.py:754 part/templates/part/detail.html:188 -msgid "Purchaseable" +#: common/models.py:122 part/models.py:794 part/templates/part/detail.html:165 +#: templates/js/table_filters.js:278 +msgid "Assembly" msgstr "" #: common/models.py:123 -msgid "Parts are purchaseable by default" +msgid "Parts can be assembled from other components by default" msgstr "" -#: common/models.py:129 part/models.py:759 part/templates/part/detail.html:198 -#: templates/js/table_filters.js:280 -msgid "Salable" +#: common/models.py:129 part/models.py:800 part/templates/part/detail.html:175 +#: templates/js/table_filters.js:282 +msgid "Component" msgstr "" #: common/models.py:130 -msgid "Parts are salable by default" +msgid "Parts can be used as sub-components by default" msgstr "" -#: common/models.py:136 part/models.py:749 part/templates/part/detail.html:178 -#: templates/js/table_filters.js:31 templates/js/table_filters.js:284 -msgid "Trackable" +#: common/models.py:136 part/models.py:811 part/templates/part/detail.html:195 +msgid "Purchaseable" msgstr "" #: common/models.py:137 -msgid "Parts are trackable by default" +msgid "Parts are purchaseable by default" msgstr "" -#: common/models.py:143 -msgid "Build Order Reference Prefix" +#: common/models.py:143 part/models.py:816 part/templates/part/detail.html:205 +#: templates/js/table_filters.js:290 +msgid "Salable" msgstr "" #: common/models.py:144 +msgid "Parts are salable by default" +msgstr "" + +#: common/models.py:150 part/models.py:806 part/templates/part/detail.html:185 +#: templates/js/table_filters.js:31 templates/js/table_filters.js:294 +msgid "Trackable" +msgstr "" + +#: common/models.py:151 +msgid "Parts are trackable by default" +msgstr "" + +#: common/models.py:157 part/models.py:826 part/templates/part/detail.html:145 +#: templates/js/table_filters.js:27 +msgid "Virtual" +msgstr "" + +#: common/models.py:158 +msgid "Parts are virtual by default" +msgstr "" + +#: common/models.py:164 +msgid "Stock Expiry" +msgstr "" + +#: common/models.py:165 +msgid "Enable stock expiry functionality" +msgstr "" + +#: common/models.py:171 +msgid "Sell Expired Stock" +msgstr "" + +#: common/models.py:172 +msgid "Allow sale of expired stock" +msgstr "" + +#: common/models.py:178 +msgid "Stock Stale Time" +msgstr "" + +#: common/models.py:179 +msgid "Number of days stock items are considered stale before expiring" +msgstr "" + +#: common/models.py:181 part/templates/part/detail.html:116 +msgid "days" +msgstr "" + +#: common/models.py:186 +msgid "Build Expired Stock" +msgstr "" + +#: common/models.py:187 +msgid "Allow building with expired stock" +msgstr "" + +#: common/models.py:193 +msgid "Build Order Reference Prefix" +msgstr "" + +#: common/models.py:194 msgid "Prefix value for build order reference" msgstr "" -#: common/models.py:149 +#: common/models.py:199 msgid "Build Order Reference Regex" msgstr "" -#: common/models.py:150 +#: common/models.py:200 msgid "Regular expression pattern for matching build order reference" msgstr "" -#: common/models.py:154 +#: common/models.py:204 msgid "Sales Order Reference Prefix" msgstr "" -#: common/models.py:155 +#: common/models.py:205 msgid "Prefix value for sales order reference" msgstr "" -#: common/models.py:160 +#: common/models.py:210 msgid "Purchase Order Reference Prefix" msgstr "" -#: common/models.py:161 +#: common/models.py:211 msgid "Prefix value for purchase order reference" msgstr "" -#: common/models.py:378 +#: common/models.py:434 msgid "Settings key (must be unique - case insensitive" msgstr "" -#: common/models.py:380 +#: common/models.py:436 msgid "Settings value" msgstr "" -#: common/models.py:439 +#: common/models.py:493 msgid "Value must be a boolean value" msgstr "" -#: common/models.py:453 +#: common/models.py:503 +msgid "Value must be an integer value" +msgstr "" + +#: common/models.py:517 msgid "Key string must be unique" msgstr "" -#: common/models.py:497 company/forms.py:113 +#: common/models.py:590 company/forms.py:113 msgid "Price break quantity" msgstr "" -#: common/models.py:505 company/templates/company/supplier_part_pricing.html:80 +#: common/models.py:598 company/templates/company/supplier_part_pricing.html:80 #: part/templates/part/sale_prices.html:87 templates/js/bom.js:246 msgid "Price" msgstr "" -#: common/models.py:506 +#: common/models.py:599 msgid "Unit price at specified quantity" msgstr "" -#: common/models.py:529 +#: common/models.py:622 msgid "Default" msgstr "" @@ -1334,8 +1402,8 @@ msgstr "" msgid "Currency" msgstr "" -#: company/models.py:313 stock/models.py:338 -#: stock/templates/stock/item_base.html:177 +#: company/models.py:313 stock/models.py:344 +#: stock/templates/stock/item_base.html:184 msgid "Base Part" msgstr "" @@ -1348,7 +1416,7 @@ msgstr "" #: company/templates/company/supplier_part_detail.html:21 #: order/templates/order/order_base.html:79 #: order/templates/order/order_wizard/select_pos.html:30 part/bom.py:170 -#: stock/templates/stock/item_base.html:287 templates/js/company.js:48 +#: stock/templates/stock/item_base.html:294 templates/js/company.js:48 #: templates/js/company.js:164 templates/js/order.js:162 msgid "Supplier" msgstr "" @@ -1387,7 +1455,7 @@ msgstr "" msgid "Manufacturer part number" msgstr "" -#: company/models.py:353 templates/js/company.js:208 +#: company/models.py:353 part/models.py:704 templates/js/company.js:208 msgid "Link" msgstr "" @@ -1444,8 +1512,8 @@ msgid "Uses default currency" msgstr "" #: company/templates/company/detail.html:62 -#: order/templates/order/sales_order_base.html:89 stock/models.py:373 -#: stock/models.py:374 stock/templates/stock/item_base.html:204 +#: order/templates/order/sales_order_base.html:89 stock/models.py:379 +#: stock/models.py:380 stock/templates/stock/item_base.html:211 #: templates/js/company.js:40 templates/js/order.js:250 msgid "Customer" msgstr "" @@ -1461,7 +1529,7 @@ msgstr "" #: company/templates/company/detail_part.html:18 #: order/templates/order/purchase_order_detail.html:68 -#: part/templates/part/supplier.html:14 templates/js/stock.js:855 +#: part/templates/part/supplier.html:14 templates/js/stock.js:881 msgid "New Supplier Part" msgstr "" @@ -1485,7 +1553,7 @@ msgid "Delete Parts" msgstr "" #: company/templates/company/detail_part.html:63 -#: part/templates/part/category.html:116 templates/js/stock.js:849 +#: part/templates/part/category.html:116 templates/js/stock.js:875 msgid "New Part" msgstr "" @@ -1577,8 +1645,8 @@ msgid "New Sales Order" msgstr "" #: company/templates/company/supplier_part_base.html:6 -#: company/templates/company/supplier_part_base.html:19 stock/models.py:347 -#: stock/templates/stock/item_base.html:292 templates/js/company.js:180 +#: company/templates/company/supplier_part_base.html:19 stock/models.py:353 +#: stock/templates/stock/item_base.html:299 templates/js/company.js:180 msgid "Supplier Part" msgstr "" @@ -1619,7 +1687,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:486 -#: part/templates/part/sale_prices.html:14 part/views.py:2555 +#: part/templates/part/sale_prices.html:14 part/views.py:2565 msgid "Add Price Break" msgstr "" @@ -1650,7 +1718,7 @@ msgstr "" #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: templates/InvenTree/settings/tabs.html:25 templates/js/part.js:192 -#: templates/js/part.js:418 templates/js/stock.js:508 templates/navbar.html:22 +#: templates/js/part.js:418 templates/js/stock.js:509 templates/navbar.html:22 #: users/models.py:29 msgid "Stock" msgstr "" @@ -1660,7 +1728,7 @@ msgid "Orders" msgstr "" #: company/templates/company/tabs.html:9 -#: order/templates/order/receive_parts.html:14 part/models.py:316 +#: order/templates/order/receive_parts.html:14 part/models.py:317 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94 #: part/templates/part/category_tabs.html:6 #: templates/InvenTree/settings/tabs.html:22 templates/navbar.html:19 @@ -1733,7 +1801,7 @@ msgstr "" msgid "Edit Supplier Part" msgstr "" -#: company/views.py:295 templates/js/stock.js:856 +#: company/views.py:295 templates/js/stock.js:882 msgid "Create new Supplier Part" msgstr "" @@ -1741,15 +1809,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:492 part/views.py:2561 +#: company/views.py:492 part/views.py:2571 msgid "Added new price break" msgstr "" -#: company/views.py:548 part/views.py:2605 +#: company/views.py:548 part/views.py:2615 msgid "Edit Price Break" msgstr "" -#: company/views.py:564 part/views.py:2621 +#: company/views.py:564 part/views.py:2631 msgid "Delete Price Break" msgstr "" @@ -1847,8 +1915,8 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:176 order/models.py:258 part/views.py:1494 -#: stock/models.py:244 stock/models.py:812 +#: order/models.py:176 order/models.py:258 part/views.py:1504 +#: stock/models.py:250 stock/models.py:855 msgid "Quantity must be greater than zero" msgstr "" @@ -1886,7 +1954,7 @@ msgstr "" #: order/models.py:504 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 -#: stock/templates/stock/item_base.html:259 templates/js/order.js:146 +#: stock/templates/stock/item_base.html:266 templates/js/order.js:146 msgid "Purchase Order" msgstr "" @@ -1898,8 +1966,8 @@ msgstr "" msgid "Number of items received" msgstr "" -#: order/models.py:527 stock/models.py:458 -#: stock/templates/stock/item_base.html:266 +#: order/models.py:527 stock/models.py:472 +#: stock/templates/stock/item_base.html:273 msgid "Purchase Price" msgstr "" @@ -2045,8 +2113,8 @@ msgid "Line Items" msgstr "" #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1201 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1119 +#: order/views.py:1203 msgid "Add Line Item" msgstr "" @@ -2057,7 +2125,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:39 #: order/templates/order/purchase_order_detail.html:119 #: part/templates/part/category.html:173 part/templates/part/category.html:215 -#: templates/js/stock.js:861 +#: templates/js/stock.js:627 templates/js/stock.js:887 msgid "New Location" msgstr "" @@ -2142,8 +2210,8 @@ msgid "Sales Order Items" msgstr "" #: order/templates/order/sales_order_detail.html:72 -#: order/templates/order/sales_order_detail.html:154 stock/models.py:378 -#: stock/templates/stock/item_base.html:191 templates/js/build.js:418 +#: order/templates/order/sales_order_detail.html:154 stock/models.py:384 +#: stock/templates/stock/item_base.html:198 templates/js/build.js:418 msgid "Serial Number" msgstr "" @@ -2221,143 +2289,143 @@ msgstr "" msgid "Order Items" msgstr "" -#: order/views.py:99 +#: order/views.py:101 msgid "Add Purchase Order Attachment" msgstr "" -#: order/views.py:150 +#: order/views.py:152 msgid "Add Sales Order Attachment" msgstr "" -#: order/views.py:310 +#: order/views.py:312 msgid "Create Purchase Order" msgstr "" -#: order/views.py:346 +#: order/views.py:348 msgid "Create Sales Order" msgstr "" -#: order/views.py:382 +#: order/views.py:384 msgid "Edit Purchase Order" msgstr "" -#: order/views.py:403 +#: order/views.py:405 msgid "Edit Sales Order" msgstr "" -#: order/views.py:420 +#: order/views.py:422 msgid "Cancel Order" msgstr "" -#: order/views.py:430 order/views.py:457 +#: order/views.py:432 order/views.py:459 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:433 +#: order/views.py:435 msgid "Order cannot be cancelled as either pending or placed" msgstr "" -#: order/views.py:447 +#: order/views.py:449 msgid "Cancel sales order" msgstr "" -#: order/views.py:460 +#: order/views.py:462 msgid "Order cannot be cancelled" msgstr "" -#: order/views.py:474 +#: order/views.py:476 msgid "Issue Order" msgstr "" -#: order/views.py:484 +#: order/views.py:486 msgid "Confirm order placement" msgstr "" -#: order/views.py:494 +#: order/views.py:496 msgid "Purchase order issued" msgstr "" -#: order/views.py:505 +#: order/views.py:507 msgid "Complete Order" msgstr "" -#: order/views.py:522 +#: order/views.py:524 msgid "Confirm order completion" msgstr "" -#: order/views.py:533 +#: order/views.py:535 msgid "Purchase order completed" msgstr "" -#: order/views.py:543 +#: order/views.py:545 msgid "Ship Order" msgstr "" -#: order/views.py:560 +#: order/views.py:562 msgid "Confirm order shipment" msgstr "" -#: order/views.py:566 +#: order/views.py:568 msgid "Could not ship order" msgstr "" -#: order/views.py:618 +#: order/views.py:620 msgid "Receive Parts" msgstr "" -#: order/views.py:686 +#: order/views.py:688 msgid "Items received" msgstr "" -#: order/views.py:700 +#: order/views.py:702 msgid "No destination set" msgstr "" -#: order/views.py:745 +#: order/views.py:747 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:751 +#: order/views.py:753 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:757 +#: order/views.py:759 msgid "No lines specified" msgstr "" -#: order/views.py:1127 +#: order/views.py:1129 msgid "Supplier part must be specified" msgstr "" -#: order/views.py:1133 +#: order/views.py:1135 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1253 order/views.py:1272 +#: order/views.py:1255 order/views.py:1274 msgid "Edit Line Item" msgstr "" -#: order/views.py:1289 order/views.py:1302 +#: order/views.py:1291 order/views.py:1304 msgid "Delete Line Item" msgstr "" -#: order/views.py:1295 order/views.py:1308 +#: order/views.py:1297 order/views.py:1310 msgid "Deleted line item" msgstr "" -#: order/views.py:1317 +#: order/views.py:1319 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1387 +#: order/views.py:1394 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1403 +#: order/views.py:1410 msgid "Remove allocation" msgstr "" -#: part/bom.py:138 part/templates/part/category.html:61 +#: part/bom.py:138 part/models.py:722 part/templates/part/category.html:61 #: part/templates/part/detail.html:87 msgid "Default Location" msgstr "" @@ -2379,11 +2447,11 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:61 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:261 msgid "File Format" msgstr "" -#: part/forms.py:61 stock/forms.py:255 +#: part/forms.py:61 stock/forms.py:261 msgid "Select output file format" msgstr "" @@ -2427,7 +2495,7 @@ msgstr "" msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:92 part/models.py:1720 +#: part/forms.py:92 part/models.py:1781 msgid "Parent Part" msgstr "" @@ -2459,43 +2527,43 @@ msgstr "" msgid "Select part category" msgstr "" -#: part/forms.py:188 +#: part/forms.py:189 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:189 +#: part/forms.py:190 msgid "Copy BOM" msgstr "" -#: part/forms.py:194 +#: part/forms.py:195 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:195 +#: part/forms.py:196 msgid "Copy Parameters" msgstr "" -#: part/forms.py:200 +#: part/forms.py:201 msgid "Confirm part creation" msgstr "" -#: part/forms.py:205 +#: part/forms.py:206 msgid "Include category parameter templates" msgstr "" -#: part/forms.py:210 +#: part/forms.py:211 msgid "Include parent categories parameter templates" msgstr "" -#: part/forms.py:285 +#: part/forms.py:291 msgid "Add parameter template to same level categories" msgstr "" -#: part/forms.py:289 +#: part/forms.py:295 msgid "Add parameter template to all categories" msgstr "" -#: part/forms.py:333 +#: part/forms.py:339 msgid "Input quantity for price calculation" msgstr "" @@ -2507,7 +2575,7 @@ msgstr "" msgid "Default keywords for parts in this category" msgstr "" -#: part/models.py:77 part/models.py:1765 +#: part/models.py:77 part/models.py:1826 #: part/templates/part/part_app_base.html:9 msgid "Part Category" msgstr "" @@ -2517,255 +2585,294 @@ msgstr "" msgid "Part Categories" msgstr "" -#: part/models.py:408 part/models.py:418 +#: part/models.py:409 part/models.py:419 #, python-brace-format msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" msgstr "" -#: part/models.py:515 +#: part/models.py:516 msgid "Next available serial numbers are" msgstr "" -#: part/models.py:519 +#: part/models.py:520 msgid "Next available serial number is" msgstr "" -#: part/models.py:524 +#: part/models.py:525 msgid "Most recent serial number is" msgstr "" -#: part/models.py:603 +#: part/models.py:604 msgid "Duplicate IPN not allowed in part settings" msgstr "" -#: part/models.py:614 +#: part/models.py:615 msgid "Part must be unique for name, IPN and revision" msgstr "" -#: part/models.py:644 part/templates/part/detail.html:19 +#: part/models.py:646 part/templates/part/detail.html:19 msgid "Part name" msgstr "" -#: part/models.py:648 +#: part/models.py:653 +msgid "Is Template" +msgstr "" + +#: part/models.py:654 msgid "Is this part a template part?" msgstr "" -#: part/models.py:657 +#: part/models.py:665 msgid "Is this part a variant of another part?" msgstr "" -#: part/models.py:659 +#: part/models.py:666 part/templates/part/detail.html:57 +msgid "Variant Of" +msgstr "" + +#: part/models.py:672 msgid "Part description" msgstr "" -#: part/models.py:661 +#: part/models.py:677 part/templates/part/category.html:68 +#: part/templates/part/detail.html:64 +msgid "Keywords" +msgstr "" + +#: part/models.py:678 msgid "Part keywords to improve visibility in search results" msgstr "" -#: part/models.py:666 +#: part/models.py:685 part/templates/part/detail.html:70 +#: part/templates/part/set_category.html:15 templates/js/part.js:405 +msgid "Category" +msgstr "" + +#: part/models.py:686 msgid "Part category" msgstr "" -#: part/models.py:668 +#: part/models.py:691 part/templates/part/detail.html:25 +#: part/templates/part/part_base.html:95 templates/js/part.js:180 +msgid "IPN" +msgstr "" + +#: part/models.py:692 msgid "Internal Part Number" msgstr "" -#: part/models.py:670 +#: part/models.py:698 msgid "Part revision or version number" msgstr "" -#: part/models.py:684 +#: part/models.py:699 part/templates/part/detail.html:32 +#: templates/js/part.js:184 +msgid "Revision" +msgstr "" + +#: part/models.py:720 msgid "Where is this item normally stored?" msgstr "" -#: part/models.py:728 +#: part/models.py:767 part/templates/part/detail.html:94 +msgid "Default Supplier" +msgstr "" + +#: part/models.py:768 msgid "Default supplier part" msgstr "" -#: part/models.py:731 +#: part/models.py:775 +msgid "Default Expiry" +msgstr "" + +#: part/models.py:776 +msgid "Expiry time (in days) for stock items of this part" +msgstr "" + +#: part/models.py:781 part/templates/part/detail.html:108 +msgid "Minimum Stock" +msgstr "" + +#: part/models.py:782 msgid "Minimum allowed stock level" msgstr "" -#: part/models.py:733 +#: part/models.py:788 part/templates/part/detail.html:102 +#: part/templates/part/params.html:26 +msgid "Units" +msgstr "" + +#: part/models.py:789 msgid "Stock keeping units for this part" msgstr "" -#: part/models.py:737 part/templates/part/detail.html:158 -#: templates/js/table_filters.js:268 -msgid "Assembly" -msgstr "" - -#: part/models.py:738 +#: part/models.py:795 msgid "Can this part be built from other parts?" msgstr "" -#: part/models.py:744 +#: part/models.py:801 msgid "Can this part be used to build other parts?" msgstr "" -#: part/models.py:750 +#: part/models.py:807 msgid "Does this part have tracking for unique items?" msgstr "" -#: part/models.py:755 +#: part/models.py:812 msgid "Can this part be purchased from external suppliers?" msgstr "" -#: part/models.py:760 +#: part/models.py:817 msgid "Can this part be sold to customers?" msgstr "" -#: part/models.py:764 part/templates/part/detail.html:215 +#: part/models.py:821 part/templates/part/detail.html:222 #: templates/js/table_filters.js:19 templates/js/table_filters.js:55 -#: templates/js/table_filters.js:186 templates/js/table_filters.js:251 +#: templates/js/table_filters.js:196 templates/js/table_filters.js:261 msgid "Active" msgstr "" -#: part/models.py:765 +#: part/models.py:822 msgid "Is this part active?" msgstr "" -#: part/models.py:769 part/templates/part/detail.html:138 -#: templates/js/table_filters.js:27 -msgid "Virtual" -msgstr "" - -#: part/models.py:770 +#: part/models.py:827 msgid "Is this a virtual part, such as a software product or license?" msgstr "" -#: part/models.py:772 +#: part/models.py:832 msgid "Part notes - supports Markdown formatting" msgstr "" -#: part/models.py:774 +#: part/models.py:835 msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1593 +#: part/models.py:1654 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1610 +#: part/models.py:1671 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1629 templates/js/part.js:567 templates/js/stock.js:92 +#: part/models.py:1690 templates/js/part.js:567 templates/js/stock.js:93 msgid "Test Name" msgstr "" -#: part/models.py:1630 +#: part/models.py:1691 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1635 +#: part/models.py:1696 msgid "Test Description" msgstr "" -#: part/models.py:1636 +#: part/models.py:1697 msgid "Enter description for this test" msgstr "" -#: part/models.py:1641 templates/js/part.js:576 -#: templates/js/table_filters.js:172 +#: part/models.py:1702 templates/js/part.js:576 +#: templates/js/table_filters.js:182 msgid "Required" msgstr "" -#: part/models.py:1642 +#: part/models.py:1703 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1647 templates/js/part.js:584 +#: part/models.py:1708 templates/js/part.js:584 msgid "Requires Value" msgstr "" -#: part/models.py:1648 +#: part/models.py:1709 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1653 templates/js/part.js:591 +#: part/models.py:1714 templates/js/part.js:591 msgid "Requires Attachment" msgstr "" -#: part/models.py:1654 +#: part/models.py:1715 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1687 +#: part/models.py:1748 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1692 +#: part/models.py:1753 msgid "Parameter Name" msgstr "" -#: part/models.py:1694 +#: part/models.py:1755 msgid "Parameter Units" msgstr "" -#: part/models.py:1722 part/models.py:1770 +#: part/models.py:1783 part/models.py:1831 #: templates/InvenTree/settings/category.html:62 msgid "Parameter Template" msgstr "" -#: part/models.py:1724 +#: part/models.py:1785 msgid "Parameter Value" msgstr "" -#: part/models.py:1774 +#: part/models.py:1835 msgid "Default Parameter Value" msgstr "" -#: part/models.py:1804 +#: part/models.py:1865 msgid "Select parent part" msgstr "" -#: part/models.py:1812 +#: part/models.py:1873 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1818 +#: part/models.py:1879 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1820 +#: part/models.py:1881 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1823 +#: part/models.py:1884 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1826 +#: part/models.py:1887 msgid "BOM item reference" msgstr "" -#: part/models.py:1829 +#: part/models.py:1890 msgid "BOM item notes" msgstr "" -#: part/models.py:1831 +#: part/models.py:1892 msgid "BOM line checksum" msgstr "" -#: part/models.py:1902 part/views.py:1500 part/views.py:1552 -#: stock/models.py:234 +#: part/models.py:1963 part/views.py:1510 part/views.py:1562 +#: stock/models.py:240 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1911 part/models.py:1913 +#: part/models.py:1972 part/models.py:1974 msgid "Sub part must be specified" msgstr "" -#: part/models.py:1916 +#: part/models.py:1977 msgid "BOM Item" msgstr "" -#: part/models.py:2031 +#: part/models.py:2092 msgid "Select Related Part" msgstr "" -#: part/models.py:2063 +#: part/models.py:2124 msgid "" "Error creating relationship: check that the part is not related to itself " "and that the relationship is unique" @@ -2786,9 +2893,9 @@ msgstr "" #: part/templates/part/allocation.html:45 #: stock/templates/stock/item_base.html:8 #: stock/templates/stock/item_base.html:72 -#: stock/templates/stock/item_base.html:274 +#: stock/templates/stock/item_base.html:281 #: stock/templates/stock/stock_adjust.html:16 templates/js/build.js:751 -#: templates/js/stock.js:705 templates/js/stock.js:954 +#: templates/js/stock.js:720 templates/js/stock.js:980 msgid "Stock Item" msgstr "" @@ -2853,7 +2960,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:62 part/views.py:1791 +#: part/templates/part/bom.html:62 part/views.py:1801 msgid "Export Bill of Materials" msgstr "" @@ -2953,7 +3060,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2182 +#: part/templates/part/category.html:24 part/views.py:2192 msgid "Create new part category" msgstr "" @@ -2977,10 +3084,6 @@ msgstr "" msgid "Category Description" msgstr "" -#: part/templates/part/category.html:68 part/templates/part/detail.html:64 -msgid "Keywords" -msgstr "" - #: part/templates/part/category.html:74 msgid "Subcategories" msgstr "" @@ -3009,7 +3112,7 @@ msgstr "" msgid "Export Data" msgstr "" -#: part/templates/part/category.html:174 +#: part/templates/part/category.html:174 templates/js/stock.js:628 msgid "Create new location" msgstr "" @@ -3025,7 +3128,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1359 +#: part/templates/part/category.html:216 stock/views.py:1363 msgid "Create new Stock Location" msgstr "" @@ -3049,15 +3152,6 @@ msgstr "" msgid "Part Details" msgstr "" -#: part/templates/part/detail.html:25 part/templates/part/part_base.html:95 -#: templates/js/part.js:180 -msgid "IPN" -msgstr "" - -#: part/templates/part/detail.html:32 templates/js/part.js:184 -msgid "Revision" -msgstr "" - #: part/templates/part/detail.html:39 msgid "Latest Serial Number" msgstr "" @@ -3066,101 +3160,79 @@ msgstr "" msgid "No serial numbers recorded" msgstr "" -#: part/templates/part/detail.html:57 -msgid "Variant Of" +#: part/templates/part/detail.html:115 +msgid "Stock Expiry Time" msgstr "" -#: part/templates/part/detail.html:70 part/templates/part/set_category.html:15 -#: templates/js/part.js:405 -msgid "Category" -msgstr "" - -#: part/templates/part/detail.html:94 -msgid "Default Supplier" -msgstr "" - -#: part/templates/part/detail.html:102 part/templates/part/params.html:26 -msgid "Units" -msgstr "" - -#: part/templates/part/detail.html:108 -msgid "Minimum Stock" -msgstr "" - -#: part/templates/part/detail.html:114 templates/js/order.js:276 +#: part/templates/part/detail.html:121 templates/js/order.js:276 msgid "Creation Date" msgstr "" -#: part/templates/part/detail.html:120 +#: part/templates/part/detail.html:127 msgid "Created By" msgstr "" -#: part/templates/part/detail.html:127 +#: part/templates/part/detail.html:134 msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:141 +#: part/templates/part/detail.html:148 msgid "Part is virtual (not a physical part)" msgstr "" -#: part/templates/part/detail.html:143 +#: part/templates/part/detail.html:150 msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:148 stock/forms.py:249 -#: templates/js/table_filters.js:23 templates/js/table_filters.js:256 -msgid "Template" -msgstr "" - -#: part/templates/part/detail.html:151 +#: part/templates/part/detail.html:158 msgid "Part is a template part (variants can be made from this part)" msgstr "" -#: part/templates/part/detail.html:153 +#: part/templates/part/detail.html:160 msgid "Part is not a template part" msgstr "" -#: part/templates/part/detail.html:161 +#: part/templates/part/detail.html:168 msgid "Part can be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:163 +#: part/templates/part/detail.html:170 msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:171 +#: part/templates/part/detail.html:178 msgid "Part can be used in assemblies" msgstr "" -#: part/templates/part/detail.html:173 +#: part/templates/part/detail.html:180 msgid "Part cannot be used in assemblies" msgstr "" -#: part/templates/part/detail.html:181 +#: part/templates/part/detail.html:188 msgid "Part stock is tracked by serial number" msgstr "" -#: part/templates/part/detail.html:183 +#: part/templates/part/detail.html:190 msgid "Part stock is not tracked by serial number" msgstr "" -#: part/templates/part/detail.html:191 part/templates/part/detail.html:193 +#: part/templates/part/detail.html:198 part/templates/part/detail.html:200 msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:201 +#: part/templates/part/detail.html:208 msgid "Part can be sold to customers" msgstr "" -#: part/templates/part/detail.html:203 +#: part/templates/part/detail.html:210 msgid "Part cannot be sold to customers" msgstr "" -#: part/templates/part/detail.html:218 +#: part/templates/part/detail.html:225 msgid "Part is active" msgstr "" -#: part/templates/part/detail.html:220 +#: part/templates/part/detail.html:227 msgid "Part is not active" msgstr "" @@ -3178,12 +3250,12 @@ msgstr "" #: part/templates/part/params.html:15 #: templates/InvenTree/settings/category.html:29 -#: templates/InvenTree/settings/part.html:38 +#: templates/InvenTree/settings/part.html:41 msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1420 -#: templates/js/stock.js:112 +#: part/templates/part/params.html:25 stock/models.py:1463 +#: templates/InvenTree/settings/header.html:8 templates/js/stock.js:113 msgid "Value" msgstr "" @@ -3218,19 +3290,19 @@ msgid "Star this part" msgstr "" #: part/templates/part/part_base.html:49 -#: stock/templates/stock/item_base.html:101 +#: stock/templates/stock/item_base.html:108 #: stock/templates/stock/location.html:29 msgid "Barcode actions" msgstr "" #: part/templates/part/part_base.html:51 -#: stock/templates/stock/item_base.html:103 +#: stock/templates/stock/item_base.html:110 #: stock/templates/stock/location.html:31 msgid "Show QR Code" msgstr "" #: part/templates/part/part_base.html:52 -#: stock/templates/stock/item_base.html:104 +#: stock/templates/stock/item_base.html:111 #: stock/templates/stock/location.html:32 msgid "Print Label" msgstr "" @@ -3259,7 +3331,7 @@ msgstr "" msgid "Delete part" msgstr "" -#: part/templates/part/part_base.html:124 templates/js/table_filters.js:111 +#: part/templates/part/part_base.html:124 templates/js/table_filters.js:121 msgid "In Stock" msgstr "" @@ -3368,7 +3440,7 @@ msgstr "" msgid "Used In" msgstr "" -#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:318 +#: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:339 msgid "Tests" msgstr "" @@ -3396,220 +3468,220 @@ msgstr "" msgid "New Variant" msgstr "" -#: part/views.py:84 +#: part/views.py:86 msgid "Add Related Part" msgstr "" -#: part/views.py:140 +#: part/views.py:142 msgid "Delete Related Part" msgstr "" -#: part/views.py:152 +#: part/views.py:154 msgid "Add part attachment" msgstr "" -#: part/views.py:207 templates/attachment_table.html:34 +#: part/views.py:209 templates/attachment_table.html:34 msgid "Edit attachment" msgstr "" -#: part/views.py:213 +#: part/views.py:215 msgid "Part attachment updated" msgstr "" -#: part/views.py:228 +#: part/views.py:230 msgid "Delete Part Attachment" msgstr "" -#: part/views.py:236 +#: part/views.py:238 msgid "Deleted part attachment" msgstr "" -#: part/views.py:245 +#: part/views.py:247 msgid "Create Test Template" msgstr "" -#: part/views.py:274 +#: part/views.py:276 msgid "Edit Test Template" msgstr "" -#: part/views.py:290 +#: part/views.py:292 msgid "Delete Test Template" msgstr "" -#: part/views.py:299 +#: part/views.py:301 msgid "Set Part Category" msgstr "" -#: part/views.py:349 +#: part/views.py:351 #, python-brace-format msgid "Set category for {n} parts" msgstr "" -#: part/views.py:384 +#: part/views.py:386 msgid "Create Variant" msgstr "" -#: part/views.py:466 +#: part/views.py:468 msgid "Duplicate Part" msgstr "" -#: part/views.py:473 +#: part/views.py:475 msgid "Copied part" msgstr "" -#: part/views.py:527 part/views.py:661 +#: part/views.py:529 part/views.py:667 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:592 templates/js/stock.js:850 +#: part/views.py:594 templates/js/stock.js:876 msgid "Create New Part" msgstr "" -#: part/views.py:599 +#: part/views.py:601 msgid "Created new part" msgstr "" -#: part/views.py:830 +#: part/views.py:836 msgid "Part QR Code" msgstr "" -#: part/views.py:849 +#: part/views.py:855 msgid "Upload Part Image" msgstr "" -#: part/views.py:857 part/views.py:894 +#: part/views.py:863 part/views.py:900 msgid "Updated part image" msgstr "" -#: part/views.py:866 +#: part/views.py:872 msgid "Select Part Image" msgstr "" -#: part/views.py:897 +#: part/views.py:903 msgid "Part image not found" msgstr "" -#: part/views.py:908 +#: part/views.py:914 msgid "Edit Part Properties" msgstr "" -#: part/views.py:935 +#: part/views.py:945 msgid "Duplicate BOM" msgstr "" -#: part/views.py:966 +#: part/views.py:976 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:987 +#: part/views.py:997 msgid "Validate BOM" msgstr "" -#: part/views.py:1010 +#: part/views.py:1020 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1021 +#: part/views.py:1031 msgid "Validated Bill of Materials" msgstr "" -#: part/views.py:1155 +#: part/views.py:1165 msgid "No BOM file provided" msgstr "" -#: part/views.py:1503 +#: part/views.py:1513 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1528 part/views.py:1531 +#: part/views.py:1538 part/views.py:1541 msgid "Select valid part" msgstr "" -#: part/views.py:1537 +#: part/views.py:1547 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1575 +#: part/views.py:1585 msgid "Select a part" msgstr "" -#: part/views.py:1581 +#: part/views.py:1591 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1585 +#: part/views.py:1595 msgid "Specify quantity" msgstr "" -#: part/views.py:1841 +#: part/views.py:1851 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1850 +#: part/views.py:1860 msgid "Part was deleted" msgstr "" -#: part/views.py:1859 +#: part/views.py:1869 msgid "Part Pricing" msgstr "" -#: part/views.py:1973 +#: part/views.py:1983 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1983 +#: part/views.py:1993 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1992 +#: part/views.py:2002 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:2002 +#: part/views.py:2012 msgid "Create Part Parameter" msgstr "" -#: part/views.py:2054 +#: part/views.py:2064 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:2070 +#: part/views.py:2080 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2129 +#: part/views.py:2139 msgid "Edit Part Category" msgstr "" -#: part/views.py:2166 +#: part/views.py:2176 msgid "Delete Part Category" msgstr "" -#: part/views.py:2174 +#: part/views.py:2184 msgid "Part category was deleted" msgstr "" -#: part/views.py:2230 +#: part/views.py:2240 msgid "Create Category Parameter Template" msgstr "" -#: part/views.py:2333 +#: part/views.py:2343 msgid "Edit Category Parameter Template" msgstr "" -#: part/views.py:2391 +#: part/views.py:2401 msgid "Delete Category Parameter Template" msgstr "" -#: part/views.py:2416 +#: part/views.py:2426 msgid "Create BOM Item" msgstr "" -#: part/views.py:2488 +#: part/views.py:2498 msgid "Edit BOM item" msgstr "" -#: part/views.py:2545 +#: part/views.py:2555 msgid "Confim BOM item deletion" msgstr "" @@ -3641,295 +3713,305 @@ msgstr "" msgid "Asset file description" msgstr "" -#: stock/forms.py:111 +#: stock/forms.py:116 msgid "Enter unique serial numbers (or leave blank)" msgstr "" -#: stock/forms.py:192 +#: stock/forms.py:198 msgid "Label" msgstr "" -#: stock/forms.py:193 stock/forms.py:249 +#: stock/forms.py:199 stock/forms.py:255 msgid "Select test report template" msgstr "" -#: stock/forms.py:257 +#: stock/forms.py:263 msgid "Include stock items in sub locations" msgstr "" -#: stock/forms.py:292 +#: stock/forms.py:298 msgid "Stock item to install" msgstr "" -#: stock/forms.py:299 +#: stock/forms.py:305 msgid "Stock quantity to assign" msgstr "" -#: stock/forms.py:327 +#: stock/forms.py:333 msgid "Must not exceed available quantity" msgstr "" -#: stock/forms.py:337 +#: stock/forms.py:343 msgid "Destination location for uninstalled items" msgstr "" -#: stock/forms.py:339 +#: stock/forms.py:345 msgid "Add transaction note (optional)" msgstr "" -#: stock/forms.py:341 +#: stock/forms.py:347 msgid "Confirm uninstall" msgstr "" -#: stock/forms.py:341 +#: stock/forms.py:347 msgid "Confirm removal of installed stock items" msgstr "" -#: stock/forms.py:365 +#: stock/forms.py:371 msgid "Destination stock location" msgstr "" -#: stock/forms.py:367 +#: stock/forms.py:373 msgid "Add note (required)" msgstr "" -#: stock/forms.py:371 stock/views.py:935 stock/views.py:1133 +#: stock/forms.py:377 stock/views.py:935 stock/views.py:1133 msgid "Confirm stock adjustment" msgstr "" -#: stock/forms.py:371 +#: stock/forms.py:377 msgid "Confirm movement of stock items" msgstr "" -#: stock/forms.py:373 +#: stock/forms.py:379 msgid "Set Default Location" msgstr "" -#: stock/forms.py:373 +#: stock/forms.py:379 msgid "Set the destination as the default location for selected parts" msgstr "" -#: stock/models.py:179 +#: stock/models.py:185 msgid "Created stock item" msgstr "" -#: stock/models.py:215 +#: stock/models.py:221 msgid "StockItem with this serial number already exists" msgstr "" -#: stock/models.py:251 +#: stock/models.py:257 #, python-brace-format msgid "Part type ('{pf}') must be {pe}" msgstr "" -#: stock/models.py:261 stock/models.py:270 +#: stock/models.py:267 stock/models.py:276 msgid "Quantity must be 1 for item with a serial number" msgstr "" -#: stock/models.py:262 +#: stock/models.py:268 msgid "Serial number cannot be set if quantity greater than 1" msgstr "" -#: stock/models.py:284 +#: stock/models.py:290 msgid "Item cannot belong to itself" msgstr "" -#: stock/models.py:290 +#: stock/models.py:296 msgid "Item must have a build reference if is_building=True" msgstr "" -#: stock/models.py:297 +#: stock/models.py:303 msgid "Build reference does not point to the same part object" msgstr "" -#: stock/models.py:330 +#: stock/models.py:336 msgid "Parent Stock Item" msgstr "" -#: stock/models.py:339 +#: stock/models.py:345 msgid "Base part" msgstr "" -#: stock/models.py:348 +#: stock/models.py:354 msgid "Select a matching supplier part for this stock item" msgstr "" -#: stock/models.py:353 stock/templates/stock/stock_app_base.html:7 +#: stock/models.py:359 stock/templates/stock/stock_app_base.html:7 msgid "Stock Location" msgstr "" -#: stock/models.py:356 +#: stock/models.py:362 msgid "Where is this stock item located?" msgstr "" -#: stock/models.py:361 stock/templates/stock/item_base.html:212 +#: stock/models.py:367 stock/templates/stock/item_base.html:219 msgid "Installed In" msgstr "" -#: stock/models.py:364 +#: stock/models.py:370 msgid "Is this item installed in another item?" msgstr "" -#: stock/models.py:380 +#: stock/models.py:386 msgid "Serial number for this item" msgstr "" -#: stock/models.py:392 +#: stock/models.py:398 msgid "Batch code for this stock item" msgstr "" -#: stock/models.py:396 +#: stock/models.py:402 msgid "Stock Quantity" msgstr "" -#: stock/models.py:405 +#: stock/models.py:411 msgid "Source Build" msgstr "" -#: stock/models.py:407 +#: stock/models.py:413 msgid "Build for this stock item" msgstr "" -#: stock/models.py:418 +#: stock/models.py:424 msgid "Source Purchase Order" msgstr "" -#: stock/models.py:421 +#: stock/models.py:427 msgid "Purchase order for this stock item" msgstr "" -#: stock/models.py:427 +#: stock/models.py:433 msgid "Destination Sales Order" msgstr "" -#: stock/models.py:439 +#: stock/models.py:439 stock/templates/stock/item_base.html:306 +#: templates/js/stock.js:597 +msgid "Expiry Date" +msgstr "" + +#: stock/models.py:440 +msgid "" +"Expiry date for stock item. Stock will be considered expired after this date" +msgstr "" + +#: stock/models.py:453 msgid "Delete this Stock Item when stock is depleted" msgstr "" -#: stock/models.py:449 stock/templates/stock/item_notes.html:14 +#: stock/models.py:463 stock/templates/stock/item_notes.html:14 #: stock/templates/stock/item_notes.html:30 msgid "Stock Item Notes" msgstr "" -#: stock/models.py:459 +#: stock/models.py:473 msgid "Single unit purchase price at time of purchase" msgstr "" -#: stock/models.py:510 +#: stock/models.py:573 msgid "Assigned to Customer" msgstr "" -#: stock/models.py:512 +#: stock/models.py:575 msgid "Manually assigned to customer" msgstr "" -#: stock/models.py:525 +#: stock/models.py:588 msgid "Returned from customer" msgstr "" -#: stock/models.py:527 +#: stock/models.py:590 msgid "Returned to location" msgstr "" -#: stock/models.py:652 +#: stock/models.py:715 msgid "Installed into stock item" msgstr "" -#: stock/models.py:660 +#: stock/models.py:723 msgid "Installed stock item" msgstr "" -#: stock/models.py:684 +#: stock/models.py:747 msgid "Uninstalled stock item" msgstr "" -#: stock/models.py:703 +#: stock/models.py:766 msgid "Uninstalled into location" msgstr "" -#: stock/models.py:803 +#: stock/models.py:846 msgid "Part is not set as trackable" msgstr "" -#: stock/models.py:809 +#: stock/models.py:852 msgid "Quantity must be integer" msgstr "" -#: stock/models.py:815 +#: stock/models.py:858 #, python-brace-format msgid "Quantity must not exceed available stock quantity ({n})" msgstr "" -#: stock/models.py:818 +#: stock/models.py:861 msgid "Serial numbers must be a list of integers" msgstr "" -#: stock/models.py:821 +#: stock/models.py:864 msgid "Quantity does not match serial numbers" msgstr "" -#: stock/models.py:853 +#: stock/models.py:896 msgid "Add serial number" msgstr "" -#: stock/models.py:856 +#: stock/models.py:899 #, python-brace-format msgid "Serialized {n} items" msgstr "" -#: stock/models.py:967 +#: stock/models.py:1010 msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1321 +#: stock/models.py:1364 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1323 +#: stock/models.py:1366 msgid "Entry notes" msgstr "" -#: stock/models.py:1325 +#: stock/models.py:1368 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1385 +#: stock/models.py:1428 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1391 +#: stock/models.py:1434 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1408 +#: stock/models.py:1451 msgid "Test" msgstr "" -#: stock/models.py:1409 +#: stock/models.py:1452 msgid "Test name" msgstr "" -#: stock/models.py:1414 +#: stock/models.py:1457 msgid "Result" msgstr "" -#: stock/models.py:1415 templates/js/table_filters.js:162 +#: stock/models.py:1458 templates/js/table_filters.js:172 msgid "Test result" msgstr "" -#: stock/models.py:1421 +#: stock/models.py:1464 msgid "Test output value" msgstr "" -#: stock/models.py:1427 +#: stock/models.py:1470 msgid "Attachment" msgstr "" -#: stock/models.py:1428 +#: stock/models.py:1471 msgid "Test result attachment" msgstr "" -#: stock/models.py:1434 +#: stock/models.py:1477 msgid "Test notes" msgstr "" @@ -3980,111 +4062,129 @@ msgid "" "This stock item will be automatically deleted when all stock is depleted." msgstr "" -#: stock/templates/stock/item_base.html:107 templates/js/barcode.js:283 +#: stock/templates/stock/item_base.html:74 +#: stock/templates/stock/item_base.html:310 templates/js/table_filters.js:111 +msgid "Expired" +msgstr "" + +#: stock/templates/stock/item_base.html:78 +#: stock/templates/stock/item_base.html:312 templates/js/table_filters.js:116 +msgid "Stale" +msgstr "" + +#: stock/templates/stock/item_base.html:114 templates/js/barcode.js:283 #: templates/js/barcode.js:288 msgid "Unlink Barcode" msgstr "" -#: stock/templates/stock/item_base.html:109 +#: stock/templates/stock/item_base.html:116 msgid "Link Barcode" msgstr "" -#: stock/templates/stock/item_base.html:117 +#: stock/templates/stock/item_base.html:124 msgid "Stock adjustment actions" msgstr "" -#: stock/templates/stock/item_base.html:121 +#: stock/templates/stock/item_base.html:128 #: stock/templates/stock/location.html:41 templates/stock_table.html:23 msgid "Count stock" msgstr "" -#: stock/templates/stock/item_base.html:122 templates/stock_table.html:21 +#: stock/templates/stock/item_base.html:129 templates/stock_table.html:21 msgid "Add stock" msgstr "" -#: stock/templates/stock/item_base.html:123 templates/stock_table.html:22 +#: stock/templates/stock/item_base.html:130 templates/stock_table.html:22 msgid "Remove stock" msgstr "" -#: stock/templates/stock/item_base.html:125 +#: stock/templates/stock/item_base.html:132 msgid "Transfer stock" msgstr "" -#: stock/templates/stock/item_base.html:127 +#: stock/templates/stock/item_base.html:134 msgid "Serialize stock" msgstr "" -#: stock/templates/stock/item_base.html:131 +#: stock/templates/stock/item_base.html:138 msgid "Assign to customer" msgstr "" -#: stock/templates/stock/item_base.html:134 +#: stock/templates/stock/item_base.html:141 msgid "Return to stock" msgstr "" -#: stock/templates/stock/item_base.html:138 templates/js/stock.js:991 +#: stock/templates/stock/item_base.html:145 templates/js/stock.js:1017 msgid "Uninstall stock item" msgstr "" -#: stock/templates/stock/item_base.html:138 +#: stock/templates/stock/item_base.html:145 msgid "Uninstall" msgstr "" -#: stock/templates/stock/item_base.html:147 +#: stock/templates/stock/item_base.html:154 #: stock/templates/stock/location.html:38 msgid "Stock actions" msgstr "" -#: stock/templates/stock/item_base.html:150 +#: stock/templates/stock/item_base.html:157 msgid "Convert to variant" msgstr "" -#: stock/templates/stock/item_base.html:153 +#: stock/templates/stock/item_base.html:160 msgid "Duplicate stock item" msgstr "" -#: stock/templates/stock/item_base.html:155 +#: stock/templates/stock/item_base.html:162 msgid "Edit stock item" msgstr "" -#: stock/templates/stock/item_base.html:158 +#: stock/templates/stock/item_base.html:165 msgid "Delete stock item" msgstr "" -#: stock/templates/stock/item_base.html:164 +#: stock/templates/stock/item_base.html:171 msgid "Generate test report" msgstr "" -#: stock/templates/stock/item_base.html:172 +#: stock/templates/stock/item_base.html:179 msgid "Stock Item Details" msgstr "" -#: stock/templates/stock/item_base.html:231 templates/js/build.js:442 +#: stock/templates/stock/item_base.html:238 templates/js/build.js:442 msgid "No location set" msgstr "" -#: stock/templates/stock/item_base.html:238 +#: stock/templates/stock/item_base.html:245 msgid "Barcode Identifier" msgstr "" -#: stock/templates/stock/item_base.html:252 templates/js/build.js:642 +#: stock/templates/stock/item_base.html:259 templates/js/build.js:642 #: templates/navbar.html:25 msgid "Build" msgstr "" -#: stock/templates/stock/item_base.html:273 +#: stock/templates/stock/item_base.html:280 msgid "Parent Item" msgstr "" -#: stock/templates/stock/item_base.html:298 +#: stock/templates/stock/item_base.html:310 +msgid "This StockItem expired on" +msgstr "" + +#: stock/templates/stock/item_base.html:312 +msgid "This StockItem expires on" +msgstr "" + +#: stock/templates/stock/item_base.html:319 msgid "Last Updated" msgstr "" -#: stock/templates/stock/item_base.html:303 +#: stock/templates/stock/item_base.html:324 msgid "Last Stocktake" msgstr "" -#: stock/templates/stock/item_base.html:307 +#: stock/templates/stock/item_base.html:328 msgid "No stocktake performed" msgstr "" @@ -4204,7 +4304,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1331 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1335 msgid "Convert Stock Item" msgstr "" @@ -4402,39 +4502,39 @@ msgstr "" msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1381 +#: stock/views.py:1385 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1475 templates/js/build.js:210 +#: stock/views.py:1479 templates/js/build.js:210 msgid "Create new Stock Item" msgstr "" -#: stock/views.py:1579 +#: stock/views.py:1587 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1651 +#: stock/views.py:1664 msgid "Quantity cannot be negative" msgstr "" -#: stock/views.py:1737 +#: stock/views.py:1750 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1751 +#: stock/views.py:1764 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1763 +#: stock/views.py:1776 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1782 +#: stock/views.py:1795 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1792 +#: stock/views.py:1805 msgid "Add Stock Tracking Entry" msgstr "" @@ -4466,7 +4566,11 @@ msgstr "" msgid "Pending Builds" msgstr "" -#: templates/InvenTree/index.html:4 +#: templates/InvenTree/expired_stock.html:7 +msgid "Expired Stock" +msgstr "" + +#: templates/InvenTree/index.html:5 msgid "Index" msgstr "" @@ -4494,11 +4598,11 @@ msgstr "" msgid "Enter a search query" msgstr "" -#: templates/InvenTree/search.html:191 templates/js/stock.js:289 +#: templates/InvenTree/search.html:191 templates/js/stock.js:290 msgid "Shipped to customer" msgstr "" -#: templates/InvenTree/search.html:194 templates/js/stock.js:299 +#: templates/InvenTree/search.html:194 templates/js/stock.js:300 msgid "No stock location set" msgstr "" @@ -4527,12 +4631,12 @@ msgid "Default Value" msgstr "" #: templates/InvenTree/settings/category.html:70 -#: templates/InvenTree/settings/part.html:75 +#: templates/InvenTree/settings/part.html:78 msgid "Edit Template" msgstr "" #: templates/InvenTree/settings/category.html:71 -#: templates/InvenTree/settings/part.html:76 +#: templates/InvenTree/settings/part.html:79 msgid "Delete Template" msgstr "" @@ -4540,6 +4644,10 @@ msgstr "" msgid "Global InvenTree Settings" msgstr "" +#: templates/InvenTree/settings/header.html:7 +msgid "Setting" +msgstr "" + #: templates/InvenTree/settings/part.html:9 msgid "Part Settings" msgstr "" @@ -4548,11 +4656,11 @@ msgstr "" msgid "Part Options" msgstr "" -#: templates/InvenTree/settings/part.html:34 +#: templates/InvenTree/settings/part.html:37 msgid "Part Parameter Templates" msgstr "" -#: templates/InvenTree/settings/part.html:55 +#: templates/InvenTree/settings/part.html:58 msgid "No part parameter templates found" msgstr "" @@ -4560,11 +4668,11 @@ msgstr "" msgid "Purchase Order Settings" msgstr "" -#: templates/InvenTree/settings/setting.html:16 +#: templates/InvenTree/settings/setting.html:23 msgid "No value set" msgstr "" -#: templates/InvenTree/settings/setting.html:24 +#: templates/InvenTree/settings/setting.html:31 msgid "Edit setting" msgstr "" @@ -4581,6 +4689,10 @@ msgstr "" msgid "Stock Settings" msgstr "" +#: templates/InvenTree/settings/stock.html:13 +msgid "Stock Options" +msgstr "" + #: templates/InvenTree/settings/tabs.html:3 #: templates/InvenTree/settings/user.html:10 msgid "User Settings" @@ -4656,6 +4768,10 @@ msgstr "" msgid "Overdue Sales Orders" msgstr "" +#: templates/InvenTree/stale_stock.html:7 +msgid "Stale Stock" +msgstr "" + #: templates/InvenTree/starred_parts.html:7 msgid "Starred Parts" msgstr "" @@ -4917,7 +5033,7 @@ msgstr "" msgid "No purchase orders found" msgstr "" -#: templates/js/order.js:188 templates/js/stock.js:687 +#: templates/js/order.js:188 templates/js/stock.js:702 msgid "Date" msgstr "" @@ -4957,8 +5073,8 @@ msgstr "" msgid "No parts found" msgstr "" -#: templates/js/part.js:343 templates/js/stock.js:462 -#: templates/js/stock.js:1023 +#: templates/js/part.js:343 templates/js/stock.js:463 +#: templates/js/stock.js:1049 msgid "Select" msgstr "" @@ -4966,7 +5082,7 @@ msgstr "" msgid "No category" msgstr "" -#: templates/js/part.js:429 templates/js/table_filters.js:264 +#: templates/js/part.js:429 templates/js/table_filters.js:274 msgid "Low stock" msgstr "" @@ -4986,11 +5102,11 @@ msgstr "" msgid "No test templates matching query" msgstr "" -#: templates/js/part.js:604 templates/js/stock.js:63 +#: templates/js/part.js:604 templates/js/stock.js:64 msgid "Edit test result" msgstr "" -#: templates/js/part.js:605 templates/js/stock.js:64 +#: templates/js/part.js:605 templates/js/stock.js:65 msgid "Delete test result" msgstr "" @@ -4998,103 +5114,111 @@ msgstr "" msgid "This test is defined for a parent part" msgstr "" -#: templates/js/stock.js:26 +#: templates/js/stock.js:27 msgid "PASS" msgstr "" -#: templates/js/stock.js:28 +#: templates/js/stock.js:29 msgid "FAIL" msgstr "" -#: templates/js/stock.js:33 +#: templates/js/stock.js:34 msgid "NO RESULT" msgstr "" -#: templates/js/stock.js:59 +#: templates/js/stock.js:60 msgid "Add test result" msgstr "" -#: templates/js/stock.js:78 +#: templates/js/stock.js:79 msgid "No test results found" msgstr "" -#: templates/js/stock.js:120 +#: templates/js/stock.js:121 msgid "Test Date" msgstr "" -#: templates/js/stock.js:281 +#: templates/js/stock.js:282 msgid "In production" msgstr "" -#: templates/js/stock.js:285 +#: templates/js/stock.js:286 msgid "Installed in Stock Item" msgstr "" -#: templates/js/stock.js:293 +#: templates/js/stock.js:294 msgid "Assigned to Sales Order" msgstr "" -#: templates/js/stock.js:313 +#: templates/js/stock.js:314 msgid "No stock items matching query" msgstr "" -#: templates/js/stock.js:430 +#: templates/js/stock.js:431 msgid "Undefined location" msgstr "" -#: templates/js/stock.js:524 +#: templates/js/stock.js:525 msgid "Stock item is in production" msgstr "" -#: templates/js/stock.js:529 +#: templates/js/stock.js:530 msgid "Stock item assigned to sales order" msgstr "" -#: templates/js/stock.js:532 +#: templates/js/stock.js:533 msgid "Stock item assigned to customer" msgstr "" -#: templates/js/stock.js:536 +#: templates/js/stock.js:537 +msgid "Stock item has expired" +msgstr "" + +#: templates/js/stock.js:539 +msgid "Stock item will expire soon" +msgstr "" + +#: templates/js/stock.js:543 msgid "Stock item has been allocated" msgstr "" -#: templates/js/stock.js:540 +#: templates/js/stock.js:547 msgid "Stock item has been installed in another item" msgstr "" -#: templates/js/stock.js:548 +#: templates/js/stock.js:555 msgid "Stock item has been rejected" msgstr "" -#: templates/js/stock.js:552 +#: templates/js/stock.js:559 msgid "Stock item is lost" msgstr "" -#: templates/js/stock.js:555 +#: templates/js/stock.js:562 msgid "Stock item is destroyed" msgstr "" -#: templates/js/stock.js:559 templates/js/table_filters.js:106 +#: templates/js/stock.js:566 templates/js/table_filters.js:106 msgid "Depleted" msgstr "" -#: templates/js/stock.js:753 +#: templates/js/stock.js:768 msgid "No user information" msgstr "" -#: templates/js/stock.js:862 +#: templates/js/stock.js:888 msgid "Create New Location" msgstr "" -#: templates/js/stock.js:961 +#: templates/js/stock.js:987 msgid "Serial" msgstr "" -#: templates/js/stock.js:1054 templates/js/table_filters.js:121 +#: templates/js/stock.js:1080 templates/js/table_filters.js:131 msgid "Installed" msgstr "" -#: templates/js/stock.js:1079 +#: templates/js/stock.js:1105 msgid "Install item" msgstr "" @@ -5106,36 +5230,36 @@ msgstr "" msgid "Validated" msgstr "" -#: templates/js/table_filters.js:65 templates/js/table_filters.js:131 +#: templates/js/table_filters.js:65 templates/js/table_filters.js:141 msgid "Is Serialized" msgstr "" -#: templates/js/table_filters.js:68 templates/js/table_filters.js:138 +#: templates/js/table_filters.js:68 templates/js/table_filters.js:148 msgid "Serial number GTE" msgstr "" -#: templates/js/table_filters.js:69 templates/js/table_filters.js:139 +#: templates/js/table_filters.js:69 templates/js/table_filters.js:149 msgid "Serial number greater than or equal to" msgstr "" -#: templates/js/table_filters.js:72 templates/js/table_filters.js:142 +#: templates/js/table_filters.js:72 templates/js/table_filters.js:152 msgid "Serial number LTE" msgstr "" -#: templates/js/table_filters.js:73 templates/js/table_filters.js:143 +#: templates/js/table_filters.js:73 templates/js/table_filters.js:153 msgid "Serial number less than or equal to" msgstr "" #: templates/js/table_filters.js:76 templates/js/table_filters.js:77 -#: templates/js/table_filters.js:134 templates/js/table_filters.js:135 +#: templates/js/table_filters.js:144 templates/js/table_filters.js:145 msgid "Serial number" msgstr "" -#: templates/js/table_filters.js:81 templates/js/table_filters.js:152 +#: templates/js/table_filters.js:81 templates/js/table_filters.js:162 msgid "Batch code" msgstr "" -#: templates/js/table_filters.js:91 templates/js/table_filters.js:231 +#: templates/js/table_filters.js:91 templates/js/table_filters.js:241 msgid "Active parts" msgstr "" @@ -5164,74 +5288,82 @@ msgid "Show stock items which are depleted" msgstr "" #: templates/js/table_filters.js:112 -msgid "Show items which are in stock" -msgstr "" - -#: templates/js/table_filters.js:116 -msgid "In Production" +msgid "Show stock items which have expired" msgstr "" #: templates/js/table_filters.js:117 -msgid "Show items which are in production" +msgid "Show stock which is close to expiring" msgstr "" #: templates/js/table_filters.js:122 -msgid "Show stock items which are installed in another item" +msgid "Show items which are in stock" msgstr "" #: templates/js/table_filters.js:126 -msgid "Sent to customer" +msgid "In Production" msgstr "" #: templates/js/table_filters.js:127 +msgid "Show items which are in production" +msgstr "" + +#: templates/js/table_filters.js:132 +msgid "Show stock items which are installed in another item" +msgstr "" + +#: templates/js/table_filters.js:136 +msgid "Sent to customer" +msgstr "" + +#: templates/js/table_filters.js:137 msgid "Show items which have been assigned to a customer" msgstr "" -#: templates/js/table_filters.js:147 templates/js/table_filters.js:148 +#: templates/js/table_filters.js:157 templates/js/table_filters.js:158 msgid "Stock status" msgstr "" -#: templates/js/table_filters.js:181 +#: templates/js/table_filters.js:191 msgid "Build status" msgstr "" -#: templates/js/table_filters.js:200 templates/js/table_filters.js:213 +#: templates/js/table_filters.js:210 templates/js/table_filters.js:223 msgid "Order status" msgstr "" -#: templates/js/table_filters.js:205 templates/js/table_filters.js:218 +#: templates/js/table_filters.js:215 templates/js/table_filters.js:228 msgid "Outstanding" msgstr "" -#: templates/js/table_filters.js:241 +#: templates/js/table_filters.js:251 msgid "Include subcategories" msgstr "" -#: templates/js/table_filters.js:242 +#: templates/js/table_filters.js:252 msgid "Include parts in subcategories" msgstr "" -#: templates/js/table_filters.js:246 +#: templates/js/table_filters.js:256 msgid "Has IPN" msgstr "" -#: templates/js/table_filters.js:247 +#: templates/js/table_filters.js:257 msgid "Part has internal part number" msgstr "" -#: templates/js/table_filters.js:252 +#: templates/js/table_filters.js:262 msgid "Show active parts" msgstr "" -#: templates/js/table_filters.js:260 +#: templates/js/table_filters.js:270 msgid "Stock available" msgstr "" -#: templates/js/table_filters.js:276 +#: templates/js/table_filters.js:286 msgid "Starred" msgstr "" -#: templates/js/table_filters.js:288 +#: templates/js/table_filters.js:298 msgid "Purchasable" msgstr "" From 7ac7e8f9690971f5a8cf9c4404ad4fb25ff770ce Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 6 Jan 2021 23:38:01 +1100 Subject: [PATCH 27/27] Fixed unit test --- InvenTree/stock/test_api.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 520110469c..9b3e926456 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -15,6 +15,8 @@ from django.contrib.auth import get_user_model from InvenTree.helpers import addUserPermissions from InvenTree.status_codes import StockStatus +from common.models import InvenTreeSetting + from .models import StockItem, StockLocation @@ -36,6 +38,9 @@ class StockAPITestCase(APITestCase): self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') + self.user.is_staff = True + self.user.save() + # Add the necessary permissions to the user perms = [ 'view_stockitemtestresult', @@ -223,6 +228,13 @@ class StockItemListTest(StockAPITestCase): Filter StockItem by expiry status """ + # First, we can assume that the 'stock expiry' feature is disabled + response = self.get_stock(expired=1) + self.assertEqual(len(response), 19) + + # Now, ensure that the expiry date feature is enabled! + InvenTreeSetting.set_setting('STOCK_ENABLE_EXPIRY', True, self.user) + response = self.get_stock(expired=1) self.assertEqual(len(response), 1)